]> git.lyx.org Git - lyx.git/blobdiff - src/Cursor.cpp
Fix text direction issue for InsetInfo in RTL context
[lyx.git] / src / Cursor.cpp
index baf529bfc84208b2579b6373d1b0a3ba23d33de9..b78c1f59d0dfa52bb4e9de543d2a9f57113f9549 100644 (file)
 #include "Cursor.h"
 #include "CutAndPaste.h"
 #include "DispatchResult.h"
-#include "Encoding.h"
-#include "Font.h"
 #include "FuncCode.h"
 #include "FuncRequest.h"
-#include "Language.h"
 #include "Layout.h"
 #include "LyXAction.h"
 #include "LyXRC.h"
 #include "Paragraph.h"
 #include "ParIterator.h"
 #include "Row.h"
+#include "texstream.h"
 #include "Text.h"
 #include "TextMetrics.h"
 #include "TocBackend.h"
 
 #include "mathed/InsetMath.h"
 #include "mathed/InsetMathBrace.h"
+#include "mathed/InsetMathEnsureMath.h"
 #include "mathed/InsetMathScript.h"
 #include "mathed/MacroTable.h"
 #include "mathed/MathData.h"
-#include "mathed/MathMacro.h"
-
-#include "support/bind.h"
+#include "mathed/MathFactory.h"
+#include "mathed/InsetMathMacro.h"
 
 #include <sstream>
 #include <limits>
@@ -113,30 +111,29 @@ DocIterator bruteFind(Cursor const & c, int x, int y)
 }
 
 
-} // namespace anon
+} // namespace
+
+
+//
+// CursorData
+//
 
 
 CursorData::CursorData()
-       : DocIterator(), anchor_(),
-         selection_(false), mark_(false), word_selection_(false),
-         current_font(inherit_font),
-         autocorrect_(false), macromode_(false)
+       : DocIterator(), anchor_(), selection_(false), mark_(false),
+         word_selection_(false), autocorrect_(false), current_font(inherit_font)
 {}
 
 
 CursorData::CursorData(Buffer * buffer)
-       : DocIterator(buffer), anchor_(),
-         selection_(false), mark_(false), word_selection_(false),
-         current_font(inherit_font),
-         autocorrect_(false), macromode_(false)
+       : DocIterator(buffer), anchor_(), selection_(false), mark_(false),
+         word_selection_(false), autocorrect_(false), current_font(inherit_font)
 {}
 
 
 CursorData::CursorData(DocIterator const & dit)
-       : DocIterator(dit), anchor_(),
-         selection_(false), mark_(false), word_selection_(false),
-         current_font(inherit_font),
-         autocorrect_(false), macromode_(false)
+       : DocIterator(dit), anchor_(), selection_(false), mark_(false),
+         word_selection_(false), autocorrect_(false), current_font(inherit_font)
 {}
 
 
@@ -156,20 +153,568 @@ ostream & operator<<(ostream & os, CursorData const & cur)
        for (size_t i = cur.depth(), n = cur.anchor_.depth(); i < n; ++i) {
                os << "------------------------------- | " << cur.anchor_[i] << "\n";
        }
