]> git.lyx.org Git - lyx.git/blobdiff - src/Text.cpp
Rename a variable
[lyx.git] / src / Text.cpp
index d00d2d5356ee710b7fa26a2e9df288173a89a118..2d916c834b3c08265e0d3938866e2d0201eeda33 100644 (file)
@@ -46,7 +46,6 @@
 #include "ParIterator.h"
 #include "TextClass.h"
 #include "TextMetrics.h"
-#include "VSpace.h"
 #include "WordLangTuple.h"
 #include "WordList.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/gettext.h"
 #include "support/lassert.h"
 #include "support/lstrings.h"
+#include "support/lyxalgo.h"
+#include "support/lyxtime.h"
 #include "support/textutils.h"
-
-#include <boost/next_prior.hpp>
+#include "support/unique_ptr.h"
 
 #include <sstream>
 
+
 using namespace std;
 using namespace lyx::support;
 
@@ -107,17 +110,17 @@ static bool moveItem(Paragraph & fromPar, pos_type fromPos,
 
 
 void breakParagraphConservative(BufferParams const & bparams,
-       ParagraphList & pars, pit_type par_offset, pos_type pos)
+       ParagraphList & pars, pit_type pit, pos_type pos)
 {
        // create a new paragraph
-       Paragraph & tmp = *pars.insert(boost::next(pars.begin(), par_offset + 1),
+       Paragraph & tmp = *pars.insert(lyx::next(pars.begin(), pit + 1),
                                       Paragraph());
-       Paragraph & par = pars[par_offset];
+       Paragraph & par = pars[pit];
 
        tmp.setInsetOwner(&par.inInset());
        tmp.makeSameLayout(par);
 
-       LASSERT(pos <= par.size(), /**/);
+       LASSERT(pos <= par.size(), return);
 
        if (pos < par.size()) {
                // move everything behind the break position to the new paragraph
@@ -130,7 +133,7 @@ void breakParagraphConservative(BufferParams const & bparams,
                }
                // Move over the end-of-par change information
                tmp.setChange(tmp.size(), par.lookupChange(par.size()));
-               par.setChange(par.size(), Change(bparams.trackChanges ?
+               par.setChange(par.size(), Change(bparams.track_changes ?
                                           Change::INSERTED : Change::UNCHANGED));
        }
 }
@@ -166,12 +169,12 @@ void mergeParagraph(BufferParams const & bparams,
        // move the change of the end-of-paragraph character
        par.setChange(par.size(), change);
 
-       pars.erase(boost::next(pars.begin(), par_offset + 1));
+       pars.erase(lyx::next(pars.begin(), par_offset + 1));
 }
 
 
 Text::Text(InsetText * owner, bool use_default_layout)
-       : owner_(owner), autoBreakRows_(false), undo_counter_(0)
+       : owner_(owner)
 {
        pars_.push_back(Paragraph());
        Paragraph & par = pars_.back();
@@ -185,9 +188,8 @@ Text::Text(InsetText * owner, bool use_default_layout)
 
 
 Text::Text(InsetText * owner, Text const & text)
-       : owner_(owner), autoBreakRows_(text.autoBreakRows_), undo_counter_(0)
+       : owner_(owner), pars_(text.pars_)
 {
-       pars_ = text.pars_;
        ParagraphList::iterator const end = pars_.end();
        ParagraphList::iterator it = pars_.begin();
        for (; it != end; ++it)
@@ -238,15 +240,43 @@ 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];
+
+       if (par.layout().isEnvironment() && !isFirstInSequence(par_offset))
+               return Layout::NOT_IN_TOC;
+
+       return par.layout().toclevel;
+}
+
+
 Font const Text::outerFont(pit_type par_offset) const
 {
        depth_type par_depth = pars_[par_offset].getDepth();
        FontInfo tmpfont = inherit_font;
-
+       depth_type prev_par_depth = 0;
        // Resolve against environment font information
        while (par_offset != pit_type(pars_.size())
+              && par_depth != prev_par_depth
               && par_depth
               && !tmpfont.resolved()) {
+               prev_par_depth = par_depth;
                par_offset = outerHook(par_offset);
                if (par_offset != pit_type(pars_.size())) {
                        tmpfont.realize(pars_[par_offset].layout().font);
@@ -317,11 +347,12 @@ InsetText const & Text::inset() const
 }
 
 
+
 void Text::readParToken(Paragraph & par, Lexer & lex,
        string const & token, Font & font, Change & change, ErrorList & errorList)
 {
        Buffer * buf = const_cast<Buffer *>(&owner_->buffer());
-       BufferParams const & bp = buf->params();
+       BufferParams & bp = buf->params();
 
        if (token[0] != '\\') {
                docstring dstr = lex.getDocString();
@@ -343,10 +374,10 @@ void Text::readParToken(Paragraph & par, Lexer & lex,
                        // in this case only the empty layout is allowed
                        layoutname = tclass.plainLayoutName();
                } else if (par.usePlainLayout()) {
-                       // in this case, default layout maps to empty layout 
+                       // in this case, default layout maps to empty layout
                        if (layoutname == tclass.defaultLayoutName())
                                layoutname = tclass.plainLayoutName();
-               } else { 
+               } else {
                        // otherwise, the empty layout maps to the default
                        if (layoutname == tclass.plainLayoutName())
                                layoutname = tclass.defaultLayoutName();
@@ -356,7 +387,13 @@ void Text::readParToken(Paragraph & par, Lexer & lex,
                // of this document. For example, when you apply class article to a beamer document,
                // all unknown layouts such as frame will be added to document class article so that
                // these layouts can keep their original names.
-               tclass.addLayoutIfNeeded(layoutname);
+               bool const added_one = tclass.addLayoutIfNeeded(layoutname);
+               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.id(), -1}));
+               }
 
                par.setLayout(bp.documentClass()[layoutname]);
 
@@ -381,7 +418,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();
@@ -427,6 +464,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()));
@@ -439,16 +479,44 @@ void Text::readParToken(Paragraph & par, Lexer & lex,
        } else if (token == "\\color") {
                lex.next();
                setLyXColor(lex.getString(), font.fontInfo());
-       } else if (token == "\\SpecialChar") {
-               auto_ptr<Inset> inset;
-               inset.reset(new InsetSpecialChar);
+       } else if (token == "\\SpecialChar" ||
+                  (token == "\\SpecialCharNoPassThru" &&
+                   !par.layout().pass_thru && !inset().isPassThru())) {
+               auto inset = make_unique<InsetSpecialChar>();
                inset->read(lex);
                inset->setBuffer(*buf);
                par.insertInset(par.size(), inset.release(), font, change);
+       } else if (token == "\\SpecialCharNoPassThru") {
+               lex.next();
+               docstring const s = ltrim(lex.getDocString(), "\\");
+               par.insert(par.size(), s, font, change);
+       } else if (token == "\\IPAChar") {
+               auto inset = make_unique<InsetIPAChar>();
+               inset->read(lex);
+               inset->setBuffer(*buf);
+               par.insertInset(par.size(), inset.release(), font, change);
+       } else if (token == "\\twohyphens" || token == "\\threehyphens") {
+               // Ideally, this should be done by lyx2lyx, but lyx2lyx does not know the
+               // running font and does not know anything about layouts (and CopyStyle).
+               Layout const & layout(par.layout());
+               FontInfo info = font.fontInfo();
+               info.realize(layout.resfont);
+               if (layout.pass_thru || inset().isPassThru() ||
+                   info.family() == TYPEWRITER_FAMILY) {
+                       if (token == "\\twohyphens")
+                               par.insert(par.size(), from_ascii("--"), font, change);
+                       else
+                               par.insert(par.size(), from_ascii("---"), font, change);
+               } else {
+                       if (token == "\\twohyphens")
+                               par.insertChar(par.size(), 0x2013, font, change);
+                       else
+                               par.insertChar(par.size(), 0x2014, font, change);
+               }
        } else if (token == "\\backslash") {
                par.appendChar('\\', font, change);
        } else if (token == "\\LyXTable") {
-               auto_ptr<Inset> inset(new InsetTabular(buf));
+               auto inset = make_unique<InsetTabular>(buf);
                inset->read(lex);
                par.insertInset(par.size(), inset.release(), font, change);
        } else if (token == "\\change_unchanged") {
@@ -456,27 +524,34 @@ void Text::readParToken(Paragraph & par, Lexer & lex,
        } else if (token == "\\change_inserted" || token == "\\change_deleted") {
                lex.eatLine();
                istringstream is(lex.getString());
-               unsigned int aid;
+               int aid;
                time_t ct;
                is >> aid >> ct;
-               map<unsigned int, int> const & am = bp.author_map;
+               BufferParams::AuthorMap const & am = bp.author_map_;
                if (am.find(aid) == am.end()) {
-                       errorList.push_back(ErrorItem(_("Change tracking error"),
-                                           bformat(_("Unknown author index for change: %1$d\n"), aid),
-                                           par.id(), 0, par.size()));
-                       change = Change(Change::UNCHANGED);
-               } else {
-                       if (token == "\\change_inserted")
-                               change = Change(Change::INSERTED, am.find(aid)->second, ct);
-                       else
-                               change = Change(Change::DELETED, am.find(aid)->second, ct);
+                       errorList.push_back(ErrorItem(
+                               _("Change tracking author index missing"),
+                               bformat(_("A change tracking author information for index "
+                                         "%1$d is missing. This can happen after a wrong "
+                                         "merge by a version control system. In this case, "
+                                         "either fix the merge, or have this information "
+                                         "missing until the corresponding tracked changes "
+                                         "are merged or this user edits the file again.\n"),
+                                       aid),
+                               {par.id(), par.size()}, {par.id(), par.size() + 1}));
+                       bp.addAuthor(Author(aid));
                }
+               if (token == "\\change_inserted")
+                       change = Change(Change::INSERTED, am.find(aid)->second, ct);
+               else
+                       change = Change(Change::DELETED, am.find(aid)->second, ct);
        } 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}));
        }
 }
 
