X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2FText.cpp;h=12ab04f043397925fbfdd47059172509702d7149;hb=1440b6a2fc6d39782a486f045d72506eb9215712;hp=a2ae1aed8f33b772ea527dbb824c2ff9b874bc4f;hpb=9c55af4a223ce4db29d643251109e245665344bd;p=lyx.git diff --git a/src/Text.cpp b/src/Text.cpp index a2ae1aed8f..12ab04f043 100644 --- a/src/Text.cpp +++ b/src/Text.cpp @@ -66,38 +66,14 @@ #include "support/gettext.h" #include "support/lassert.h" #include "support/lstrings.h" +#include "support/lyxalgo.h" +#include "support/lyxtime.h" #include "support/textutils.h" +#include "support/unique_ptr.h" -#include - -#include #include -// TODO: replace if in Text::readParToken() with compile time switch -#if 0 - -#include "support/metahash.h" - -typedef boost::mpl::string<'\\end','_lay','out'> end_layout; -typedef boost::mpl::string<'\\end','in','set'> end_inset; - -void foo() -{ - std::string token = "\\end_layout"; - - switch (boost::hash_value(token)) { - case lyx::support::hash_string::value: - return; - case lyx::support::hash_string::value: - return; - default: ; - }; - -} -#endif - - using namespace std; using namespace lyx::support; @@ -134,12 +110,12 @@ static bool moveItem(Paragraph & fromPar, pos_type fromPos, void breakParagraphConservative(BufferParams const & bparams, - ParagraphList & pars, pit_type par_offset, pos_type pos) + ParagraphList & pars, pit_type pit, pos_type pos) { // create a new paragraph - Paragraph & tmp = *pars.insert(boost::next(pars.begin(), par_offset + 1), + Paragraph & tmp = *pars.insert(lyx::next(pars.begin(), pit + 1), Paragraph()); - Paragraph & par = pars[par_offset]; + Paragraph & par = pars[pit]; tmp.setInsetOwner(&par.inInset()); tmp.makeSameLayout(par); @@ -193,12 +169,12 @@ void mergeParagraph(BufferParams const & bparams, // move the change of the end-of-paragraph character par.setChange(par.size(), change); - pars.erase(boost::next(pars.begin(), par_offset + 1)); + pars.erase(lyx::next(pars.begin(), par_offset + 1)); } Text::Text(InsetText * owner, bool use_default_layout) - : owner_(owner), autoBreakRows_(false), undo_counter_(0) + : owner_(owner) { pars_.push_back(Paragraph()); Paragraph & par = pars_.back(); @@ -212,9 +188,8 @@ Text::Text(InsetText * owner, bool use_default_layout) Text::Text(InsetText * owner, Text const & text) - : owner_(owner), autoBreakRows_(text.autoBreakRows_), undo_counter_(0) + : owner_(owner), pars_(text.pars_) { - pars_ = text.pars_; ParagraphList::iterator const end = pars_.end(); ParagraphList::iterator it = pars_.begin(); for (; it != end; ++it) @@ -362,7 +337,7 @@ void Text::readParToken(Paragraph & par, Lexer & lex, string const & token, Font & font, Change & change, ErrorList & errorList) { Buffer * buf = const_cast(&owner_->buffer()); - BufferParams const & bp = buf->params(); + BufferParams & bp = buf->params(); if (token[0] != '\\') { docstring dstr = lex.getDocString(); @@ -486,22 +461,44 @@ void Text::readParToken(Paragraph & par, Lexer & lex, } else if (token == "\\color") { lex.next(); setLyXColor(lex.getString(), font.fontInfo()); - } else if (token == "\\SpecialChar") { - auto_ptr inset; - inset.reset(new InsetSpecialChar); + } else if (token == "\\SpecialChar" || + (token == "\\SpecialCharNoPassThru" && + !par.layout().pass_thru && !inset().isPassThru())) { + auto inset = make_unique(); inset->read(lex); inset->setBuffer(*buf); par.insertInset(par.size(), inset.release(), font, change); + } else if (token == "\\SpecialCharNoPassThru") { + lex.next(); + docstring const s = ltrim(lex.getDocString(), "\\"); + par.insert(par.size(), s, font, change); } else if (token == "\\IPAChar") { - auto_ptr inset; - inset.reset(new InsetIPAChar); + auto inset = make_unique(); inset->read(lex); inset->setBuffer(*buf); par.insertInset(par.size(), inset.release(), font, change); + } else if (token == "\\twohyphens" || token == "\\threehyphens") { + // Ideally, this should be done by lyx2lyx, but lyx2lyx does not know the + // running font and does not know anything about layouts (and CopyStyle). + Layout const & layout(par.layout()); + FontInfo info = font.fontInfo(); + info.realize(layout.resfont); + if (layout.pass_thru || inset().isPassThru() || + info.family() == TYPEWRITER_FAMILY) { + if (token == "\\twohyphens") + par.insert(par.size(), from_ascii("--"), font, change); + else + par.insert(par.size(), from_ascii("---"), font, change); + } else { + if (token == "\\twohyphens") + par.insertChar(par.size(), 0x2013, font, change); + else + par.insertChar(par.size(), 0x2014, font, change); + } } else if (token == "\\backslash") { par.appendChar('\\', font, change); } else if (token == "\\LyXTable") { - auto_ptr inset(new InsetTabular(buf)); + auto inset = make_unique(buf); inset->read(lex); par.insertInset(par.size(), inset.release(), font, change); } else if (token == "\\change_unchanged") { @@ -512,18 +509,25 @@ void Text::readParToken(Paragraph & par, Lexer & lex, int aid; time_t ct; is >> aid >> ct; - BufferParams::AuthorMap const & am = bp.author_map; + BufferParams::AuthorMap const & am = bp.author_map_; if (am.find(aid) == am.end()) { - errorList.push_back(ErrorItem(_("Change tracking error"), - bformat(_("Unknown author index for change: %1$d\n"), aid), - par.id(), 0, par.size())); - change = Change(Change::UNCHANGED); - } else { - if (token == "\\change_inserted") - change = Change(Change::INSERTED, am.find(aid)->second, ct); - else - change = Change(Change::DELETED, am.find(aid)->second, ct); + errorList.push_back(ErrorItem( + _("Change tracking author index missing"), + bformat(_("A change tracking author information for index " + "%1$d is missing. This can happen after a wrong " + "merge by a version control system. In this case, " + "either fix the merge, or have this information " + "missing until the corresponding tracked changes " + "are merged or this user edits the file again.\n"), + aid), + par.id(), par.size(), par.size() + 1 + )); + bp.addAuthor(Author(aid)); } + if (token == "\\change_inserted") + change = Change(Change::INSERTED, am.find(aid)->second, ct); + else + change = Change(Change::DELETED, am.find(aid)->second, ct); } else { lex.eatLine(); errorList.push_back(ErrorItem(_("Unknown token"), @@ -639,7 +643,7 @@ static void breakParagraph(Text & text, pit_type par_offset, pos_type pos, ParagraphList & pars = text.paragraphs(); // create a new paragraph, and insert into the list ParagraphList::iterator tmp = - pars.insert(boost::next(pars.begin(), par_offset + 1), + pars.insert(lyx::next(pars.begin(), par_offset + 1), Paragraph()); Paragraph & par = pars[par_offset]; @@ -815,7 +819,7 @@ void Text::insertStringAsLines(Cursor & cur, docstring const & str, cit != str.end(); ++cit) { Paragraph & par = pars_[pit]; if (*cit == '\n') { - if (autoBreakRows_ && (!par.empty() || par.allowEmpty())) { + if (inset().allowMultiPar() && (!par.empty() || par.allowEmpty())) { lyx::breakParagraph(*this, pit, pos, par.layout().isEnvironment()); ++pit; @@ -824,7 +828,7 @@ void Text::insertStringAsLines(Cursor & cur, docstring const & str, } else { continue; } - // do not insert consecutive spaces if !free_spacing + // do not insert consecutive spaces if !free_spacing } else if ((*cit == ' ' || *cit == '\t') && space_inserted && !par.isFreeSpacing()) { continue; @@ -901,11 +905,11 @@ void Text::insertChar(Cursor & cur, char_type c) if (lyxrc.auto_number) { static docstring const number_operators = from_ascii("+-/*"); static docstring const number_unary_operators = from_ascii("+-"); - static docstring const number_seperators = from_ascii(".,:"); + static docstring const number_separators = from_ascii(".,:"); if (cur.current_font.fontInfo().number() == FONT_ON) { if (!isDigitASCII(c) && !contains(number_operators, c) && - !(contains(number_seperators, c) && + !(contains(number_separators, c) && cur.pos() != 0 && cur.pos() != cur.lastpos() && tm.displayFont(pit, cur.pos()).fontInfo().number() == FONT_ON && @@ -926,7 +930,7 @@ void Text::insertChar(Cursor & cur, char_type c) ) { setCharFont(pit, cur.pos() - 1, cur.current_font, tm.font_); - } else if (contains(number_seperators, c) + } else if (contains(number_separators, c) && cur.pos() >= 2 && tm.displayFont(pit, cur.pos() - 2).fontInfo().number() == FONT_ON) { setCharFont(pit, cur.pos() - 1, cur.current_font, @@ -1019,14 +1023,36 @@ void Text::insertChar(Cursor & cur, char_type c) } } - par.insertChar(cur.pos(), c, cur.current_font, + pos_type pos = cur.pos(); + if (!cur.paragraph().isPassThru() && owner_->lyxCode() != IPA_CODE && + cur.real_current_font.fontInfo().family() != TYPEWRITER_FAMILY && + c == '-' && pos > 0) { + if (par.getChar(pos - 1) == '-') { + // convert "--" to endash + par.eraseChar(pos - 1, cur.buffer()->params().track_changes); + c = 0x2013; + pos--; + } else if (par.getChar(pos - 1) == 0x2013) { + // convert "---" to emdash + par.eraseChar(pos - 1, cur.buffer()->params().track_changes); + c = 0x2014; + pos--; + } else if (par.getChar(pos - 1) == 0x2014) { + // convert "----" to "-" + par.eraseChar(pos - 1, cur.buffer()->params().track_changes); + c = '-'; + pos--; + } + } + + par.insertChar(pos, c, cur.current_font, cur.buffer()->params().track_changes); cur.checkBufferStructure(); // cur.screenUpdateFlags(Update::Force); bool boundary = cur.boundary() - || tm.isRTLBoundary(cur.pit(), cur.pos() + 1); - setCursor(cur, cur.pit(), cur.pos() + 1, false, boundary); + || tm.isRTLBoundary(cur.pit(), pos + 1); + setCursor(cur, cur.pit(), pos + 1, false, boundary); charInserted(cur); } @@ -1035,15 +1061,6 @@ void Text::charInserted(Cursor & cur) { Paragraph & par = cur.paragraph(); - // Here we call finishUndo for every 20 characters inserted. - // This is from my experience how emacs does it. (Lgb) - if (undo_counter_ < 20) { - ++undo_counter_; - } else { - cur.finishUndo(); - undo_counter_ = 0; - } - // register word if a non-letter was entered if (cur.pos() > 1 && !par.isWordSeparator(cur.pos() - 2) @@ -1068,7 +1085,7 @@ bool Text::cursorForwardOneWord(Cursor & cur) Paragraph const & par = cur.paragraph(); // Paragraph boundary is a word boundary - if (pos == lastpos) { + if (pos == lastpos || (pos + 1 == lastpos && par.isEnvSeparator(pos))) { if (pit != cur.lastpit()) return setCursor(cur, pit + 1, 0); else @@ -1078,7 +1095,7 @@ bool Text::cursorForwardOneWord(Cursor & cur) if (lyxrc.mac_like_cursor_movement) { // Skip through trailing punctuation and spaces. while (pos != lastpos && (par.isChar(pos) || par.isSpace(pos))) - ++pos; + ++pos; // Skip over either a non-char inset or a full word if (pos != lastpos && par.isWordSeparator(pos)) @@ -1101,6 +1118,10 @@ bool Text::cursorForwardOneWord(Cursor & cur) ++pos; } + // Don't skip a separator inset at the end of a paragraph + if (pos == lastpos && pos && par.isEnvSeparator(pos - 1)) + --pos; + return setCursor(cur, pit, pos); } @@ -1114,8 +1135,14 @@ bool Text::cursorBackwardOneWord(Cursor & cur) Paragraph & par = cur.paragraph(); // Paragraph boundary is a word boundary - if (pos == 0 && pit != 0) - return setCursor(cur, pit - 1, getPar(pit - 1).size()); + if (pos == 0 && pit != 0) { + Paragraph & prevpar = getPar(pit - 1); + pos = prevpar.size(); + // Don't stop after an environment separator + if (pos && prevpar.isEnvSeparator(pos - 1)) + --pos; + return setCursor(cur, pit - 1, pos); + } if (lyxrc.mac_like_cursor_movement) { // Skip through punctuation and spaces. @@ -1151,7 +1178,6 @@ bool Text::cursorVisLeftOneWord(Cursor & cur) LBUFERR(this == cur.text()); pos_type left_pos, right_pos; - bool left_is_letter, right_is_letter; Cursor temp_cur = cur; @@ -1160,9 +1186,9 @@ bool Text::cursorVisLeftOneWord(Cursor & cur) // collect some information about current cursor position temp_cur.getSurroundingPos(left_pos, right_pos); - left_is_letter = + bool left_is_letter = (left_pos > -1 ? !temp_cur.paragraph().isWordSeparator(left_pos) : false); - right_is_letter = + bool right_is_letter = (right_pos > -1 ? !temp_cur.paragraph().isWordSeparator(right_pos) : false); // if we're not at a letter/non-letter boundary, continue moving @@ -1188,7 +1214,6 @@ bool Text::cursorVisRightOneWord(Cursor & cur) LBUFERR(this == cur.text()); pos_type left_pos, right_pos; - bool left_is_letter, right_is_letter; Cursor temp_cur = cur; @@ -1197,9 +1222,9 @@ bool Text::cursorVisRightOneWord(Cursor & cur) // collect some information about current cursor position temp_cur.getSurroundingPos(left_pos, right_pos); - left_is_letter = + bool left_is_letter = (left_pos > -1 ? !temp_cur.paragraph().isWordSeparator(left_pos) : false); - right_is_letter = + bool right_is_letter = (right_pos > -1 ? !temp_cur.paragraph().isWordSeparator(right_pos) : false); // if we're not at a letter/non-letter boundary, continue moving @@ -1226,7 +1251,7 @@ void Text::selectWord(Cursor & cur, word_location loc) { LBUFERR(this == cur.text()); CursorSlice from = cur.top(); - CursorSlice to = cur.top(); + CursorSlice to; getWord(from, to, loc); if (cur.top() != from) setCursor(cur, from.pit(), from.pos()); @@ -1428,7 +1453,7 @@ void Text::deleteWordForward(Cursor & cur) cursorForward(cur); else { cur.resetAnchor(); - cur.setSelection(true); + cur.selection(true); cursorForwardOneWord(cur); cur.setSelection(); cutSelection(cur, true, false); @@ -1444,7 +1469,7 @@ void Text::deleteWordBackward(Cursor & cur) cursorBackward(cur); else { cur.resetAnchor(); - cur.setSelection(true); + cur.selection(true); cursorBackwardOneWord(cur); cur.setSelection(); cutSelection(cur, true, false); @@ -1454,20 +1479,19 @@ void Text::deleteWordBackward(Cursor & cur) // Kill to end of line. -void Text::changeCase(Cursor & cur, TextCase action) +void Text::changeCase(Cursor & cur, TextCase action, bool partial) { LBUFERR(this == cur.text()); CursorSlice from; CursorSlice to; - bool gotsel = false; - if (cur.selection()) { + bool const gotsel = cur.selection(); + if (gotsel) { from = cur.selBegin(); to = cur.selEnd(); - gotsel = true; } else { from = cur.top(); - getWord(from, to, PARTIAL_WORD); + getWord(from, to, partial ? PARTIAL_WORD : WHOLE_WORD); cursorForwardOneWord(cur); } @@ -1521,7 +1545,7 @@ bool Text::handleBibitems(Cursor & cur) // if a bibitem is deleted, merge with previous paragraph // if this is a bibliography item as well if (cur.pit() > 0 && par.layout() == prevpar.layout()) { - cur.recordUndo(ATOMIC_UNDO, prevcur.pit()); + cur.recordUndo(prevcur.pit()); mergeParagraph(bufparams, cur.text()->paragraphs(), prevcur.pit()); cur.forceBufferUpdate(); @@ -1575,7 +1599,7 @@ bool Text::erase(Cursor & cur) if (needsUpdate) { // Make sure the cursor is correct. Is this really needed? // No, not really... at least not here! - cur.text()->setCursor(cur.top(), cur.pit(), cur.pos()); + cur.top().setPitPos(cur.pit(), cur.pos()); cur.checkBufferStructure(); } @@ -1603,15 +1627,15 @@ bool Text::backspacePos0(Cursor & cur) // is it an empty paragraph? if (cur.lastpos() == 0 || (cur.lastpos() == 1 && par.isSeparator(0))) { - cur.recordUndo(ATOMIC_UNDO, prevcur.pit(), cur.pit()); - plist.erase(boost::next(plist.begin(), cur.pit())); + cur.recordUndo(prevcur.pit()); + plist.erase(lyx::next(plist.begin(), cur.pit())); needsUpdate = true; } // is previous par empty? else if (prevcur.lastpos() == 0 || (prevcur.lastpos() == 1 && prevpar.isSeparator(0))) { - cur.recordUndo(ATOMIC_UNDO, prevcur.pit(), cur.pit()); - plist.erase(boost::next(plist.begin(), prevcur.pit())); + cur.recordUndo(prevcur.pit()); + plist.erase(lyx::next(plist.begin(), prevcur.pit())); needsUpdate = true; } // Pasting is not allowed, if the paragraphs have different @@ -1622,7 +1646,7 @@ bool Text::backspacePos0(Cursor & cur) else if (par.layout() == prevpar.layout() || tclass.isDefaultLayout(par.layout()) || tclass.isPlainLayout(par.layout())) { - cur.recordUndo(ATOMIC_UNDO, prevcur.pit()); + cur.recordUndo(prevcur.pit()); mergeParagraph(bufparams, plist, prevcur.pit()); needsUpdate = true; } @@ -1648,7 +1672,7 @@ bool Text::backspace(Cursor & cur) --prev_cur.pit(); if (!prev_cur.paragraph().isMergedOnEndOfParDeletion(cur.buffer()->params().track_changes)) { - cur.recordUndo(ATOMIC_UNDO, prev_cur.pit(), prev_cur.pit()); + cur.recordUndo(prev_cur.pit(), prev_cur.pit()); prev_cur.paragraph().setChange(prev_cur.lastpos(), Change(Change::DELETED)); setCursorIntern(cur, prev_cur.pit(), prev_cur.lastpos()); return true; @@ -1682,7 +1706,7 @@ bool Text::backspace(Cursor & cur) // A singlePar update is not enough in this case. // cur.screenUpdateFlags(Update::Force); - setCursor(cur.top(), cur.pit(), cur.pos()); + cur.top().setPitPos(cur.pit(), cur.pos()); return needsUpdate; } @@ -1851,13 +1875,11 @@ docstring Text::currentState(Cursor const & cur) const Change change = par.lookupChange(cur.pos()); if (change.changed()) { - Author const & a = buf.params().authors().get(change.author); - os << _("Change: ") << a.name(); - if (!a.email().empty()) - os << " (" << a.email() << ")"; - // FIXME ctime is english, we should translate that - os << _(" at ") << ctime(&change.changetime); - os << " : "; + docstring const author = + buf.params().authors().get(change.author).nameAndEmail(); + docstring const date = formatted_datetime(change.changetime); + os << bformat(_("Changed by %1$s[[author]] on %2$s[[date]]. "), + author, date); } // I think we should only show changes from the default @@ -2011,16 +2033,25 @@ docstring Text::asString(pit_type beg, pit_type end, int options) const } -void Text::forOutliner(docstring & os, size_t maxlen, bool shorten) const +void Text::shortenForOutliner(docstring & str, size_t const maxlen) { - if (maxlen == 0) - maxlen = std::numeric_limits::max(); - else - LASSERT(maxlen >= 8, maxlen = TOC_ENTRY_LENGTH); - for (size_t i = 0; i != pars_.size() && os.length() < maxlen; ++i) - pars_[i].forOutliner(os, maxlen); - if (shorten && os.length() >= maxlen) - os = os.substr(0, maxlen - 3) + from_ascii("..."); + support::truncateWithEllipsis(str, maxlen); + docstring::iterator it = str.begin(); + docstring::iterator end = str.end(); + for (; it != end; ++it) + if ((*it) == L'\n' || (*it) == L'\t') + (*it) = L' '; +} + + +void Text::forOutliner(docstring & os, size_t const maxlen, + bool const shorten) const +{ + size_t tmplen = shorten ? maxlen + 1 : maxlen; + for (size_t i = 0; i != pars_.size() && os.length() < tmplen; ++i) + pars_[i].forOutliner(os, tmplen, false); + if (shorten) + shortenForOutliner(os, maxlen); }