-       os << " selection: " << cur.selection_
-//        << " x_target: " << cur.x_target_
-          << " boundary: " << cur.boundary() << endl;
-       return os;
+       os << " selection: " << cur.selection_
+//        << " x_target: " << cur.x_target_
+          << " boundary: " << cur.boundary() << endl;
+       return os;
+}
+
+
+LyXErr & operator<<(LyXErr & os, CursorData const & cur)
+{
+       os.stream() << cur;
+       return os;
+}
+
+
+void CursorData::reset()
+{
+       clear();
+       push_back(CursorSlice(buffer()->inset()));
+       anchor_ = doc_iterator_begin(buffer());
+       anchor_.clear();
+       new_word_ = doc_iterator_begin(buffer());
+       new_word_.clear();
+       selection_ = false;
+       mark_ = false;
+}
+
+
+void CursorData::setCursor(DocIterator const & cur)
+{
+       DocIterator::operator=(cur);
+}
+
+
+void CursorData::setCursorSelectionTo(DocIterator dit)
+{
+       size_t i = 0;
+       // normalise dit
+       while (i < dit.depth() && i < anchor_.depth() && dit[i] == anchor_[i])
+               ++i;
+       if (i != dit.depth()) {
+               // otherwise the cursor is already normal
+               if (i == anchor_.depth())
+                       // dit is a proper extension of the anchor_
+                       dit.cutOff(i - 1);
+               else if (i + 1 < dit.depth()) {
+                       // one has dit[i] != anchor_[i] but either dit[i-1] == anchor_[i-1]
+                       // or i == 0. Remove excess.
+                       dit.cutOff(i);
+                       if (dit[i] > anchor_[i])
+                               // place dit after the inset it was in
+                               ++dit.pos();
+               }
+       }
+       setCursor(dit);
+       setSelection();
+}
+
+
+void CursorData::setCursorToAnchor()
+{
+       if (selection()) {
+               DocIterator normal = anchor_;
+               while (depth() < normal.depth())
+                       normal.pop_back();
+               if (depth() < anchor_.depth() && top() <= anchor_[depth() - 1])
+                       ++normal.pos();
+               setCursor(normal);
+       }
+}
+
+
+CursorSlice CursorData::normalAnchor() const
+{
+       if (!selection())
+               return top();
+       // LASSERT: There have been several bugs around this code, that seem
+       // to involve failures to reset the anchor. We can at least not crash
+       // in release mode by resetting it ourselves.
+       if (anchor_.depth() < depth()) {
+               LYXERR0("Cursor is deeper than anchor. PLEASE REPORT.\nCursor is"
+                       << *this);
+               const_cast<DocIterator &>(anchor_) = *this;
+       }
+
+       CursorSlice normal = anchor_[depth() - 1];
+       if (depth() < anchor_.depth() && top() <= normal) {
+               // anchor is behind cursor -> move anchor behind the inset
+               ++normal.pos();
+       }
+       return normal;
+}
+
+
+void CursorData::setSelection()
+{
+       selection(true);
+       if (idx() == normalAnchor().idx() &&
+           pit() == normalAnchor().pit() &&
+           pos() == normalAnchor().pos())
+               selection(false);
+}
+
+
+void CursorData::setSelection(DocIterator const & where, int n)
+{
+       setCursor(where);
+       selection(true);
+       anchor_ = where;
+       pos() += n;
+}
+
+
+void CursorData::resetAnchor()
+{
+       anchor_ = *this;
+       checkNewWordPosition();
+}
+
+
+CursorSlice CursorData::selBegin() const
+{
+       if (!selection())
+               return top();
+       return normalAnchor() < top() ? normalAnchor() : top();
+}
+
+
+CursorSlice CursorData::selEnd() const
+{
+       if (!selection())
+               return top();
+       return normalAnchor() > top() ? normalAnchor() : top();
+}
+
+
+DocIterator CursorData::selectionBegin() const
+{
+       if (!selection())
+               return *this;
+
+       DocIterator di;
+       // FIXME: This is a work-around for the problem that
+       // CursorSlice doesn't keep track of the boundary.
+       if (normalAnchor() == top())
+               di = anchor_.boundary() > boundary() ? anchor_ : *this;
+       else
+               di = normalAnchor() < top() ? anchor_ : *this;
+       di.resize(depth());
+       return di;
+}
+
+
+DocIterator CursorData::selectionEnd() const
+{
+       if (!selection())
+               return *this;
+
+       DocIterator di;
+       // FIXME: This is a work-around for the problem that
+       // CursorSlice doesn't keep track of the boundary.
+       if (normalAnchor() == top())
+               di = anchor_.boundary() < boundary() ? anchor_ : *this;
+       else
+               di = normalAnchor() > top() ? anchor_ : *this;
+
+       if (di.depth() > depth()) {
+               di.resize(depth());
+               ++di.pos();
+       }
+       return di;
+}
+
+
+namespace {
+
+docstring parbreak(CursorData const * cur)
+{
+       odocstringstream os;
+       os << '\n';
+       // only add blank line if we're not in a ParbreakIsNewline situation
+       if (!cur->inset().getLayout().parbreakIsNewline()
+           && !cur->paragraph().layout().parbreak_is_newline)
+               os << '\n';
+       return os.str();
+}
+
+}
+
+
+docstring CursorData::selectionAsString(bool with_label) const
+{
+       if (!selection())
+               return docstring();
+
+       if (inMathed())
+               return cap::grabSelection(*this);
+
+       int const label = with_label
+               ? AS_STR_LABEL | AS_STR_INSETS : AS_STR_INSETS;
+
+       idx_type const startidx = selBegin().idx();
+       idx_type const endidx = selEnd().idx();
+       if (startidx != endidx) {
+               // multicell selection
+               InsetTabular * table = inset().asInsetTabular();
+               LASSERT(table, return docstring());
+               return table->asString(startidx, endidx);
+       }
+
+       ParagraphList const & pars = text()->paragraphs();
+
+       pit_type const startpit = selBegin().pit();
+       pit_type const endpit = selEnd().pit();
+       size_t const startpos = selBegin().pos();
+       size_t const endpos = selEnd().pos();
+
+       if (startpit == endpit)
+               return pars[startpit].asString(startpos, endpos, label);
+
+       // First paragraph in selection
+       docstring result = pars[startpit].
+               asString(startpos, pars[startpit].size(), label)
+               + parbreak(this);
+
+       // The paragraphs in between (if any)
+       for (pit_type pit = startpit + 1; pit != endpit; ++pit) {
+               Paragraph const & par = pars[pit];
+               result += par.asString(0, par.size(), label)
+                       + parbreak(this);
+       }
+
+       // Last paragraph in selection
+       result += pars[endpit].asString(0, endpos, label);
+
+       return result;
+}
+
+
+void CursorData::info(odocstream & os, bool devel_mode) const
+{
+       for (int i = 1, n = depth(); i < n; ++i) {
+               operator[](i).inset().infoize(os);
+               os << "  ";
+       }
+       if (pos() != 0) {
+               Inset const * inset = prevInset();
+               // prevInset() can return 0 in certain case.
+               if (inset)
+                       prevInset()->infoize2(os);
+       }
+       if (devel_mode) {
+               InsetMath * math = inset().asInsetMath();
+               if (math)
+                       os << _(", Inset: ") << math->id();
+               os << _(", Cell: ") << idx();
+               os << _(", Position: ") << pos();
+       }
+
+}
+
+docstring CursorData::currentState(bool devel_mode) const
+{
+       if (inMathed()) {
+               odocstringstream os;
+               info(os, devel_mode);
+               return os.str();
+       }
+
+       if (inTexted())
+               return text()->currentState(*this, devel_mode);
+
+       return docstring();
+}
+
+
+void CursorData::markNewWordPosition()
+{
+       if (lyxrc.spellcheck_continuously && inTexted() && new_word_.empty()) {
+               FontSpan nw = locateWord(WHOLE_WORD);
+               if (nw.size() == 1) {
+                       LYXERR(Debug::DEBUG, "start new word: "
+                               << " par: " << pit()
+                               << " pos: " << nw.first);
+                       new_word_ = *this;
+               }
+       }
+}
+
+
+void CursorData::clearNewWordPosition()
+{
+       if (!new_word_.empty()) {
+               LYXERR(Debug::DEBUG, "clear new word: "
+                       << " par: " << pit()
+                       << " pos: " << pos());
+               new_word_.resize(0);
+       }
+}
+
+
+void CursorData::checkNewWordPosition()
+{
+       if (!lyxrc.spellcheck_continuously || new_word_.empty())
+               return ;
+       // forget the position of the current new word if
+       // 1) or the remembered position was "broken"
+       // 2) or the count of nested insets changed
+       // 3) the top-level inset is not the same anymore
+       // 4) the cell index changed
+       // 5) or the paragraph changed
+       // 6) or the cursor pos is out of paragraph bound
+       if (new_word_.fixIfBroken()
+           || depth() != new_word_.depth()
+           || &inset() != &new_word_.inset()
+           || pit() != new_word_.pit()
+           || idx() != new_word_.idx()
+           || new_word_.pos() > new_word_.lastpos())
+                       clearNewWordPosition();
+       else {
+               FontSpan nw = locateWord(WHOLE_WORD);
+               if (!nw.empty()) {
+                       FontSpan ow = new_word_.locateWord(WHOLE_WORD);
+                       if (nw.intersect(ow).empty())
+                               clearNewWordPosition();
+                       else
+                               LYXERR(Debug::DEBUG, "new word: "
+                                          << " par: " << pit()
+                                          << " pos: " << nw.first << ".." << nw.last);
+               } else
+                       clearNewWordPosition();
+       }
+}
+
+
+void CursorData::clearSelection()
+{
+       selection(false);
+       setWordSelection(false);
+       setMark(false);
+       resetAnchor();
+}
+
+
+int CursorData::countInsetsInSelection(InsetCode const & inset_code)
+{
+       if (!selection_)
+               return 0;
+
+       DocIterator from, to;
+       from = selectionBegin();
+       to = selectionEnd();
+
+       int count = 0;
+
+       if (!from.nextInset())      //move to closest inset
+               from.forwardInset();
+
+       while (!from.empty() && from < to) {
+               Inset * inset = from.nextInset();
+               if (!inset)
+                       break;
+               if (inset->lyxCode() == inset_code)
+                       count ++;
+               from.forwardInset();
+       }
+       return count;
+}
+
+
+bool CursorData::insetInSelection(InsetCode const & inset_code)
+{
+       if (!selection_)
+               return false;
+
+       DocIterator from, to;
+       from = selectionBegin();
+       to = selectionEnd();
+
+       if (!from.nextInset())      //move to closest inset
+               from.forwardInset();
+
+       while (!from.empty() && from < to) {
+               Inset * inset = from.nextInset();
+               if (!inset)
+                       break;
+               if (inset->lyxCode() == inset_code)
+                       return true;
+               from.forwardInset();
+       }
+       return false;
+}
+
+
+bool CursorData::fixIfBroken()
+{
+       bool const broken_cursor = DocIterator::fixIfBroken();
+       bool const broken_anchor = anchor_.fixIfBroken();
+
+       if (broken_cursor || broken_anchor) {
+               clearNewWordPosition();
+               clearSelection();
+               return true;
+       }
+       return false;
+}
+
+
+void CursorData::sanitize()
+{
+       DocIterator::sanitize();
+       new_word_.sanitize();
+       if (selection())
+               anchor_.sanitize();
+       else
+               resetAnchor();
+}
+
+
+bool CursorData::isInside(Inset const * p) const
+{
+       for (size_t i = 0; i != depth(); ++i)
+               if (&operator[](i).inset() == p)
+                       return true;
+       return false;
+}
+
+
+void CursorData::leaveInset(Inset const & inset)
+{
+       for (size_t i = 0; i != depth(); ++i) {
+               if (&operator[](i).inset() == &inset) {
+                       resize(i);
+                       return;
+               }
+       }
+}
+
+
+bool CursorData::textUndo()
+{
+       if (!buffer()->undo().textUndo(*this))
+               return false;
+       sanitize();
+       return true;
+}
+
+
+bool CursorData::textRedo()
+{
+       if (!buffer()->undo().textRedo(*this))
+               return false;
+       sanitize();
+       return true;
+}
+
+
+void CursorData::finishUndo() const
+{
+       buffer()->undo().finishUndo();
+}
+
+
+void CursorData::beginUndoGroup() const
+{
+       buffer()->undo().beginUndoGroup(*this);
+}
+
+
+void CursorData::endUndoGroup() const
+{
+       buffer()->undo().endUndoGroup(*this);
+}
+
+
+void CursorData::recordUndo(pit_type from, pit_type to) const
+{
+       buffer()->undo().recordUndo(*this, from, to);
+}
+
+
+void CursorData::recordUndo(pit_type from) const
+{
+       buffer()->undo().recordUndo(*this, from, pit());
+}
+
+
+void CursorData::recordUndo(UndoKind kind) const
+{
+       buffer()->undo().recordUndo(*this, kind);
+}
+
+
+void CursorData::recordUndoInset(Inset const * in) const
+{
+       buffer()->undo().recordUndoInset(*this, in);
+}
+
+
+void CursorData::recordUndoFullBuffer() const
+{
+       buffer()->undo().recordUndoFullBuffer(*this);
+}
+
+
+void CursorData::recordUndoBufferParams() const
+{
+       buffer()->undo().recordUndoBufferParams(*this);
+}
+
+
+void CursorData::recordUndoSelection() const
+{
+       if (inMathed()) {
+               if (cap::multipleCellsSelected(*this))
+                       recordUndoInset();
+               else
+                       recordUndo();
+       } else {
+               buffer()->undo().recordUndo(*this,
+                       selBegin().pit(), selEnd().pit());
+       }
+}
+
+
+int CursorData::currentMode()
+{
+       LASSERT(!empty(), return Inset::UNDECIDED_MODE);
+       for (int i = depth() - 1; i >= 0; --i) {
+               int res = operator[](i).inset().currentMode();
+               bool locked_mode = operator[](i).inset().lockedMode();
+               // Also return UNDECIDED_MODE when the mode is locked,
+               // as in this case it is treated the same as TEXT_MODE
+               if (res != Inset::UNDECIDED_MODE || locked_mode)
+                       return res;
+       }
+       return Inset::TEXT_MODE;
 }
 
 