@@ -519,7 +594,7 @@ void Text::readParagraph(Paragraph & par, Lexer & lex,
 
        // Initialize begin_of_body_ on load; redoParagraph maintains
        par.setBeginOfBody();
-       
+
        // mark paragraph for spell checking on load
        // par.requestSpellCheck();
 }
@@ -529,39 +604,37 @@ class TextCompletionList : public CompletionList
 {
 public:
        ///
-       TextCompletionList(Cursor const & cur, WordList const * list)
-               : buffer_(cur.buffer()), pos_(0), list_(list)
+       TextCompletionList(Cursor const & cur, WordList const & list)
+               : buffer_(cur.buffer()), list_(list)
        {}
        ///
        virtual ~TextCompletionList() {}
-       
+
        ///
        virtual bool sorted() const { return true; }
        ///
        virtual size_t size() const
        {
-               return list_->size();
+               return list_.size();
        }
        ///
        virtual docstring const & data(size_t idx) const
        {
-               return list_->word(idx);
+               return list_.word(idx);
        }
-       
+
 private:
        ///
        Buffer const * buffer_;
        ///
-       size_t pos_;
-       ///
-       WordList const * list_;
+       WordList const & list_;
 };
 
 
 bool Text::empty() const
 {
        return pars_.empty() || (pars_.size() == 1 && pars_[0].empty()
-               // FIXME: Should we consider the labeled type as empty too? 
+               // FIXME: Should we consider the labeled type as empty too?
                && pars_[0].layout().labeltype == LABEL_NO_LABEL);
 }
 
@@ -578,17 +651,17 @@ double Text::spacing(Paragraph const & par) const
  * This breaks a paragraph at the specified position.
  * The new paragraph will:
  * - Decrease depth by one (or change layout to default layout) when
- *    keep_layout == false  
+ *    keep_layout == false
  * - keep current depth and layout when keep_layout == true
  */
