]> git.lyx.org Git - lyx.git/blobdiff - src/Paragraph.cpp
Sanitize cursors after a buffer has been reloaded
[lyx.git] / src / Paragraph.cpp
index 7b0120ab87bbe44e051690e31a97dd45b93a1a21..4282defa4a8fc5fed58bd71507138b012a8a6537 100644 (file)
@@ -25,6 +25,7 @@
 #include "BufferEncodings.h"
 #include "Changes.h"
 #include "Counters.h"
+#include "Cursor.h"
 #include "InsetList.h"
 #include "Language.h"
 #include "LaTeXFeatures.h"
@@ -37,6 +38,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 +49,7 @@
 
 #include "frontends/alert.h"
 
+#include "insets/InsetArgument.h"
 #include "insets/InsetBibitem.h"
 #include "insets/InsetLabel.h"
 #include "insets/InsetSpecialChar.h"
@@ -69,8 +72,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)
@@ -306,7 +309,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).
@@ -317,7 +320,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 &,
@@ -341,7 +344,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(
@@ -353,7 +356,7 @@ public:
                                   Layout const & style,
                                   pos_type & i,
                                   pos_type end_pos,
-                                  unsigned int & column);
+                                  unsigned int & column) const;
 
        ///
        bool latexSpecialT1(
@@ -400,7 +403,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)
        {
@@ -459,10 +462,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
        {
@@ -756,7 +760,6 @@ void Paragraph::acceptChanges(pos_type start, pos_type end)
                                }
                                break;
                }
-
        }
 }
 
@@ -830,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);
 }
 
 
@@ -914,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;
 }
 
@@ -935,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
@@ -972,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;
@@ -1025,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);
@@ -1077,22 +1090,45 @@ void Paragraph::Private::latexInset(BufferParams const & bparams,
                running_change = Change(Change::UNCHANGED);
        }
 