-LyXErr & operator<<(LyXErr & os, CursorData const & cur)
+bool CursorData::confirmDeletion(bool const before) const
 {
-       os.stream() << cur;
-       return os;
+       if (!selection()) {
+               if (Inset const * inset = before ? prevInset() : nextInset())
+                       return inset->confirmDeletion();
+       } else {
+               DocIterator dit = selectionBegin();
+               CursorSlice const end = selectionEnd().top();
+               for (; dit.top() < end; dit.top().forwardPos())
+                       if (Inset const * inset = dit.nextInset())
+                               if (inset->confirmDeletion())
+                                       return true;
+       }
+       return false;
 }
 
 
+
+//
+// Cursor
+//
+
+
 // be careful: this is called from the bv's constructor, too, so
 // bv functions are not yet available!
 Cursor::Cursor(BufferView & bv)
@@ -181,35 +726,8 @@ Cursor::Cursor(BufferView & bv)
 
 void Cursor::reset()
 {
-       clear();
-       push_back(CursorSlice(buffer()->inset()));
-       anchor_ = doc_iterator_begin(buffer());
-       anchor_.clear();
-       new_word_ = doc_iterator_begin(buffer());
-       new_word_.clear();
+       CursorData::reset();
        clearTargetX();
-       selection_ = false;
-       mark_ = false;
-}
-
-
-// this (intentionally) does neither touch anchor nor selection status
-void Cursor::setCursor(DocIterator const & cur)
-{
-       DocIterator::operator=(cur);
-}
-
-
-void Cursor::setCursorToAnchor()
-{
-       if (selection()) {
-               DocIterator normal = anchor_;
-               while (depth() < normal.depth())
-                       normal.pop_back();
-               if (depth() < anchor_.depth() && top() <= anchor_[depth() - 1])
-                       ++normal.pos();
-               setCursor(normal);
-       }
 }
 
 
@@ -360,6 +878,19 @@ DispatchResult const & Cursor::result() const
 }
 
 
+void Cursor::message(docstring const & msg) const
+{
+       disp_.setMessage(msg);
+}
+
+
+void Cursor::errorMessage(docstring const & msg) const
+{
+       disp_.setMessage(msg);
+       disp_.setError(true);
+}
+
+
 BufferView & Cursor::bv() const
 {
        LBUFERR(bv_);
@@ -413,34 +944,6 @@ bool Cursor::popForward()
 }
 
 
-int Cursor::currentMode()
-{
-       LASSERT(!empty(), return Inset::UNDECIDED_MODE);
-       for (int i = depth() - 1; i >= 0; --i) {
-               int res = operator[](i).inset().currentMode();
-               bool locked_mode = operator[](i).inset().lockedMode();
-               // Also return UNDECIDED_MODE when the mode is locked,
-               // as in this case it is treated the same as TEXT_MODE
-               if (res != Inset::UNDECIDED_MODE || locked_mode)
-                       return res;
-       }
-       return Inset::TEXT_MODE;
-}
-
-
-bool Cursor::inCoordCache() const
-{
-       // the root inset is not in cache, but we do not need it.
-       if (depth() == 1)
-               return true;
-       CoordCache::Insets const & icache = bv_->coordCache().getInsets();
-       for (size_t i = 1 ; i < depth() ; ++i)
-               if (!icache.has(&(*this)[i].inset()))
-                       return false;
-       return true;
-}
-
-
 void Cursor::getPos(int & x, int & y) const
 {
        Point p = bv().getPos(*this);
@@ -457,92 +960,6 @@ Row const & Cursor::textRow() const
 }
 
 
