]> git.lyx.org Git - lyx.git/blobdiff - src/Text.cpp
Move several common types to support/types.h
[lyx.git] / src / Text.cpp
index 007271d639134892f5343ee7df18aeed8c8008bd..0bb66f6666e46ee6bf69fe89390cc9cb2df3ac15 100644 (file)
 
 #include "Author.h"
 #include "Buffer.h"
-#include "buffer_funcs.h"
 #include "BufferParams.h"
 #include "BufferView.h"
 #include "Changes.h"
 #include "CompletionList.h"
 #include "Cursor.h"
+#include "CursorSlice.h"
 #include "CutAndPaste.h"
 #include "DispatchResult.h"
 #include "Encoding.h"
 #include "ErrorList.h"
-#include "FuncRequest.h"
 #include "factory.h"
-#include "InsetList.h"
+#include "Font.h"
 #include "Language.h"
 #include "Layout.h"
-#include "Length.h"
 #include "Lexer.h"
 #include "lyxfind.h"
 #include "LyXRC.h"
 #include "Paragraph.h"
 #include "ParagraphParameters.h"
-#include "ParIterator.h"
 #include "TextClass.h"
 #include "TextMetrics.h"
-#include "WordLangTuple.h"
+#include "Undo.h"
 #include "WordList.h"
 
+#include "insets/Inset.h"
 #include "insets/InsetText.h"
-#include "insets/InsetBibitem.h"
 #include "insets/InsetCaption.h"
-#include "insets/InsetNewline.h"
-#include "insets/InsetNewpage.h"
-#include "insets/InsetArgument.h"
 #include "insets/InsetIPAMacro.h"
-#include "insets/InsetSpace.h"
 #include "insets/InsetSpecialChar.h"
 #include "insets/InsetTabular.h"
 
 #include "support/convert.h"
 #include "support/debug.h"
 #include "support/docstream.h"
+#include "support/docstring.h"
 #include "support/gettext.h"
 #include "support/lassert.h"
 #include "support/lstrings.h"
-#include "support/lyxalgo.h"
 #include "support/lyxtime.h"
 #include "support/textutils.h"
 #include "support/unique_ptr.h"
@@ -113,8 +106,7 @@ void breakParagraphConservative(BufferParams const & bparams,
        ParagraphList & pars, pit_type pit, pos_type pos)
 {
        // create a new paragraph
-       Paragraph & tmp = *pars.insert(lyx::next(pars.begin(), pit + 1),
-                                      Paragraph());
+       Paragraph & tmp = *pars.insert(pars.iterator_at(pit + 1), Paragraph());
        Paragraph & par = pars[pit];
 
        tmp.setInsetOwner(&par.inInset());
@@ -169,7 +161,7 @@ void mergeParagraph(BufferParams const & bparams,
        // move the change of the end-of-paragraph character
        par.setChange(par.size(), change);
 
-       pars.erase(lyx::next(pars.begin(), par_offset + 1));
+       pars.erase(pars.iterator_at(par_offset + 1));
 }
 
 
@@ -190,10 +182,8 @@ Text::Text(InsetText * owner, bool use_default_layout)
 Text::Text(InsetText * owner, Text const & text)
        : owner_(owner), pars_(text.pars_)
 {
-       ParagraphList::iterator const end = pars_.end();
-       ParagraphList::iterator it = pars_.begin();
-       for (; it != end; ++it)
-               it->setInsetOwner(owner);
+       for (auto & p : pars_)
+               p.setInsetOwner(owner);
 }
 
 
@@ -240,6 +230,21 @@ bool Text::isFirstInSequence(pit_type par_offset) const
 }
 
 
+pit_type Text::lastInSequence(pit_type pit) const
+{
+       depth_type const depth = pars_[pit].getDepth();
+       pit_type newpit = pit;
+
+       while (size_t(newpit + 1) < pars_.size() &&
+              (pars_[newpit + 1].getDepth() > depth ||
+               (pars_[newpit + 1].getDepth() == depth &&
+                pars_[newpit + 1].layout() == pars_[pit].layout())))
+               ++newpit;
+
+       return newpit;
+}
+
+
 int Text::getTocLevel(pit_type par_offset) const
 {
        Paragraph const & par = pars_[par_offset];
@@ -273,6 +278,35 @@ Font const Text::outerFont(pit_type par_offset) const
 }
 
 
