X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2FParagraph.cpp;h=4ce94415f796f23e1f83a60a378e1257d1d77145;hb=fd5adacef25eb40f813dedd961920c353448213a;hp=6578e2a19c730de9ab9a3df3c28285dd3435fcc4;hpb=8af747c9d9f4d5f496313256e044e8a5b99147f4;p=features.git diff --git a/src/Paragraph.cpp b/src/Paragraph.cpp index 6578e2a19c..4ce94415f7 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 @@ -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,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; @@ -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; } @@ -865,10 +944,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 @@ -905,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; @@ -958,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); @@ -1068,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, @@ -1124,20 +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 + if (style.pass_thru || runparams.pass_thru || runparams.find_effective() || contains(style.pass_thru_chars, c) || contains(runparams.pass_thru_chars, c)) { - if (c != '\0') { + if (runparams.find_effective()) { + switch (c) { + case '\\': + os << "\\\\"; + 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 @@ -1149,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") @@ -1235,7 +1341,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 << "--"; @@ -1316,7 +1422,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 '>': @@ -1344,7 +1450,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); @@ -1352,7 +1458,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 '*': @@ -1389,7 +1495,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)); } @@ -1441,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; @@ -1484,7 +1590,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); @@ -1560,19 +1666,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; } } } @@ -1685,17 +1791,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 @@ -1708,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; @@ -1815,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); } @@ -2171,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 @@ -2242,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; @@ -2379,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. @@ -2408,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 @@ -2432,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; @@ -2477,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 @@ -2506,10 +2655,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.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)) - runparams.ctObject = getInset(i)->CtObject(runparams); + runparams.ctObject = getInset(i)->getCtObject(runparams); InsetMath const * im = getInset(i)->asInsetMath(); if (im && im->asHullInset() @@ -2521,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()) { @@ -2544,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 << "}"; @@ -2567,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; } @@ -2581,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()); @@ -2590,7 +2744,13 @@ void Paragraph::latex(BufferParams const & bparams, c == META_INSET && getInset(i) && getInset(i)->allowMultiPar() - && getInset(i)->lyxCode() != ERT_CODE; + && getInset(i)->lyxCode() != ERT_CODE + && (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; @@ -2623,17 +2783,19 @@ 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) + 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( os, bparams, runparams, basefont, nextfont, needPar); @@ -2644,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 @@ -2662,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) @@ -2701,8 +2864,8 @@ void Paragraph::latex(BufferParams const & bparams, 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; + bool needPar = false; column += running_font.latexWriteEndChanges( os, bparams, rp, basefont, basefont, needPar); @@ -2714,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, @@ -2747,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; } } @@ -2818,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; @@ -2834,14 +3003,14 @@ void Paragraph::latex(BufferParams const & bparams, 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)) { @@ -2901,6 +3070,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 @@ -2912,31 +3106,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, @@ -2954,15 +3123,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()) { @@ -2980,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); } @@ -3327,46 +3497,70 @@ 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; // If this is an InsetNewline, generate a new paragraph. Also reset the fonts, so that tags are closed in // this paragraph. - if (getInset(i) != nullptr && getInset(i)->lyxCode() == NEWLINE_CODE) { - if (!ignore_fonts) + if (getInset(i) && getInset(i)->lyxCode() == NEWLINE_CODE) { + if (!ignore_fonts_i) xs->closeFontTags(); // Output one paragraph (i.e. one string entry in generatedParagraphs). @@ -3379,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; } @@ -3387,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) @@ -3414,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. @@ -3424,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 @@ -3446,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; - return generatedParagraphs; + // 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); } @@ -3805,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; } @@ -3923,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; @@ -4055,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); } @@ -4129,12 +4339,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); } @@ -4375,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 { @@ -4384,23 +4593,49 @@ 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; + 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 ((cs && str[i] != insetstring[j]) + || (!cs && uppercase(str[i]) != uppercase(insetstring[j]))) { + nonmatch = true; + break; + } + } + } pos++; } - if (cs && str[i] != d->text_[pos]) + if (nonmatch || i == strsize) break; - if (!cs && uppercase(str[i]) != uppercase(d->text_[pos])) + char_type dp = d->text_[pos]; + if (cs && str[i] != dp) break; - if (!del && isDeleted(pos)) + if (!cs && uppercase(str[i]) != uppercase(dp)) break; } @@ -4647,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(); + } + } } @@ -4696,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); @@ -4708,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 << "\" [" << @@ -4758,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, @@ -4779,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 @@ -4789,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) { @@ -4823,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 {