-static void breakParagraph(Text & text, pit_type par_offset, pos_type pos, 
+static void breakParagraph(Text & text, pit_type par_offset, pos_type pos,
                    bool keep_layout)
 {
        BufferParams const & bparams = text.inset().buffer().params();
        ParagraphList & pars = text.paragraphs();
        // create a new paragraph, and insert into the list
        ParagraphList::iterator tmp =
-               pars.insert(boost::next(pars.begin(), par_offset + 1),
+               pars.insert(lyx::next(pars.begin(), par_offset + 1),
                            Paragraph());
 
        Paragraph & par = pars[par_offset];
@@ -599,7 +672,6 @@ static void breakParagraph(Text & text, pit_type par_offset, pos_type pos,
        // end of a paragraph
        tmp->setPlainOrDefaultLayout(bparams.documentClass());
 
-       // layout stays the same with latex-environments
        if (keep_layout) {
                tmp->setLayout(par.layout());
                tmp->setLabelWidthString(par.params().labelWidthString());
@@ -641,7 +713,7 @@ static void breakParagraph(Text & text, pit_type par_offset, pos_type pos,
 
        // Move over the end-of-par change information
        tmp->setChange(tmp->size(), par.lookupChange(par.size()));
-       par.setChange(par.size(), Change(bparams.trackChanges ?
+       par.setChange(par.size(), Change(bparams.track_changes ?
                                           Change::INSERTED : Change::UNCHANGED));
 
        if (pos) {
@@ -665,7 +737,6 @@ static void breakParagraph(Text & text, pit_type par_offset, pos_type pos,
                par.setPlainOrDefaultLayout(bparams.documentClass());
        }
 
-       // layout stays the same with latex-environments
        if (keep_layout) {
                par.setLayout(tmp->layout());
                par.setLabelWidthString(tmp->params().labelWidthString());
@@ -676,7 +747,7 @@ static void breakParagraph(Text & text, pit_type par_offset, pos_type pos,
 
 void Text::breakParagraph(Cursor & cur, bool inverse_logic)
 {
-       LASSERT(this == cur.text(), /**/);
+       LBUFERR(this == cur.text());
 
        Paragraph & cpar = cur.paragraph();
        pit_type cpit = cur.pit();
@@ -685,25 +756,33 @@ 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 
-                       setLayout(cur, tclass.defaultLayoutName());
+                       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())
+                               setLayout(cur, lay);
+               }
                return;
        }
 
-       // a layout change may affect also the following paragraph
-       recUndo(cur, cur.pit(), undoSpan(cur.pit()) - 1);
+       cur.recordUndo();
 
        // Always break behind a space
        // It is better to erase the space (Dekel)
        if (cur.pos() != cur.lastpos() && cpar.isLineSeparator(cur.pos()))
-               cpar.eraseChar(cur.pos(), cur.buffer()->params().trackChanges);
+               cpar.eraseChar(cur.pos(), cur.buffer()->params().track_changes);
 
        // What should the layout for the new paragraph be?
-       bool keep_layout = inverse_logic ? 
-               !layout.isEnvironment() 
-               : layout.isEnvironment();
+       bool keep_layout = layout.isEnvironment()
+               || (layout.isParagraph() && layout.parbreak_is_newline);
+       if (inverse_logic)
+               keep_layout = !keep_layout;
 
        // We need to remember this before we break the paragraph, because
        // that invalidates the layout variable
@@ -732,7 +811,7 @@ void Text::breakParagraph(Cursor & cur, bool inverse_logic)
        }
 
        while (!pars_[next_par].empty() && pars_[next_par].isNewline(0)) {
-               if (!pars_[next_par].eraseChar(0, cur.buffer()->params().trackChanges))
+               if (!pars_[next_par].eraseChar(0, cur.buffer()->params().track_changes))
                        break; // the character couldn't be deleted physically due to change tracking
        }
 
@@ -750,12 +829,12 @@ void Text::breakParagraph(Cursor & cur, bool inverse_logic)
 
 
 // needed to insert the selection
-void Text::insertStringAsLines(DocIterator const & dit, docstring const & str,
+void Text::insertStringAsLines(Cursor & cur, docstring const & str,
                Font const & font)
 {
        BufferParams const & bparams = owner_->buffer().params();
-       pit_type pit = dit.pit();
-       pos_type pos = dit.pos();
+       pit_type pit = cur.pit();
+       pos_type pos = cur.pos();
 
        // insert the string, don't insert doublespace
        bool space_inserted = true;
@@ -763,7 +842,7 @@ void Text::insertStringAsLines(DocIterator const & dit, docstring const & str,
            cit != str.end(); ++cit) {
                Paragraph & par = pars_[pit];
                if (*cit == '\n') {
-                       if (autoBreakRows_ && (!par.empty() || par.allowEmpty())) {
+                       if (inset().allowMultiPar() && (!par.empty() || par.allowEmpty())) {
                                lyx::breakParagraph(*this, pit, pos,
                                        par.layout().isEnvironment());
                                ++pit;
@@ -772,18 +851,18 @@ void Text::insertStringAsLines(DocIterator const & dit, docstring const & str,
                        } else {
                                continue;
                        }
-                       // do not insert consecutive spaces if !free_spacing
+               // do not insert consecutive spaces if !free_spacing
                } else if ((*cit == ' ' || *cit == '\t') &&
                           space_inserted && !par.isFreeSpacing()) {
                        continue;
                } else if (*cit == '\t') {
                        if (!par.isFreeSpacing()) {
                                // tabs are like spaces here
-                               par.insertChar(pos, ' ', font, bparams.trackChanges);
+                               par.insertChar(pos, ' ', font, bparams.track_changes);
                                ++pos;
                                space_inserted = true;
                        } else {
-                               par.insertChar(pos, *cit, font, bparams.trackChanges);
+                               par.insertChar(pos, *cit, font, bparams.track_changes);
                                ++pos;
                                space_inserted = true;
                        }
@@ -792,17 +871,18 @@ void Text::insertStringAsLines(DocIterator const & dit, docstring const & str,
                        continue;
                } else {
                        // just insert the character
-                       par.insertChar(pos, *cit, font, bparams.trackChanges);
+                       par.insertChar(pos, *cit, font, bparams.track_changes);
                        ++pos;
                        space_inserted = (*cit == ' ');
                }
        }
+       setCursor(cur, pit, pos);
 }
 
 
 // turn double CR to single CR, others are converted into one
 // blank. Then insertStringAsLines is called
-void Text::insertStringAsParagraphs(DocIterator const & dit, docstring const & str,
+void Text::insertStringAsParagraphs(Cursor & cur, docstring const & str,
                Font const & font)
 {
        docstring linestr = str;
@@ -824,15 +904,58 @@ void Text::insertStringAsParagraphs(DocIterator const & dit, docstring const & s
                        newline_inserted = false;
                }
        }
-       insertStringAsLines(dit, linestr, font);
+       insertStringAsLines(cur, linestr, font);
+}
+
+
+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;
+               }
+               // 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 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)
 {
-       LASSERT(this == cur.text(), /**/);
+       LBUFERR(this == cur.text());
+
+       if (!canInsertChar(cur,c))
+               return;
 
        cur.recordUndo(INSERT_UNDO);
 
@@ -842,24 +965,26 @@ 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_seperators = from_ascii(".,:");
 
+               // European Number Separators: comma, dot etc.
+               // European Number Terminators: percent, permille, degree, euro etc.
                if (cur.current_font.fontInfo().number() == FONT_ON) {
-                       if (!isDigit(c) && !contains(number_operators, c) &&
-                           !(contains(number_seperators, c) &&
+                       if (!isDigitASCII(c) && !contains(number_operators, c) &&
+                           !(isEuropeanNumberSeparator(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
-               } else if (isDigit(c) &&
+               } else if (isDigitASCII(c) &&
                           cur.real_current_font.isVisibleRightToLeft()) {
                        number(cur); // Set current_font.number to ON
 
@@ -868,11 +993,12 @@ void Text::insertChar(Cursor & cur, char_type c)
                                if (contains(number_unary_operators, c) &&
                                    (cur.pos() == 1
                                     || par.isSeparator(cur.pos() - 2)
+                                    || par.isEnvSeparator(cur.pos() - 2)
                                     || par.isNewline(cur.pos() - 2))
                                  ) {
                                        setCharFont(pit, cur.pos() - 1, cur.current_font,
                                                tm.font_);
-                               } else if (contains(number_seperators, c)
+                               } else if (isEuropeanNumberSeparator(c)
                                     && cur.pos() >= 2
                                     && tm.displayFont(pit, cur.pos() - 2).fontInfo().number() == FONT_ON) {
                                        setCharFont(pit, cur.pos() - 1, cur.current_font,
@@ -883,40 +1009,40 @@ void Text::insertChar(Cursor & cur, char_type c)
        }
 
        // In Bidi text, we want spaces to be treated in a special way: spaces
-       // which are between words in different languages should get the 
-       // paragraph's language; otherwise, spaces should keep the language 
+       // which are between words in different languages should get the
+       // paragraph's language; otherwise, spaces should keep the language
        // they were originally typed in. This is only in effect while typing;
        // after the text is already typed in, the user can always go back and
        // explicitly set the language of a space as desired. But 99.9% of the
        // time, what we're doing here is what the user actually meant.
-       // 
+       //
        // The following cases are the ones in which the language of the space
        // should be changed to match that of the containing paragraph. In the
-       // depictions, lowercase is LTR, uppercase is RTL, underscore (_) 
+       // depictions, lowercase is LTR, uppercase is RTL, underscore (_)
        // represents a space, pipe (|) represents the cursor position (so the
        // character before it is the one just typed in). The different cases
        // are depicted logically (not visually), from left to right:
-       // 
+       //
        // 1. A_a|
        // 2. a_A|
        //
        // Theoretically, there are other situations that we should, perhaps, deal
-       // with (e.g.: a|_A, A|_a). In practice, though, there really isn't any 
+       // with (e.g.: a|_A, A|_a). In practice, though, there really isn't any
        // point (to understand why, just try to create this situation...).
 
        if ((cur.pos() >= 2) && (par.isLineSeparator(cur.pos() - 1))) {
-               // get font in front and behind the space in question. But do NOT 
+               // get font in front and behind the space in question. But do NOT
                // use getFont(cur.pos()) because the character c is not inserted yet
                Font const pre_space_font  = tm.displayFont(cur.pit(), cur.pos() - 2);
                Font const & post_space_font = cur.real_current_font;
                bool pre_space_rtl  = pre_space_font.isVisibleRightToLeft();
                bool post_space_rtl = post_space_font.isVisibleRightToLeft();
-               
+
                if (pre_space_rtl != post_space_rtl) {
-                       // Set the space's language to match the language of the 
+                       // Set the space's language to match the language of the
                        // adjacent character whose direction is the paragraph's
                        // direction; don't touch other properties of the font
-                       Language const * lang = 
+                       Language const * lang =
                                (pre_space_rtl == par.isRTL(buffer.params())) ?
                                pre_space_font.language() : post_space_font.language();
 
@@ -925,40 +1051,32 @@ void Text::insertChar(Cursor & cur, char_type c)
                        par.setFont(cur.pos() - 1, space_font);
                }
        }
-       
-       // 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(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;
+       pos_type pos = cur.pos();
+       if (!cur.paragraph().isPassThru() && owner_->lyxCode() != IPA_CODE &&
+           cur.real_current_font.fontInfo().family() != TYPEWRITER_FAMILY &&
+           c == '-' && pos > 0) {
+               if (par.getChar(pos - 1) == '-') {
+                       // convert "--" to endash
+                       par.eraseChar(pos - 1, cur.buffer()->params().track_changes);
+                       c = 0x2013;
+                       pos--;
+               } else if (par.getChar(pos - 1) == 0x2013) {
+                       // convert "---" to emdash
+                       par.eraseChar(pos - 1, cur.buffer()->params().track_changes);
+                       c = 0x2014;
+                       pos--;
                }
        }
 
-       par.insertChar(cur.pos(), c, cur.current_font,
-               cur.buffer()->params().trackChanges);
+       par.insertChar(pos, c, cur.current_font,
+               cur.buffer()->params().track_changes);
        cur.checkBufferStructure();
 
 //             cur.screenUpdateFlags(Update::Force);
        bool boundary = cur.boundary()
-               || tm.isRTLBoundary(cur.pit(), cur.pos() + 1);
-       setCursor(cur, cur.pit(), cur.pos() + 1, false, boundary);
+               || tm.isRTLBoundary(cur.pit(), pos + 1);
+       setCursor(cur, cur.pit(), pos + 1, false, boundary);
        charInserted(cur);
 }
 
@@ -967,21 +1085,12 @@ void Text::charInserted(Cursor & cur)
 {
        Paragraph & par = cur.paragraph();
 
-       // Here we call finishUndo for every 20 characters inserted.
-       // This is from my experience how emacs does it. (Lgb)
-       if (undo_counter_ < 20) {
-               ++undo_counter_;
-       } else {
-               cur.finishUndo();
-               undo_counter_ = 0;
-       }
-
        // register word if a non-letter was entered
        if (cur.pos() > 1
            && !par.isWordSeparator(cur.pos() - 2)
            && par.isWordSeparator(cur.pos() - 1)) {
                // get the word in front of cursor
-               LASSERT(this == cur.text(), /**/);
+               LBUFERR(this == cur.text());
                cur.paragraph().updateWords();
        }
 }
@@ -992,7 +1101,7 @@ void Text::charInserted(Cursor & cur)
 
 bool Text::cursorForwardOneWord(Cursor & cur)
 {
-       LASSERT(this == cur.text(), /**/);
+       LBUFERR(this == cur.text());
 
        pos_type const lastpos = cur.lastpos();
        pit_type pit = cur.pit();
@@ -1000,17 +1109,17 @@ bool Text::cursorForwardOneWord(Cursor & cur)
        Paragraph const & par = cur.paragraph();
 
        // Paragraph boundary is a word boundary
-       if (pos == lastpos) {
+       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_word_movement) {
+       if (lyxrc.mac_like_cursor_movement) {
                // Skip through trailing punctuation and spaces.
                while (pos != lastpos && (par.isChar(pos) || par.isSpace(pos)))
-                        ++pos;
+                       ++pos;
 
                // Skip over either a non-char inset or a full word
                if (pos != lastpos && par.isWordSeparator(pos))
@@ -1018,7 +1127,7 @@ bool Text::cursorForwardOneWord(Cursor & cur)
                else while (pos != lastpos && !par.isWordSeparator(pos))
                             ++pos;
        } else {
-               LASSERT(pos < lastpos, /**/); // see above
+               LASSERT(pos < lastpos, return false); // see above
                if (!par.isWordSeparator(pos))
                        while (pos != lastpos && !par.isWordSeparator(pos))
                                ++pos;
@@ -1030,26 +1139,36 @@ bool Text::cursorForwardOneWord(Cursor & cur)
 
                // Skip over white space
                while (pos != lastpos && par.isSpace(pos))
-                            ++pos;             
+                            ++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);
 }
 
 
 bool Text::cursorBackwardOneWord(Cursor & cur)
 {
-       LASSERT(this == cur.text(), /**/);
+       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)
-               return setCursor(cur, pit - 1, getPar(pit - 1).size());
+       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_word_movement) {
+       if (lyxrc.mac_like_cursor_movement) {
                // Skip through punctuation and spaces.
                while (pos != 0 && (par.isChar(pos - 1) || par.isSpace(pos - 1)))
                        --pos;
@@ -1080,10 +1199,9 @@ bool Text::cursorBackwardOneWord(Cursor & cur)
 
 bool Text::cursorVisLeftOneWord(Cursor & cur)
 {
-       LASSERT(this == cur.text(), /**/);
+       LBUFERR(this == cur.text());
 
        pos_type left_pos, right_pos;
-       bool left_is_letter, right_is_letter;
 
        Cursor temp_cur = cur;
 
@@ -1092,9 +1210,9 @@ bool Text::cursorVisLeftOneWord(Cursor & cur)
 
                // collect some information about current cursor position
                temp_cur.getSurroundingPos(left_pos, right_pos);
-               left_is_letter = 
+               bool left_is_letter =
                        (left_pos > -1 ? !temp_cur.paragraph().isWordSeparator(left_pos) : false);
-               right_is_letter = 
+               bool right_is_letter =
                        (right_pos > -1 ? !temp_cur.paragraph().isWordSeparator(right_pos) : false);
 
                // if we're not at a letter/non-letter boundary, continue moving
@@ -1110,17 +1228,16 @@ bool Text::cursorVisLeftOneWord(Cursor & cur)
                        break;
        }
 
-       return setCursor(cur, temp_cur.pit(), temp_cur.pos(), 
+       return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
                                         true, temp_cur.boundary());
 }
 
 
 bool Text::cursorVisRightOneWord(Cursor & cur)
 {
-       LASSERT(this == cur.text(), /**/);
+       LBUFERR(this == cur.text());
 
        pos_type left_pos, right_pos;
-       bool left_is_letter, right_is_letter;
 
        Cursor temp_cur = cur;
 
@@ -1129,9 +1246,9 @@ bool Text::cursorVisRightOneWord(Cursor & cur)
 
                // collect some information about current cursor position
                temp_cur.getSurroundingPos(left_pos, right_pos);
-               left_is_letter = 
+               bool left_is_letter =
                        (left_pos > -1 ? !temp_cur.paragraph().isWordSeparator(left_pos) : false);
-               right_is_letter = 
+               bool right_is_letter =
                        (right_pos > -1 ? !temp_cur.paragraph().isWordSeparator(right_pos) : false);
 
                // if we're not at a letter/non-letter boundary, continue moving
@@ -1141,24 +1258,24 @@ bool Text::cursorVisRightOneWord(Cursor & cur)
                // we should stop when we have an LTR word on our right or an RTL word
                // on our left
                if ((left_is_letter && temp_cur.paragraph().getFontSettings(
-                               temp_cur.buffer()->params(), 
+                               temp_cur.buffer()->params(),
                                left_pos).isRightToLeft())
                        || (right_is_letter && !temp_cur.paragraph().getFontSettings(
-                               temp_cur.buffer()->params(), 
+                               temp_cur.buffer()->params(),
                                right_pos).isRightToLeft()))
                        break;
        }
 
-       return setCursor(cur, temp_cur.pit(), temp_cur.pos(), 
+       return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
                                         true, temp_cur.boundary());
 }
 
 
 void Text::selectWord(Cursor & cur, word_location loc)
 {
-       LASSERT(this == cur.text(), /**/);
+       LBUFERR(this == cur.text());
        CursorSlice from = cur.top();
-       CursorSlice to = cur.top();
+       CursorSlice to;
        getWord(from, to, loc);
        if (cur.top() != from)
                setCursor(cur, from.pit(), from.pos());
@@ -1174,14 +1291,14 @@ void Text::selectWord(Cursor & cur, word_location loc)
 
 void Text::selectAll(Cursor & cur)
 {
-       LASSERT(this == cur.text(), /**/);
+       LBUFERR(this == cur.text());
        if (cur.lastpos() == 0 && cur.lastpit() == 0)
                return;
        // If the cursor is at the beginning, make sure the cursor ends there
        if (cur.pit() == 0 && cur.pos() == 0) {
                setCursor(cur, cur.lastpit(), getPar(cur.lastpit()).size());
                cur.resetAnchor();
-               setCursor(cur, 0, 0);           
+               setCursor(cur, 0, 0);
        } else {
                setCursor(cur, 0, 0);
                cur.resetAnchor();
@@ -1195,7 +1312,7 @@ void Text::selectAll(Cursor & cur)
 // selection is currently set
 bool Text::selectWordWhenUnderCursor(Cursor & cur, word_location loc)
 {
-       LASSERT(this == cur.text(), /**/);
+       LBUFERR(this == cur.text());
        if (cur.selection())
                return false;
        selectWord(cur, loc);
@@ -1205,11 +1322,10 @@ bool Text::selectWordWhenUnderCursor(Cursor & cur, word_location loc)
 
 void Text::acceptOrRejectChanges(Cursor & cur, ChangeOp op)
 {
-       LASSERT(this == cur.text(), /**/);
+       LBUFERR(this == cur.text());
 
        if (!cur.selection()) {
-               bool const changed = cur.paragraph().isChanged(cur.pos());
-               if (!(changed && findNextChange(&cur.bv())))
+               if (!selectChange(cur))
                        return;
        }
 
@@ -1225,7 +1341,6 @@ void Text::acceptOrRejectChanges(Cursor & cur, ChangeOp op)
        bool 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) {
                pos_type parSize = pars_[pit].size();
 
@@ -1245,6 +1360,10 @@ void Text::acceptOrRejectChanges(Cursor & cur, ChangeOp op)
                pos_type left  = (pit == begPit ? begPos : 0);
                pos_type right = (pit == endPit ? endPos : parSize);
 
+               if (left == right)
+                       // there is no change here
+                       continue;
+
                if (op == ACCEPT) {
                        pars_[pit].acceptChanges(left, right);
                } else {
@@ -1297,10 +1416,7 @@ void Text::acceptOrRejectChanges(Cursor & cur, ChangeOp op)
        }
 
        // finally, invoke the DEPM
-
-       deleteEmptyParagraphMechanism(begPit, endPit, cur.buffer()->params().trackChanges);
-
-       //
+       deleteEmptyParagraphMechanism(begPit, endPit, cur.buffer()->params().track_changes);
 
        cur.finishUndo();
        cur.clearSelection();
@@ -1314,7 +1430,7 @@ void Text::acceptChanges()
 {
        BufferParams const & bparams = owner_->buffer().params();
        lyx::acceptChanges(pars_, bparams);
-       deleteEmptyParagraphMechanism(0, pars_.size() - 1, bparams.trackChanges);
+       deleteEmptyParagraphMechanism(0, pars_.size() - 1, bparams.track_changes);
 }
 
 
@@ -1350,57 +1466,60 @@ void Text::rejectChanges()
        }
 
        // finally, invoke the DEPM
-       deleteEmptyParagraphMechanism(0, pars_size - 1, bparams.trackChanges);
+       deleteEmptyParagraphMechanism(0, pars_size - 1, bparams.track_changes);
 }
 
 
-void Text::deleteWordForward(Cursor & cur)
+void Text::deleteWordForward(Cursor & cur, bool const force)
 {
-       LASSERT(this == cur.text(), /**/);
+       LBUFERR(this == cur.text());
        if (cur.lastpos() == 0)
                cursorForward(cur);
        else {
                cur.resetAnchor();
-               cur.setSelection(true);
+               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)
 {
-       LASSERT(this == cur.text(), /**/);
+       LBUFERR(this == cur.text());
        if (cur.lastpos() == 0)
                cursorBackward(cur);
        else {
                cur.resetAnchor();
-               cur.setSelection(true);
+               cur.selection(true);
                cursorBackwardOneWord(cur);
                cur.setSelection();
-               cutSelection(cur, true, false);
-               cur.checkBufferStructure();
+               if (force || !cur.confirmDeletion()) {
+                       cutSelection(cur, false);
+                       cur.checkBufferStructure();
+               }
        }
 }
 
 
 // Kill to end of line.
-void Text::changeCase(Cursor & cur, TextCase action)
+void Text::changeCase(Cursor & cur, TextCase action, bool partial)
 {
-       LASSERT(this == cur.text(), /**/);
+       LBUFERR(this == cur.text());
        CursorSlice from;
        CursorSlice to;
 
-       bool gotsel = false;
-       if (cur.selection()) {
+       bool const gotsel = cur.selection();
+       if (gotsel) {
                from = cur.selBegin();
                to = cur.selEnd();
-               gotsel = true;
        } else {
                from = cur.top();
-               getWord(from, to, PARTIAL_WORD);
+               getWord(from, to, partial ? PARTIAL_WORD : WHOLE_WORD);
                cursorForwardOneWord(cur);
        }
 
@@ -1454,14 +1573,14 @@ bool Text::handleBibitems(Cursor & cur)
        // if a bibitem is deleted, merge with previous paragraph
        // if this is a bibliography item as well
        if (cur.pit() > 0 && par.layout() == prevpar.layout()) {
-               cur.recordUndo(ATOMIC_UNDO, prevcur.pit());
+               cur.recordUndo(prevcur.pit());
                mergeParagraph(bufparams, cur.text()->paragraphs(),
                                                        prevcur.pit());
                cur.forceBufferUpdate();
                setCursorIntern(cur, prevcur.pit(), prevcur.pos());
                cur.screenUpdateFlags(Update::Force);
                return true;
-       } 
+       }
 
        // otherwise reset to default
        cur.paragraph().setPlainOrDefaultLayout(bufparams.documentClass());
@@ -1480,7 +1599,7 @@ bool Text::erase(Cursor & cur)
                // any paragraphs
                cur.recordUndo(DELETE_UNDO);
                bool const was_inset = cur.paragraph().isInset(cur.pos());
-               if(!par.eraseChar(cur.pos(), cur.buffer()->params().trackChanges))
+               if(!par.eraseChar(cur.pos(), cur.buffer()->params().track_changes))
                        // the character has been logically deleted only => skip it
                        cur.top().forwardPos();
 
@@ -1493,7 +1612,8 @@ bool Text::erase(Cursor & cur)
                if (cur.pit() == cur.lastpit())
                        return dissolveInset(cur);
 
-               if (!par.isMergedOnEndOfParDeletion(cur.buffer()->params().trackChanges)) {
+               if (!par.isMergedOnEndOfParDeletion(cur.buffer()->params().track_changes)) {
+                       cur.recordUndo(DELETE_UNDO);
                        par.setChange(cur.pos(), Change(Change::DELETED));
                        cur.forwardPos();
                        needsUpdate = true;
@@ -1508,7 +1628,7 @@ bool Text::erase(Cursor & cur)
        if (needsUpdate) {
                // Make sure the cursor is correct. Is this really needed?
                // No, not really... at least not here!
-               cur.text()->setCursor(cur.top(), cur.pit(), cur.pos());
+               cur.top().setPitPos(cur.pit(), cur.pos());
                cur.checkBufferStructure();
        }
 
@@ -1518,7 +1638,7 @@ bool Text::erase(Cursor & cur)
 
 bool Text::backspacePos0(Cursor & cur)
 {
-       LASSERT(this == cur.text(), /**/);
+       LBUFERR(this == cur.text());
        if (cur.pit() == 0)
                return false;
 
@@ -1536,15 +1656,15 @@ bool Text::backspacePos0(Cursor & cur)
        // is it an empty paragraph?
        if (cur.lastpos() == 0
            || (cur.lastpos() == 1 && par.isSeparator(0))) {
-               cur.recordUndo(ATOMIC_UNDO, prevcur.pit(), cur.pit());
-               plist.erase(boost::next(plist.begin(), cur.pit()));
+               cur.recordUndo(prevcur.pit());
+               plist.erase(lyx::next(plist.begin(), cur.pit()));
                needsUpdate = true;
        }
        // is previous par empty?
        else if (prevcur.lastpos() == 0
                 || (prevcur.lastpos() == 1 && prevpar.isSeparator(0))) {
-               cur.recordUndo(ATOMIC_UNDO, prevcur.pit(), cur.pit());
-               plist.erase(boost::next(plist.begin(), prevcur.pit()));
+               cur.recordUndo(prevcur.pit());
+               plist.erase(lyx::next(plist.begin(), prevcur.pit()));
                needsUpdate = true;
        }
        // Pasting is not allowed, if the paragraphs have different
@@ -1555,7 +1675,7 @@ bool Text::backspacePos0(Cursor & cur)
        else if (par.layout() == prevpar.layout()
                 || tclass.isDefaultLayout(par.layout())
                 || tclass.isPlainLayout(par.layout())) {
-               cur.recordUndo(ATOMIC_UNDO, prevcur.pit());
+               cur.recordUndo(prevcur.pit());
                mergeParagraph(bufparams, plist, prevcur.pit());
                needsUpdate = true;
        }
@@ -1571,17 +1691,19 @@ bool Text::backspacePos0(Cursor & cur)
 
 bool Text::backspace(Cursor & cur)
 {
-       LASSERT(this == cur.text(), /**/);
+       LBUFERR(this == cur.text());
        bool needsUpdate = false;
        if (cur.pos() == 0) {
                if (cur.pit() == 0)
                        return dissolveInset(cur);
 
-               Paragraph & prev_par = pars_[cur.pit() - 1];
+               Cursor prev_cur = cur;
+               --prev_cur.pit();
 
-               if (!prev_par.isMergedOnEndOfParDeletion(cur.buffer()->params().trackChanges)) {
-                       prev_par.setChange(prev_par.size(), Change(Change::DELETED));
-                       setCursorIntern(cur, cur.pit() - 1, prev_par.size());
+               if (!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());
                        return true;
                }
                // The cursor is at the beginning of a paragraph, so
@@ -1599,7 +1721,7 @@ bool Text::backspace(Cursor & cur)
                setCursorIntern(cur, cur.pit(), cur.pos() - 1,
                                false, cur.boundary());
                bool const was_inset = cur.paragraph().isInset(cur.pos());
-               cur.paragraph().eraseChar(cur.pos(), cur.buffer()->params().trackChanges);
+               cur.paragraph().eraseChar(cur.pos(), cur.buffer()->params().track_changes);
                if (was_inset)
                        cur.forceBufferUpdate();
                else
@@ -1613,7 +1735,7 @@ bool Text::backspace(Cursor & cur)
 
        // A singlePar update is not enough in this case.
 //             cur.screenUpdateFlags(Update::Force);
-       setCursor(cur.top(), cur.pit(), cur.pos());
+       cur.top().setPitPos(cur.pit(), cur.pos());
 
        return needsUpdate;
 }
@@ -1629,40 +1751,58 @@ 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();
        cur.popBackward();
-       // store cursor offset
+       // update cursor offset
        if (spit == 0)
                spos += cur.pos();
        spit += cur.pit();
+       // 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();
+
        Buffer & b = *cur.buffer();
-       cur.paragraph().eraseChar(cur.pos(), b.params().trackChanges);
        if (!plist.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
+               // its being empty matters. if not (i.e., if we encounter bugs),
+               // then this should instead be:
+               //        cur.buffer().updateBuffer();
+               // but we'll try the cheaper solution here.
+               cur.buffer()->clearReferenceCache();
+
                // 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++)
+               for (; it != it_end; ++it)
                        it->changeLanguage(b.params(), latex_language, b.language());
 
                pasteParagraphList(cur, plist, b.params().documentClassPtr(),
                                   b.errorList("Paste"));
-               // restore position
-               cur.pit() = min(cur.lastpit(), spit);
-               cur.pos() = min(cur.lastpos(), spos);
-       } else
-               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;
 }
 
@@ -1690,7 +1830,7 @@ void Text::write(ostream & os) const
 }
 
 
-bool Text::read(Lexer & lex, 
+bool Text::read(Lexer & lex,
                ErrorList & errorList, InsetText * insetPtr)
 {
        Buffer const & buf = owner_->buffer();
@@ -1747,36 +1887,35 @@ bool Text::read(Lexer & lex,
                Paragraph par;
                par.setInsetOwner(insetPtr);
                par.params().depth(depth);
-               par.setFont(0, Font(inherit_font, 
+               par.setFont(0, Font(inherit_font,
                                    buf.params().language));
                par.setPlainOrDefaultLayout(buf.params().documentClass());
                pars_.push_back(par);
        }
-       
+
        return res;
 }
 
+
 // Returns the current font and depth as a message.
-docstring Text::currentState(Cursor const & cur) const
+docstring Text::currentState(Cursor const & cur, bool devel_mode) const
 {
-       LASSERT(this == cur.text(), /**/);
+       LBUFERR(this == cur.text());
        Buffer & buf = *cur.buffer();
        Paragraph const & par = cur.paragraph();
        odocstringstream os;
 
-       if (buf.params().trackChanges)
+       if (buf.params().track_changes)
                os << _("[Change Tracking] ");
 
        Change change = par.lookupChange(cur.pos());
 
        if (change.changed()) {
-               Author const & a = buf.params().authors().get(change.author);
-               os << _("Change: ") << a.name();
-               if (!a.email().empty())
-                       os << " (" << a.email() << ")";
-               // FIXME ctime is english, we should translate that
-               os << _(" at ") << ctime(&change.changetime);
-               os << " : ";
+               docstring const author =
+                       buf.params().authors().get(change.author).nameAndEmail();
+               docstring const date = formatted_datetime(change.changetime);
+               os << bformat(_("Changed by %1$s[[author]] on %2$s[[date]]. "),
+                             author, date);
        }
 
        // I think we should only show changes from the default
@@ -1816,34 +1955,66 @@ 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;
+       if (devel_mode) {
+               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;
+               }
+               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
 {
-       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', '-');
@@ -1858,51 +2029,24 @@ docstring Text::getPossibleLabel(Cursor const & cur) const
                        text += '-';
                text += head;
        }
-       
+
        // Make sure it isn't too long
        unsigned int const max_label_length = 32;
        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)->type();
-               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())
-               // FIXME refstyle
-               // We should allow customization of the separator or else change it
                text = name + ':' + text;
 
-       return text;
+       // We need a unique label
+       docstring label = text;
+       int i = 1;
+       while (cur.buffer()->activeLabel(label)) {
+                       label = text + '-' + convert<docstring>(i);
+                       ++i;
+               }
+
+       return label;
 }
 
 
@@ -1924,10 +2068,47 @@ 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);
+       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,
+                       pit_type pit_start, pit_type pit_end,
+                       bool const shorten) const
+{
+       size_t tmplen = shorten ? maxlen + 1 : maxlen;
+       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);
+}
+
 
 void Text::charsTranspose(Cursor & cur)
 {
-       LASSERT(this == cur.text(), /**/);
+       LBUFERR(this == cur.text());
 
        pos_type pos = cur.pos();
 
@@ -1967,7 +2148,7 @@ void Text::charsTranspose(Cursor & cur)
 
        // And finally, we are ready to perform the transposition.
        // Track the changes if Change Tracking is enabled.
-       bool const trackChanges = cur.buffer()->params().trackChanges;
+       bool const trackChanges = cur.buffer()->params().track_changes;
 
        cur.recordUndo();
 
@@ -2003,7 +2184,7 @@ docstring Text::previousWord(CursorSlice const & sl) const
        getWord(from, to, PREVIOUS_WORD);
        if (sl == from || to == from)
                return docstring();
-       
+
        Paragraph const & par = sl.paragraph();
        return par.asString(from.pos(), to.pos());
 }
@@ -2012,7 +2193,8 @@ docstring Text::previousWord(CursorSlice const & sl) const
 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);
 }
@@ -2020,22 +2202,22 @@ bool Text::completionSupported(Cursor const & cur) const
 
 CompletionList const * Text::createCompletionList(Cursor const & cur) const
 {
-       WordList const * list = theWordList(*cur.getFont().language());
+       WordList const & list = theWordList(cur.getFont().language()->lang());
        return new TextCompletionList(cur, list);
 }
 
 
 bool Text::insertCompletion(Cursor & cur, docstring const & s, bool /*finished*/)
-{      
-       LASSERT(cur.bv().cursor() == cur, /**/);
+{
+       LBUFERR(cur.bv().cursor() == cur);
        cur.insert(s);
        cur.bv().cursor() = cur;
        if (!(cur.result().screenUpdate() & Update::Force))
                cur.screenUpdateFlags(cur.result().screenUpdate() | Update::SinglePar);
        return true;
 }
-       
-       
+
+
 docstring Text::completionPrefix(Cursor const & cur) const
 {
        return previousWord(cur.top());