-void Cursor::resetAnchor()
-{
-       anchor_ = *this;
-       checkNewWordPosition();
-}
-
-
-void Cursor::markNewWordPosition()
-{
-       if (lyxrc.spellcheck_continuously && inTexted() && new_word_.empty()) {
-               FontSpan nw = locateWord(WHOLE_WORD);
-               if (nw.size() == 1) {
-                       LYXERR(Debug::DEBUG, "start new word: "
-                               << " par: " << pit()
-                               << " pos: " << nw.first);
-                       new_word_ = *this;
-               }
-       }
-}
-
-
-void Cursor::clearNewWordPosition()
-{
-       if (!new_word_.empty()) {
-               LYXERR(Debug::DEBUG, "clear new word: "
-                       << " par: " << pit()
-                       << " pos: " << pos());
-               new_word_.resize(0);
-       }
-}
-
-
-void Cursor::checkNewWordPosition()
-{
-       if (!lyxrc.spellcheck_continuously || new_word_.empty())
-               return ;
-       if (!inTexted())
-               clearNewWordPosition();
-       else {
-               // forget the position of the current new word if
-               // 1) the paragraph changes or
-               // 2) the count of nested insets changes or
-               // 3) the cursor pos is out of paragraph bound
-               if (pit() != new_word_.pit() ||
-                       depth() != new_word_.depth() ||
-                       new_word_.pos() > new_word_.lastpos()) {
-                       clearNewWordPosition();
-               } else if (new_word_.fixIfBroken())
-                       // 4) or the remembered position was "broken"
-                       clearNewWordPosition();
-               else {
-                       FontSpan nw = locateWord(WHOLE_WORD);
-                       if (!nw.empty()) {
-                               FontSpan ow = new_word_.locateWord(WHOLE_WORD);
-                               if (nw.intersect(ow).empty())
-                                       clearNewWordPosition();
-                               else
-                                       LYXERR(Debug::DEBUG, "new word: "
-                                                  << " par: " << pit()
-                                                  << " pos: " << nw.first << ".." << nw.last);
-                       } else {
-                               clearNewWordPosition();
-                       }
-               }
-       }
-}
-
-
-bool Cursor::posBackward()
-{
-       if (pos() == 0)
-               return false;
-       --pos();
-       return true;
-}
-
-
-bool Cursor::posForward()
-{
-       if (pos() == lastpos())
-               return false;
-       ++pos();
-       return true;
-}
-
-
 bool Cursor::posVisRight(bool skip_inset)
 {
        Cursor new_cur = *this; // where we will move to
@@ -750,7 +1167,7 @@ bool findNonVirtual(Row const & row, Row::const_iterator & cit, bool onleft)
        return cit != row.end() && !cit->isVirtual();
 }
 
-}
+} // namespace
 
 void Cursor::getSurroundingPos(pos_type & left_pos, pos_type & right_pos) const
 {
@@ -759,12 +1176,11 @@ void Cursor::getSurroundingPos(pos_type & left_pos, pos_type & right_pos) const
        right_pos = -1;
 
        Row const & row = textRow();
-       TextMetrics const & tm = bv_->textMetrics(text());
        double dummy = 0;
-       Row::const_iterator cit = tm.findRowElement(row, pos(), boundary(), dummy);
+       Row::const_iterator cit = row.findElement(pos(), boundary(), dummy);
        // Handle the case of empty row
        if (cit == row.end()) {
-               if (paragraph().isRTL(buffer()->params()))
+               if (row.isRTL())
                        right_pos = row.pos();
                else
                        left_pos = row.pos() - 1;
@@ -838,10 +1254,8 @@ void Cursor::getSurroundingPos(pos_type & left_pos, pos_type & right_pos) const
 
 bool Cursor::posVisToNewRow(bool movingLeft)
 {
-       Paragraph const & par = paragraph();
-       Buffer const & buf = *buffer();
        Row const & row = textRow();
-       bool par_is_LTR = !par.isRTL(buf.params());
+       bool par_is_LTR = !row.isRTL();
 
        // Inside a table, determining whether to move to the next or
        // previous row should be done based on the table's direction.
@@ -875,151 +1289,47 @@ bool Cursor::posVisToNewRow(bool movingLeft)
                        ++pit();
                        pos() = 0;
                        boundary(false);
-               } else { // move to next row in this par
-                       pos() = row.endpos();
-                       boundary(false);
-               }
-       }
-
-       // make sure we're at left-/right-most pos in new row
-       posVisToRowExtremity(!movingLeft);
-
-       return true;
-}
-
-
-void Cursor::posVisToRowExtremity(bool left)
-{
-       LYXERR(Debug::RTL, "entering extremity: " << pit() << "," << pos() << ","
-               << (boundary() ? 1 : 0));
-
-       TextMetrics const & tm = bv_->textMetrics(text());
-       // Looking for extremities is like clicking on the left or the
-       // right of the row.
-       int x = tm.origin().x_ + (left ? 0 : textRow().width());
-       bool b = false;
-       pos() = tm.getPosNearX(textRow(), x, b);
-       boundary(b);
-
-       LYXERR(Debug::RTL, "leaving extremity: " << pit() << "," << pos() << ","
-               << (boundary() ? 1 : 0));
-}
-
-
-bool Cursor::reverseDirectionNeeded() const
-{
-       /*
-        * We determine the directions based on the direction of the
-        * bottom() --- i.e., outermost --- paragraph, because that is
-        * the only way to achieve consistency of the arrow's movements
-        * within a paragraph, and thus avoid situations in which the
-        * cursor gets stuck.
-        */
-       return bottom().paragraph().isRTL(bv().buffer().params());
-}
-
-
-CursorSlice Cursor::normalAnchor() const
-{
-       if (!selection())
-               return top();
-       // LASSERT: There have been several bugs around this code, that seem
-       // to involve failures to reset the anchor. We can at least not crash
-       // in release mode by resetting it ourselves.
-       if (anchor_.depth() < depth()) {
-               LYXERR0("Cursor is deeper than anchor. PLEASE REPORT.\nCursor is"
-                       << *this);
-               const_cast<DocIterator &>(anchor_) = *this;
-       }
-
-       CursorSlice normal = anchor_[depth() - 1];
-       if (depth() < anchor_.depth() && top() <= normal) {
-               // anchor is behind cursor -> move anchor behind the inset
-               ++normal.pos();
-       }
-       return normal;
-}
-
-
-CursorSlice Cursor::selBegin() const
-{
-       if (!selection())
-               return top();
-       return normalAnchor() < top() ? normalAnchor() : top();
-}
-
-
-CursorSlice Cursor::selEnd() const
-{
-       if (!selection())
-               return top();
-       return normalAnchor() > top() ? normalAnchor() : top();
-}
-
-
-DocIterator Cursor::selectionBegin() const
-{
-       if (!selection())
-               return *this;
-
-       DocIterator di;
-       // FIXME: This is a work-around for the problem that
-       // CursorSlice doesn't keep track of the boundary.
-       if (normalAnchor() == top())
-               di = anchor_.boundary() > boundary() ? anchor_ : *this;
-       else
-               di = normalAnchor() < top() ? anchor_ : *this;
-       di.resize(depth());
-       return di;
-}
-
-
-DocIterator Cursor::selectionEnd() const
-{
-       if (!selection())
-               return *this;
-
-       DocIterator di;
-       // FIXME: This is a work-around for the problem that
-       // CursorSlice doesn't keep track of the boundary.
-       if (normalAnchor() == top())
-               di = anchor_.boundary() < boundary() ? anchor_ : *this;
-       else
-               di = normalAnchor() > top() ? anchor_ : *this;
-
-       if (di.depth() > depth()) {
-               di.resize(depth());
-               ++di.pos();
+               } else { // move to next row in this par
+                       pos() = row.endpos();
+                       boundary(false);
+               }
        }
-       return di;
-}
 
+       // make sure we're at left-/right-most pos in new row
+       posVisToRowExtremity(!movingLeft);
 
-void Cursor::setSelection()
-{
-       selection(true);
-       if (idx() == normalAnchor().idx() &&
-           pit() == normalAnchor().pit() &&
-           pos() == normalAnchor().pos())
-               selection(false);
+       return true;
 }
 
 
