]> git.lyx.org Git - lyx.git/blobdiff - src/Paragraph.cpp
Fix language direction switch issue
[lyx.git] / src / Paragraph.cpp
index 559a61a7d915822bead4e73969c5e385766e6ae8..9a1171f99ebfc983dd84fcc2a08895154a1a8c94 100644 (file)
@@ -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"
@@ -70,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)
@@ -307,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).
@@ -318,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 +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(
@@ -354,7 +355,7 @@ public:
                                   Layout const & style,
                                   pos_type & i,
                                   pos_type end_pos,
-                                  unsigned int & column);
+                                  unsigned int & column) const;
 
        ///
        bool latexSpecialT1(
@@ -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
        {
@@ -831,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);
 }
 
 
@@ -915,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;
 }
 
@@ -936,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
@@ -973,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;
@@ -1026,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);
@@ -1136,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,
@@ -1192,7 +1205,7 @@ 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);
 
@@ -1227,7 +1240,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")
@@ -1519,12 +1532,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;
@@ -2255,6 +2268,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
@@ -2326,13 +2355,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;
 
@@ -2492,10 +2539,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
@@ -2682,7 +2729,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;
@@ -2736,12 +2788,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()
                                        && 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
@@ -2754,7 +2806,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)
@@ -2806,6 +2859,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,
@@ -2841,7 +2895,7 @@ void Paragraph::latex(BufferParams const & bparams,
                                }
                        } else {
                                running_font = current_font;
-                               open_font = !langClosed;
+                               open_font &= !langClosed;
                        }
                }
 
@@ -3591,7 +3645,7 @@ std::tuple<std::vector<docstring>, std::vector<docstring>, std::vector<docstring
         }
     }
 
-       return std::tuple(prependedParagraphs, generatedParagraphs, appendedParagraphs);
+       return std::make_tuple(prependedParagraphs, generatedParagraphs, appendedParagraphs);
 }
 
 
@@ -4061,27 +4115,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;
@@ -4811,6 +4850,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();
+               }
+       }
 }
 
 
@@ -4860,7 +4912,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);
@@ -4872,10 +4926,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 << "\" [" <<
@@ -4922,6 +4976,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,
@@ -4943,8 +4998,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
@@ -4953,12 +5009,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) {
@@ -4987,9 +5052,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 {