+int Text::getEndLabel(pit_type p) const
+{
+       pit_type pit = p;
+       depth_type par_depth = pars_[p].getDepth();
+       while (pit != pit_type(pars_.size())) {
+               Layout const & layout = pars_[pit].layout();
+               int const endlabeltype = layout.endlabeltype;
+
+               if (endlabeltype != END_LABEL_NO_LABEL) {
+                       if (p + 1 == pit_type(pars_.size()))
+                               return endlabeltype;
+
+                       depth_type const next_depth =
+                               pars_[p + 1].getDepth();
+                       if (par_depth > next_depth ||
+                           (par_depth == next_depth && layout != pars_[p + 1].layout()))
+                               return endlabeltype;
+                       break;
+               }
+               if (par_depth == 0)
+                       break;
+               pit = outerHook(pit);
+               if (pit != pit_type(pars_.size()))
+                       par_depth = pars_[pit].getDepth();
+       }
+       return END_LABEL_NO_LABEL;
+}
+
+
 static void acceptOrRejectChanges(ParagraphList & pars,
        BufferParams const & bparams, Text::ChangeOp op)
 {
@@ -376,8 +410,8 @@ void Text::readParToken(Paragraph & par, Lexer & lex,
                if (added_one) {
                        // Warn the user.
                        docstring const s = bformat(_("Layout `%1$s' was not found."), layoutname);
-                       errorList.push_back(
-                               ErrorItem(_("Layout Not Found"), s, par.id(), 0, par.size()));
+                       errorList.push_back(ErrorItem(_("Layout Not Found"), s,
+                                                     {par.id(), 0}, {par.id(), -1}));
                }
 
                par.setLayout(bp.documentClass()[layoutname]);
@@ -403,7 +437,7 @@ void Text::readParToken(Paragraph & par, Lexer & lex,
                        lex.eatLine();
                        docstring line = lex.getDocString();
                        errorList.push_back(ErrorItem(_("Unknown Inset"), line,
-                                           par.id(), 0, par.size()));
+                                                     {par.id(), 0}, {par.id(), -1}));
                }
        } else if (token == "\\family") {
                lex.next();
@@ -430,6 +464,9 @@ void Text::readParToken(Paragraph & par, Lexer & lex,
        } else if (token == "\\numeric") {
                lex.next();
                font.fontInfo().setNumber(setLyXMisc(lex.getString()));
+       } else if (token == "\\nospellcheck") {
+               lex.next();
+               font.fontInfo().setNoSpellcheck(setLyXMisc(lex.getString()));
        } else if (token == "\\emph") {
                lex.next();
                font.fontInfo().setEmph(setLyXMisc(lex.getString()));
@@ -449,6 +486,9 @@ void Text::readParToken(Paragraph & par, Lexer & lex,
        } else if (token == "\\strikeout") {
                lex.next();
                font.fontInfo().setStrikeout(setLyXMisc(lex.getString()));
+       } else if (token == "\\xout") {
+               lex.next();
+               font.fontInfo().setXout(setLyXMisc(lex.getString()));
        } else if (token == "\\uuline") {
                lex.next();
                font.fontInfo().setUuline(setLyXMisc(lex.getString()));
@@ -520,8 +560,7 @@ void Text::readParToken(Paragraph & par, Lexer & lex,
                                          "missing until the corresponding tracked changes "
                                          "are merged or this user edits the file again.\n"),
                                        aid),
-                               par.id(), par.size(), par.size() + 1
-                               ));
+                               {par.id(), par.size()}, {par.id(), par.size() + 1}));
                        bp.addAuthor(Author(aid));
                }
                if (token == "\\change_inserted")
@@ -531,9 +570,10 @@ void Text::readParToken(Paragraph & par, Lexer & lex,
        } else {
                lex.eatLine();
                errorList.push_back(ErrorItem(_("Unknown token"),
-                       bformat(_("Unknown token: %1$s %2$s\n"), from_utf8(token),
-                       lex.getDocString()),
-                       par.id(), 0, par.size()));
+                                             bformat(_("Unknown token: %1$s %2$s\n"),
+                                                     from_utf8(token),
+                                                     lex.getDocString()),
+                                             {par.id(), 0}, {par.id(), -1}));
        }
 }
 
@@ -572,7 +612,8 @@ void Text::readParagraph(Paragraph & par, Lexer & lex,
                }
        }
        // Final change goes to paragraph break:
-       par.setChange(par.size(), change);
+       if (inset().allowMultiPar())
+               par.setChange(par.size(), change);
 
        // Initialize begin_of_body_ on load; redoParagraph maintains
        par.setBeginOfBody();
@@ -586,30 +627,30 @@ class TextCompletionList : public CompletionList
 {
 public:
        ///
-       TextCompletionList(Cursor const & cur, WordList const * list)
+       TextCompletionList(Cursor const & cur, WordList const & list)
                : buffer_(cur.buffer()), list_(list)
        {}
        ///
        virtual ~TextCompletionList() {}
 
        ///
-       virtual bool sorted() const { return true; }
+       bool sorted() const override { return true; }
        ///
-       virtual size_t size() const
+       size_t size() const override
        {
-               return list_->size();
+               return list_.size();
        }
        ///
-       virtual docstring const & data(size_t idx) const
+       docstring const & data(size_t idx) const override
        {
-               return list_->word(idx);
+               return list_.word(idx);
        }
 
 private:
        ///
        Buffer const * buffer_;
        ///
-       WordList const * list_;
+       WordList const & list_;
 };
 
 