-void Cursor::setSelection(DocIterator const & where, int n)
+void Cursor::posVisToRowExtremity(bool left)
 {
-       setCursor(where);
-       selection(true);
-       anchor_ = where;
-       pos() += n;
+       LYXERR(Debug::RTL, "entering extremity: " << pit() << "," << pos() << ","
+               << (boundary() ? 1 : 0));
+
+       TextMetrics const & tm = bv_->textMetrics(text());
+       // Looking for extremities is like clicking on the left or the
+       // right of the row.
+       int x = tm.origin().x_ + (left ? 0 : textRow().width());
+       bool b = false;
+       pos() = tm.getPosNearX(textRow(), x, b);
+       boundary(b);
+
+       LYXERR(Debug::RTL, "leaving extremity: " << pit() << "," << pos() << ","
+               << (boundary() ? 1 : 0));
 }
 
 
-void Cursor::clearSelection()
+bool Cursor::reverseDirectionNeeded() const
 {
-       selection(false);
-       setWordSelection(false);
-       setMark(false);
-       resetAnchor();
+       /*
+        * We determine the directions based on the direction of the
+        * bottom() --- i.e., outermost --- paragraph, because that is
+        * the only way to achieve consistency of the arrow's movements
+        * within a paragraph, and thus avoid situations in which the
+        * cursor gets stuck.
+        */
+       return bottom().paragraph().isRTL(bv().buffer().params());
 }
 
 
@@ -1052,21 +1362,6 @@ void Cursor::updateTextTargetOffset()
 }
 
 
-void Cursor::info(odocstream & os) const
-{
-       for (int i = 1, n = depth(); i < n; ++i) {
-               operator[](i).inset().infoize(os);
-               os << "  ";
-       }
-       if (pos() != 0) {
-               Inset const * inset = prevInset();
-               // prevInset() can return 0 in certain case.
-               if (inset)
-                       prevInset()->infoize2(os);
-       }
-}
-
-
 bool Cursor::selHandle(bool sel)
 {
        //lyxerr << "Cursor::selHandle" << endl;
@@ -1082,6 +1377,31 @@ bool Cursor::selHandle(bool sel)
        selection(sel);
        return true;
 }
+
+
+bool Cursor::atFirstOrLastRow(bool up)
+{
+       TextMetrics const & tm = bv_->textMetrics(text());
+       ParagraphMetrics const & pm = tm.parMetrics(pit());
+
+       int row;
+       if (pos() && boundary())
+               row = pm.pos2row(pos() - 1);
+       else
+               row = pm.pos2row(pos());
+
+       if (up) {
+               if (pit() == 0 && row == 0)
+                       return true;
+       } else {
+               if (pit() + 1 >= int(text()->paragraphs().size()) &&
+                       row + 1 >= int(pm.rows().size()))
+                       return true;
+       }
+       return false;
+}
+
+
 } // namespace lyx
 
 
@@ -1105,26 +1425,6 @@ bool Cursor::selHandle(bool sel)
 
 namespace lyx {
 
-bool Cursor::isInside(Inset const * p) const
-{
-       for (size_t i = 0; i != depth(); ++i)
-               if (&operator[](i).inset() == p)
-                       return true;
-       return false;
-}
-
-
-void Cursor::leaveInset(Inset const & inset)
-{
-       for (size_t i = 0; i != depth(); ++i) {
-               if (&operator[](i).inset() == &inset) {
-                       resize(i);
-                       return;
-               }
-       }
-}
-
-
 bool Cursor::openable(MathAtom const & t) const
 {
        if (!t->isActive())
@@ -1137,41 +1437,21 @@ bool Cursor::openable(MathAtom const & t) const
                return true;
 
        // we can't move into anything new during selection
-       if (depth() >= anchor_.depth())
+       if (depth() >= realAnchor().depth())
                return false;
-       if (t.nucleus() != &anchor_[depth()].inset())
+       if (t.nucleus() != &realAnchor()[depth()].inset())
                return false;
 
        return true;
 }
 
 
-void Cursor::setScreenPos(int x, int /*y*/)
-{
-       setTargetX(x);
-       //bruteFind(*this, x, y, 0, bv().workWidth(), 0, bv().workHeight());
-}
-
-
-
 void Cursor::plainErase()
 {
        cell().erase(pos());
 }
 
 
-void Cursor::markInsert()
-{
-       insert(char_type(0));
-}
-
-
-void Cursor::markErase()
-{
-       cell().erase(pos());
-}
-
-
 void Cursor::plainInsert(MathAtom const & t)
 {
        cell().insert(pos(), t);
@@ -1182,14 +1462,6 @@ void Cursor::plainInsert(MathAtom const & t)
 }
 
 
-void Cursor::insert(docstring const & str)
-{
-       for_each(str.begin(), str.end(),
-                bind(static_cast<void(Cursor::*)(char_type)>
-                            (&Cursor::insert), this, _1));
-}
-
-
 void Cursor::insert(char_type c)
 {
        //lyxerr << "Cursor::insert char '" << c << "'" << endl;
@@ -1203,12 +1475,10 @@ void Cursor::insert(char_type c)
 }
 
 
-void Cursor::insert(MathAtom const & t)
+void Cursor::insert(docstring const & str)
 {
-       //lyxerr << "Cursor::insert MathAtom '" << t << "'" << endl;
-       macroModeClose();
-       cap::selClearOrDel(*this);
-       plainInsert(t);
+       for (char_type c : str)
+               insert(c);
 }
 
 
@@ -1227,8 +1497,32 @@ void Cursor::insert(Inset * inset0)
 }
 
 