-       bool close = false;
+       unsigned int close_brace = 0;
+       bool const disp_env = (inset->isEnvironment() && inset->getLayout().isDisplay())
+                       || runparams.inDisplayMath;
+       string close_env;
        odocstream::pos_type const len = os.os().tellp();
 
        if (inset->forceLTR(runparams)
-           && running_font.isRightToLeft()
-           // ERT is an exception, it should be output with no
-           // decorations at all
-           && inset->lyxCode() != ERT_CODE) {
-               if (runparams.use_polyglossia) {
-                       os << "\\LRE{";
+           // babel with LuaTeX does not need a switch
+           // babel with XeTeX needs a switch only if bidi is used
+           // and \L is not defined there.
+           && (!runparams.isFullUnicode() || bparams.useBidiPackage(runparams) || runparams.use_polyglossia)
+           && running_font.isRightToLeft()) {
+               if (bparams.useBidiPackage(runparams) || runparams.use_polyglossia) {
+                       // (lua)bidi
+                       // Displayed environments go in an LTR environment
+                       if (disp_env) {
+                               os << "\\begin{LTR}";
+                               close_env = "LTR";
+                       } else {
+                               if (runparams.flavor == Flavor::LuaTeX) {
+                                       // luabidi's \LRE needs extra grouping
+                                       // (possibly a LuaTeX bug)
+                                       os << '{';
+                                       close_brace = 1;
+                               }
+                               os << "\\LRE{";
+                               close_brace += 1;
+                       }
                } else if (running_font.language()->lang() == "farsi"
-                          || running_font.language()->lang() == "arabic_arabi")
+                        || running_font.language()->lang() == "arabic_arabi") {
                        os << "\\textLR{" << termcmd;
-               else
+                       close_brace = 1;
+               } else {
+                       // babel classic
                        os << "\\L{";
-               close = true;
+                       if (disp_env)
+                               os << safebreakln;
+                       close_brace = 1;
+               }
        }
 
        if (open_font && fontswitch_inset) {
@@ -1135,6 +1171,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,
@@ -1168,8 +1205,15 @@ void Paragraph::Private::latexInset(BufferParams const & bparams,
                throw;
        }
 
-       if (close)
-               os << '}';
+       if (!close_env.empty())
+               os << "\\end{" << close_env << "}";
+
+       if (close_brace > 0) {
+               for (unsigned i = 0; i < close_brace; ++i)
+                       os << '}';
+               if (disp_env)
+                       os << safebreakln;
+       }
 
        if (os.texrow().rows() > previous_row_count) {
                os.texrow().start(owner_->id(), i + 1);
@@ -1191,30 +1235,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_searchAdv != OutputParams::NoSearch)
+       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_searchAdv != OutputParams::NoSearch) {
-                       if (c == '\\')
+               if (runparams.find_effective()) {
+                       switch (c) {
+                       case '\\':
                                os << "\\\\";
-                       else if (c == '{')
+                               return;
+                       case '{':
                                os << "\\braceleft ";
-                       else if (c == '}')
+                               return;
+                       case '}':
                                os << "\\braceright ";
-                       else if (c != '\0')
+                               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
@@ -1226,7 +1285,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")
@@ -1518,12 +1577,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;
@@ -1717,16 +1776,34 @@ void Paragraph::write(ostream & os, BufferParams const & bparams,
                        column = 0;
                        break;
                case '.':
+               case '!':
+               case '?':
+               case ':':
+               case ';':
+               case ',':
+               case 0x061F:  // ؟ U+061F  ARABIC QUESTION MARK
+               case 0x061B:  // ؛ U+061B  ARABIC SEMICOLON
+               case 0x060C:  // ، U+060C  ARABIC COMMA
                        flushString(os, write_buffer);
                        if (i + 1 < size() && d->text_[i + 1] == ' ') {
-                               os << ".\n";
+                               os << to_utf8(docstring(1, c)) << '\n';
                                column = 0;
                        } else
-                               os << '.';
+                               os << to_utf8(docstring(1, c));
+                       break;
+               case 0x2014:  // — U+2014  EM DASH
+               case 0x3002:  // 。 U+3002  IDEOGRAPHIC FULL STOP
+               case 0xFF01:  // ! U+FF01  FULLWIDTH EXCLAMATION MARK
+               case 0xFF1F:  // ? U+FF1F  FULLWIDTH QUESTION MARK
+               case 0xFF1A:  // : U+FF1A  FULLWIDTH COLON
+               case 0xFF1B:  // ; U+FF1B  FULLWIDTH SEMICOLON
+               case 0xFF0C:  // , U+FF0C  FULLWIDTH COMMA
+                       flushString(os, write_buffer);
+                       os << to_utf8(docstring(1, c)) << '\n';
+                       column = 0;
                        break;
                default:
-                       if ((column > 70 && c == ' ')
-                           || column > 79) {
+                       if (column > 500) {
                                flushString(os, write_buffer);
                                os << '\n';
                                column = 0;
@@ -1898,7 +1975,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);
 }
 
 
@@ -1922,7 +2000,7 @@ Font const & Paragraph::getFirstFontSettings(BufferParams const & bparams) const
 
 
 // Gets the fully instantiated font at a given position in a paragraph
-// This is basically the same function as Text::GetFont() in text2.cpp.
+// This is basically the same function as TextMetrics::displayFont().
 // The difference is that this one is used for generating the LaTeX file,
 // and thus cosmetic "improvements" are disallowed: This has to deliver
 // the true picture of the buffer. (Asger)
@@ -1973,13 +2051,16 @@ char_type Paragraph::getUChar(BufferParams const & bparams,
 {
        char_type c = d->text_[pos];
 
-       // Return unchanged character in LTR languages
-       // or if we use poylglossia/bidi (XeTeX).
-       if (rp.useBidiPackage()
-           || !getFontSettings(bparams, pos).isRightToLeft())
+       // Return unchanged character
+       // 1. in all LTR languages
+       // 2. if we use XeTeX (both with babel and polyglossia)
+       // 3. if we use LuaTeX with babel
+       if (!getFontSettings(bparams, pos).isRightToLeft()
+           || rp.flavor == Flavor::XeTeX
+           || (rp.use_babel && rp.flavor == Flavor::LuaTeX))
                return c;
 
-       // Without polyglossia/bidi, we need to account for some special cases.
+       // For the remaining cases, we need to account for some special cases.
        // FIXME This needs to be audited!
        // Check if:
        // * The input is as expected for all delimiters
@@ -1990,18 +2071,27 @@ char_type Paragraph::getUChar(BufferParams const & bparams,
        //   => checked for Hebrew!
        // * In arabic_arabi, brackets are transformed to Arabic
        //   Ornate Parentheses. Is this is really wanted?
+       //   => Yes, in file ararabeyes.enc from the arabi bundle
+       //      the slot of the left bracket (slot 91) is encoded as
+       //      "ornaterightparenthesis". This is also the reason
+       //      brackets don't need to be mirrored with arabi
 
        string const & lang = getFontSettings(bparams, pos).language()->lang();
        char_type uc = c;
 
-       // 1. In the following languages, parentheses need to be reversed.
-       //    Also with polyglodia/luabidi
-       bool const reverseparens = (lang == "hebrew" || rp.use_polyglossia);
-
-       // 2. In the following languages, brackets don't need to be reversed.
-       bool const reversebrackets = lang != "arabic_arabtex"
-                       && lang != "arabic_arabi"
-                       && lang != "farsi";
+       // These are the cases where we need to mirror delimiters in RTL context
+       // in the remaining cases (polyglossia + LuaTeX or classic [pdf]latex):
+       // 1. With polyglossia and LuaTeX (luabidi) parentheses and brackets
+       //    need to be mirrored in RTL, regardless of the language, or script.
+       // 2. In the languages that follow, parentheses need to be mirrored
+       //    in classic (pdf)latex
+       bool const reverseparens = (rp.use_polyglossia || lang == "hebrew");
+       // 3. In all RTL languages except for those that follow, brackets
+       //    need to be mirrored in classic (pdf)latex
+       bool const reversebrackets = rp.use_polyglossia
+                       || (lang != "arabic_arabtex"
+                           && lang != "arabic_arabi"
+                           && lang != "farsi");
 
        // Now swap delimiters if needed.
        switch (c) {
@@ -2043,10 +2133,8 @@ void Paragraph::setFont(pos_type pos, Font const & font)
 {
        LASSERT(pos <= size(), return);
 
-       // First, reduce font against layout/label font
-       // Update: The setCharFont() routine in text2.cpp already
-       // reduces font, so we don't need to do that here. (Asger)
-
+       // Text::setCharFont() already reduces font against layout/label
+       // font, so we don't need to do that here. (Asger)
        d->fontlist_.set(pos, font);
 }
 
@@ -2254,6 +2342,100 @@ 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::allowedInContext(Cursor const & cur, InsetLayout const & il) const
+{
+       set<docstring> const & allowed_insets = il.allowedInInsets();
+       set<docstring> const & allowed_layouts = il.allowedInLayouts();
+
+       bool in_allowed_inset =
+               allowed_insets.find(inInset().getLayout().name()) != allowed_insets.end();
+
+       bool in_allowed_layout =
+               allowed_layouts.find(d->layout_->name()) != allowed_layouts.end();
+
+       if (!in_allowed_inset && inInset().asInsetArgument()) {
+               // check if the argument allows the inset in question
+               if (cur.depth() > 1) {
+                       docstring parlayout = cur[cur.depth() - 2].inset().getLayout().name()
+                                       + from_ascii("@") + from_ascii(inInset().asInsetArgument()->name());
+                       if (allowed_insets.find(parlayout) != allowed_insets.end())
+                               in_allowed_inset = true;
+               }
+       }
+       
+       int have_ins = 0;
+       // check if we exceed the number of allowed insets in this inset
+       if (in_allowed_inset && inInset().asInsetText() && il.allowedOccurrences() != -1) {
+               ParagraphList & pars = cur.text()->paragraphs();
+                       for (Paragraph const & par : pars) {
+                               for (auto const & elem : par.insetList())
+                               if (elem.inset->getLayout().name() == il.name())
+                                       ++have_ins;
+                       }
+               if (have_ins >= il.allowedOccurrences())
+                       return false;
+       }
+       
+       have_ins = 0;
+       // check if we exceed the number of allowed insets in the layout group
+       if (in_allowed_layout && il.allowedOccurrences() != -1) {
+               pit_type pit = cur.pit();
+               pit_type lastpit = cur.pit();
+               ParagraphList & pars = cur.text()->paragraphs();
+               // If we are not on a list-type environment or AllowedOccurrencesPerItem
+               // is false, we check the whole paragraph group
+               if (d->layout_->isEnvironment()
+                   && !(il.allowedOccurrencesPerItem()
+                        && (d->layout_->latextype == LATEX_LIST_ENVIRONMENT
+                            || d->layout_->latextype == LATEX_ITEM_ENVIRONMENT))) {
+                       lastpit = cur.lastpit();
+                       // get the first paragraph in sequence with this layout
+                       depth_type const current_depth = params().depth();
+                       while (true) {
+                               if (pit == 0)
+                                       break;
+                               Paragraph cpar = pars[pit - 1];
+                               if (&cpar.layout() == d->layout_
+                                   && cpar.params().depth() == current_depth)
+                                       --pit;
+                               else
+                                       break;
+                       }
+               }
+               for (; pit <= lastpit; ++pit) {
+                       if (&pars[pit].layout() != d->layout_)
+                               break;
+                       for (auto const & elem : pars[pit].insetList())
+                               if (elem.inset->getLayout().name() == il.name())
+                                       ++have_ins;
+               }
+               if (have_ins >= il.allowedOccurrences())
+                       return false;
+       }
+       
+       if (in_allowed_layout || in_allowed_inset)
+               return true;
+
+       return (allowed_insets.empty() && allowed_layouts.empty());
+}
+
+
+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
@@ -2325,13 +2507,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;
 
@@ -2358,7 +2558,7 @@ int Paragraph::Private::startTeXParParams(BufferParams const & bparams,
        // RTL in classic (PDF)LaTeX (without the Bidi package)
        // Luabibdi (used by LuaTeX) behaves like classic
        bool const rtl_classic = owner_->getParLanguage(bparams)->rightToLeft()
-               && !runparams.useBidiPackage();
+               && !bparams.useBidiPackage(runparams);
 
        switch (curAlign) {
        case LYX_ALIGN_NONE:
@@ -2422,7 +2622,7 @@ bool Paragraph::Private::endTeXParParams(BufferParams const & bparams,
        // RTL in classic (PDF)LaTeX (without the Bidi package)
        // Luabibdi (used by LuaTeX) behaves like classic
        bool const rtl_classic = owner_->getParLanguage(bparams)->rightToLeft()
-               && !runparams.useBidiPackage();
+               && !bparams.useBidiPackage(runparams);
 
        switch (curAlign) {
        case LYX_ALIGN_NONE:
@@ -2462,7 +2662,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.
@@ -2491,10 +2691,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
@@ -2515,7 +2715,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;
 
@@ -2560,7 +2762,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
@@ -2590,10 +2792,10 @@ void Paragraph::latex(BufferParams const & bparams,
 
                // Check whether a display math inset follows
                bool output_changes;
-               if (runparams.for_searchAdv == OutputParams::NoSearch)
+               if (!runparams.find_effective())
                        output_changes = bparams.output_changes;
                else
-                       output_changes = (runparams.for_searchAdv == OutputParams::SearchWithDeleted);
+                       output_changes = runparams.find_with_deleted();
                if (c == META_INSET
                    && i >= start_pos && (end_pos == -1 || i < end_pos)) {
                        if (isDeleted(i))
@@ -2679,7 +2881,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;
@@ -2718,9 +2925,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(
@@ -2733,12 +2942,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
@@ -2751,7 +2960,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)
@@ -2803,6 +3013,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,
@@ -2836,9 +3047,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;
                        }
                }
 
@@ -2907,7 +3123,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;
@@ -2934,8 +3150,9 @@ void Paragraph::latex(BufferParams const & bparams,
                                }
                        }
                } else if (i >= start_pos && (end_pos == -1 || i < end_pos)) {
-                       if (!bparams.useNonTeXFonts)
-                         script = Encodings::isKnownScriptChar(c);
+                       if (!bparams.useNonTeXFonts && !runparams.pass_thru
+                           && !contains(runparams.pass_thru_chars, c))
+                               script = Encodings::isKnownScriptChar(c);
                        if (script != alien_script) {
                                if (!alien_script.empty()) {
                                        os << "}";
@@ -3050,8 +3267,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()) {
@@ -3069,7 +3287,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);
 }
 
 
@@ -3416,35 +3634,60 @@ std::tuple<vector<xml::FontTag>, vector<xml::EndFontTag>> computeDocBookFontSwit
 } // anonymous namespace
 
 
-std::vector<docstring> Paragraph::simpleDocBookOnePar(Buffer const & buf,
-                                                      OutputParams const & runparams,
-                                                      Font const & outerfont,
-                                                      pos_type initial,
-                                                      bool is_last_par,
-                                                      bool ignore_fonts) const
+std::tuple<std::vector<docstring>, std::vector<docstring>, std::vector<docstring>>
+    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;
+       // Return values: segregation of the content of this paragraph.
+       std::vector<docstring> prependedParagraphs; // Anything that must be output before the main tag of this paragraph.
+       std::vector<docstring> generatedParagraphs; // The main content of the paragraph.
+       std::vector<docstring> appendedParagraphs;  // Anything that must be output after the main tag of this paragraph.
 
-       Layout const & style = *d->layout_;
-       FontInfo font_old =
-                       style.labeltype == LABEL_MANUAL ? style.labelfont : style.font;
+       // Internal string stream to store the output before being added to one of the previous lists.
+       odocstringstream os;
 
-       string const default_family =
-                       buf.masterBuffer()->params().fonts_default_family;
+       // 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);
+            }
+        }
+    }
+       rp.lastid = id();
 
-       vector<xml::FontTag> tagsToOpen;
-       vector<xml::EndFontTag> tagsToClose;
+    // 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).
+       xs->startDivision(false);
+    std::vector<docstring> 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.
 
-       std::vector<docstring> generatedParagraphs;
-       DocBookFontState old_fs = fs;
-       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).
+       // Track whether we have opened font tags
+    DocBookFontState fs;
+    DocBookFontState old_fs = fs;
 
-       // 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<char_type> delayedChars;
+    Layout const & style = *d->layout_;
+
+       // Conversion of the font opening/closing into DocBook tags.
+    vector<xml::FontTag> tagsToOpen;
+    vector<xml::EndFontTag> tagsToClose;
 
        // Parsing main loop.
        for (pos_type i = initial; i < size(); ++i) {
@@ -3456,8 +3699,8 @@ std::vector<docstring> Paragraph::simpleDocBookOnePar(Buffer const & buf,
                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 this is an InsetNewline, generate a new paragraph (this is the reason why generatedParagraphs is a list
+               // of paragraphs). Also reset the fonts, so that tags are closed in this paragraph.
                if (getInset(i) && getInset(i)->lyxCode() == NEWLINE_CODE) {
                        if (!ignore_fonts_i)
                                xs->closeFontTags();
@@ -3465,22 +3708,34 @@ std::vector<docstring> Paragraph::simpleDocBookOnePar(Buffer const & buf,
                        // Output one paragraph (i.e. one string entry in generatedParagraphs).
                        generatedParagraphs.push_back(os.str());
 
-                       // Create a new XMLStream for the new paragraph, completely independent from the previous one. This implies
+                       xs->endDivision();
+
+                       // Create a new XMLStream for the new paragraph, completely independent of the previous one. This implies
                        // that the string stream must be reset.
                        os.str(from_ascii(""));
                        delete xs;
                        xs = new XMLStream(os);
+                       xs->startDivision(false);
 
                        // Restore the fonts for the new paragraph, so that the right tags are opened for the new entry.
                        if (!ignore_fonts_i) {
-                               font_old = outerfont.fontInfo();
                                fs = old_fs;
                        }
                }
 
-               // Determine which tags should be opened or closed regarding fonts.
+               // Determine which tags should be opened or closed regarding fonts. Consider the last output character (i.e. not
+               // deleted).
+               int last_output_char = (i == 0) ? 0 : i - 1;
+               if (i > 0) {
+                       while (last_output_char > 0 && isDeleted(last_output_char))
+                               --last_output_char;
+               }
+               FontInfo const font_old = (i == 0 ?
+                               (style.labeltype == LABEL_MANUAL ? style.labelfont : style.font) :
+                               getFont(buf.masterBuffer()->params(), last_output_char, outerfont).fontInfo());
                Font const font = getFont(buf.masterBuffer()->params(), i, outerfont);
-        tie(tagsToOpen, tagsToClose) = computeDocBookFontSwitch(font_old, font, default_family, fs);
+        tie(tagsToOpen, tagsToClose) = computeDocBookFontSwitch(
+                               font_old, font, buf.masterBuffer()->params().fonts_default_family, fs);
 
                if (!ignore_fonts_i) {
             vector<xml::EndFontTag>::const_iterator cit = tagsToClose.begin();
@@ -3491,8 +3746,8 @@ std::vector<docstring> Paragraph::simpleDocBookOnePar(Buffer const & buf,
 
         // Deal with the delayed characters *after* closing font tags.
         if (!delayedChars.empty()) {
-            for (char_type c: delayedChars)
-                *xs << c;
+            for (const docstring& c: delayedChars)
+                *xs << XMLStream::ESCAPE_NONE << c;
             delayedChars.clear();
         }
 
@@ -3501,15 +3756,21 @@ std::vector<docstring> Paragraph::simpleDocBookOnePar(Buffer const & buf,
                        vector<xml::FontTag>::const_iterator sen = tagsToOpen.end();
                        for (; sit != sen; ++sit)
                                *xs << *sit;
-
-                       tagsToClose.clear();
-                       tagsToOpen.clear();
                }
 
+               // The font tags are no longer useful; free their memory right now.
+               tagsToClose.clear();
+               tagsToOpen.clear();
+
         // Finally, write the next character or inset.
                if (Inset const * inset = getInset(i)) {
-                       if (!runparams.for_toc || inset->isInToc()) {
-                               OutputParams np = runparams;
+                   bool inset_is_argument_elsewhere = getInset(i)->asInsetArgument() &&
+                           rp.docbook_appended_arguments.find(inset->asInsetArgument()) != rp.docbook_appended_arguments.end() &&
+                           rp.docbook_prepended_arguments.find(inset->asInsetArgument()) != rp.docbook_prepended_arguments.end();
+
+                       if ((!rp.for_toc || inset->isInToc()) && !inset_is_argument_elsewhere) {
+                           // Arguments may need to be output
+                               OutputParams np = rp;
                                np.local_font = &font;
 
                                // TODO: special case will bite here.
@@ -3517,36 +3778,70 @@ std::vector<docstring> Paragraph::simpleDocBookOnePar(Buffer const & buf,
                                inset->docbook(*xs, np);
                        }
                } else {
-                       char_type c = getUChar(buf.masterBuffer()->params(), runparams, i);
-                       if (lyx::isSpace(c) && !ignore_fonts)
-                               delayedChars.push_back(c);
-                       else
-                               *xs << c;
+                       char_type c = getUChar(buf.masterBuffer()->params(), rp, i);
+                       if (lyx::isSpace(c) && !ignore_fonts) { // Delay spaces *after* the font-tag closure for cleaner output.
+                               if (c == ' ' && (style.free_spacing || rp.free_spacing)) {
+                                       delayedChars.push_back(from_ascii("&#160;"));
+                               } else {
+                                       delayedChars.emplace_back(1, c);
+                               }
+                       } else { // No need to delay the character.
+                               if (c == '\'' && !ignore_fonts)
+                                       *xs << XMLStream::ESCAPE_NONE << "&#8217;";
+                               else
+                                       *xs << c;
+                       }
                }
-               font_old = font.fontInfo();
        }
 
+       // Ensure that the tags are closed at the right place. Otherwise, there might be an open font tag with no content
+       // that no other code cares to close.
+       *xs << xml::NullTag();
+
        // FIXME, this code is just imported from XHTML
        // I'm worried about what happens if a branch, say, is itself
        // wrapped in some font stuff. I think that will not work.
        if (!ignore_fonts)
                xs->closeFontTags();
 
+       // Close the potentially remaining tags, like pending font tags.
+       // There is no need to check for ignore_fonts, as these tags won't be
+       // inserted in the stack in the first place if ignore_fonts is false.
+       xs->endDivision();
+
        // Deal with the delayed characters *after* closing font tags.
-       if (!delayedChars.empty())
-               for (char_type c: delayedChars)
-                       *xs << c;
+       if (!delayedChars.empty()) {
+               for (const docstring &c: delayedChars)
+                       *xs << XMLStream::ESCAPE_NONE << c;
+               delayedChars.clear();
+       }
 
        // 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);
 }
 
 
@@ -3869,16 +4164,10 @@ docstring Paragraph::simpleLyXHTMLOnePar(Buffer const & buf,
                // FIXME XHTML
                // Other such tags? What about the other text ranges?
 
-               vector<xml::EndFontTag>::const_iterator cit = tagsToClose.begin();
-               vector<xml::EndFontTag>::const_iterator cen = tagsToClose.end();
-               for (; cit != cen; ++cit)
-                       xs << *cit;
-
-               vector<xml::FontTag>::const_iterator sit = tagsToOpen.begin();
-               vector<xml::FontTag>::const_iterator sen = tagsToOpen.end();
-               for (; sit != sen; ++sit)
-                       xs << *sit;
-
+               for (auto const & t : tagsToClose)
+                       xs << t;
+               for (auto const & t : tagsToOpen)
+                       xs << t;
                tagsToClose.clear();
                tagsToOpen.clear();
 
@@ -3897,7 +4186,9 @@ docstring Paragraph::simpleLyXHTMLOnePar(Buffer const & buf,
                        char_type c = getUChar(buf.masterBuffer()->params(),
                                               runparams, i);
                        if (c == ' ' && (style.free_spacing || runparams.free_spacing))
-                               xs << XMLStream::ESCAPE_NONE << "&nbsp;";
+                               xs << XMLStream::ESCAPE_NONE << "&#160;";
+                       else if (c == '\'')
+                               xs << XMLStream::ESCAPE_NONE << "&#8217;";
                        else
                                xs << c;
                }
@@ -3993,9 +4284,10 @@ bool Paragraph::isHardHyphenOrApostrophe(pos_type pos) const
 bool Paragraph::needsCProtection(bool const fragile) const
 {
        // first check the layout of the paragraph, but only in insets
+       // and not in tables
        InsetText const * textinset = inInset().asInsetText();
        bool const maintext = textinset
-               ? textinset->text().isMainText()
+               ? textinset->text().isMainText() || inInset().lyxCode() == CELL_CODE
                : false;
 
        if (!maintext && layout().needcprotect) {
@@ -4016,27 +4308,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;
@@ -4134,6 +4411,7 @@ docstring Paragraph::asString(pos_type beg, pos_type end, int options, const Out
 
        if (beg == 0
            && options & AS_STR_LABEL
+           && d->layout_->labeltype != LABEL_MANUAL
            && !d->params_.labelString().empty())
                os << d->params_.labelString() << ' ';
 
@@ -4147,7 +4425,10 @@ docstring Paragraph::asString(pos_type beg, pos_type end, int options, const Out
                else if (c == META_INSET && (options & AS_STR_INSETS)) {
                        if (c == META_INSET && (options & AS_STR_PLAINTEXT)) {
                                LASSERT(runparams != nullptr, return docstring());
-                               getInset(i)->plaintext(os, *runparams);
+                               if (runparams->find_effective() && getInset(i)->findUsesToString())
+                                       getInset(i)->toString(os);
+                               else
+                                       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();
@@ -4274,6 +4555,27 @@ bool Paragraph::allowEmpty() const
 }
 
 
+int Paragraph::getInsetPos(InsetCode const code, int startpos,
+                          bool ignore_deleted) const
+{
+       while (startpos != -1) {
+               int found_pos = d->insetlist_.find(code, startpos);
+               if (found_pos == -1)
+                       // nothing found
+                       return -1;
+               if (isDeleted(found_pos) && ignore_deleted) {
+                       // we're not interested in deleted insets
+                       if (found_pos + 1 == size())
+                               return -1;
+                       startpos = found_pos + 1;
+                       continue;
+               } else
+                       return found_pos;
+       }
+       return -1;
+}
+
+
 bool Paragraph::brokenBiblio() const
 {
        // There is a problem if there is no bibitem at position 0 in
@@ -4281,10 +4583,10 @@ bool Paragraph::brokenBiblio() const
        // paragraph or if this paragraph is not supposed to have
        // a bibitem inset at all.
        return ((d->layout_->labeltype == LABEL_BIBLIO
-               && (d->insetlist_.find(BIBITEM_CODE) != 0
-                   || d->insetlist_.find(BIBITEM_CODE, 1) > 0))
+               && (getInsetPos(BIBITEM_CODE, 0, true) != 0
+                   || getInsetPos(BIBITEM_CODE, 1, true) > 0))
                || (d->layout_->labeltype != LABEL_BIBLIO
-                   && d->insetlist_.find(BIBITEM_CODE) != -1));
+                   && getInsetPos(BIBITEM_CODE, 0, true) != -1));
 }
 
 
@@ -4295,7 +4597,7 @@ int Paragraph::fixBiblio(Buffer const & buffer)
        // cursor cannot be correctly updated.
 
        bool const track_changes = buffer.params().track_changes;
-       int bibitem_pos = d->insetlist_.find(BIBITEM_CODE);
+       int bibitem_pos = getInsetPos(BIBITEM_CODE, 0, true);
 
        // The case where paragraph is not BIBLIO
        if (d->layout_->labeltype != LABEL_BIBLIO) {
@@ -4310,7 +4612,7 @@ int Paragraph::fixBiblio(Buffer const & buffer)
 
        bool const hasbibitem0 = bibitem_pos == 0;
        if (hasbibitem0) {
-               bibitem_pos = d->insetlist_.find(BIBITEM_CODE, 1);
+               bibitem_pos = getInsetPos(BIBITEM_CODE, 0, true);
                // There was an InsetBibitem at pos 0,
                // and no other one => OK
                if (bibitem_pos == -1)
@@ -4331,11 +4633,19 @@ int Paragraph::fixBiblio(Buffer const & buffer)
        // We need to create an inset at the beginning
        Inset * inset = nullptr;
        if (bibitem_pos > 0) {
-               // there was one somewhere in the paragraph, let's move it
-               inset = d->insetlist_.release(bibitem_pos);
+               // There was one somewhere in the paragraph, let's move it
+               // * With change tracking, we use a clone
+               //   and leave the old inset at its position
+               //   (marked deleted)
+               // * Without change tracking, we release the inset
+               //   from its previous InsetList position
+               inset = track_changes
+                               ? new InsetBibitem(const_cast<Buffer *>(&buffer),
+                                                  getInset(bibitem_pos)->asInsetCommand()->params())
+                               : d->insetlist_.release(bibitem_pos);
                eraseChar(bibitem_pos, track_changes);
        } else
-               // make a fresh one
+               // No inset found -- make a fresh one
                inset = new InsetBibitem(const_cast<Buffer *>(&buffer),
                                         InsetCommandParams(BIBITEM_CODE));
 
@@ -4476,7 +4786,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
 {
@@ -4501,11 +4810,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 || inset->lyxCode() == lyx::SPACE_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;
                                        }
@@ -4515,9 +4833,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;
        }
 
@@ -4764,6 +5083,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();
+               }
+       }
 }
 
 
@@ -4813,7 +5145,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);
@@ -4825,10 +5159,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 << "\" [" <<
@@ -4875,6 +5209,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,
@@ -4896,8 +5231,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
@@ -4906,12 +5242,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) {
@@ -4940,9 +5285,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 {