@@ -643,8 +684,7 @@ static void breakParagraph(Text & text, pit_type par_offset, pos_type pos,
        ParagraphList & pars = text.paragraphs();
        // create a new paragraph, and insert into the list
        ParagraphList::iterator tmp =
-               pars.insert(lyx::next(pars.begin(), par_offset + 1),
-                           Paragraph());
+               pars.insert(pars.iterator_at(par_offset + 1), Paragraph());
 
        Paragraph & par = pars[par_offset];
 
@@ -675,6 +715,7 @@ static void breakParagraph(Text & text, pit_type par_offset, pos_type pos,
 
                tmp->params().depth(par.params().depth());
                tmp->params().noindent(par.params().noindent());
+               tmp->params().spacing(par.params().spacing());
 
                // move everything behind the break position
                // to the new paragraph
@@ -738,9 +779,13 @@ void Text::breakParagraph(Cursor & cur, bool inverse_logic)
        Layout const & layout = cpar.layout();
 
        if (cur.lastpos() == 0 && !cpar.allowEmpty()) {
-               if (changeDepthAllowed(cur, DEC_DEPTH))
+               if (changeDepthAllowed(cur, DEC_DEPTH)) {
                        changeDepth(cur, DEC_DEPTH);
-               else {
+                       pit_type const prev = depthHook(cpit, cpar.getDepth());
+                       docstring const & lay = pars_[prev].layout().name();
+                       if (lay != layout.name())
+                               setLayout(cur, lay);
+               } else {
                        docstring const & lay = cur.paragraph().usePlainLayout()
                            ? tclass.plainLayoutName() : tclass.defaultLayoutName();
                        if (lay != layout.name())
@@ -814,12 +859,18 @@ void Text::insertStringAsLines(Cursor & cur, docstring const & str,
        pit_type pit = cur.pit();
        pos_type pos = cur.pos();
 
+       // The special chars we handle
+       map<wchar_t, InsetSpecialChar::Kind> specialchars;
+       specialchars[0x200c] = InsetSpecialChar::LIGATURE_BREAK;
+       specialchars[0x200b] = InsetSpecialChar::ALLOWBREAK;
+       specialchars[0x2026] = InsetSpecialChar::LDOTS;
+       specialchars[0x2011] = InsetSpecialChar::NOBREAKDASH;
+
        // insert the string, don't insert doublespace
        bool space_inserted = true;
-       for (docstring::const_iterator cit = str.begin();
-           cit != str.end(); ++cit) {
+       for (auto const & ch : str) {
                Paragraph & par = pars_[pit];
-               if (*cit == '\n') {
+               if (ch == '\n') {
                        if (inset().allowMultiPar() && (!par.empty() || par.allowEmpty())) {
                                lyx::breakParagraph(*this, pit, pos,
                                        par.layout().isEnvironment());
@@ -830,28 +881,35 @@ void Text::insertStringAsLines(Cursor & cur, docstring const & str,
                                continue;
                        }
                // do not insert consecutive spaces if !free_spacing
-               } else if ((*cit == ' ' || *cit == '\t') &&
+               } else if ((ch == ' ' || ch == '\t') &&
                           space_inserted && !par.isFreeSpacing()) {
                        continue;
-               } else if (*cit == '\t') {
+               } else if (ch == '\t') {
                        if (!par.isFreeSpacing()) {
                                // tabs are like spaces here
                                par.insertChar(pos, ' ', font, bparams.track_changes);
                                ++pos;
                                space_inserted = true;
                        } else {
-                               par.insertChar(pos, *cit, font, bparams.track_changes);
+                               par.insertChar(pos, ch, font, bparams.track_changes);
                                ++pos;
                                space_inserted = true;
                        }
-               } else if (!isPrintable(*cit)) {
-                       // Ignore unprintables
+               } else if (specialchars.find(ch) != specialchars.end()) {
+                       par.insertInset(pos, new InsetSpecialChar(specialchars.find(ch)->second),
+                                       font, bparams.track_changes ?
+                                               Change(Change::INSERTED)
+                                             : Change(Change::UNCHANGED));
+                       ++pos;
+                       space_inserted = false;
+               } else if (!isPrintable(ch)) {
+                       // Ignore (other) unprintables
                        continue;
                } else {
                        // just insert the character
-                       par.insertChar(pos, *cit, font, bparams.track_changes);
+                       par.insertChar(pos, ch, font, bparams.track_changes);
                        ++pos;
-                       space_inserted = (*cit == ' ');
+                       space_inserted = (ch == ' ');
                }
        }
        setCursor(cur, pit, pos);
@@ -886,12 +944,55 @@ void Text::insertStringAsParagraphs(Cursor & cur, docstring const & str,
 }
 
 
+namespace {
+
+bool canInsertChar(Cursor const & cur, char_type c)
+{
+       Paragraph const & par = cur.paragraph();
+       // If not in free spacing mode, check if there will be two blanks together or a blank at
+       // the beginning of a paragraph.
+       if (!par.isFreeSpacing() && isLineSeparatorChar(c)) {
+               if (cur.pos() == 0) {
+                       cur.message(_(
+                                       "You cannot insert a space at the "
+                                       "beginning of a paragraph. Please read the Tutorial."));
+                       return false;
+               }
+               // If something is wrong, ignore this character.
+               LASSERT(cur.pos() > 0, return false);
+               if ((par.isLineSeparator(cur.pos() - 1) || par.isNewline(cur.pos() - 1))
+                               && !par.isDeleted(cur.pos() - 1)) {
+                       cur.message(_(
+                                       "You cannot type two spaces this way. "
+                                       "Please read the Tutorial."));
+                       return false;
+               }
+       }
+
+       // Prevent to insert uncodable characters in verbatim and ERT.
+       // The encoding is inherited from the context here.
+       if (par.isPassThru() && cur.getEncoding()) {
+               Encoding const * e = cur.getEncoding();
+               if (!e->encodable(c)) {
+                       cur.message(_("Character is uncodable in this verbatim context."));
+                       return false;
+               }
+       }
+       return true;
+}
+
+} // namespace
+
+
 // insert a character, moves all the following breaks in the
 // same Paragraph one to the right and make a rebreak
 void Text::insertChar(Cursor & cur, char_type c)
 {
        LBUFERR(this == cur.text());
 
+       if (!canInsertChar(cur,c))
+               return;
+
        cur.recordUndo(INSERT_UNDO);
 
        TextMetrics const & tm = cur.bv().textMetrics(this);
@@ -900,20 +1001,22 @@ void Text::insertChar(Cursor & cur, char_type c)
        // try to remove this
        pit_type const pit = cur.pit();
 
-       bool const freeSpacing = par.layout().free_spacing ||
-               par.isFreeSpacing();
-
        if (lyxrc.auto_number) {
                static docstring const number_operators = from_ascii("+-/*");
                static docstring const number_unary_operators = from_ascii("+-");
-               static docstring const number_separators = from_ascii(".,:");
 
+               // Common Number Separators: comma, dot etc.
+               // European Number Terminators: percent, permille, degree, euro etc.
                if (cur.current_font.fontInfo().number() == FONT_ON) {
                        if (!isDigitASCII(c) && !contains(number_operators, c) &&
-                           !(contains(number_separators, c) &&
+                           !(isCommonNumberSeparator(c) &&
                              cur.pos() != 0 &&
                              cur.pos() != cur.lastpos() &&
                              tm.displayFont(pit, cur.pos()).fontInfo().number() == FONT_ON &&
+                             tm.displayFont(pit, cur.pos() - 1).fontInfo().number() == FONT_ON) &&
+                           !(isEuropeanNumberTerminator(c) &&
+                             cur.pos() != 0 &&
+                             tm.displayFont(pit, cur.pos()).fontInfo().number() == FONT_ON &&
                              tm.displayFont(pit, cur.pos() - 1).fontInfo().number() == FONT_ON)
                           )
                                number(cur); // Set current_font.number to OFF
@@ -922,8 +1025,8 @@ void Text::insertChar(Cursor & cur, char_type c)
                        number(cur); // Set current_font.number to ON
 
                        if (cur.pos() != 0) {
-                               char_type const c = par.getChar(cur.pos() - 1);
-                               if (contains(number_unary_operators, c) &&
+                               char_type const ch = par.getChar(cur.pos() - 1);
+                               if (contains(number_unary_operators, ch) &&
                                    (cur.pos() == 1
                                     || par.isSeparator(cur.pos() - 2)
                                     || par.isEnvSeparator(cur.pos() - 2)
@@ -931,7 +1034,7 @@ void Text::insertChar(Cursor & cur, char_type c)
                                  ) {
                                        setCharFont(pit, cur.pos() - 1, cur.current_font,
                                                tm.font_);
-                               } else if (contains(number_separators, c)
+                               } else if (isCommonNumberSeparator(ch)
                                     && cur.pos() >= 2
                                     && tm.displayFont(pit, cur.pos() - 2).fontInfo().number() == FONT_ON) {
                                        setCharFont(pit, cur.pos() - 1, cur.current_font,
@@ -985,45 +1088,6 @@ void Text::insertChar(Cursor & cur, char_type c)
                }
        }
 
-       // Next check, if there will be two blanks together or a blank at
-       // the beginning of a paragraph.
-       // I decided to handle blanks like normal characters, the main
-       // difference are the special checks when calculating the row.fill
-       // (blank does not count at the end of a row) and the check here
-
-       // When the free-spacing option is set for the current layout,
-       // disable the double-space checking
-       if (!freeSpacing && isLineSeparatorChar(c)) {
-               if (cur.pos() == 0) {
-                       cur.message(_(
-                                       "You cannot insert a space at the "
-                                       "beginning of a paragraph. Please read the Tutorial."));
-                       return;
-               }
-               // LASSERT: Is it safe to continue here?
-               LASSERT(cur.pos() > 0, /**/);
-               if ((par.isLineSeparator(cur.pos() - 1) || par.isNewline(cur.pos() - 1))
-                               && !par.isDeleted(cur.pos() - 1)) {
-                       cur.message(_(
-                                       "You cannot type two spaces this way. "
-                                       "Please read the Tutorial."));
-                       return;
-               }
-       }
-
-       // Prevent to insert uncodable characters in verbatim and ERT
-       // (workaround for bug 9012)
-       // Don't do it for listings inset, since InsetListings::latex() tries
-       // to switch to a usable encoding which works in many cases (bug 9102).
-       if (cur.paragraph().isPassThru() && owner_->lyxCode() != LISTINGS_CODE &&
-           cur.current_font.language()) {
-               Encoding const * e = cur.current_font.language()->encoding();
-               if (!e->encodable(c)) {
-                       cur.message(_("Character is uncodable in verbatim paragraphs."));
-                       return;
-               }
-       }
-
        pos_type pos = cur.pos();
        if (!cur.paragraph().isPassThru() && owner_->lyxCode() != IPA_CODE &&
            cur.real_current_font.fontInfo().family() != TYPEWRITER_FAMILY &&
@@ -1038,11 +1102,6 @@ void Text::insertChar(Cursor & cur, char_type c)
                        par.eraseChar(pos - 1, cur.buffer()->params().track_changes);
                        c = 0x2014;
                        pos--;
-               } else if (par.getChar(pos - 1) == 0x2014) {
-                       // convert "----" to "-"
-                       par.eraseChar(pos - 1, cur.buffer()->params().track_changes);
-                       c = '-';
-                       pos--;
                }
        }
 
@@ -1068,7 +1127,7 @@ void Text::charInserted(Cursor & cur)
            && par.isWordSeparator(cur.pos() - 1)) {
                // get the word in front of cursor
                LBUFERR(this == cur.text());
-               cur.paragraph().updateWords();
+               par.updateWords();
        }
 }
 
@@ -1080,30 +1139,53 @@ bool Text::cursorForwardOneWord(Cursor & cur)
 {
        LBUFERR(this == cur.text());
 
-       pos_type const lastpos = cur.lastpos();
-       pit_type pit = cur.pit();
-       pos_type pos = cur.pos();
-       Paragraph const & par = cur.paragraph();
-
-       // Paragraph boundary is a word boundary
-       if (pos == lastpos || (pos + 1 == lastpos && par.isEnvSeparator(pos))) {
-               if (pit != cur.lastpit())
-                       return setCursor(cur, pit + 1, 0);
-               else
-                       return false;
-       }
-
        if (lyxrc.mac_like_cursor_movement) {
-               // Skip through trailing punctuation and spaces.
-               while (pos != lastpos && (par.isChar(pos) || par.isSpace(pos)))
-                       ++pos;
-
-               // Skip over either a non-char inset or a full word
-               if (pos != lastpos && par.isWordSeparator(pos))
-                       ++pos;
-               else while (pos != lastpos && !par.isWordSeparator(pos))
-                            ++pos;
+               DocIterator dit(cur);
+               DocIterator prv(cur);
+               bool inword = false;
+               bool intext = dit.inTexted();
+               while (!dit.atEnd()) {
+                       if (dit.inTexted()) { // no paragraphs in mathed
+                               Paragraph const & par = dit.paragraph();
+                               pos_type const pos = dit.pos();
+
+                               if (!par.isDeleted(pos)) {
+                                       bool wordsep = par.isWordSeparator(pos);
+                                       if (inword && wordsep)
+                                               break; // stop at word end
+                                       else if (!inword && !wordsep)
+                                               inword = true;
+                               }
+                               intext = true;
+                       } else if (intext) {
+                               // move to end of math
+                               while (!dit.inTexted() && !dit.atEnd()) dit.forwardPos();
+                               break;
+                       }
+                       prv = dit;
+                       dit.forwardPosIgnoreCollapsed();
+               }
+               if (dit.atEnd()) dit = prv;
+               if (dit == cur) return false; // we didn't move
+               Cursor orig(cur);
+               cur.setCursor(dit);
+               // see comment above
+               cur.bv().checkDepm(cur, orig);
+               return true;
        } else {
+               pos_type const lastpos = cur.lastpos();
+               pit_type pit = cur.pit();
+               pos_type pos = cur.pos();
+               Paragraph const & par = cur.paragraph();
+
+               // Paragraph boundary is a word boundary
+               if (pos == lastpos || (pos + 1 == lastpos && par.isEnvSeparator(pos))) {
+                       if (pit != cur.lastpit())
+                               return setCursor(cur, pit + 1, 0);
+                       else
+                               return false;
+               }
+
                LASSERT(pos < lastpos, return false); // see above
                if (!par.isWordSeparator(pos))
                        while (pos != lastpos && !par.isWordSeparator(pos))
@@ -1117,13 +1199,13 @@ bool Text::cursorForwardOneWord(Cursor & cur)
                // Skip over white space
                while (pos != lastpos && par.isSpace(pos))
                             ++pos;
-       }
 
-       // Don't skip a separator inset at the end of a paragraph
-       if (pos == lastpos && pos && par.isEnvSeparator(pos - 1))
-               --pos;
+               // Don't skip a separator inset at the end of a paragraph
+               if (pos == lastpos && pos && par.isEnvSeparator(pos - 1))
+                       --pos;
 
-       return setCursor(cur, pit, pos);
+               return setCursor(cur, pit, pos);
+       }
 }
 
 
@@ -1131,34 +1213,55 @@ bool Text::cursorBackwardOneWord(Cursor & cur)
 {
        LBUFERR(this == cur.text());
 
-       pit_type pit = cur.pit();
-       pos_type pos = cur.pos();
-       Paragraph & par = cur.paragraph();
-
-       // Paragraph boundary is a word boundary
-       if (pos == 0 && pit != 0) {
-               Paragraph & prevpar = getPar(pit - 1);
-               pos = prevpar.size();
-               // Don't stop after an environment separator
-               if (pos && prevpar.isEnvSeparator(pos - 1))
-                       --pos;
-               return setCursor(cur, pit - 1, pos);
-       }
-
        if (lyxrc.mac_like_cursor_movement) {
-               // Skip through punctuation and spaces.
-               while (pos != 0 && (par.isChar(pos - 1) || par.isSpace(pos - 1)))
-                       --pos;
-
-               // Skip over either a non-char inset or a full word
-               if (pos != 0 && par.isWordSeparator(pos - 1) && !par.isChar(pos - 1))
-                       --pos;
-               else while (pos != 0 && !par.isWordSeparator(pos - 1))
-                            --pos;
+               DocIterator dit(cur);
+               bool inword = false;
+               bool intext = dit.inTexted();
+               while (!dit.atBegin()) {
+                       DocIterator prv(dit);
+                       dit.backwardPosIgnoreCollapsed();
+                       if (dit.inTexted()) { // no paragraphs in mathed
+                               Paragraph const & par = dit.paragraph();
+                               pos_type pos = dit.pos();
+
+                               if (!par.isDeleted(pos)) {
+                                       bool wordsep = par.isWordSeparator(pos);
+                                       if (inword && wordsep) {
+                                               dit = prv;
+                                               break; // stop at word begin
+                                       } else if (!inword && !wordsep)
+                                               inword = true;
+                               }
+                               intext = true;
+                       } else if (intext) {
+                               // move to begin of math
+                               while (!dit.inTexted() && !dit.atBegin()) dit.backwardPos();
+                               break;
+                       }
+               }
+               if (dit == cur) return false; // we didn't move
+               Cursor orig(cur);
+               cur.setCursor(dit);
+               // see comment above cursorForwardOneWord
+               cur.bv().checkDepm(cur, orig);
+               return true;
        } else {
+               Paragraph const & par = cur.paragraph();
+               pit_type const pit = cur.pit();
+               pos_type pos = cur.pos();
+
+               // Paragraph boundary is a word boundary
+               if (pos == 0 && pit != 0) {
+                       Paragraph & prevpar = getPar(pit - 1);
+                       pos = prevpar.size();
+                       // Don't stop after an environment separator
+                       if (pos && prevpar.isEnvSeparator(pos - 1))
+                               --pos;
+                       return setCursor(cur, pit - 1, pos);
+               }
                // Skip over white space
                while (pos != 0 && par.isSpace(pos - 1))
-                            --pos;
+                       --pos;
 
                if (pos != 0 && !par.isWordSeparator(pos - 1))
                        while (pos != 0 && !par.isWordSeparator(pos - 1))
@@ -1168,9 +1271,9 @@ bool Text::cursorBackwardOneWord(Cursor & cur)
                                --pos;
                else if (pos != 0 && !par.isSpace(pos - 1)) // non-char inset
                        --pos;
-       }
 
-       return setCursor(cur, pit, pos);
+               return setCursor(cur, pit, pos);
+       }
 }
 
 
@@ -1315,7 +1418,7 @@ void Text::acceptOrRejectChanges(Cursor & cur, ChangeOp op)
        pos_type endPos = cur.selectionEnd().pos();
 
        // keep selection info, because endPos becomes invalid after the first loop
-       bool endsBeforeEndOfPar = (endPos < pars_[endPit].size());
+       bool const endsBeforeEndOfPar = (endPos < pars_[endPit].size());
 
        // first, accept/reject changes within each individual paragraph (do not consider end-of-par)
        for (pit_type pit = begPit; pit <= endPit; ++pit) {
@@ -1334,8 +1437,8 @@ void Text::acceptOrRejectChanges(Cursor & cur, ChangeOp op)
                if (pit == endPit && endPos == 0)
                        break; // last iteration anyway
 
-               pos_type left  = (pit == begPit ? begPos : 0);
-               pos_type right = (pit == endPit ? endPos : parSize);
+               pos_type const left  = (pit == begPit ? begPos : 0);
+               pos_type const right = (pit == endPit ? endPos : parSize);
 
                if (left == right)
                        // there is no change here
@@ -1447,7 +1550,7 @@ void Text::rejectChanges()
 }
 
 
-void Text::deleteWordForward(Cursor & cur)
+void Text::deleteWordForward(Cursor & cur, bool const force)
 {
        LBUFERR(this == cur.text());
        if (cur.lastpos() == 0)
@@ -1457,13 +1560,15 @@ void Text::deleteWordForward(Cursor & cur)
                cur.selection(true);
                cursorForwardOneWord(cur);
                cur.setSelection();
-               cutSelection(cur, true, false);
-               cur.checkBufferStructure();
+               if (force || !cur.confirmDeletion()) {
+                       cutSelection(cur, false);
+                       cur.checkBufferStructure();
+               }
        }
 }
 
 
-void Text::deleteWordBackward(Cursor & cur)
+void Text::deleteWordBackward(Cursor & cur, bool const force)
 {
        LBUFERR(this == cur.text());
        if (cur.lastpos() == 0)
@@ -1473,8 +1578,10 @@ void Text::deleteWordBackward(Cursor & cur)
                cur.selection(true);
                cursorBackwardOneWord(cur);
                cur.setSelection();
-               cutSelection(cur, true, false);
-               cur.checkBufferStructure();
+               if (force || !cur.confirmDeletion()) {
+                       cutSelection(cur, false);
+                       cur.checkBufferStructure();
+               }
        }
 }
 
@@ -1615,10 +1722,7 @@ bool Text::backspacePos0(Cursor & cur)
        if (cur.pit() == 0)
                return false;
 
-       bool needsUpdate = false;
-
        BufferParams const & bufparams = cur.buffer()->params();
-       DocumentClass const & tclass = bufparams.documentClass();
        ParagraphList & plist = cur.text()->paragraphs();
        Paragraph const & par = cur.paragraph();
        Cursor prevcur = cur;
@@ -1630,35 +1734,29 @@ bool Text::backspacePos0(Cursor & cur)
        if (cur.lastpos() == 0
            || (cur.lastpos() == 1 && par.isSeparator(0))) {
                cur.recordUndo(prevcur.pit());
-               plist.erase(lyx::next(plist.begin(), cur.pit()));
-               needsUpdate = true;
+               plist.erase(plist.iterator_at(cur.pit()));
        }
        // is previous par empty?
        else if (prevcur.lastpos() == 0
                 || (prevcur.lastpos() == 1 && prevpar.isSeparator(0))) {
                cur.recordUndo(prevcur.pit());
-               plist.erase(lyx::next(plist.begin(), prevcur.pit()));
-               needsUpdate = true;
+               plist.erase(plist.iterator_at(prevcur.pit()));
        }
+       // FIXME: Do we really not want to allow this???
        // Pasting is not allowed, if the paragraphs have different
        // layouts. I think it is a real bug of all other
        // word processors to allow it. It confuses the user.
        // Correction: Pasting is always allowed with standard-layout
        // or the empty layout.
-       else if (par.layout() == prevpar.layout()
-                || tclass.isDefaultLayout(par.layout())
-                || tclass.isPlainLayout(par.layout())) {
+       else {
                cur.recordUndo(prevcur.pit());
                mergeParagraph(bufparams, plist, prevcur.pit());
-               needsUpdate = true;
        }
 
-       if (needsUpdate) {
-               cur.forceBufferUpdate();
-               setCursorIntern(cur, prevcur.pit(), prevcur.pos());
-       }
+       cur.forceBufferUpdate();
+       setCursorIntern(cur, prevcur.pit(), prevcur.pos());
 
-       return needsUpdate;
+       return true;
 }
 
 
@@ -1673,7 +1771,8 @@ bool Text::backspace(Cursor & cur)
                Cursor prev_cur = cur;
                --prev_cur.pit();
 
-               if (!prev_cur.paragraph().isMergedOnEndOfParDeletion(cur.buffer()->params().track_changes)) {
+               if (!cur.paragraph().empty()
+                   && !prev_cur.paragraph().isMergedOnEndOfParDeletion(cur.buffer()->params().track_changes)) {
                        cur.recordUndo(prev_cur.pit(), prev_cur.pit());
                        prev_cur.paragraph().setChange(prev_cur.lastpos(), Change(Change::DELETED));
                        setCursorIntern(cur, prev_cur.pit(), prev_cur.lastpos());
@@ -1707,7 +1806,7 @@ bool Text::backspace(Cursor & cur)
        needsUpdate |= handleBibitems(cur);
 
        // A singlePar update is not enough in this case.
-//             cur.screenUpdateFlags(Update::Force);
+       // cur.screenUpdateFlags(Update::Force);
        cur.top().setPitPos(cur.pit(), cur.pos());
 
        return needsUpdate;
@@ -1724,21 +1823,24 @@ bool Text::dissolveInset(Cursor & cur)
        cur.recordUndoInset();
        cur.setMark(false);
        cur.selHandle(false);
-       // save position
+       // save position inside inset
        pos_type spos = cur.pos();
        pit_type spit = cur.pit();
-       ParagraphList plist;
-       if (cur.lastpit() != 0 || cur.lastpos() != 0)
-               plist = paragraphs();
+       bool const inset_non_empty = cur.lastpit() != 0 || cur.lastpos() != 0;
        cur.popBackward();
-       // store cursor offset
+       // update cursor offset
        if (spit == 0)
                spos += cur.pos();
        spit += cur.pit();
-       Buffer & b = *cur.buffer();
-       cur.paragraph().eraseChar(cur.pos(), b.params().track_changes);
+       // remember position outside inset to delete inset later
+       // we do not do it now to avoid memory reuse issues (see #10667).
+       DocIterator inset_it = cur;
+       // jump over inset
+       ++cur.pos();
 
-       if (!plist.empty()) {
+       Buffer & b = *cur.buffer();
+       // Is there anything in this text?
+       if (inset_non_empty) {
                // see bug 7319
                // we clear the cache so that we won't get conflicts with labels
                // that get pasted into the buffer. we should update this before
@@ -1748,27 +1850,46 @@ bool Text::dissolveInset(Cursor & cur)
                // but we'll try the cheaper solution here.
                cur.buffer()->clearReferenceCache();
 
+               ParagraphList & plist = paragraphs();
+               if (!lyxrc.ct_markup_copied)
+                       // Do not revive deleted text
+                       lyx::acceptChanges(plist, b.params());
+
                // ERT paragraphs have the Language latex_language.
                // This is invalid outside of ERT, so we need to
                // change it to the buffer language.
-               ParagraphList::iterator it = plist.begin();
-               ParagraphList::iterator it_end = plist.end();
-               for (; it != it_end; ++it)
-                       it->changeLanguage(b.params(), latex_language, b.language());
+               for (auto & p : plist)
+                       p.changeLanguage(b.params(), latex_language, b.language());
+
+               /* If the inset is the only thing in paragraph and the layout
+                * is not plain, then the layout of the first paragraph of
+                * inset should be remembered.
+                * FIXME: this does not work as expected when change tracking
+                *   is on However, we do not really know what to do in this
+                *   case.
+                */
+               DocumentClass const & tclass = cur.buffer()->params().documentClass();
+               if (inset_it.lastpos() == 1
+                       && !tclass.isPlainLayout(plist[0].layout())
+                       && !tclass.isDefaultLayout(plist[0].layout()))
+                       cur.paragraph().makeSameLayout(plist[0]);
 
                pasteParagraphList(cur, plist, b.params().documentClassPtr(),
                                   b.errorList("Paste"));
-               // restore position
-               cur.pit() = min(cur.lastpit(), spit);
-               cur.pos() = min(cur.lastpos(), spos);
        }
 
-       cur.forceBufferUpdate();
+       // delete the inset now
+       inset_it.paragraph().eraseChar(inset_it.pos(), b.params().track_changes);
 
+       // restore position
+       cur.pit() = min(cur.lastpit(), spit);
+       cur.pos() = min(cur.lastpos(), spos);
        // Ensure the current language is set correctly (bug 6292)
        cur.text()->setCursor(cur, cur.pit(), cur.pos());
        cur.clearSelection();
        cur.resetAnchor();
+       cur.forceBufferUpdate();
+
        return true;
 }
 
@@ -1863,8 +1984,8 @@ bool Text::read(Lexer & lex,
 }
 
 
-// Returns the current font and depth as a message.
-docstring Text::currentState(Cursor const & cur) const
+// Returns the current state (font, depth etc.) as a message for status bar.
+docstring Text::currentState(CursorData const & cur, bool devel_mode) const
 {
        LBUFERR(this == cur.text());
        Buffer & buf = *cur.buffer();
@@ -1893,7 +2014,7 @@ docstring Text::currentState(Cursor const & cur) const
        os << bformat(_("Font: %1$s"), font.stateText(&buf.params()));
 
        // The paragraph depth
-       int depth = cur.paragraph().getDepth();
+       int depth = par.getDepth();
        if (depth > 0)
                os << bformat(_(", Depth: %1$d"), depth);
 
@@ -1921,34 +2042,73 @@ docstring Text::currentState(Cursor const & cur) const
                }
        }
 
-#ifdef DEVEL_VERSION
-       os << _(", Inset: ") << &cur.inset();
-       os << _(", Paragraph: ") << cur.pit();
-       os << _(", Id: ") << par.id();
-       os << _(", Position: ") << cur.pos();
-       // FIXME: Why is the check for par.size() needed?
-       // We are called with cur.pos() == par.size() quite often.
-       if (!par.empty() && cur.pos() < par.size()) {
-               // Force output of code point, not character
-               size_t const c = par.getChar(cur.pos());
-               os << _(", Char: 0x") << hex << c;
+       // Custom text style
+       InsetLayout const & layout = cur.inset().getLayout();
+       if (layout.lyxtype() == InsetLayout::CHARSTYLE)
+               os << _(", Style: ") << translateIfPossible(layout.labelstring());
+
+       if (devel_mode) {
+               os << _(", Inset: ") << &cur.inset();
+               if (cur.lastidx() > 0)
+                       os << _(", Cell: ") << cur.idx();
+               os << _(", Paragraph: ") << cur.pit();
+               os << _(", Id: ") << par.id();
+               os << _(", Position: ") << cur.pos();
+               // FIXME: Why is the check for par.size() needed?
+               // We are called with cur.pos() == par.size() quite often.
+               if (!par.empty() && cur.pos() < par.size()) {
+                       // Force output of code point, not character
+                       size_t const c = par.getChar(cur.pos());
+                       os << _(", Char: 0x") << hex << c;
+               }
+               os << _(", Boundary: ") << cur.boundary();
+//             Row & row = cur.textRow();
+//             os << bformat(_(", Row b:%1$d e:%2$d"), row.pos(), row.endpos());
        }
-       os << _(", Boundary: ") << cur.boundary();
-//     Row & row = cur.textRow();
-//     os << bformat(_(", Row b:%1$d e:%2$d"), row.pos(), row.endpos());
-#endif
        return os.str();
 }
 
 
-docstring Text::getPossibleLabel(Cursor const & cur) const
+docstring Text::getPossibleLabel(DocIterator const & cur) const
 {
-       pit_type pit = cur.pit();
+       pit_type textpit = cur.pit();
+       Layout const * layout = &(pars_[textpit].layout());
+
+       // Will contain the label prefix.
+       docstring name;
+
+       // For captions, we just take the caption type
+       Inset * caption_inset = cur.innerInsetOfType(CAPTION_CODE);
+       if (caption_inset) {
+               string const & ftype = static_cast<InsetCaption *>(caption_inset)->floattype();
+               FloatList const & fl = cur.buffer()->params().documentClass().floats();
+               if (fl.typeExist(ftype)) {
+                       Floating const & flt = fl.getType(ftype);
+                       name = from_utf8(flt.refPrefix());
+               }
+               if (name.empty())
+                       name = from_utf8(ftype.substr(0,3));
+       } else {
+               // For section, subsection, etc...
+               if (layout->latextype == LATEX_PARAGRAPH && textpit != 0) {
+                       Layout const * layout2 = &(pars_[textpit - 1].layout());
+                       if (layout2->latextype != LATEX_PARAGRAPH) {
+                               --textpit;
+                               layout = layout2;
+                       }
+               }
+               if (layout->latextype != LATEX_PARAGRAPH)
+                       name = layout->refprefix;
 
-       Layout const * layout = &(pars_[pit].layout());
+               // If none of the above worked, see if the inset knows.
+               if (name.empty()) {
+                       InsetLayout const & il = cur.inset().getLayout();
+                       name = il.refprefix();
+               }
+       }
 
        docstring text;
-       docstring par_text = pars_[pit].asString();
+       docstring par_text = pars_[textpit].asString(AS_STR_SKIPDELETE);
 
        // The return string of math matrices might contain linebreaks
        par_text = subst(par_text, '\n', '-');
@@ -1969,46 +2129,13 @@ docstring Text::getPossibleLabel(Cursor const & cur) const
        if (text.size() > max_label_length)
                text.resize(max_label_length);
 
-       // Will contain the label prefix.
-       docstring name;
-
-       // For section, subsection, etc...
-       if (layout->latextype == LATEX_PARAGRAPH && pit != 0) {
-               Layout const * layout2 = &(pars_[pit - 1].layout());
-               if (layout2->latextype != LATEX_PARAGRAPH) {
-                       --pit;
-                       layout = layout2;
-               }
-       }
-       if (layout->latextype != LATEX_PARAGRAPH)
-               name = layout->refprefix;
-
-       // For captions, we just take the caption type
-       Inset * caption_inset = cur.innerInsetOfType(CAPTION_CODE);
-       if (caption_inset) {
-               string const & ftype = static_cast<InsetCaption *>(caption_inset)->floattype();
-               FloatList const & fl = cur.buffer()->params().documentClass().floats();
-               if (fl.typeExist(ftype)) {
-                       Floating const & flt = fl.getType(ftype);
-                       name = from_utf8(flt.refPrefix());
-               }
-               if (name.empty())
-                       name = from_utf8(ftype.substr(0,3));
-       }
-
-       // If none of the above worked, see if the inset knows.
-       if (name.empty()) {
-               InsetLayout const & il = cur.inset().getLayout();
-               name = il.refprefix();
-       }
-
        if (!name.empty())
                text = name + ':' + text;
 
        // We need a unique label
        docstring label = text;
        int i = 1;
-       while (cur.buffer()->insetLabel(label)) {
+       while (cur.buffer()->activeLabel(label)) {
                        label = text + '-' + convert<docstring>(i);
                        ++i;
                }
@@ -2038,20 +2165,36 @@ docstring Text::asString(pit_type beg, pit_type end, int options) const
 void Text::shortenForOutliner(docstring & str, size_t const maxlen)
 {
        support::truncateWithEllipsis(str, maxlen);
-       docstring::iterator it = str.begin();
-       docstring::iterator end = str.end();
-       for (; it != end; ++it)
-               if ((*it) == L'\n' || (*it) == L'\t')
-                       (*it) = L' ';   
+       for (char_type & c : str)
+               if (c == L'\n' || c == L'\t')
+                       c = L' ';
+}
+
+
+void Text::forOutliner(docstring & os, size_t const maxlen,
+                       bool const shorten) const
+{
+       pit_type end = pars_.size() - 1;
+       if (0 <= end && !pars_[0].labelString().empty())
+               os += pars_[0].labelString() + ' ';
+       forOutliner(os, maxlen, 0, end, shorten);
 }
 
 
 void Text::forOutliner(docstring & os, size_t const maxlen,
-                                          bool const shorten) const
+                       pit_type pit_start, pit_type pit_end,
+                       bool const shorten) const
 {
        size_t tmplen = shorten ? maxlen + 1 : maxlen;
-       for (size_t i = 0; i != pars_.size() && os.length() < tmplen; ++i)
-               pars_[i].forOutliner(os, tmplen, false);
+       pit_type end = min(size_t(pit_end), pars_.size() - 1);
+       bool first = true;
+       for (pit_type i = pit_start; i <= end && os.length() < tmplen; ++i) {
+               if (!first)
+                       os += ' ';
+               // This function lets the first label be treated separately
+               pars_[i].forOutliner(os, tmplen, false, !first);
+               first = false;
+       }
        if (shorten)
                shortenForOutliner(os, maxlen);
 }
@@ -2128,23 +2271,11 @@ void Text::setMacrocontextPosition(DocIterator const & pos)
 }
 
 
-docstring Text::previousWord(CursorSlice const & sl) const
-{
-       CursorSlice from = sl;
-       CursorSlice to = sl;
-       getWord(from, to, PREVIOUS_WORD);
-       if (sl == from || to == from)
-               return docstring();
-
-       Paragraph const & par = sl.paragraph();
-       return par.asString(from.pos(), to.pos());
-}
-
-
 bool Text::completionSupported(Cursor const & cur) const
 {
        Paragraph const & par = cur.paragraph();
-       return cur.pos() > 0
+       return !cur.selection()
+               && cur.pos() > 0
                && (cur.pos() >= par.size() || par.isWordSeparator(cur.pos()))
                && !par.isWordSeparator(cur.pos() - 1);
 }
@@ -2152,7 +2283,7 @@ bool Text::completionSupported(Cursor const & cur) const
 
 CompletionList const * Text::createCompletionList(Cursor const & cur) const
 {
-       WordList const * list = theWordList(cur.getFont().language()->lang());
+       WordList const & list = theWordList(cur.getFont().language()->lang());
        return new TextCompletionList(cur, list);
 }
 
@@ -2170,7 +2301,11 @@ bool Text::insertCompletion(Cursor & cur, docstring const & s, bool /*finished*/
 
 docstring Text::completionPrefix(Cursor const & cur) const
 {
-       return previousWord(cur.top());
+       CursorSlice from = cur.top();
+       CursorSlice to = from;
+       getWord(from, to, PREVIOUS_WORD);
+
+       return cur.paragraph().asString(from.pos(), to.pos());
 }
 
 } // namespace lyx