X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;ds=sidebyside;f=src%2FText.cpp;h=4a447577f22c9e91d0139a739525d3a276f1538c;hb=8f3c95f7570f84effc4601e88f13ff48c6084f0c;hp=589fd25d83150d3275006e9ecb74927679841836;hpb=8b00b355ca7012ca703c78a8be89193ac6435fcb;p=lyx.git diff --git a/src/Text.cpp b/src/Text.cpp index 589fd25d83..4a447577f2 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); @@ -157,7 +133,7 @@ void breakParagraphConservative(BufferParams const & bparams, } // Move over the end-of-par change information tmp.setChange(tmp.size(), par.lookupChange(par.size())); - par.setChange(par.size(), Change(bparams.trackChanges ? + par.setChange(par.size(), Change(bparams.track_changes ? Change::INSERTED : Change::UNCHANGED)); } } @@ -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,13 +188,10 @@ 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) - it->setInsetOwner(owner); + for (auto & p : pars_) + p.setInsetOwner(owner); } @@ -265,6 +238,21 @@ bool Text::isFirstInSequence(pit_type par_offset) const } +pit_type Text::lastInSequence(pit_type pit) const +{ + depth_type const depth = pars_[pit].getDepth(); + pit_type newpit = pit; + + while (size_t(newpit + 1) < pars_.size() && + (pars_[newpit + 1].getDepth() > depth || + (pars_[newpit + 1].getDepth() == depth && + pars_[newpit + 1].layout() == pars_[pit].layout()))) + ++newpit; + + return newpit; +} + + int Text::getTocLevel(pit_type par_offset) const { Paragraph const & par = pars_[par_offset]; @@ -298,6 +286,35 @@ Font const Text::outerFont(pit_type par_offset) const } +int Text::getEndLabel(pit_type p) const +{ + pit_type pit = p; + depth_type par_depth = pars_[p].getDepth(); + while (pit != pit_type(pars_.size())) { + Layout const & layout = pars_[pit].layout(); + int const endlabeltype = layout.endlabeltype; + + if (endlabeltype != END_LABEL_NO_LABEL) { + if (p + 1 == pit_type(pars_.size())) + return endlabeltype; + + depth_type const next_depth = + pars_[p + 1].getDepth(); + if (par_depth > next_depth || + (par_depth == next_depth && layout != pars_[p + 1].layout())) + return endlabeltype; + break; + } + if (par_depth == 0) + break; + pit = outerHook(pit); + if (pit != pit_type(pars_.size())) + par_depth = pars_[pit].getDepth(); + } + return END_LABEL_NO_LABEL; +} + + static void acceptOrRejectChanges(ParagraphList & pars, BufferParams const & bparams, Text::ChangeOp op) { @@ -362,7 +379,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(); @@ -401,8 +418,8 @@ void Text::readParToken(Paragraph & par, Lexer & lex, if (added_one) { // Warn the user. docstring const s = bformat(_("Layout `%1$s' was not found."), layoutname); - errorList.push_back( - ErrorItem(_("Layout Not Found"), s, par.id(), 0, par.size())); + errorList.push_back(ErrorItem(_("Layout Not Found"), s, + {par.id(), 0}, {par.id(), -1})); } par.setLayout(bp.documentClass()[layoutname]); @@ -428,7 +445,7 @@ void Text::readParToken(Paragraph & par, Lexer & lex, lex.eatLine(); docstring line = lex.getDocString(); errorList.push_back(ErrorItem(_("Unknown Inset"), line, - par.id(), 0, par.size())); + {par.id(), 0}, {par.id(), -1})); } } else if (token == "\\family") { lex.next(); @@ -455,6 +472,9 @@ void Text::readParToken(Paragraph & par, Lexer & lex, } else if (token == "\\numeric") { lex.next(); font.fontInfo().setNumber(setLyXMisc(lex.getString())); + } else if (token == "\\nospellcheck") { + lex.next(); + font.fontInfo().setNoSpellcheck(setLyXMisc(lex.getString())); } else if (token == "\\emph") { lex.next(); font.fontInfo().setEmph(setLyXMisc(lex.getString())); @@ -474,6 +494,9 @@ void Text::readParToken(Paragraph & par, Lexer & lex, } else if (token == "\\strikeout") { lex.next(); font.fontInfo().setStrikeout(setLyXMisc(lex.getString())); + } else if (token == "\\xout") { + lex.next(); + font.fontInfo().setXout(setLyXMisc(lex.getString())); } else if (token == "\\uuline") { lex.next(); font.fontInfo().setUuline(setLyXMisc(lex.getString())); @@ -486,22 +509,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,24 +557,31 @@ 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.id(), 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"), - bformat(_("Unknown token: %1$s %2$s\n"), from_utf8(token), - lex.getDocString()), - par.id(), 0, par.size())); + bformat(_("Unknown token: %1$s %2$s\n"), + from_utf8(token), + lex.getDocString()), + {par.id(), 0}, {par.id(), -1})); } } @@ -582,7 +634,7 @@ class TextCompletionList : public CompletionList { public: /// - TextCompletionList(Cursor const & cur, WordList const * list) + TextCompletionList(Cursor const & cur, WordList const & list) : buffer_(cur.buffer()), list_(list) {} /// @@ -593,19 +645,19 @@ public: /// virtual size_t size() const { - return list_->size(); + return list_.size(); } /// virtual docstring const & data(size_t idx) const { - return list_->word(idx); + return list_.word(idx); } private: /// Buffer const * buffer_; /// - WordList const * list_; + WordList const & list_; }; @@ -639,7 +691,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]; @@ -691,7 +743,7 @@ static void breakParagraph(Text & text, pit_type par_offset, pos_type pos, // Move over the end-of-par change information tmp->setChange(tmp->size(), par.lookupChange(par.size())); - par.setChange(par.size(), Change(bparams.trackChanges ? + par.setChange(par.size(), Change(bparams.track_changes ? Change::INSERTED : Change::UNCHANGED)); if (pos) { @@ -734,12 +786,17 @@ void Text::breakParagraph(Cursor & cur, bool inverse_logic) Layout const & layout = cpar.layout(); if (cur.lastpos() == 0 && !cpar.allowEmpty()) { - if (changeDepthAllowed(cur, DEC_DEPTH)) + if (changeDepthAllowed(cur, DEC_DEPTH)) { changeDepth(cur, DEC_DEPTH); - else { + pit_type const prev = depthHook(cpit, cpar.getDepth()); + docstring const & lay = pars_[prev].layout().name(); + if (lay != layout.name()) + setLayout(cur, lay); + } else { docstring const & lay = cur.paragraph().usePlainLayout() ? tclass.plainLayoutName() : tclass.defaultLayoutName(); - setLayout(cur, lay); + if (lay != layout.name()) + setLayout(cur, lay); } return; } @@ -749,7 +806,7 @@ void Text::breakParagraph(Cursor & cur, bool inverse_logic) // Always break behind a space // It is better to erase the space (Dekel) if (cur.pos() != cur.lastpos() && cpar.isLineSeparator(cur.pos())) - cpar.eraseChar(cur.pos(), cur.buffer()->params().trackChanges); + cpar.eraseChar(cur.pos(), cur.buffer()->params().track_changes); // What should the layout for the new paragraph be? bool keep_layout = layout.isEnvironment() @@ -784,7 +841,7 @@ void Text::breakParagraph(Cursor & cur, bool inverse_logic) } while (!pars_[next_par].empty() && pars_[next_par].isNewline(0)) { - if (!pars_[next_par].eraseChar(0, cur.buffer()->params().trackChanges)) + if (!pars_[next_par].eraseChar(0, cur.buffer()->params().track_changes)) break; // the character couldn't be deleted physically due to change tracking } @@ -809,13 +866,19 @@ void Text::insertStringAsLines(Cursor & cur, docstring const & str, pit_type pit = cur.pit(); pos_type pos = cur.pos(); + // The special chars we handle + map specialchars; + specialchars[0x200c] = InsetSpecialChar::LIGATURE_BREAK; + specialchars[0x200b] = InsetSpecialChar::ALLOWBREAK; + specialchars[0x2026] = InsetSpecialChar::LDOTS; + specialchars[0x2011] = InsetSpecialChar::NOBREAKDASH; + // insert the string, don't insert doublespace bool space_inserted = true; - for (docstring::const_iterator cit = str.begin(); - cit != str.end(); ++cit) { + for (auto const & ch : str) { Paragraph & par = pars_[pit]; - if (*cit == '\n') { - if (autoBreakRows_ && (!par.empty() || par.allowEmpty())) { + if (ch == '\n') { + if (inset().allowMultiPar() && (!par.empty() || par.allowEmpty())) { lyx::breakParagraph(*this, pit, pos, par.layout().isEnvironment()); ++pit; @@ -824,29 +887,36 @@ void Text::insertStringAsLines(Cursor & cur, docstring const & str, } else { continue; } - // do not insert consecutive spaces if !free_spacing - } else if ((*cit == ' ' || *cit == '\t') && + // do not insert consecutive spaces if !free_spacing + } else if ((ch == ' ' || ch == '\t') && space_inserted && !par.isFreeSpacing()) { continue; - } else if (*cit == '\t') { + } else if (ch == '\t') { if (!par.isFreeSpacing()) { // tabs are like spaces here - par.insertChar(pos, ' ', font, bparams.trackChanges); + par.insertChar(pos, ' ', font, bparams.track_changes); ++pos; space_inserted = true; } else { - par.insertChar(pos, *cit, font, bparams.trackChanges); + par.insertChar(pos, ch, font, bparams.track_changes); ++pos; space_inserted = true; } - } else if (!isPrintable(*cit)) { - // Ignore unprintables + } else if (specialchars.find(ch) != specialchars.end()) { + par.insertInset(pos, new InsetSpecialChar(specialchars.find(ch)->second), + font, bparams.track_changes ? + Change(Change::INSERTED) + : Change(Change::UNCHANGED)); + ++pos; + space_inserted = false; + } else if (!isPrintable(ch)) { + // Ignore (other) unprintables continue; } else { // just insert the character - par.insertChar(pos, *cit, font, bparams.trackChanges); + par.insertChar(pos, ch, font, bparams.track_changes); ++pos; - space_inserted = (*cit == ' '); + space_inserted = (ch == ' '); } } setCursor(cur, pit, pos); @@ -881,12 +951,55 @@ void Text::insertStringAsParagraphs(Cursor & cur, docstring const & str, } +namespace { + +bool canInsertChar(Cursor const & cur, char_type c) +{ + Paragraph const & par = cur.paragraph(); + // If not in free spacing mode, check if there will be two blanks together or a blank at + // the beginning of a paragraph. + if (!par.isFreeSpacing() && isLineSeparatorChar(c)) { + if (cur.pos() == 0) { + cur.message(_( + "You cannot insert a space at the " + "beginning of a paragraph. Please read the Tutorial.")); + return false; + } + // If something is wrong, ignore this character. + LASSERT(cur.pos() > 0, return false); + if ((par.isLineSeparator(cur.pos() - 1) || par.isNewline(cur.pos() - 1)) + && !par.isDeleted(cur.pos() - 1)) { + cur.message(_( + "You cannot type two spaces this way. " + "Please read the Tutorial.")); + return false; + } + } + + // Prevent to insert uncodable characters in verbatim and ERT. + // The encoding is inherited from the context here. + if (par.isPassThru() && cur.getEncoding()) { + Encoding const * e = cur.getEncoding(); + if (!e->encodable(c)) { + cur.message(_("Character is uncodable in this verbatim context.")); + return false; + } + } + return true; +} + +} // namespace + + // insert a character, moves all the following breaks in the // same Paragraph one to the right and make a rebreak void Text::insertChar(Cursor & cur, char_type c) { LBUFERR(this == cur.text()); + if (!canInsertChar(cur,c)) + return; + cur.recordUndo(INSERT_UNDO); TextMetrics const & tm = cur.bv().textMetrics(this); @@ -895,20 +1008,22 @@ void Text::insertChar(Cursor & cur, char_type c) // try to remove this pit_type const pit = cur.pit(); - bool const freeSpacing = par.layout().free_spacing || - par.isFreeSpacing(); - 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(".,:"); + // European Number Separators: comma, dot etc. + // European Number Terminators: percent, permille, degree, euro etc. if (cur.current_font.fontInfo().number() == FONT_ON) { if (!isDigitASCII(c) && !contains(number_operators, c) && - !(contains(number_seperators, c) && + !(isEuropeanNumberSeparator(c) && cur.pos() != 0 && cur.pos() != cur.lastpos() && tm.displayFont(pit, cur.pos()).fontInfo().number() == FONT_ON && + tm.displayFont(pit, cur.pos() - 1).fontInfo().number() == FONT_ON) && + !(isEuropeanNumberTerminator(c) && + cur.pos() != 0 && + tm.displayFont(pit, cur.pos()).fontInfo().number() == FONT_ON && tm.displayFont(pit, cur.pos() - 1).fontInfo().number() == FONT_ON) ) number(cur); // Set current_font.number to OFF @@ -917,15 +1032,16 @@ void Text::insertChar(Cursor & cur, char_type c) number(cur); // Set current_font.number to ON if (cur.pos() != 0) { - char_type const c = par.getChar(cur.pos() - 1); - if (contains(number_unary_operators, c) && + char_type const ch = par.getChar(cur.pos() - 1); + if (contains(number_unary_operators, ch) && (cur.pos() == 1 || par.isSeparator(cur.pos() - 2) + || par.isEnvSeparator(cur.pos() - 2) || par.isNewline(cur.pos() - 2)) ) { setCharFont(pit, cur.pos() - 1, cur.current_font, tm.font_); - } else if (contains(number_seperators, c) + } else if (isEuropeanNumberSeparator(ch) && cur.pos() >= 2 && tm.displayFont(pit, cur.pos() - 2).fontInfo().number() == FONT_ON) { setCharFont(pit, cur.pos() - 1, cur.current_font, @@ -979,50 +1095,31 @@ void Text::insertChar(Cursor & cur, char_type c) } } - // Next check, if there will be two blanks together or a blank at - // the beginning of a paragraph. - // I decided to handle blanks like normal characters, the main - // difference are the special checks when calculating the row.fill - // (blank does not count at the end of a row) and the check here - - // When the free-spacing option is set for the current layout, - // disable the double-space checking - if (!freeSpacing && isLineSeparatorChar(c)) { - if (cur.pos() == 0) { - cur.message(_( - "You cannot insert a space at the " - "beginning of a paragraph. Please read the Tutorial.")); - return; - } - // LASSERT: Is it safe to continue here? - LASSERT(cur.pos() > 0, /**/); - if ((par.isLineSeparator(cur.pos() - 1) || par.isNewline(cur.pos() - 1)) - && !par.isDeleted(cur.pos() - 1)) { - cur.message(_( - "You cannot type two spaces this way. " - "Please read the Tutorial.")); - return; - } - } - - // Prevent to insert uncodable characters in verbatim and ERT - // (workaround for bug 9012) - if (cur.paragraph().isPassThru() && cur.current_font.language()) { - Encoding const * e = cur.current_font.language()->encoding(); - if (!e->encodable(c)) { - cur.message(_("Character is uncodable in verbatim paragraphs.")); - return; + 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--; } } - par.insertChar(cur.pos(), c, cur.current_font, - cur.buffer()->params().trackChanges); + 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); } @@ -1031,15 +1128,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) @@ -1064,7 +1152,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 @@ -1074,7 +1162,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)) @@ -1097,6 +1185,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); } @@ -1110,8 +1202,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. @@ -1147,7 +1245,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; @@ -1156,9 +1253,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 @@ -1184,7 +1281,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; @@ -1193,9 +1289,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 @@ -1222,7 +1318,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()); @@ -1272,8 +1368,7 @@ void Text::acceptOrRejectChanges(Cursor & cur, ChangeOp op) LBUFERR(this == cur.text()); if (!cur.selection()) { - bool const changed = cur.paragraph().isChanged(cur.pos()); - if (!(changed && findNextChange(&cur.bv()))) + if (!selectChange(cur)) return; } @@ -1286,10 +1381,9 @@ void Text::acceptOrRejectChanges(Cursor & cur, ChangeOp op) pos_type endPos = cur.selectionEnd().pos(); // keep selection info, because endPos becomes invalid after the first loop - bool endsBeforeEndOfPar = (endPos < pars_[endPit].size()); + bool const endsBeforeEndOfPar = (endPos < pars_[endPit].size()); // first, accept/reject changes within each individual paragraph (do not consider end-of-par) - for (pit_type pit = begPit; pit <= endPit; ++pit) { pos_type parSize = pars_[pit].size(); @@ -1306,8 +1400,8 @@ void Text::acceptOrRejectChanges(Cursor & cur, ChangeOp op) if (pit == endPit && endPos == 0) break; // last iteration anyway - pos_type left = (pit == begPit ? begPos : 0); - pos_type right = (pit == endPit ? endPos : parSize); + pos_type const left = (pit == begPit ? begPos : 0); + pos_type const right = (pit == endPit ? endPos : parSize); if (left == right) // there is no change here @@ -1365,10 +1459,7 @@ void Text::acceptOrRejectChanges(Cursor & cur, ChangeOp op) } // finally, invoke the DEPM - - deleteEmptyParagraphMechanism(begPit, endPit, cur.buffer()->params().trackChanges); - - // + deleteEmptyParagraphMechanism(begPit, endPit, cur.buffer()->params().track_changes); cur.finishUndo(); cur.clearSelection(); @@ -1382,7 +1473,7 @@ void Text::acceptChanges() { BufferParams const & bparams = owner_->buffer().params(); lyx::acceptChanges(pars_, bparams); - deleteEmptyParagraphMechanism(0, pars_.size() - 1, bparams.trackChanges); + deleteEmptyParagraphMechanism(0, pars_.size() - 1, bparams.track_changes); } @@ -1418,57 +1509,60 @@ void Text::rejectChanges() } // finally, invoke the DEPM - deleteEmptyParagraphMechanism(0, pars_size - 1, bparams.trackChanges); + deleteEmptyParagraphMechanism(0, pars_size - 1, bparams.track_changes); } -void Text::deleteWordForward(Cursor & cur) +void Text::deleteWordForward(Cursor & cur, bool const force) { LBUFERR(this == cur.text()); if (cur.lastpos() == 0) cursorForward(cur); else { cur.resetAnchor(); - cur.setSelection(true); + cur.selection(true); cursorForwardOneWord(cur); cur.setSelection(); - cutSelection(cur, true, false); - cur.checkBufferStructure(); + if (force || !cur.confirmDeletion()) { + cutSelection(cur, false); + cur.checkBufferStructure(); + } } } -void Text::deleteWordBackward(Cursor & cur) +void Text::deleteWordBackward(Cursor & cur, bool const force) { LBUFERR(this == cur.text()); if (cur.lastpos() == 0) cursorBackward(cur); else { cur.resetAnchor(); - cur.setSelection(true); + cur.selection(true); cursorBackwardOneWord(cur); cur.setSelection(); - cutSelection(cur, true, false); - cur.checkBufferStructure(); + if (force || !cur.confirmDeletion()) { + cutSelection(cur, false); + cur.checkBufferStructure(); + } } } // 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); } @@ -1522,7 +1616,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(); @@ -1548,7 +1642,7 @@ bool Text::erase(Cursor & cur) // any paragraphs cur.recordUndo(DELETE_UNDO); bool const was_inset = cur.paragraph().isInset(cur.pos()); - if(!par.eraseChar(cur.pos(), cur.buffer()->params().trackChanges)) + if(!par.eraseChar(cur.pos(), cur.buffer()->params().track_changes)) // the character has been logically deleted only => skip it cur.top().forwardPos(); @@ -1561,7 +1655,8 @@ bool Text::erase(Cursor & cur) if (cur.pit() == cur.lastpit()) return dissolveInset(cur); - if (!par.isMergedOnEndOfParDeletion(cur.buffer()->params().trackChanges)) { + if (!par.isMergedOnEndOfParDeletion(cur.buffer()->params().track_changes)) { + cur.recordUndo(DELETE_UNDO); par.setChange(cur.pos(), Change(Change::DELETED)); cur.forwardPos(); needsUpdate = true; @@ -1576,7 +1671,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(); } @@ -1604,17 +1699,18 @@ 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; } + // FIXME: Do we really not want to allow this??? // Pasting is not allowed, if the paragraphs have different // layouts. I think it is a real bug of all other // word processors to allow it. It confuses the user. @@ -1623,7 +1719,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,8 +1744,9 @@ bool Text::backspace(Cursor & cur) Cursor prev_cur = cur; --prev_cur.pit(); - if (!prev_cur.paragraph().isMergedOnEndOfParDeletion(cur.buffer()->params().trackChanges)) { - cur.recordUndo(ATOMIC_UNDO, prev_cur.pit(), prev_cur.pit()); + if (cur.paragraph().size() > 0 + && !prev_cur.paragraph().isMergedOnEndOfParDeletion(cur.buffer()->params().track_changes)) { + 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; @@ -1669,7 +1766,7 @@ bool Text::backspace(Cursor & cur) setCursorIntern(cur, cur.pit(), cur.pos() - 1, false, cur.boundary()); bool const was_inset = cur.paragraph().isInset(cur.pos()); - cur.paragraph().eraseChar(cur.pos(), cur.buffer()->params().trackChanges); + cur.paragraph().eraseChar(cur.pos(), cur.buffer()->params().track_changes); if (was_inset) cur.forceBufferUpdate(); else @@ -1682,8 +1779,8 @@ bool Text::backspace(Cursor & cur) needsUpdate |= handleBibitems(cur); // A singlePar update is not enough in this case. -// cur.screenUpdateFlags(Update::Force); - setCursor(cur.top(), cur.pit(), cur.pos()); + // cur.screenUpdateFlags(Update::Force); + cur.top().setPitPos(cur.pit(), cur.pos()); return needsUpdate; } @@ -1699,20 +1796,24 @@ bool Text::dissolveInset(Cursor & cur) cur.recordUndoInset(); cur.setMark(false); cur.selHandle(false); - // save position + // save position inside inset pos_type spos = cur.pos(); pit_type spit = cur.pit(); ParagraphList plist; if (cur.lastpit() != 0 || cur.lastpos() != 0) plist = paragraphs(); cur.popBackward(); - // store cursor offset + // update cursor offset if (spit == 0) spos += cur.pos(); spit += cur.pit(); - Buffer & b = *cur.buffer(); - cur.paragraph().eraseChar(cur.pos(), b.params().trackChanges); + // remember position outside inset to delete inset later + // we do not do it now to avoid memory reuse issues (see #10667). + DocIterator inset_it = cur; + // jump over inset + ++cur.pos(); + Buffer & b = *cur.buffer(); if (!plist.empty()) { // see bug 7319 // we clear the cache so that we won't get conflicts with labels @@ -1723,27 +1824,44 @@ bool Text::dissolveInset(Cursor & cur) // but we'll try the cheaper solution here. cur.buffer()->clearReferenceCache(); + if (!lyxrc.ct_markup_copied) + // Do not revive deleted text + lyx::acceptChanges(plist, b.params()); + // ERT paragraphs have the Language latex_language. // This is invalid outside of ERT, so we need to // change it to the buffer language. - ParagraphList::iterator it = plist.begin(); - ParagraphList::iterator it_end = plist.end(); - for (; it != it_end; ++it) - it->changeLanguage(b.params(), latex_language, b.language()); + for (auto & p : plist) + p.changeLanguage(b.params(), latex_language, b.language()); + + /* If the inset is the only thing in paragraph and the layout + * is not plain, then the layout of the first paragraph of + * inset should be remembered. + * FIXME: this does not work as expected when change tracking + * is on However, we do not really know what to do in this + * case. + */ + DocumentClass const & tclass = cur.buffer()->params().documentClass(); + if (inset_it.lastpos() == 1 + && plist[0].layout().name() != tclass.plainLayoutName()) + cur.paragraph().makeSameLayout(plist[0]); pasteParagraphList(cur, plist, b.params().documentClassPtr(), b.errorList("Paste")); - // restore position - cur.pit() = min(cur.lastpit(), spit); - cur.pos() = min(cur.lastpos(), spos); } - cur.forceBufferUpdate(); + // delete the inset now + inset_it.paragraph().eraseChar(inset_it.pos(), b.params().track_changes); + // restore position + cur.pit() = min(cur.lastpit(), spit); + cur.pos() = min(cur.lastpos(), spos); // Ensure the current language is set correctly (bug 6292) cur.text()->setCursor(cur, cur.pit(), cur.pos()); cur.clearSelection(); cur.resetAnchor(); + cur.forceBufferUpdate(); + return true; } @@ -1839,26 +1957,24 @@ bool Text::read(Lexer & lex, // Returns the current font and depth as a message. -docstring Text::currentState(Cursor const & cur) const +docstring Text::currentState(CursorData const & cur, bool devel_mode) const { LBUFERR(this == cur.text()); Buffer & buf = *cur.buffer(); Paragraph const & par = cur.paragraph(); odocstringstream os; - if (buf.params().trackChanges) + if (buf.params().track_changes) os << _("[Change Tracking] "); 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 @@ -1898,34 +2014,68 @@ docstring Text::currentState(Cursor const & cur) const } } -#ifdef DEVEL_VERSION - os << _(", Inset: ") << &cur.inset(); - os << _(", Paragraph: ") << cur.pit(); - os << _(", Id: ") << par.id(); - os << _(", Position: ") << cur.pos(); - // FIXME: Why is the check for par.size() needed? - // We are called with cur.pos() == par.size() quite often. - if (!par.empty() && cur.pos() < par.size()) { - // Force output of code point, not character - size_t const c = par.getChar(cur.pos()); - os << _(", Char: 0x") << hex << c; + if (devel_mode) { + os << _(", Inset: ") << &cur.inset(); + if (cur.lastidx() > 0) + os << _(", Cell: ") << cur.idx(); + os << _(", Paragraph: ") << cur.pit(); + os << _(", Id: ") << par.id(); + os << _(", Position: ") << cur.pos(); + // FIXME: Why is the check for par.size() needed? + // We are called with cur.pos() == par.size() quite often. + if (!par.empty() && cur.pos() < par.size()) { + // Force output of code point, not character + size_t const c = par.getChar(cur.pos()); + os << _(", Char: 0x") << hex << c; + } + os << _(", Boundary: ") << cur.boundary(); +// Row & row = cur.textRow(); +// os << bformat(_(", Row b:%1$d e:%2$d"), row.pos(), row.endpos()); } - os << _(", Boundary: ") << cur.boundary(); -// Row & row = cur.textRow(); -// os << bformat(_(", Row b:%1$d e:%2$d"), row.pos(), row.endpos()); -#endif return os.str(); } -docstring Text::getPossibleLabel(Cursor const & cur) const +docstring Text::getPossibleLabel(DocIterator const & cur) const { - pit_type pit = cur.pit(); + pit_type textpit = cur.pit(); + Layout const * layout = &(pars_[textpit].layout()); + + // Will contain the label prefix. + docstring name; + + // For captions, we just take the caption type + Inset * caption_inset = cur.innerInsetOfType(CAPTION_CODE); + if (caption_inset) { + string const & ftype = static_cast(caption_inset)->floattype(); + FloatList const & fl = cur.buffer()->params().documentClass().floats(); + if (fl.typeExist(ftype)) { + Floating const & flt = fl.getType(ftype); + name = from_utf8(flt.refPrefix()); + } + if (name.empty()) + name = from_utf8(ftype.substr(0,3)); + } else { + // For section, subsection, etc... + if (layout->latextype == LATEX_PARAGRAPH && textpit != 0) { + Layout const * layout2 = &(pars_[textpit - 1].layout()); + if (layout2->latextype != LATEX_PARAGRAPH) { + --textpit; + layout = layout2; + } + } + if (layout->latextype != LATEX_PARAGRAPH) + name = layout->refprefix; - Layout const * layout = &(pars_[pit].layout()); + // If none of the above worked, see if the inset knows. + if (name.empty()) { + InsetLayout const & il = cur.inset().getLayout(); + name = il.refprefix(); + } + } docstring text; - docstring par_text = pars_[pit].asString(); + docstring par_text = pars_[textpit].asString(AS_STR_SKIPDELETE); // The return string of math matrices might contain linebreaks par_text = subst(par_text, '\n', '-'); @@ -1946,46 +2096,13 @@ docstring Text::getPossibleLabel(Cursor const & cur) const if (text.size() > max_label_length) text.resize(max_label_length); - // Will contain the label prefix. - docstring name; - - // For section, subsection, etc... - if (layout->latextype == LATEX_PARAGRAPH && pit != 0) { - Layout const * layout2 = &(pars_[pit - 1].layout()); - if (layout2->latextype != LATEX_PARAGRAPH) { - --pit; - layout = layout2; - } - } - if (layout->latextype != LATEX_PARAGRAPH) - name = layout->refprefix; - - // For captions, we just take the caption type - Inset * caption_inset = cur.innerInsetOfType(CAPTION_CODE); - if (caption_inset) { - string const & ftype = static_cast(caption_inset)->floattype(); - FloatList const & fl = cur.buffer()->params().documentClass().floats(); - if (fl.typeExist(ftype)) { - Floating const & flt = fl.getType(ftype); - name = from_utf8(flt.refPrefix()); - } - if (name.empty()) - name = from_utf8(ftype.substr(0,3)); - } - - // If none of the above worked, see if the inset knows. - if (name.empty()) { - InsetLayout const & il = cur.inset().getLayout(); - name = il.refprefix(); - } - if (!name.empty()) text = name + ':' + text; // We need a unique label docstring label = text; int i = 1; - while (cur.buffer()->insetLabel(label)) { + while (cur.buffer()->activeLabel(label)) { label = text + '-' + convert(i); ++i; } @@ -2012,16 +2129,41 @@ 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); + for (char_type & c : str) + if (c == L'\n' || c == L'\t') + c = L' '; +} + + +void Text::forOutliner(docstring & os, size_t const maxlen, + bool const shorten) const +{ + pit_type end = pars_.size() - 1; + if (0 <= end && !pars_[0].labelString().empty()) + os += pars_[0].labelString() + ' '; + forOutliner(os, maxlen, 0, end, shorten); +} + + +void Text::forOutliner(docstring & os, size_t const maxlen, + pit_type pit_start, pit_type pit_end, + bool const shorten) const +{ + size_t tmplen = shorten ? maxlen + 1 : maxlen; + pit_type end = min(size_t(pit_end), pars_.size() - 1); + bool first = true; + for (pit_type i = pit_start; i <= end && os.length() < tmplen; ++i) { + if (!first) + os += ' '; + // This function lets the first label be treated separately + pars_[i].forOutliner(os, tmplen, false, !first); + first = false; + } + if (shorten) + shortenForOutliner(os, maxlen); } @@ -2067,7 +2209,7 @@ void Text::charsTranspose(Cursor & cur) // And finally, we are ready to perform the transposition. // Track the changes if Change Tracking is enabled. - bool const trackChanges = cur.buffer()->params().trackChanges; + bool const trackChanges = cur.buffer()->params().track_changes; cur.recordUndo(); @@ -2112,7 +2254,8 @@ docstring Text::previousWord(CursorSlice const & sl) const bool Text::completionSupported(Cursor const & cur) const { Paragraph const & par = cur.paragraph(); - return cur.pos() > 0 + return !cur.selection() + && cur.pos() > 0 && (cur.pos() >= par.size() || par.isWordSeparator(cur.pos())) && !par.isWordSeparator(cur.pos() - 1); } @@ -2120,7 +2263,7 @@ bool Text::completionSupported(Cursor const & cur) const CompletionList const * Text::createCompletionList(Cursor const & cur) const { - WordList const * list = theWordList(cur.getFont().language()->lang()); + WordList const & list = theWordList(cur.getFont().language()->lang()); return new TextCompletionList(cur, list); }