X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2FParagraph.cpp;h=4ce94415f796f23e1f83a60a378e1257d1d77145;hb=fd5adacef25eb40f813dedd961920c353448213a;hp=8a634a6b0c9d4fcbaf0293f90156e460988149ba;hpb=ec387b6d65142c2c3aaf7e9510475d9af9224833;p=features.git diff --git a/src/Paragraph.cpp b/src/Paragraph.cpp index 8a634a6b0c..4ce94415f7 100644 --- a/src/Paragraph.cpp +++ b/src/Paragraph.cpp @@ -37,6 +37,7 @@ #include "output_xhtml.h" #include "output_docbook.h" #include "ParagraphParameters.h" +#include "Session.h" #include "SpellChecker.h" #include "texstream.h" #include "TexRow.h" @@ -47,6 +48,7 @@ #include "frontends/alert.h" +#include "insets/InsetArgument.h" #include "insets/InsetBibitem.h" #include "insets/InsetLabel.h" #include "insets/InsetSpecialChar.h" @@ -56,6 +58,7 @@ #include "support/debug.h" #include "support/docstring_list.h" +#include "support/ExceptionMessage.h" #include "support/gettext.h" #include "support/lassert.h" #include "support/lstrings.h" @@ -68,8 +71,8 @@ using namespace std; using namespace lyx::support; -// OSX clang, gcc < 4.8.0, and msvc < 2015 do not support C++11 thread_local -#if defined(__APPLE__) || (defined(__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ < 8) +// OSX clang and msvc < 2015 do not support C++11 thread_local +#if defined(__APPLE__) #define THREAD_LOCAL_STATIC static __thread #elif defined(_MSC_VER) && (_MSC_VER < 1900) #define THREAD_LOCAL_STATIC static __declspec(thread) @@ -305,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). @@ -316,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 &, @@ -340,7 +343,7 @@ public: unsigned int & column, bool const fontswitch_inset, bool const closeLanguage, - bool const lang_switched_at_inset); + bool const lang_switched_at_inset) const; /// void latexSpecialChar( @@ -352,7 +355,7 @@ public: Layout const & style, pos_type & i, pos_type end_pos, - unsigned int & column); + unsigned int & column) const; /// bool latexSpecialT1( @@ -399,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) { @@ -458,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 { @@ -564,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 { @@ -629,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); @@ -655,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); @@ -674,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); @@ -712,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: @@ -747,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); @@ -770,6 +833,11 @@ void Paragraph::Private::insertChar(pos_type pos, char_type c, // Update list of misspelled positions speller_state_.increasePosAfterPos(pos); + + // Update bookmarks + if (inset_owner_ && inset_owner_->isBufferValid()) + theSession().bookmarks().adjustPosAfterPos(inset_owner_->buffer().fileName(), + id_, pos, 1); } @@ -779,6 +847,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())) @@ -801,6 +872,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) { @@ -848,6 +922,11 @@ bool Paragraph::eraseChar(pos_type pos, bool trackChanges) d->speller_state_.decreasePosAfterPos(pos); d->speller_state_.refreshLast(size()); + // Update bookmarks + if (d->inset_owner_ && d->inset_owner_->isBufferValid()) + theSession().bookmarks().adjustPosAfterPos(d->inset_owner_->buffer().fileName(), + d->id_, pos, -1); + return true; } @@ -869,7 +948,7 @@ int Paragraph::eraseChars(pos_type start, pos_type end, bool trackChanges) // 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 @@ -906,7 +985,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; @@ -959,7 +1038,7 @@ void Paragraph::Private::latexInset(BufferParams const & bparams, unsigned int & column, bool const fontswitch_inset, bool const closeLanguage, - bool const lang_switched_at_inset) + bool const lang_switched_at_inset) const { Inset * inset = owner_->getInset(i); LBUFERR(inset); @@ -1069,6 +1148,7 @@ void Paragraph::Private::latexInset(BufferParams const & bparams, bool const cprotect = textinset ? textinset->hasCProtectContent(runparams.moving_arg) && !textinset->text().isMainText() + && inset->lyxCode() != BRANCH_CODE : false; unsigned int count2 = basefont.latexWriteStartChanges(os, bparams, rp, running_font, @@ -1125,30 +1205,45 @@ 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 || runparams.for_search + if (style.pass_thru || runparams.pass_thru || runparams.find_effective() || contains(style.pass_thru_chars, c) || contains(runparams.pass_thru_chars, c)) { - if (runparams.for_search) { - if (c == '\\') + if (runparams.find_effective()) { + switch (c) { + case '\\': os << "\\\\"; - else if (c == '{') - os << "\\braceleft"; - else if (c == '}') - os << "\\braceright"; - else if (c != '\0') + return; + case '{': + os << "\\braceleft "; + return; + case '}': + os << "\\braceright "; + return; + case '$': + os << "\\lyxdollar "; + return; + case '~': + os << "\\lyxtilde "; + return; + case ' ': + case '\0': + break; + default: os.put(c); + return; + } } else if (c != '\0') { Encoding const * const enc = runparams.encoding; if (enc && !enc->encodable(c)) throw EncodingException(c); os.put(c); + return; } - return; } // TIPA uses its own T3 encoding @@ -1160,7 +1255,7 @@ void Paragraph::Private::latexSpecialChar(otexstream & os, // non-standard font encoding. If we are using such a language, // we do not output special T1 chars. if (!runparams.inIPA && !running_font.language()->internalFontEncoding() - && !runparams.isFullUnicode() && bparams.main_font_encoding() == "T1" + && !runparams.isFullUnicode() && runparams.main_fontenc == "T1" && latexSpecialT1(c, os, i, column)) return; // NOTE: "fontspec" (non-TeX fonts) sets the font encoding to "TU" (untill 2017 "EU1" or "EU2") @@ -1452,12 +1547,12 @@ void Paragraph::Private::validate(LaTeXFeatures & features) const if (c == 0x0022) { if (features.runparams().isFullUnicode() && bp.useNonTeXFonts) features.require("textquotedblp"); - else if (bp.main_font_encoding() != "T1" + else if (features.runparams().main_fontenc != "T1" || ((&owner_->getFontSettings(bp, i))->language()->internalFontEncoding())) features.require("textquotedbl"); - } else if (ci.textfeature() && contains(ci.textpreamble(), '=')) { + } else if (ci.textFeature() && contains(ci.textPreamble(), '=')) { // features that depend on the font or input encoding - string feats = ci.textpreamble(); + string feats = ci.textPreamble(); string fontenc = (&owner_->getFontSettings(bp, i))->language()->fontenc(bp); if (fontenc.empty()) fontenc = features.runparams().main_fontenc; @@ -1707,6 +1802,9 @@ void Paragraph::insert(pos_type pos, docstring const & str, 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 @@ -1719,6 +1817,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; @@ -1826,7 +1927,8 @@ FontSpan Paragraph::fontSpan(pos_type pos) const // This should not happen, but if so, we take no chances. LYXERR0("Paragraph::fontSpan: position not found in fontinfo table!"); - LASSERT(false, return FontSpan(pos, pos)); + LASSERT(false, /**/); + return FontSpan(pos, pos); } @@ -2182,6 +2284,22 @@ bool Paragraph::isPassThru() const return inInset().isPassThru() || d->layout_->pass_thru; } + +bool Paragraph::parbreakIsNewline() const +{ + return inInset().getLayout().parbreakIsNewline() || d->layout_->parbreak_is_newline; +} + + +bool Paragraph::isPartOfTextSequence() const +{ + for (pos_type i = 0; i < size(); ++i) { + if (!isInset(i) || getInset(i)->isPartOfTextSequence()) + return true; + } + return false; +} + namespace { // paragraphs inside floats need different alignment tags to avoid @@ -2253,13 +2371,31 @@ int Paragraph::Private::startTeXParParams(BufferParams const & bparams, (layout_->toggle_indent != ITOGGLE_NEVER) : (layout_->toggle_indent == ITOGGLE_ALWAYS); - if (canindent && params_.noindent() && !layout_->pass_thru) { - os << "\\noindent "; - column += 10; - } - LyXAlignment const curAlign = params_.align(); + // Do not output \\noindent for paragraphs + // 1. that cannot have indentation or are indented always, + // 2. that are not part of the immediate text sequence (e.g., contain only floats), + // 3. that are PassThru, + // 4. or that are centered. + if (canindent && params_.noindent() + && owner_->isPartOfTextSequence() + && !layout_->pass_thru + && curAlign != LYX_ALIGN_CENTER) { + if (!owner_->empty() + && owner_->getInset(0) + && owner_->getInset(0)->lyxCode() == VSPACE_CODE) + // If the paragraph starts with a vspace, the \\noindent + // needs to come after that (as it leaves vmode). + // If the paragraph consists only of the vspace, + // \\noindent is not needed at all. + runparams.need_noindent = owner_->size() > 1; + else { + os << "\\noindent" << termcmd; + column += 10; + } + } + if (curAlign == layout_->align) return column; @@ -2390,7 +2526,7 @@ void Paragraph::latex(BufferParams const & bparams, OutputParams const & runparams, int start_pos, int end_pos, bool force) const { - LYXERR(Debug::LATEX, "Paragraph::latex... " << this); + LYXERR(Debug::OUTFILE, "Paragraph::latex... " << this); // FIXME This check should not be needed. Perhaps issue an // error if it triggers. @@ -2419,10 +2555,10 @@ void Paragraph::latex(BufferParams const & bparams, pos_type body_pos = beginOfBody(); unsigned int column = 0; - // If we are inside an non inheritFont() inset, the real outerfont is local_font - Font const real_outerfont = (!inInset().inheritFont() - && runparams.local_font != nullptr) - ? Font(runparams.local_font->fontInfo()) : outerfont; + // If we are inside an non inheritFont() inset, + // the outerfont is the buffer's main font + Font const real_outerfont = + inInset().inheritFont() ? outerfont : Font(bparams.getFont()); if (body_pos > 0) { // the optional argument is kept in curly brackets in @@ -2443,7 +2579,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; @@ -2488,7 +2626,7 @@ void Paragraph::latex(BufferParams const & bparams, runparams); runningChange = Change(Change::UNCHANGED); - os << "}] "; + os << ((isEnvSeparator(i) && !runparams.find_effective()) ? "}]~" : "}] "); column +=3; } // For InTitle commands, we have already opened a group @@ -2517,6 +2655,11 @@ 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.find_effective()) + output_changes = bparams.output_changes; + else + output_changes = runparams.find_with_deleted(); if (c == META_INSET && i >= start_pos && (end_pos == -1 || i < end_pos)) { if (isDeleted(i)) @@ -2532,7 +2675,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()) { @@ -2555,7 +2698,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 << "}"; @@ -2578,7 +2721,7 @@ 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; } @@ -2592,7 +2735,7 @@ void Paragraph::latex(BufferParams const & bparams, : 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()); @@ -2602,7 +2745,12 @@ void Paragraph::latex(BufferParams const & bparams, && getInset(i) && getInset(i)->allowMultiPar() && getInset(i)->lyxCode() != ERT_CODE - && getInset(i)->producesOutput(); + && (getInset(i)->producesOutput() + // FIXME Something more general? + // Comments do not "produce output" but are still + // part of the TeX source and require font switches + // to be closed (otherwise LaTeX fails). + || getInset(i)->layoutName() == "Note:Comment"); bool closeLanguage = false; bool lang_switched_at_inset = false; @@ -2641,9 +2789,11 @@ void Paragraph::latex(BufferParams const & bparams, os << '}'; column += 1; } - if (closeLanguage) + if (closeLanguage) { // Force language closing current_font.setLanguage(basefont.language()); + langClosed = true; + } Font const nextfont = (i == body_pos-1) ? basefont : current_font; bool needPar = false; column += running_font.latexWriteEndChanges( @@ -2656,12 +2806,12 @@ void Paragraph::latex(BufferParams const & bparams, column += Changes::latexMarkChange(os, bparams, Change(Change::UNCHANGED), Change(Change::DELETED), rp); } - open_font = false; // Has the language been closed in the latexWriteEndChanges() call above? - langClosed = running_font.language() != basefont.language() + langClosed |= running_font.language() != basefont.language() && running_font.language() != nextfont.language() && (running_font.language()->encoding()->package() != Encoding::CJK); running_font = basefont; + open_font &= !langClosed; } // if necessary, close language environment before opening CJK @@ -2674,7 +2824,8 @@ void Paragraph::latex(BufferParams const & bparams, string end_tag = subst(lang_end_command, "$$lang", running_lang); os << from_ascii(end_tag); column += end_tag.length(); - popLanguageName(); + if (!languageStackEmpty()) + popLanguageName(); } // Switch file encoding if necessary (and allowed) @@ -2726,6 +2877,7 @@ void Paragraph::latex(BufferParams const & bparams, bool const cprotect = textinset ? textinset->hasCProtectContent(runparams.moving_arg) && !textinset->text().isMainText() + && inInset().lyxCode() != BRANCH_CODE : false; column += current_font.latexWriteStartChanges(ots, bparams, runparams, basefont, last_font, false, @@ -2759,9 +2911,14 @@ void Paragraph::latex(BufferParams const & bparams, column += Changes::latexMarkChange(os, bparams, Change(Change::UNCHANGED), change, rp); } - } else { + } else {// if fontswitch_inset + if (current_font != running_font || !langClosed) + // font is still open in fontswitch_insets if we have + // a non-lang font difference or if the language + // is the only difference but has not been forcedly + // closed meanwhile + open_font = true; running_font = current_font; - open_font = !langClosed; } } @@ -2830,7 +2987,7 @@ void Paragraph::latex(BufferParams const & bparams, d->latexInset(bparams, os, rp, running_font, basefont, real_outerfont, open_font, runningChange, style, i, column, fontswitch_inset, - closeLanguage, lang_switched_at_inset); + closeLanguage, (lang_switched_at_inset || langClosed)); if (fontswitch_inset) { if (open_font) { bool needPar = false; @@ -2973,8 +3130,9 @@ void Paragraph::latex(BufferParams const & bparams, 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()) { @@ -2992,7 +3150,7 @@ void Paragraph::latex(BufferParams const & bparams, os << setEncoding(prev_encoding->iconvName()); } - LYXERR(Debug::LATEX, "Paragraph::latex... done " << this); + LYXERR(Debug::OUTFILE, "Paragraph::latex... done " << this); } @@ -3339,38 +3497,62 @@ std::tuple, vector> computeDocBookFontSwit } // anonymous namespace -std::vector Paragraph::simpleDocBookOnePar(Buffer const & buf, +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; - - 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; - DocBookFontState old_fs = fs; + std::vector appendedParagraphs; odocstringstream os; - 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). - // When a font tag ends with a space, output it after the closing font tag. This requires to store delayed - // characters at some point. - std::vector delayedChars; + // 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) { + 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; @@ -3378,7 +3560,7 @@ std::vector Paragraph::simpleDocBookOnePar(Buffer const & buf, // 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) + if (!ignore_fonts_i) xs->closeFontTags(); // Output one paragraph (i.e. one string entry in generatedParagraphs). @@ -3391,7 +3573,7 @@ std::vector Paragraph::simpleDocBookOnePar(Buffer const & buf, 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) { + if (!ignore_fonts_i) { font_old = outerfont.fontInfo(); fs = old_fs; } @@ -3399,24 +3581,23 @@ std::vector Paragraph::simpleDocBookOnePar(Buffer const & buf, // Determine which tags should be opened or closed regarding fonts. Font const font = getFont(buf.masterBuffer()->params(), i, outerfont); - if (!ignore_fonts) { - 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; - - // Deal with the delayed characters *after* closing font tags. - if (!delayedChars.empty()) { - for (char_type c: delayedChars) - *xs << c; - delayedChars.clear(); - } - + 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) @@ -3426,9 +3607,15 @@ std::vector Paragraph::simpleDocBookOnePar(Buffer const & buf, 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; // TODO: special case will bite here. @@ -3436,7 +3623,7 @@ std::vector Paragraph::simpleDocBookOnePar(Buffer const & buf, inset->docbook(*xs, np); } } else { - char_type c = getUChar(buf.masterBuffer()->params(), runparams, i); + char_type c = getUChar(buf.masterBuffer()->params(), rp, i); if (lyx::isSpace(c) && !ignore_fonts) delayedChars.push_back(c); else @@ -3458,14 +3645,30 @@ std::vector Paragraph::simpleDocBookOnePar(Buffer const & buf, // 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 (runparams.docbook_in_listing && !is_last_par) + if (rp.docbook_in_listing && !is_last_par) *xs << xml::CR(); // Finalise the last (and most likely only) paragraph. generatedParagraphs.push_back(os.str()); - delete xs; + 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 generatedParagraphs; + return std::make_tuple(prependedParagraphs, generatedParagraphs, appendedParagraphs); } @@ -3817,6 +4020,8 @@ docstring Paragraph::simpleLyXHTMLOnePar(Buffer const & buf, runparams, i); if (c == ' ' && (style.free_spacing || runparams.free_spacing)) xs << XMLStream::ESCAPE_NONE << " "; + else if (c == '\'') + xs << XMLStream::ESCAPE_NONE << "’"; else xs << c; } @@ -3935,27 +4140,12 @@ bool Paragraph::needsCProtection(bool const fragile) const } // now check whether we have insets that need cprotection - pos_type size = pos_type(d->text_.size()); - for (pos_type i = 0; i < size; ++i) { - if (!isInset(i)) + for (auto const & icit : d->insetlist_) { + Inset const * ins = icit.inset; + if (!ins) continue; - Inset const * ins = getInset(i); if (ins->needsCProtection(maintext, fragile)) return true; - // Now check math environments - InsetMath const * im = getInset(i)->asInsetMath(); - if (!im || im->cell(0).empty()) - continue; - switch(im->cell(0)[0]->lyxCode()) { - case MATH_AMSARRAY_CODE: - case MATH_SUBSTACK_CODE: - case MATH_ENV_CODE: - case MATH_XYMATRIX_CODE: - // these need cprotection - return true; - default: - break; - } } return false; @@ -4067,6 +4257,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); } @@ -4387,7 +4585,6 @@ void Paragraph::changeCase(BufferParams const & bparams, pos_type pos, } } - int Paragraph::find(docstring const & str, bool cs, bool mw, pos_type start_pos, bool del) const { @@ -4412,11 +4609,20 @@ int Paragraph::find(docstring const & str, bool cs, bool mw, if (!inset->isLetter() && !inset->isChar()) break; odocstringstream os; - inset->toString(os); - if (!os.str().empty()) { - int const insetstringsize = os.str().length(); + if (inset->lyxCode() == lyx::QUOTE_CODE) { + OutputParams op(0); + op.find_set_feature(OutputParams::SearchQuick); + inset->plaintext(os, op); + } + else { + inset->toString(os); + } + docstring const insetstring = os.str(); + if (!insetstring.empty()) { + int const insetstringsize = insetstring.length(); for (int j = 0; j < insetstringsize && pos < parsize; ++i, ++j) { - if (str[i] != os.str()[j]) { + if ((cs && str[i] != insetstring[j]) + || (!cs && uppercase(str[i]) != uppercase(insetstring[j]))) { nonmatch = true; break; } @@ -4426,9 +4632,10 @@ int Paragraph::find(docstring const & str, bool cs, bool mw, } if (nonmatch || i == strsize) break; - if (cs && str[i] != d->text_[pos]) + char_type dp = d->text_[pos]; + if (cs && str[i] != dp) break; - if (!cs && uppercase(str[i]) != uppercase(d->text_[pos])) + if (!cs && uppercase(str[i]) != uppercase(dp)) break; } @@ -4675,6 +4882,19 @@ Language * Paragraph::Private::getSpellLanguage(pos_type const from) const void Paragraph::requestSpellCheck(pos_type pos) { d->requestSpellCheck(pos); + if (pos == -1) { + // Also request spellcheck within (text) insets + for (auto const & insets : insetList()) { + if (!insets.inset->asInsetText()) + continue; + ParagraphList & inset_pars = + insets.inset->asInsetText()->paragraphs(); + ParagraphList::iterator pit = inset_pars.begin(); + ParagraphList::iterator pend = inset_pars.end(); + for (; pit != pend; ++pit) + pit->requestSpellCheck(); + } + } } @@ -4724,7 +4944,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); @@ -4736,10 +4958,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 << "\" [" << @@ -4786,6 +5008,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, @@ -4807,8 +5030,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 @@ -4817,12 +5041,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) { @@ -4851,9 +5084,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); + BufferParams const & bparams = d->inset_owner_->buffer().params(); SpellChecker::Result result = !word.empty() ? - speller->check(wl) : SpellChecker::WORD_OK; - d->markMisspelledWords(first, last, result, word, skips); + speller->check(wl, bparams.spellignore()) : SpellChecker::WORD_OK; + d->markMisspelledWords(lang, first, last, result, word, skips); first = ++last; } } else {