X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2FParagraph.cpp;h=8ac87d31c2d2060cfa08f56d30d417a110c02023;hb=7a000652c02f98dcff4d3c611b22af244409df73;hp=92f6ea129bcca1dbc2d025a5ee4ae5e544246130;hpb=012c0f6bacfa1aeaa4bf58c23e1ad1802c967267;p=lyx.git diff --git a/src/Paragraph.cpp b/src/Paragraph.cpp index 92f6ea129b..8ac87d31c2 100644 --- a/src/Paragraph.cpp +++ b/src/Paragraph.cpp @@ -5,7 +5,7 @@ * * \author Asger Alstrup * \author Lars Gullik Bjønnes - * \author Richard Heck (XHTML output) + * \author Richard Kimberly Heck (XHTML output) * \author Jean-Marc Lasgouttes * \author Angus Leeming * \author John Levon @@ -20,12 +20,11 @@ #include "Paragraph.h" -#include "LayoutFile.h" #include "Buffer.h" #include "BufferParams.h" +#include "BufferEncodings.h" #include "Changes.h" #include "Counters.h" -#include "BufferEncodings.h" #include "InsetList.h" #include "Language.h" #include "LaTeXFeatures.h" @@ -38,17 +37,18 @@ #include "output_xhtml.h" #include "output_docbook.h" #include "ParagraphParameters.h" +#include "Session.h" #include "SpellChecker.h" -#include "xml.h" #include "texstream.h" -#include "TextClass.h" #include "TexRow.h" #include "Text.h" +#include "TextClass.h" #include "WordLangTuple.h" #include "WordList.h" #include "frontends/alert.h" +#include "insets/InsetArgument.h" #include "insets/InsetBibitem.h" #include "insets/InsetLabel.h" #include "insets/InsetSpecialChar.h" @@ -61,10 +61,8 @@ #include "support/ExceptionMessage.h" #include "support/gettext.h" #include "support/lassert.h" -#include "support/Length.h" #include "support/lstrings.h" #include "support/textutils.h" -#include "output_docbook.h" #include #include @@ -310,7 +308,7 @@ public: /// \return the number of characters written. int latexSurrogatePair(BufferParams const &, otexstream & os, char_type c, char_type next, - OutputParams const &); + OutputParams const &) const; /// Output a space in appropriate formatting (or a surrogate pair /// if the next character is a combining character). @@ -321,7 +319,7 @@ public: pos_type i, unsigned int & column, Font const & font, - Layout const & style); + Layout const & style) const; /// This could go to ParagraphParameters if we want to. int startTeXParParams(BufferParams const &, otexstream &, @@ -342,7 +340,10 @@ public: Change & running_change, Layout const & style, pos_type & i, - unsigned int & column); + unsigned int & column, + bool const fontswitch_inset, + bool const closeLanguage, + bool const lang_switched_at_inset) const; /// void latexSpecialChar( @@ -354,26 +355,26 @@ public: Layout const & style, pos_type & i, pos_type end_pos, - unsigned int & column); + unsigned int & column) const; /// bool latexSpecialT1( char_type const c, otexstream & os, pos_type i, - unsigned int & column); + unsigned int & column) const; /// bool latexSpecialTU( char_type const c, otexstream & os, pos_type i, - unsigned int & column); + unsigned int & column) const; /// bool latexSpecialT3( char_type const c, otexstream & os, pos_type i, - unsigned int & column); + unsigned int & column) const; /// void validate(LaTeXFeatures & features) const; @@ -401,7 +402,7 @@ public: return speller_change_number > speller_state_.currentChangeNumber(); } - bool ignoreWord(docstring const & word) const ; + bool ignoreWord(docstring const & word) const; void setMisspelled(pos_type from, pos_type to, SpellChecker::Result state) { @@ -460,10 +461,11 @@ public: return numskips; } - void markMisspelledWords(pos_type const & first, pos_type const & last, - SpellChecker::Result result, - docstring const & word, - SkipPositions const & skips); + void markMisspelledWords(Language const * lang, + pos_type const & first, pos_type const & last, + SpellChecker::Result result, + docstring const & word, + SkipPositions const & skips); InsetCode ownerCode() const { @@ -566,6 +568,50 @@ Paragraph::Private::Private(Private const & p, Paragraph * owner, } +///////////////////////////////////////////////////////////////////// +// +// Paragraph +// +///////////////////////////////////////////////////////////////////// + +namespace { + +/** This helper class should be instantiated at the start of methods + * that can create or merge changes. If as a result the value of + * Paragraph::isChanged is modified, it makes sure that updateBuffer() + * will be run. + */ +struct ChangesMonitor { + /// + ChangesMonitor(Paragraph & par) + : par_(par), was_changed_(par.isChanged()) {} + /// + ~ChangesMonitor() + { + /* We may need to run updateBuffer to check whether the buffer + * contains changes (and toggle the changes toolbar). We do it + * when: + * 1. the `changedness' of the paragraph has changed, + * 2. and we are not in the situation where the buffer has changes + * and new changes are added to the paragraph. + */ + try { + if (par_.isChanged() != was_changed_ + && par_.inInset().isBufferValid() + && !(par_.inInset().buffer().areChangesPresent() && par_.isChanged())) + par_.inInset().buffer().forceUpdate(); + } catch(support::ExceptionMessage const &) {} + } + +private: + /// + Paragraph const & par_; + /// + bool was_changed_; +}; + +} + void Paragraph::addChangesToToc(DocIterator const & cdit, Buffer const & buf, bool output_active, TocBackend & backend) const { @@ -631,6 +677,9 @@ Change Paragraph::parEndChange() const void Paragraph::setChange(Change const & change) { + // Make sure that Buffer::hasChangesPresent is updated + ChangesMonitor cm(*this); + // beware of the imaginary end-of-par character! d->changes_.set(change, 0, size() + 1); @@ -657,6 +706,9 @@ void Paragraph::setChange(Change const & change) void Paragraph::setChange(pos_type pos, Change const & change) { + // Make sure that Buffer::hasChangesPresent is updated + ChangesMonitor cm(*this); + LASSERT(pos >= 0 && pos <= size(), return); d->changes_.set(change, pos); @@ -676,6 +728,9 @@ Change const & Paragraph::lookupChange(pos_type pos) const void Paragraph::acceptChanges(pos_type start, pos_type end) { + // Make sure that Buffer::hasChangesPresent is updated + ChangesMonitor cm(*this); + LASSERT(start >= 0 && start <= size(), return); LASSERT(end > start && end <= size() + 1, return); @@ -714,6 +769,9 @@ void Paragraph::rejectChanges(pos_type start, pos_type end) LASSERT(start >= 0 && start <= size(), return); LASSERT(end > start && end <= size() + 1, return); + // Make sure that Buffer::hasChangesPresent is updated + ChangesMonitor cm(*this); + for (pos_type pos = start; pos < end; ++pos) { switch (lookupChange(pos).type) { case Change::UNCHANGED: @@ -749,6 +807,9 @@ void Paragraph::Private::insertChar(pos_type pos, char_type c, { LASSERT(pos >= 0 && pos <= int(text_.size()), return); + // Make sure that Buffer::hasChangesPresent is updated + ChangesMonitor cm(*owner_); + // track change changes_.insert(change, pos); @@ -772,6 +833,10 @@ void Paragraph::Private::insertChar(pos_type pos, char_type c, // Update list of misspelled positions speller_state_.increasePosAfterPos(pos); + + // Update bookmarks + theSession().bookmarks().adjustPosAfterPos(inset_owner_->buffer().fileName(), + id_, pos, 1); } @@ -781,6 +846,9 @@ bool Paragraph::insertInset(pos_type pos, Inset * inset, LASSERT(inset, return false); LASSERT(pos >= 0 && pos <= size(), return false); + // Make sure that Buffer::hasChangesPresent is updated + ChangesMonitor cm(*this); + // Paragraph::insertInset() can be used in cut/copy/paste operation where // d->inset_owner_ is not set yet. if (d->inset_owner_ && !d->inset_owner_->insetAllowed(inset->lyxCode())) @@ -803,6 +871,9 @@ bool Paragraph::eraseChar(pos_type pos, bool trackChanges) { LASSERT(pos >= 0 && pos <= size(), return false); + // Make sure that Buffer::hasChangesPresent is updated + ChangesMonitor cm(*this); + // keep the logic here in sync with the logic of isMergedOnEndOfParDeletion() if (trackChanges) { @@ -850,6 +921,10 @@ bool Paragraph::eraseChar(pos_type pos, bool trackChanges) d->speller_state_.decreasePosAfterPos(pos); d->speller_state_.refreshLast(size()); + // Update bookmarks + theSession().bookmarks().adjustPosAfterPos(d->inset_owner_->buffer().fileName(), + d->id_, pos, -1); + return true; } @@ -867,10 +942,11 @@ int Paragraph::eraseChars(pos_type start, pos_type end, bool trackChanges) return end - i; } + // Handle combining characters int Paragraph::Private::latexSurrogatePair(BufferParams const & bparams, otexstream & os, char_type c, char_type next, - OutputParams const & runparams) + OutputParams const & runparams) const { // Writing next here may circumvent a possible font change between // c and next. Since next is only output if it forms a surrogate pair @@ -907,7 +983,7 @@ bool Paragraph::Private::simpleTeXBlanks(BufferParams const & bparams, pos_type i, unsigned int & column, Font const & font, - Layout const & style) + Layout const & style) const { if (style.pass_thru || runparams.pass_thru) return false; @@ -957,7 +1033,10 @@ void Paragraph::Private::latexInset(BufferParams const & bparams, Change & running_change, Layout const & style, pos_type & i, - unsigned int & column) + unsigned int & column, + bool const fontswitch_inset, + bool const closeLanguage, + bool const lang_switched_at_inset) const { Inset * inset = owner_->getInset(i); LBUFERR(inset); @@ -1027,24 +1106,10 @@ void Paragraph::Private::latexInset(BufferParams const & bparams, close = true; } - if (open_font && (!inset->inheritFont() || inset->allowMultiPar())) { - // Some insets cannot be inside a font change command. - // However, even such insets *can* be placed in \L or \R - // or their equivalents (for RTL language switches), - // so we don't close the language in those cases - // (= differing isRightToLeft()). - // ArabTeX, though, doesn't seem to handle this special behavior. - bool const inRLSwitch = - basefont.isRightToLeft() != running_font.isRightToLeft() - && basefont.language()->lang() != "arabic_arabtex" - && running_font.language()->lang() != "arabic_arabtex"; - // Having said that, PassThru insets must be inside a font change command, - // as we do not re-open the font inside. So: - bool const closeLanguage = !inset->isPassThru() && !inRLSwitch; + if (open_font && fontswitch_inset) { bool lang_closed = false; - bool lang_switched_at_inset = false; // Close language if needed - if (closeLanguage) { + if (closeLanguage && !lang_switched_at_inset) { // We need prev_font here as language changes directly at inset // will only be started inside the inset. Font const prev_font = (i > 0) ? @@ -1058,20 +1123,7 @@ void Paragraph::Private::latexInset(BufferParams const & bparams, needPar, closeLanguage); column += count; lang_closed = count > 0; - lang_switched_at_inset = prev_font.language() != running_font.language(); } - // Now re-do font changes in a way needed here - // (using switches with multi-par insets) - InsetText const * textinset = inset->asInsetText(); - bool const cprotect = textinset - ? textinset->hasCProtectContent(runparams.moving_arg) - && !textinset->text().isMainText() - : false; - unsigned int count2 = running_font.latexWriteStartChanges(os, bparams, - runparams, basefont, - running_font, true, - cprotect); - column += count2; // Update the running_font, making sure, however, // to leave the language as it was. // FIXME: probably a better way to keep track of the old @@ -1081,11 +1133,26 @@ void Paragraph::Private::latexInset(BufferParams const & bparams, running_font = basefont; if (!closeLanguage) running_font.setLanguage(copy_font.language()); + OutputParams rp = runparams; + rp.encoding = basefont.language()->encoding(); // For these, we use switches, so they should be taken as // base inside the inset. basefont.fontInfo().setSize(copy_font.fontInfo().size()); basefont.fontInfo().setFamily(copy_font.fontInfo().family()); basefont.fontInfo().setSeries(copy_font.fontInfo().series()); + // Now re-do font changes in a way needed here + // (using switches with multi-par insets) + InsetText const * textinset = inset->asInsetText(); + bool const cprotect = textinset + ? textinset->hasCProtectContent(runparams.moving_arg) + && !textinset->text().isMainText() + : false; + unsigned int count2 = basefont.latexWriteStartChanges(os, bparams, + rp, running_font, + basefont, true, + cprotect); + open_font = true; + column += count2; if (count2 == 0 && (lang_closed || lang_switched_at_inset)) // All fonts closed open_font = false; @@ -1093,6 +1160,12 @@ void Paragraph::Private::latexInset(BufferParams const & bparams, runparams.local_font = &basefont; } + if (fontswitch_inset && !closeLanguage) { + // The directionality has been switched at inset. + // Force markup inside. + runparams.local_font = &basefont; + } + size_t const previous_row_count = os.texrow().rows(); try { @@ -1103,7 +1176,7 @@ void Paragraph::Private::latexInset(BufferParams const & bparams, // add location information and throw again. e.par_id = id_; e.pos = i; - throw(e); + throw; } if (close) @@ -1129,14 +1202,24 @@ void Paragraph::Private::latexSpecialChar(otexstream & os, Layout const & style, pos_type & i, pos_type end_pos, - unsigned int & column) + unsigned int & column) const { char_type const c = owner_->getUChar(bparams, runparams, i); - if (style.pass_thru || runparams.pass_thru + if (style.pass_thru || runparams.pass_thru || (runparams.for_searchAdv != OutputParams::NoSearch) || contains(style.pass_thru_chars, c) || contains(runparams.pass_thru_chars, c)) { - if (c != '\0') { + if (runparams.for_searchAdv != OutputParams::NoSearch) { + if (c == '\\') + os << "\\\\"; + else if (c == '{') + os << "\\braceleft "; + else if (c == '}') + os << "\\braceright "; + else if (c != '\0') + os.put(c); + } + else if (c != '\0') { Encoding const * const enc = runparams.encoding; if (enc && !enc->encodable(c)) throw EncodingException(c); @@ -1240,7 +1323,7 @@ void Paragraph::Private::latexSpecialChar(otexstream & os, && !runparams.inIPA // TODO #10961: && not in inset Flex Code // TODO #10961: && not in layout LyXCode - && (!bparams.useNonTeXFonts || runparams.flavor != OutputParams::XETEX)) { + && (!bparams.useNonTeXFonts || runparams.flavor != Flavor::XeTeX)) { if (c == 0x2013) { // en-dash os << "--"; @@ -1321,7 +1404,7 @@ void Paragraph::Private::latexSpecialChar(otexstream & os, bool Paragraph::Private::latexSpecialT1(char_type const c, otexstream & os, - pos_type i, unsigned int & column) + pos_type i, unsigned int & column) const { switch (c) { case '>': @@ -1349,7 +1432,7 @@ bool Paragraph::Private::latexSpecialT1(char_type const c, otexstream & os, bool Paragraph::Private::latexSpecialTU(char_type const c, otexstream & os, - pos_type i, unsigned int & column) + pos_type i, unsigned int & column) const { // TU encoding is currently on par with T1. return latexSpecialT1(c, os, i, column); @@ -1357,7 +1440,7 @@ bool Paragraph::Private::latexSpecialTU(char_type const c, otexstream & os, bool Paragraph::Private::latexSpecialT3(char_type const c, otexstream & os, - pos_type /*i*/, unsigned int & column) + pos_type /*i*/, unsigned int & column) const { switch (c) { case '*': @@ -1394,7 +1477,7 @@ void Paragraph::Private::validate(LaTeXFeatures & features) const features.addPreambleSnippet(os.release(), true); } - if (features.runparams().flavor == OutputParams::HTML + if (features.runparams().flavor == Flavor::Html && layout_->htmltitle()) { features.setHTMLTitle(owner_->asString(AS_STR_INSETS | AS_STR_SKIPDELETE)); } @@ -1489,7 +1572,7 @@ void Paragraph::Private::validate(LaTeXFeatures & features) const } else if (!bp.use_dash_ligatures && (c == 0x2013 || c == 0x2014) && bp.useNonTeXFonts - && features.runparams().flavor == OutputParams::XETEX) + && features.runparams().flavor == Flavor::XeTeX) // XeTeX's dash behaviour is determined via a global setting features.require("xetexdashbreakstate"); BufferEncodings::validate(c, features); @@ -1565,19 +1648,19 @@ void flushString(ostream & os, docstring & s) void Paragraph::write(ostream & os, BufferParams const & bparams, - depth_type & dth) const + depth_type & depth) const { // The beginning or end of a deeper (i.e. nested) area? - if (dth != d->params_.depth()) { - if (d->params_.depth() > dth) { - while (d->params_.depth() > dth) { + if (depth != d->params_.depth()) { + if (d->params_.depth() > depth) { + while (d->params_.depth() > depth) { os << "\n\\begin_deeper"; - ++dth; + ++depth; } } else { - while (d->params_.depth() < dth) { + while (d->params_.depth() < depth) { os << "\n\\end_deeper"; - --dth; + --depth; } } } @@ -1690,17 +1773,20 @@ void Paragraph::validate(LaTeXFeatures & features) const } -void Paragraph::insert(pos_type start, docstring const & str, +void Paragraph::insert(pos_type pos, docstring const & str, Font const & font, Change const & change) { for (size_t i = 0, n = str.size(); i != n ; ++i) - insertChar(start + i, str[i], font, change); + insertChar(pos + i, str[i], font, change); } void Paragraph::appendChar(char_type c, Font const & font, Change const & change) { + // Make sure that Buffer::hasChangesPresent is updated + ChangesMonitor cm(*this); + // track change d->changes_.insert(change, d->text_.size()); // when appending characters, no need to update tables @@ -1713,6 +1799,9 @@ void Paragraph::appendChar(char_type c, Font const & font, void Paragraph::appendString(docstring const & s, Font const & font, Change const & change) { + // Make sure that Buffer::hasChangesPresent is updated + ChangesMonitor cm(*this); + pos_type end = s.size(); size_t oldsize = d->text_.size(); size_t newsize = oldsize + end; @@ -2437,7 +2526,9 @@ void Paragraph::latex(BufferParams const & bparams, // Do we have an open font change? bool open_font = false; - Change runningChange = Change(Change::UNCHANGED); + Change runningChange = + runparams.inDeletedInset && !inInset().canTrackChanges() + ? runparams.changeOfDeletedInset : Change(Change::UNCHANGED); Encoding const * const prev_encoding = runparams.encoding; @@ -2482,7 +2573,7 @@ void Paragraph::latex(BufferParams const & bparams, runparams); runningChange = Change(Change::UNCHANGED); - os << "}] "; + os << (isEnvSeparator(i) ? "}]~" : "}] "); column +=3; } // For InTitle commands, we have already opened a group @@ -2511,10 +2602,15 @@ void Paragraph::latex(BufferParams const & bparams, char_type const c = d->text_[i]; // Check whether a display math inset follows + bool output_changes; + if (runparams.for_searchAdv == OutputParams::NoSearch) + output_changes = bparams.output_changes; + else + output_changes = (runparams.for_searchAdv == OutputParams::SearchWithDeleted); if (c == META_INSET && i >= start_pos && (end_pos == -1 || i < end_pos)) { if (isDeleted(i)) - runparams.ctObject = getInset(i)->CtObject(runparams); + runparams.ctObject = getInset(i)->getCtObject(runparams); InsetMath const * im = getInset(i)->asInsetMath(); if (im && im->asHullInset() @@ -2526,7 +2622,7 @@ void Paragraph::latex(BufferParams const & bparams, // cannot set it here because it is a counter. deleted_display_math = isDeleted(i); } - if (bparams.output_changes && deleted_display_math + if (output_changes && deleted_display_math && runningChange == change && change.type == Change::DELETED && !os.afterParbreak()) { @@ -2549,7 +2645,7 @@ void Paragraph::latex(BufferParams const & bparams, } } - if (bparams.output_changes && runningChange != change) { + if (output_changes && runningChange != change) { if (!alien_script.empty()) { column += 1; os << "}"; @@ -2572,34 +2668,56 @@ void Paragraph::latex(BufferParams const & bparams, // do not output text which is marked deleted // if change tracking output is disabled - if (!bparams.output_changes && change.deleted()) { + if (!output_changes && change.deleted()) { continue; } ++column; // Fully instantiated font - Font const current_font = getFont(bparams, i, outerfont); + Font current_font = getFont(bparams, i, outerfont); // Previous font Font const prev_font = (i > 0) ? getFont(bparams, i - 1, outerfont) : current_font; Font const last_font = running_font; - bool const in_ct_deletion = (bparams.output_changes + bool const in_ct_deletion = (output_changes && runningChange == change && change.type == Change::DELETED && !os.afterParbreak()); - bool const multipar_inset = - (c == META_INSET && getInset(i) && getInset(i)->allowMultiPar()); + // Insets where font switches are used (rather than font commands) + bool const fontswitch_inset = + c == META_INSET + && getInset(i) + && getInset(i)->allowMultiPar() + && getInset(i)->lyxCode() != ERT_CODE + && getInset(i)->producesOutput(); + + bool closeLanguage = false; + bool lang_switched_at_inset = false; + if (fontswitch_inset) { + // Some insets cannot be inside a font change command. + // However, even such insets *can* be placed in \L or \R + // or their equivalents (for RTL language switches), + // so we don't close the language in those cases + // (= differing isRightToLeft()). + // ArabTeX, though, doesn't seem to handle this special behavior. + closeLanguage = basefont.isRightToLeft() == current_font.isRightToLeft() + || basefont.language()->lang() == "arabic_arabtex" + || current_font.language()->lang() == "arabic_arabtex"; + // We need to check prev_font as language changes directly at inset + // will only be started inside the inset. + lang_switched_at_inset = prev_font.language() != current_font.language(); + } // Do we need to close the previous font? + bool langClosed = false; if (open_font && ((current_font != running_font || current_font.language() != running_font.language()) - || (multipar_inset - && (current_font == prev_font - || current_font.language() == prev_font.language())))) + || (fontswitch_inset + && (current_font == prev_font)))) { // ensure there is no open script-wrapper if (!alien_script.empty()) { @@ -2607,17 +2725,20 @@ void Paragraph::latex(BufferParams const & bparams, os << "}"; alien_script.clear(); } - bool needPar = false; if (in_ct_deletion) { // We have to close and then reopen \lyxdeleted, // as strikeout needs to be on lowest level. os << '}'; column += 1; } + if (closeLanguage) + // Force language closing + current_font.setLanguage(basefont.language()); + Font const nextfont = (i == body_pos-1) ? basefont : current_font; + bool needPar = false; column += running_font.latexWriteEndChanges( os, bparams, runparams, basefont, - (i == body_pos-1) ? basefont : current_font, - needPar); + nextfont, needPar); if (in_ct_deletion) { // We have to close and then reopen \lyxdeleted, // as strikeout needs to be on lowest level. @@ -2625,8 +2746,12 @@ void Paragraph::latex(BufferParams const & bparams, column += Changes::latexMarkChange(os, bparams, Change(Change::UNCHANGED), Change(Change::DELETED), rp); } - running_font = basefont; open_font = false; + // Has the language been closed in the latexWriteEndChanges() call above? + langClosed = running_font.language() != basefont.language() + && running_font.language() != nextfont.language() + && (running_font.language()->encoding()->package() != Encoding::CJK); + running_font = basefont; } // if necessary, close language environment before opening CJK @@ -2643,7 +2768,8 @@ void Paragraph::latex(BufferParams const & bparams, } // Switch file encoding if necessary (and allowed) - if (!runparams.pass_thru && !style.pass_thru && + if ((!fontswitch_inset || closeLanguage) + && !runparams.pass_thru && !style.pass_thru && runparams.encoding->package() != Encoding::none && current_font.language()->encoding()->package() != Encoding::none) { pair const enc_switch = @@ -2673,19 +2799,19 @@ void Paragraph::latex(BufferParams const & bparams, current_font.language() != running_font.language()) && i != body_pos - 1) { - if (in_ct_deletion) { - // We have to close and then reopen \lyxdeleted, - // as strikeout needs to be on lowest level. - bool needPar = false; - OutputParams rp = runparams; - column += running_font.latexWriteEndChanges( - os, bparams, rp, basefont, - basefont, needPar); - os << '}'; - column += 1; - } - otexstringstream ots; - if (!multipar_inset) { + if (!fontswitch_inset) { + if (in_ct_deletion) { + // We have to close and then reopen \lyxdeleted, + // as strikeout needs to be on lowest level. + OutputParams rp = runparams; + bool needPar = false; + column += running_font.latexWriteEndChanges( + os, bparams, rp, basefont, + basefont, needPar); + os << '}'; + column += 1; + } + otexstringstream ots; InsetText const * textinset = inInset().asInsetText(); bool const cprotect = textinset ? textinset->hasCProtectContent(runparams.moving_arg) @@ -2694,35 +2820,38 @@ void Paragraph::latex(BufferParams const & bparams, column += current_font.latexWriteStartChanges(ots, bparams, runparams, basefont, last_font, false, cprotect); - } - // Check again for display math in ulem commands as a - // font change may also occur just before a math inset. - if (runparams.inDisplayMath && !deleted_display_math - && runparams.inulemcmd) { - if (os.afterParbreak()) - os << "\\noindent"; - else - os << "\\\\\n"; - } - running_font = current_font; - open_font = true; - docstring fontchange = ots.str(); - os << fontchange; - // check whether the fontchange ends with a \\textcolor - // modifier and the text starts with a space. If so we - // need to add } in order to prevent \\textcolor from gobbling - // the space (bug 4473). - docstring const last_modifier = rsplit(fontchange, '\\'); - if (prefixIs(last_modifier, from_ascii("textcolor")) && c == ' ') - os << from_ascii("{}"); - else if (ots.terminateCommand()) - os << termcmd; - if (in_ct_deletion) { - // We have to close and then reopen \lyxdeleted, - // as strikeout needs to be on lowest level. - OutputParams rp = runparams; - column += Changes::latexMarkChange(os, bparams, - Change(Change::UNCHANGED), change, rp); + // Check again for display math in ulem commands as a + // font change may also occur just before a math inset. + if (runparams.inDisplayMath && !deleted_display_math + && runparams.inulemcmd) { + if (os.afterParbreak()) + os << "\\noindent"; + else + os << "\\\\\n"; + } + running_font = current_font; + open_font = true; + docstring fontchange = ots.str(); + os << fontchange; + // check whether the fontchange ends with a \\textcolor + // modifier and the text starts with a space. If so we + // need to add } in order to prevent \\textcolor from gobbling + // the space (bug 4473). + docstring const last_modifier = rsplit(fontchange, '\\'); + if (prefixIs(last_modifier, from_ascii("textcolor")) && c == ' ') + os << from_ascii("{}"); + else if (ots.terminateCommand()) + os << termcmd; + if (in_ct_deletion) { + // We have to close and then reopen \lyxdeleted, + // as strikeout needs to be on lowest level. + OutputParams rp = runparams; + column += Changes::latexMarkChange(os, bparams, + Change(Change::UNCHANGED), change, rp); + } + } else { + running_font = current_font; + open_font = !langClosed; } } @@ -2785,28 +2914,36 @@ void Paragraph::latex(BufferParams const & bparams, incremented = true; } } - // We need to restore these after insets with + // We need to restore parts of this after insets with // allowMultiPar() true - Font const save_running_font = running_font; Font const save_basefont = basefont; d->latexInset(bparams, os, rp, running_font, basefont, real_outerfont, open_font, - runningChange, style, i, column); - if (multipar_inset) { - running_font = save_running_font; - basefont = save_basefont; + runningChange, style, i, column, fontswitch_inset, + closeLanguage, lang_switched_at_inset); + if (fontswitch_inset) { + if (open_font) { + bool needPar = false; + column += running_font.latexWriteEndChanges( + os, bparams, runparams, + basefont, basefont, needPar); + open_font = false; + } + basefont.fontInfo().setSize(save_basefont.fontInfo().size()); + basefont.fontInfo().setFamily(save_basefont.fontInfo().family()); + basefont.fontInfo().setSeries(save_basefont.fontInfo().series()); } if (incremented) --parInline; - if (runparams.ctObject == OutputParams::CT_DISPLAYOBJECT - || runparams.ctObject == OutputParams::CT_UDISPLAYOBJECT) { + if (runparams.ctObject == CtObject::DisplayObject + || runparams.ctObject == CtObject::UDisplayObject) { // Close \lyx*deleted and force its // reopening (if needed) os << '}'; column++; runningChange = Change(Change::UNCHANGED); - runparams.ctObject = OutputParams::CT_NORMAL; + runparams.ctObject = CtObject::Normal; } } } else if (i >= start_pos && (end_pos == -1 || i < end_pos)) { @@ -2838,7 +2975,7 @@ void Paragraph::latex(BufferParams const & bparams, // add location information and throw again. e.par_id = id(); e.pos = i; - throw(e); + throw; } } } @@ -2866,6 +3003,31 @@ void Paragraph::latex(BufferParams const & bparams, alien_script.clear(); } + Font const font = empty() + ? getLayoutFont(bparams, real_outerfont) + : getFont(bparams, size() - 1, real_outerfont); + + InsetText const * textinset = inInset().asInsetText(); + + bool const maintext = textinset + ? textinset->text().isMainText() + : false; + + size_t const numpars = textinset + ? textinset->text().paragraphs().size() + : 0; + + bool needPar = false; + + if (style.resfont.size() != font.fontInfo().size() + && (!runparams.isLastPar || maintext + || (numpars > 1 && d->ownerCode() != CELL_CODE + && (inInset().getLayout().isDisplay() + || parInline))) + && !style.isCommand()) { + needPar = true; + } + // If we have an open font definition, we have to close it if (open_font) { // Make sure that \\par is done with the font of the last @@ -2877,31 +3039,6 @@ void Paragraph::latex(BufferParams const & bparams, // We must not change the font for the last paragraph // of non-multipar insets, tabular cells or commands, // since this produces unwanted whitespace. - - Font const font = empty() - ? getLayoutFont(bparams, real_outerfont) - : getFont(bparams, size() - 1, real_outerfont); - - InsetText const * textinset = inInset().asInsetText(); - - bool const maintext = textinset - ? textinset->text().isMainText() - : false; - - size_t const numpars = textinset - ? textinset->text().paragraphs().size() - : 0; - - bool needPar = false; - - if (style.resfont.size() != font.fontInfo().size() - && (!runparams.isLastPar || maintext - || (numpars > 1 && d->ownerCode() != CELL_CODE - && (inInset().getLayout().isDisplay() - || parInline))) - && !style.isCommand()) { - needPar = true; - } #ifdef FIXED_LANGUAGE_END_DETECTION if (next_) { running_font.latexWriteEndChanges(os, bparams, @@ -2919,15 +3056,16 @@ void Paragraph::latex(BufferParams const & bparams, running_font.latexWriteEndChanges(os, bparams, runparams, basefont, basefont, needPar); #endif - if (needPar) { - // The \par could not be inserted at the same nesting - // level of the font size change, so do it now. - os << "{\\" << font.latexSize() << "\\par}"; - } + } + if (needPar) { + // The \par could not be inserted at the same nesting + // level of the font size change, so do it now. + os << "{\\" << font.latexSize() << "\\par}"; } - column += Changes::latexMarkChange(os, bparams, runningChange, - Change(Change::UNCHANGED), runparams); + if (!runparams.inDeletedInset || inInset().canTrackChanges()) + column += Changes::latexMarkChange(os, bparams, runningChange, + Change(Change::UNCHANGED), runparams); // Needed if there is an optional argument but no contents. if (body_pos > 0 && body_pos == size()) { @@ -3292,71 +3430,137 @@ std::tuple, vector> computeDocBookFontSwit } // anonymous namespace -void Paragraph::simpleDocBookOnePar(Buffer const & buf, - XMLStream & xs, - OutputParams const & runparams, - Font const & outerfont, - bool start_paragraph, bool close_paragraph, - pos_type initial) const +std::tuple, std::vector, std::vector> + Paragraph::simpleDocBookOnePar(Buffer const & buf, + OutputParams const & runparams, + Font const & outerfont, + pos_type initial, + bool is_last_par, + bool ignore_fonts) const { - // track whether we have opened these tags - DocBookFontState fs; - - if (start_paragraph) - xs.startDivision(allowEmpty()); - - Layout const & style = *d->layout_; - FontInfo font_old = - style.labeltype == LABEL_MANUAL ? style.labelfont : style.font; - - string const default_family = - buf.masterBuffer()->params().fonts_default_family; - - vector tagsToOpen; - vector tagsToClose; + std::vector prependedParagraphs; + std::vector generatedParagraphs; + std::vector appendedParagraphs; + odocstringstream os; - // parsing main loop + // If there is an argument that must be output before the main tag, do it before handling the rest of the paragraph. + // Also tag all arguments that shouldn't go in the main content right now, so that they are never generated at the + // wrong place. + OutputParams rp = runparams; + for (pos_type i = initial; i < size(); ++i) { + if (getInset(i) && getInset(i)->lyxCode() == ARG_CODE) { + const InsetArgument * arg = getInset(i)->asInsetArgument(); + if (arg->docbookargumentbeforemaintag()) { + auto xs_local = XMLStream(os); + arg->docbook(xs_local, rp); + + prependedParagraphs.push_back(os.str()); + os.str(from_ascii("")); + + rp.docbook_prepended_arguments.insert(arg); + } else if (arg->docbookargumentaftermaintag()) { + rp.docbook_appended_arguments.insert(arg); + } + } + } + + // State variables for the main loop. + auto xs = new XMLStream(os); // XMLStream has no copy constructor: to create a new object, the only solution + // is to hold a pointer to the XMLStream (xs = XMLStream(os) is not allowed once the first object is built). + std::vector delayedChars; // When a font tag ends with a space, output it after the closing font tag. + // This requires to store delayed characters at some point. + + DocBookFontState fs; // Track whether we have opened font tags + DocBookFontState old_fs = fs; + + Layout const & style = *d->layout_; + FontInfo font_old = style.labeltype == LABEL_MANUAL ? style.labelfont : style.font; + string const default_family = buf.masterBuffer()->params().fonts_default_family; + + vector tagsToOpen; + vector tagsToClose; + + // Parsing main loop. for (pos_type i = initial; i < size(); ++i) { - // let's not show deleted material in the output + bool ignore_fonts_i = ignore_fonts + || style.docbooknofontinside() + || (getInset(i) && getInset(i)->getLayout().docbooknofontinside()); + + // Don't show deleted material in the output. if (isDeleted(i)) continue; - Font const font = getFont(buf.masterBuffer()->params(), i, outerfont); - - // Determine which tags should be opened or closed. - tie(tagsToOpen, tagsToClose) = computeDocBookFontSwitch(font_old, font, default_family, fs); - - // FIXME XHTML - // Other such tags? What about the other text ranges? - - vector::const_iterator cit = tagsToClose.begin(); - vector::const_iterator cen = tagsToClose.end(); - for (; cit != cen; ++cit) - xs << *cit; - - vector::const_iterator sit = tagsToOpen.begin(); - vector::const_iterator sen = tagsToOpen.end(); - for (; sit != sen; ++sit) - xs << *sit; + // If this is an InsetNewline, generate a new paragraph. Also reset the fonts, so that tags are closed in + // this paragraph. + if (getInset(i) && getInset(i)->lyxCode() == NEWLINE_CODE) { + if (!ignore_fonts_i) + xs->closeFontTags(); + + // Output one paragraph (i.e. one string entry in generatedParagraphs). + generatedParagraphs.push_back(os.str()); + + // Create a new XMLStream for the new paragraph, completely independent from the previous one. This implies + // that the string stream must be reset. + os.str(from_ascii("")); + delete xs; + xs = new XMLStream(os); + + // Restore the fonts for the new paragraph, so that the right tags are opened for the new entry. + if (!ignore_fonts_i) { + font_old = outerfont.fontInfo(); + fs = old_fs; + } + } - tagsToClose.clear(); - tagsToOpen.clear(); + // Determine which tags should be opened or closed regarding fonts. + Font const font = getFont(buf.masterBuffer()->params(), i, outerfont); + tie(tagsToOpen, tagsToClose) = computeDocBookFontSwitch(font_old, font, default_family, fs); + + if (!ignore_fonts_i) { + vector::const_iterator cit = tagsToClose.begin(); + vector::const_iterator cen = tagsToClose.end(); + for (; cit != cen; ++cit) + *xs << *cit; + } + + // Deal with the delayed characters *after* closing font tags. + if (!delayedChars.empty()) { + for (char_type c: delayedChars) + *xs << c; + delayedChars.clear(); + } + + if (!ignore_fonts_i) { + vector::const_iterator sit = tagsToOpen.begin(); + vector::const_iterator sen = tagsToOpen.end(); + for (; sit != sen; ++sit) + *xs << *sit; + + tagsToClose.clear(); + tagsToOpen.clear(); + } + // Finally, write the next character or inset. if (Inset const * inset = getInset(i)) { - if (!runparams.for_toc || inset->isInToc()) { - OutputParams np = runparams; + bool inset_is_argument_elsewhere = getInset(i)->asInsetArgument() && + rp.docbook_appended_arguments.find(inset->asInsetArgument()) != rp.docbook_appended_arguments.end() && + rp.docbook_prepended_arguments.find(inset->asInsetArgument()) != rp.docbook_prepended_arguments.end(); + + if ((!rp.for_toc || inset->isInToc()) && !inset_is_argument_elsewhere) { + // Arguments may need to be output + OutputParams np = rp; np.local_font = &font; - // If the paragraph has size 1, then we are in the "special - // case" where we do not output the containing paragraph info. - // This "special case" is defined in more details in output_docbook.cpp, makeParagraphs. The results - // of that brittle logic is passed to this function through open_par. - if (!inset->getLayout().htmlisblock() && size() != 1) // TODO: htmlisblock here too! - np.docbook_in_par = true; - inset->docbook(xs, np); + + // TODO: special case will bite here. + np.docbook_in_par = true; + inset->docbook(*xs, np); } } else { - char_type c = getUChar(buf.masterBuffer()->params(), runparams, i); - xs << c; + char_type c = getUChar(buf.masterBuffer()->params(), rp, i); + if (lyx::isSpace(c) && !ignore_fonts) + delayedChars.push_back(c); + else + *xs << c; } font_old = font.fontInfo(); } @@ -3364,11 +3568,40 @@ void Paragraph::simpleDocBookOnePar(Buffer const & buf, // FIXME, this code is just imported from XHTML // I'm worried about what happens if a branch, say, is itself // wrapped in some font stuff. I think that will not work. - xs.closeFontTags(); - if (runparams.docbook_in_listing) - xs << xml::CR(); - if (close_paragraph) - xs.endDivision(); + if (!ignore_fonts) + xs->closeFontTags(); + + // Deal with the delayed characters *after* closing font tags. + if (!delayedChars.empty()) + for (char_type c: delayedChars) + *xs << c; + + // In listings, new lines (i.e. \n characters in the output) are very important. Avoid generating one for the + // last line to get a clean output. + if (rp.docbook_in_listing && !is_last_par) + *xs << xml::CR(); + + // Finalise the last (and most likely only) paragraph. + generatedParagraphs.push_back(os.str()); + os.str(from_ascii("")); + delete xs; + + // If there is an argument that must be output after the main tag, do it after handling the rest of the paragraph. + for (pos_type i = initial; i < size(); ++i) { + if (getInset(i) && getInset(i)->lyxCode() == ARG_CODE) { + const InsetArgument * arg = getInset(i)->asInsetArgument(); + if (arg->docbookargumentaftermaintag()) { + // Don't use rp, as this argument would not generate anything. + auto xs_local = XMLStream(os); + arg->docbook(xs_local, runparams); + + appendedParagraphs.push_back(os.str()); + os.str(from_ascii("")); + } + } + } + + return std::make_tuple(prependedParagraphs, generatedParagraphs, appendedParagraphs); } @@ -3803,8 +4036,8 @@ bool Paragraph::isHardHyphenOrApostrophe(pos_type pos) const char_type const c = d->text_[pos]; if (c != '-' && c != '\'') return false; - int nextpos = pos + 1; - int prevpos = pos > 0 ? pos - 1 : 0; + pos_type nextpos = pos + 1; + pos_type prevpos = pos > 0 ? pos - 1 : 0; if ((nextpos == psize || isSpace(nextpos)) && (pos == 0 || isSpace(prevpos))) return false; @@ -3845,9 +4078,6 @@ bool Paragraph::needsCProtection(bool const fragile) const Inset const * ins = getInset(i); if (ins->needsCProtection(maintext, fragile)) return true; - if (ins->getLayout().latextype() == InsetLayout::ENVIRONMENT) - // Environments need cprotection regardless the content - return true; // Now check math environments InsetMath const * im = getInset(i)->asInsetMath(); if (!im || im->cell(0).empty()) @@ -3973,6 +4203,14 @@ docstring Paragraph::asString(pos_type beg, pos_type end, int options, const Out if (c == META_INSET && (options & AS_STR_PLAINTEXT)) { LASSERT(runparams != nullptr, return docstring()); getInset(i)->plaintext(os, *runparams); + } else if (c == META_INSET && (options & AS_STR_MATHED) + && getInset(i)->lyxCode() == REF_CODE) { + Buffer const & buf = getInset(i)->buffer(); + OutputParams rp(&buf.params().encoding()); + Font const font(inherit_font, buf.params().language); + rp.local_font = &font; + otexstream ots(os); + getInset(i)->latex(ots, rp); } else { getInset(i)->toString(os); } @@ -4047,12 +4285,12 @@ void Paragraph::setPlainLayout(DocumentClass const & tc) } -void Paragraph::setPlainOrDefaultLayout(DocumentClass const & tclass) +void Paragraph::setPlainOrDefaultLayout(DocumentClass const & tc) { if (usePlainLayout()) - setPlainLayout(tclass); + setPlainLayout(tc); else - setDefaultLayout(tclass); + setDefaultLayout(tc); } @@ -4281,10 +4519,8 @@ void Paragraph::changeCase(BufferParams const & bparams, pos_type pos, } int erasePos = pos - changes.size(); - for (size_t i = 0; i < changes.size(); i++) { - insertChar(pos, changes[i].first, - changes[i].second, - trackChanges); + for (auto const & change : changes) { + insertChar(pos, change.first, change.second, trackChanges); if (!eraseChar(erasePos, trackChanges)) { ++erasePos; ++pos; // advance @@ -4304,24 +4540,42 @@ int Paragraph::find(docstring const & str, bool cs, bool mw, int i = 0; pos_type const parsize = d->text_.size(); for (i = 0; i < strsize && pos < parsize; ++i, ++pos) { + // ignore deleted matter + if (!del && isDeleted(pos)) { + if (pos == parsize - 1) + break; + pos++; + --i; + continue; + } // Ignore "invisible" letters such as ligature breaks // and hyphenation chars while searching - while (pos < parsize - 1 && isInset(pos)) { + bool nonmatch = false; + while (pos < parsize && isInset(pos)) { Inset const * inset = getInset(pos); - if (!inset->isLetter()) + if (!inset->isLetter() && !inset->isChar()) break; odocstringstream os; inset->toString(os); - if (!os.str().empty()) - break; + docstring const insetstring = os.str(); + if (!insetstring.empty()) { + int const insetstringsize = insetstring.length(); + for (int j = 0; j < insetstringsize && pos < parsize; ++i, ++j) { + if ((cs && str[i] != insetstring[j]) + || (!cs && uppercase(str[i]) != uppercase(insetstring[j]))) { + nonmatch = true; + break; + } + } + } pos++; } + if (nonmatch || i == strsize) + break; if (cs && str[i] != d->text_[pos]) break; if (!cs && uppercase(str[i]) != uppercase(d->text_[pos])) break; - if (!del && isDeleted(pos)) - break; } if (i != strsize) @@ -4507,7 +4761,7 @@ Language * Paragraph::Private::locateSpellRange( // hop to end of word while (last < to && !owner_->isWordSeparator(last)) { Inset const * inset = owner_->getInset(last); - if (inset && inset->lyxCode() == SPECIALCHAR_CODE) { + if (inset && dynamic_cast(inset)) { // check for "invisible" letters such as ligature breaks odocstringstream os; inset->toString(os); @@ -4616,7 +4870,9 @@ SpellChecker::Result Paragraph::spellCheck(pos_type & from, pos_type & to, docstring word = asString(from, to, AS_STR_INSETS | AS_STR_SKIPDELETE); Language * lang = d->getSpellLanguage(from); - if (getFontSettings(d->inset_owner_->buffer().params(), from).fontInfo().nospellcheck() == FONT_ON) + BufferParams const & bparams = d->inset_owner_->buffer().params(); + + if (getFontSettings(bparams, from).fontInfo().nospellcheck() == FONT_ON) return result; wl = WordLangTuple(word, lang); @@ -4628,10 +4884,10 @@ SpellChecker::Result Paragraph::spellCheck(pos_type & from, pos_type & to, pos_type end = to; if (!d->ignoreWord(word)) { bool const trailing_dot = to < size() && d->text_[to] == '.'; - result = speller->check(wl); + result = speller->check(wl, bparams.spellignore()); if (SpellChecker::misspelled(result) && trailing_dot) { wl = WordLangTuple(word.append(from_ascii(".")), lang); - result = speller->check(wl); + result = speller->check(wl, bparams.spellignore()); if (!SpellChecker::misspelled(result)) { LYXERR(Debug::GUI, "misspelled word is correct with dot: \"" << word << "\" [" << @@ -4678,6 +4934,7 @@ void Paragraph::anonymize() void Paragraph::Private::markMisspelledWords( + Language const * lang, pos_type const & first, pos_type const & last, SpellChecker::Result result, docstring const & word, @@ -4699,8 +4956,9 @@ void Paragraph::Private::markMisspelledWords( int wlen = 0; speller->misspelledWord(index, wstart, wlen); /// should not happen if speller supports range checks - if (!wlen) continue; - docstring const misspelled = word.substr(wstart, wlen); + if (!wlen) + continue; + WordLangTuple const candidate(word.substr(wstart, wlen), lang); wstart += first + numskipped; if (snext < wstart) { /// mark the range of correct spelling @@ -4709,12 +4967,21 @@ void Paragraph::Private::markMisspelledWords( wstart - 1, SpellChecker::WORD_OK); } snext = wstart + wlen; + // Check whether the candidate is in the document's local dict + SpellChecker::Result actresult = result; + if (inset_owner_->buffer().params().spellignored(candidate)) + actresult = SpellChecker::DOCUMENT_LEARNED_WORD; numskipped += countSkips(it, et, snext); /// mark the range of misspelling - setMisspelled(wstart, snext, result); - LYXERR(Debug::GUI, "misspelled word: \"" << - misspelled << "\" [" << - wstart << ".." << (snext-1) << "]"); + setMisspelled(wstart, snext, actresult); + if (actresult == SpellChecker::DOCUMENT_LEARNED_WORD) + LYXERR(Debug::GUI, "local dictionary word: \"" << + candidate.word() << "\" [" << + wstart << ".." << (snext-1) << "]"); + else + LYXERR(Debug::GUI, "misspelled word: \"" << + candidate.word() << "\" [" << + wstart << ".." << (snext-1) << "]"); ++snext; } if (snext <= last) { @@ -4743,9 +5010,10 @@ void Paragraph::spellCheck() const // start the spell checker on the unit of meaning docstring word = asString(first, last, AS_STR_INSETS + AS_STR_SKIPDELETE); WordLangTuple wl = WordLangTuple(word, lang); - SpellChecker::Result result = word.size() ? - speller->check(wl) : SpellChecker::WORD_OK; - d->markMisspelledWords(first, last, result, word, skips); + BufferParams const & bparams = d->inset_owner_->buffer().params(); + SpellChecker::Result result = !word.empty() ? + speller->check(wl, bparams.spellignore()) : SpellChecker::WORD_OK; + d->markMisspelledWords(lang, first, last, result, word, skips); first = ++last; } } else {