+void Cursor::insert(MathAtom const & t)
+{
+       LATTEST(inMathed());
+       //lyxerr << "Cursor::insert MathAtom '" << t << "'" << endl;
+       macroModeClose();
+       cap::selClearOrDel(*this);
+       plainInsert(t);
+}
+
+
+void Cursor::insert(MathData const & ar)
+{
+       LATTEST(inMathed());
+       macroModeClose();
+       if (selection())
+               cap::eraseSelection(*this);
+       cell().insert(pos(), ar);
+       pos() += ar.size();
+       // FIXME audit setBuffer calls
+       inset().setBuffer(bv_->buffer());
+}
+
+
 int Cursor::niceInsert(docstring const & t, Parse::flags f, bool enter)
 {
+       LATTEST(inMathed());
        MathData ar(buffer());
        asArray(t, ar, f);
        if (ar.size() == 1 && (enter || selection()))
@@ -1241,6 +1535,7 @@ int Cursor::niceInsert(docstring const & t, Parse::flags f, bool enter)
 
 void Cursor::niceInsert(MathAtom const & t)
 {
+       LATTEST(inMathed());
        macroModeClose();
        docstring const safe = cap::grabAndEraseSelection(*this);
        plainInsert(t);
@@ -1267,19 +1562,7 @@ void Cursor::niceInsert(MathAtom const & t)
 }
 
 
-void Cursor::insert(MathData const & ar)
-{
-       macroModeClose();
-       if (selection())
-               cap::eraseSelection(*this);
-       cell().insert(pos(), ar);
-       pos() += ar.size();
-       // FIXME audit setBuffer calls
-       inset().setBuffer(bv_->buffer());
-}
-
-
-bool Cursor::backspace()
+bool Cursor::backspace(bool const force)
 {
        if (selection()) {
                cap::eraseSelection(*this);
@@ -1315,7 +1598,7 @@ bool Cursor::backspace()
                }
        }
 
-       if (pos() != 0 && prevAtom()->nargs() > 0) {
+       if (pos() != 0 && !force && prevAtom()->confirmDeletion()) {
                // let's require two backspaces for 'big stuff' and
                // highlight on the first
                resetAnchor();
@@ -1329,7 +1612,7 @@ bool Cursor::backspace()
 }
 
 
-bool Cursor::erase()
+bool Cursor::erase(bool const force)
 {
        if (inMacroMode())
                return true;
@@ -1364,7 +1647,7 @@ bool Cursor::erase()
        }
 
        // 'clever' UI hack: only erase large items if previously slected
-       if (pos() != lastpos() && nextAtom()->nargs() > 0) {
+       if (pos() != lastpos() && !force && nextAtom()->confirmDeletion()) {
                resetAnchor();
                selection(true);
                ++pos();
@@ -1402,7 +1685,50 @@ bool Cursor::down()
 }
 
 
-bool Cursor::macroModeClose()
+void Cursor::handleNest(MathAtom const & a, int c)
+{
+       //lyxerr << "Cursor::handleNest: " << c << endl;
+       MathAtom t = a;
+       asArray(cap::grabAndEraseSelection(*this), t.nucleus()->cell(c));
+       insert(t);
+       posBackward();
+       pushBackward(*nextInset());
+}
+
+
+void Cursor::handleNest(MathAtom const & a)
+{
+       handleNest(a, a.nucleus()->asNestInset()->firstIdx());
+}
+
+
+int Cursor::targetX() const
+{
+       if (x_target() != -1)
+               return x_target();
+       int x = 0;
+       int y = 0;
+       getPos(x, y);
+       return x;
+}
+
+
+int Cursor::textTargetOffset() const
+{
+       return textTargetOffset_;
+}
+
+
+void Cursor::setTargetX()
+{
+       int x;
+       int y;
+       getPos(x, y);
+       setTargetX(x);
+}
+
+
+bool Cursor::macroModeClose(bool cancel)
 {
        if (!inMacroMode())
                return false;
@@ -1414,24 +1740,27 @@ bool Cursor::macroModeClose()
        --pos();
        cell().erase(pos());
 
-       // do nothing if the macro name is empty
-       if (s == "\\")
-               return false;
-
        // trigger updates of macros, at least, if no full
        // updates take place anyway
        screenUpdateFlags(Update::Force);
 
+       // do nothing if the macro name is empty
+       if (s == "\\" || cancel) {
+               return false;
+       }
+
        docstring const name = s.substr(1);
        InsetMathNest * const in = inset().asInsetMath()->asNestInset();
        if (in && in->interpretString(*this, s))
                return true;
-       MathAtom atom = buffer()->getMacro(name, *this, false) ?
-               MathAtom(new MathMacro(buffer(), name)) : createInsetMath(name, buffer());
+       bool const user_macro = buffer()->getMacro(name, *this, false);
+       MathAtom atom = user_macro ? MathAtom(new InsetMathMacro(buffer(), name))
+                                  : createInsetMath(name, buffer());
 
        // try to put argument into macro, if we just inserted a macro
        bool macroArg = false;
-       MathMacro * atomAsMacro = atom.nucleus()->asMacro();
+       InsetMathMacro * atomAsMacro = atom.nucleus()->asMacro();
+       InsetMathNest * atomAsNest = atom.nucleus()->asNestInset();
        if (atomAsMacro) {
                // macros here are still unfolded (in init mode in fact). So
                // we have to resolve the macro here manually and check its arity
@@ -1439,17 +1768,40 @@ bool Cursor::macroModeClose()
                MacroData const * data = buffer()->getMacro(atomAsMacro->name());
                if (!selection.empty() && data && data->numargs() - data->optionals() > 0) {
                        macroArg = true;
-                       atomAsMacro->setDisplayMode(MathMacro::DISPLAY_INTERACTIVE_INIT, 1);
+                       atomAsMacro->setDisplayMode(InsetMathMacro::DISPLAY_INTERACTIVE_INIT, 1);
                } else
                        // non-greedy case. Do not touch the arguments behind
-                       atomAsMacro->setDisplayMode(MathMacro::DISPLAY_INTERACTIVE_INIT, 0);
+                       atomAsMacro->setDisplayMode(InsetMathMacro::DISPLAY_INTERACTIVE_INIT, 0);
        }
 
        // insert remembered selection into first argument of a non-macro
-       else if (atom.nucleus()->nargs() > 0)
-               atom.nucleus()->cell(0).append(selection);
-
-       plainInsert(atom);
+       else if (atomAsNest && atomAsNest->nargs() > 0)
+               atomAsNest->cell(atomAsNest->firstIdx()).append(selection);
+
+       MathWordList const & words = mathedWordList();
+       MathWordList::const_iterator it = words.find(name);
+       bool keep_mathmode = user_macro
+               || (it != words.end() && (it->second.inset == "font"
+                                         || it->second.inset == "oldfont"
+                                         || it->second.inset == "mbox"));
+       bool ert_macro = !user_macro && it == words.end() && atomAsMacro;
+
+       if (in && in->currentMode() == Inset::TEXT_MODE
+           && atom.nucleus()->currentMode() == Inset::MATH_MODE
+           && name != from_ascii("ensuremath") && !ert_macro) {
+               MathAtom at(new InsetMathEnsureMath(buffer()));
+               at.nucleus()->cell(0).push_back(atom);
+               niceInsert(at);
+               posForward();
+       } else if (in && in->currentMode() == Inset::MATH_MODE
+                  && atom.nucleus()->currentMode() == Inset::TEXT_MODE
+                  && !keep_mathmode) {
+               MathAtom at = createInsetMath("text", buffer());
+               at.nucleus()->cell(0).push_back(atom);
+               niceInsert(at);
+               posForward();
+       } else
+               plainInsert(atom);
 
        // finally put the macro argument behind, if needed
        if (macroArg) {
@@ -1458,51 +1810,8 @@ bool Cursor::macroModeClose()
                else
                        insert(selection);
        }
-
-       return true;
-}
-
-
-docstring Cursor::macroName()
-{
-       return inMacroMode() ? activeMacro()->name() : docstring();
-}
-
-
-void Cursor::handleNest(MathAtom const & a, int c)
-{
-       //lyxerr << "Cursor::handleNest: " << c << endl;
-       MathAtom t = a;
-       asArray(cap::grabAndEraseSelection(*this), t.nucleus()->cell(c));
-       insert(t);
-       posBackward();
-       pushBackward(*nextInset());
-}
-
-
-int Cursor::targetX() const
-{
-       if (x_target() != -1)
-               return x_target();
-       int x = 0;
-       int y = 0;
-       getPos(x, y);
-       return x;
-}
-
-
-int Cursor::textTargetOffset() const
-{
-       return textTargetOffset_;
-}
-
-
-void Cursor::setTargetX()
-{
-       int x;
-       int y;
-       getPos(x, y);
-       setTargetX(x);
+
+       return true;
 }
 
 
@@ -1529,6 +1838,12 @@ InsetMathUnknown const * Cursor::activeMacro() const
 }
 
 
+docstring Cursor::macroName()
+{
+       return inMacroMode() ? activeMacro()->name() : docstring();
+}
+
+
 void Cursor::pullArg()
 {
        // FIXME: Look here
@@ -1543,18 +1858,6 @@ void Cursor::pullArg()
 }
 
 
-void Cursor::touch()
-{
-       // FIXME: look here
-#if 0
-       DocIterator::const_iterator it = begin();
-       DocIterator::const_iterator et = end();
-       for ( ; it != et; ++it)
-               it->cell().touch();
-#endif
-}
-
-
 void Cursor::normalize()
 {
        if (idx() > lastidx()) {
@@ -1569,8 +1872,7 @@ void Cursor::normalize()
                        << pos() << ' ' << lastpos() <<  " in idx: " << idx()
                       << " in atom: '";
                odocstringstream os;
-               TexRow texrow(false);
-               otexrowstream ots(os,texrow);
+               otexrowstream ots(os);
                WriteStream wi(ots, false, true, WriteStream::wsDefault);
                inset().asInsetMath()->write(wi);
                lyxerr << to_utf8(os.str()) << endl;
@@ -1695,25 +1997,79 @@ bool Cursor::upDownInMath(bool up)
 }
 
 
-bool Cursor::atFirstOrLastRow(bool up)
-{
-       TextMetrics const & tm = bv_->textMetrics(text());
-       ParagraphMetrics const & pm = tm.parMetrics(pit());
+bool Cursor::mathForward(bool word)
+{
+       LASSERT(inMathed(), return false);
+       if (pos() < lastpos()) {
+               if (word) {
+                       // word: skip a group of insets of the form X*(B*|R*|P*) (greedy
+                       // match) where X is any math class, B is mathbin, R is mathrel, and
+                       // P is mathpunct. Make sure that the following remains true:
+                       //   mathForward(true); mathBackward(true); mathForward(true)
+                       // is the same as mathForward(true) and
+                       //   mathBackward(true); mathForward(true); mathBackward(true)
+                       // is the same as mathBackward(true).
+                       MathClass mc = nextMath().mathClass();
+                       do
+                               posForward();
+                       while (pos() < lastpos() && mc == nextMath().mathClass());
+                       if (pos() < lastpos() &&
+                           ((mc = nextMath().mathClass()) == MC_BIN ||
+                            mc == MC_REL || mc == MC_PUNCT))
+                               do
+                                       posForward();
+                               while (pos() < lastpos() && mc == nextMath().mathClass());
+               } else if (openable(nextAtom())) {
+                       // single step: try to enter the next inset
+                       pushBackward(nextMath());
+                       inset().idxFirst(*this);
+               } else
+                       posForward();
+               return true;
+       }
+       if (inset().idxForward(*this))
+               return true;
+       // try to pop forwards --- but don't pop out of math! leave that to
+       // the FINISH lfuns
+       int s = depth() - 2;
+       if (s >= 0 && operator[](s).inset().asInsetMath())
+               return popForward();
+       return false;
+}
 
-       int row;
-       if (pos() && boundary())
-               row = pm.pos2row(pos() - 1);
-       else
-               row = pm.pos2row(pos());
 
-       if (up) {
-               if (pit() == 0 && row == 0)
-                       return true;
-       } else {
-               if (pit() + 1 >= int(text()->paragraphs().size()) &&
-                               row + 1 >= int(pm.rows().size()))
-                       return true;
+bool Cursor::mathBackward(bool word)
+{
+       LASSERT(inMathed(), return false);
+       if (pos() > 0) {
+               if (word) {
+                       // word: skip a group of insets. See the comment in mathForward.
+                       MathClass mc = prevMath().mathClass();
+                       do
+                               posBackward();
+                       while (pos() > 0 && mc == prevMath().mathClass());
+                       if (pos() > 0 && (mc == MC_BIN || mc == MC_REL || mc == MC_PUNCT)) {
+                               mc = prevMath().mathClass();
+                               do
+                                       posBackward();
+                               while (pos() > 0 && mc == prevMath().mathClass());
+                       }
+               } else if (openable(prevAtom())) {
+                       // single step: try to enter the preceding inset
+                       posBackward();
+                       push(nextMath());
+                       inset().idxLast(*this);
+               } else
+                       posBackward();
+               return true;
        }
+       if (inset().idxBackward(*this))
+               return true;
+       // try to pop backwards --- but don't pop out of math! leave that to
+       // the FINISH lfuns
+       int s = depth() - 2;
+       if (s >= 0 && operator[](s).inset().asInsetMath())
+               return popBackward();
        return false;
 }
 
@@ -1806,13 +2162,16 @@ bool Cursor::upDownInText(bool up, bool & updateNeeded)
 
        // with and without selection are handled differently
        if (!selection()) {
-               int yo = bv().getPos(*this).y_;
+               int yo1 = bv().getPos(*this).y_;
                Cursor old = *this;
                // To next/previous row
+               // FIXME: the y position is often guessed wrongly across styles and
+               // insets, which leads to weird behaviour.
                if (up)
-                       tm.editXY(*this, xo, yo - textRow().ascent() - 1);
+                       tm.editXY(*this, xo, yo1 - textRow().ascent() - 1);
                else
-                       tm.editXY(*this, xo, yo + textRow().descent() + 1);
+                       tm.editXY(*this, xo, yo1 + textRow().descent() + 1);
+               x_target_ = old.x_target_;
                clearSelection();
 
                // This happens when you move out of an inset.
@@ -1839,10 +2198,10 @@ bool Cursor::upDownInText(bool up, bool & updateNeeded)
                                --next_row;
                        } else if (pit() > 0) {
                                --pit();
-                               TextMetrics & tm = bv_->textMetrics(text());
-                               if (!tm.contains(pit()))
-                                       tm.newParMetricsUp();
-                               ParagraphMetrics const & pmcur = tm.parMetrics(pit());
+                               TextMetrics & tm2 = bv_->textMetrics(text());
+                               if (!tm2.contains(pit()))
+                                       tm2.newParMetricsUp();
+                               ParagraphMetrics const & pmcur = tm2.parMetrics(pit());
                                next_row = pmcur.rows().size() - 1;
                        }
                } else {
@@ -1850,9 +2209,9 @@ bool Cursor::upDownInText(bool up, bool & updateNeeded)
                                ++next_row;
                        } else if (pit() + 1 < int(text()->paragraphs().size())) {
                                ++pit();
-                               TextMetrics & tm = bv_->textMetrics(text());
-                               if (!tm.contains(pit()))
-                                       tm.newParMetricsDown();
+                               TextMetrics & tm2 = bv_->textMetrics(text());
+                               if (!tm2.contains(pit()))
+                                       tm2.newParMetricsDown();
                                next_row = 0;
                        }
                }
@@ -1861,6 +2220,8 @@ bool Cursor::upDownInText(bool up, bool & updateNeeded)
                bool bound = false;
                top().pos() = tm.getPosNearX(real_next_row, xo, bound);
                boundary(bound);
+               // When selection==false, this is done by TextMetrics::editXY
+               setCurrentFont();
 
                updateNeeded |= bv().checkDepm(*this, old);
        }
@@ -1910,124 +2271,6 @@ void Cursor::handleFont(string const & font)
 }
 
 
-void Cursor::message(docstring const & msg) const
-{
-       disp_.setMessage(msg);
-}
-
-
-void Cursor::errorMessage(docstring const & msg) const
-{
-       disp_.setMessage(msg);
-       disp_.setError(true);
-}
-
-
-namespace {
-
-docstring parbreak(Cursor const * cur)
-{
-       odocstringstream os;
-       os << '\n';
-       // only add blank line if we're not in a ParbreakIsNewline situation
-       if (!cur->inset().getLayout().parbreakIsNewline()
-           && !cur->paragraph().layout().parbreak_is_newline)
-               os << '\n';
-       return os.str();
-}
-
-}
-
-
-docstring Cursor::selectionAsString(bool with_label) const
-{
-       if (!selection())
-               return docstring();
-
-       if (inMathed())
-               return cap::grabSelection(*this);
-
-       int const label = with_label
-               ? AS_STR_LABEL | AS_STR_INSETS : AS_STR_INSETS;
-
-       idx_type const startidx = selBegin().idx();
-       idx_type const endidx = selEnd().idx();
-       if (startidx != endidx) {
-               // multicell selection
-               InsetTabular * table = inset().asInsetTabular();
-               LASSERT(table, return docstring());
-               return table->asString(startidx, endidx);
-       }
-
-       ParagraphList const & pars = text()->paragraphs();
-
-       pit_type const startpit = selBegin().pit();
-       pit_type const endpit = selEnd().pit();
-       size_t const startpos = selBegin().pos();
-       size_t const endpos = selEnd().pos();
-
-       if (startpit == endpit)
-               return pars[startpit].asString(startpos, endpos, label);
-
-       // First paragraph in selection
-       docstring result = pars[startpit].
-               asString(startpos, pars[startpit].size(), label)
-               + parbreak(this);
-
-       // The paragraphs in between (if any)
-       for (pit_type pit = startpit + 1; pit != endpit; ++pit) {
-               Paragraph const & par = pars[pit];
-               result += par.asString(0, par.size(), label)
-                       + parbreak(this);
-       }
-
-       // Last paragraph in selection
-       result += pars[endpit].asString(0, endpos, label);
-
-       return result;
-}
-
-
-docstring Cursor::currentState() const
-{
-       if (inMathed()) {
-               odocstringstream os;
-               info(os);
-#ifdef DEVEL_VERSION
-               InsetMath * math = inset().asInsetMath();
-               if (math)
-                       os << _(", Inset: ") << math->id();
-               os << _(", Cell: ") << idx();
-               os << _(", Position: ") << pos();
-#endif
-               return os.str();
-       }
-
-       if (inTexted())
-               return text()->currentState(*this);
-
-       return docstring();
-}
-
-
-docstring Cursor::getPossibleLabel() const
-{
-       return inMathed() ? from_ascii("eq:") : text()->getPossibleLabel(*this);
-}
-
-
-Encoding const * Cursor::getEncoding() const
-{
-       if (empty())
-               return 0;
-       CursorSlice const & sl = innerTextSlice();
-       Text const & text = *sl.text();
-       Font font = text.getPar(sl.pit()).getFont(
-               bv().buffer().params(), sl.pos(), text.outerFont(sl.pit()));
-       return font.language()->encoding();
-}
-
-
 void Cursor::undispatched() const
 {
        disp_.dispatched(false);
@@ -2046,6 +2289,12 @@ void Cursor::screenUpdateFlags(Update::flags f) const
 }
 
 
+void Cursor::noScreenUpdate() const
+{
+       disp_.screenUpdate(Update::None);
+}
+
+
 void Cursor::forceBufferUpdate() const
 {
        disp_.forceBufferUpdate();
@@ -2064,17 +2313,12 @@ bool Cursor::needBufferUpdate() const
 }
 
 
-void Cursor::noScreenUpdate() const
-{
-       disp_.screenUpdate(Update::None);
-}
-
-
 Font Cursor::getFont() const
 {
        // The logic here should more or less match to the
        // Cursor::setCurrentFont logic, i.e. the cursor height should
        // give a hint what will happen if a character is entered.
+       // FIXME: this is not the case, what about removing this method ? (see #10478).
 
        // HACK. far from being perfect...
 
@@ -2104,28 +2348,10 @@ Font Cursor::getFont() const
 }
 
 
-bool Cursor::fixIfBroken()
-{
-       bool const broken_cursor = DocIterator::fixIfBroken();
-       bool const broken_anchor = anchor_.fixIfBroken();
-
-       if (broken_cursor || broken_anchor) {
-               clearNewWordPosition();
-               clearSelection();
-               return true;
-       }
-       return false;
-}
-
-
 void Cursor::sanitize()
 {
        setBuffer(&bv_->buffer());
-       DocIterator::sanitize();
-       if (selection())
-               anchor_.sanitize();
-       else
-               resetAnchor();
+       CursorData::sanitize();
 }
 
 
@@ -2212,92 +2438,6 @@ void Cursor::setCurrentFont()
 }
 
 
-bool Cursor::textUndo()
-{
-       if (!buffer()->undo().textUndo(*this))
-               return false;
-       sanitize();
-       return true;
-}
-
-
-bool Cursor::textRedo()
-{
-       if (!buffer()->undo().textRedo(*this))
-               return false;
-       sanitize();
-       return true;
-}
-
-
-void Cursor::finishUndo() const
-{
-       buffer()->undo().finishUndo();
-}
-
-
-void Cursor::beginUndoGroup() const
-{
-       buffer()->undo().beginUndoGroup(*this);
-}
-
-
-void Cursor::endUndoGroup() const
-{
-       buffer()->undo().endUndoGroup(*this);
-}
-
-
-void Cursor::recordUndo(pit_type from, pit_type to) const
-{
-       buffer()->undo().recordUndo(*this, from, to);
-}
-
-
-void Cursor::recordUndo(pit_type from) const
-{
-       buffer()->undo().recordUndo(*this, from, pit());
-}
-
-
-void Cursor::recordUndo(UndoKind kind) const
-{
-       buffer()->undo().recordUndo(*this, kind);
-}
-
-
-void Cursor::recordUndoInset(Inset const * in) const
-{
-       buffer()->undo().recordUndoInset(*this, in);
-}
-
-
-void Cursor::recordUndoFullBuffer() const
-{
-       buffer()->undo().recordUndoFullBuffer(*this);
-}
-
-
-void Cursor::recordUndoBufferParams() const
-{
-       buffer()->undo().recordUndoBufferParams(*this);
-}
-
-
-void Cursor::recordUndoSelection() const
-{
-       if (inMathed()) {
-               if (cap::multipleCellsSelected(*this))
-                       recordUndoInset();
-               else
-                       recordUndo();
-       } else {
-               buffer()->undo().recordUndo(*this,
-                       selBegin().pit(), selEnd().pit());
-       }
-}
-
-
 void Cursor::checkBufferStructure()
 {
        Buffer const * master = buffer()->masterBuffer();
@@ -2315,4 +2455,22 @@ void Cursor::checkBufferStructure()
 }
 
 
+void Cursor::moveToClosestEdge(int const x, bool const edit)
+{
+       if (Inset const * inset = nextInset()) {
+               // stay in front of insets for which we want to open the dialog
+               // (e.g. InsetMathSpace).
+               if (edit && (inset->hasSettings() || !inset->contextMenuName().empty()))
+                       return;
+               CoordCache::Insets const & insetCache = bv().coordCache().getInsets();
+               if (!insetCache.has(inset))
+                       return;
+               int const wid = insetCache.dim(inset).wid;
+               Point p = insetCache.xy(inset);
+               if (x > p.x_ + (wid + 1) / 2)
+                       posForward();
+       }
+}
+
+
 } // namespace lyx