]> git.lyx.org Git - lyx.git/blobdiff - src/BufferView.cpp
Sanitize cursors after a buffer has been reloaded
[lyx.git] / src / BufferView.cpp
index 0025277e5330406ae1d33eda3113ebf668c3884b..7abaea0d40c1ea00829050c1fbccf57e159d10c7 100644 (file)
@@ -20,6 +20,7 @@
 #include "Buffer.h"
 #include "BufferList.h"
 #include "BufferParams.h"
+#include "BiblioInfo.h"
 #include "CoordCache.h"
 #include "Cursor.h"
 #include "CutAndPaste.h"
@@ -38,6 +39,7 @@
 #include "MetricsInfo.h"
 #include "Paragraph.h"
 #include "Session.h"
+#include "texstream.h"
 #include "Text.h"
 #include "TextMetrics.h"
 #include "TexRow.h"
 #include "insets/InsetCitation.h"
 #include "insets/InsetCommand.h" // ChangeRefs
 #include "insets/InsetGraphics.h"
+#include "insets/InsetIndex.h"
 #include "insets/InsetRef.h"
 #include "insets/InsetText.h"
 
-#include "mathed/InsetMath.h"
+#include "mathed/InsetMathNest.h"
+#include "mathed/InsetMathRef.h"
 #include "mathed/MathData.h"
 #include "mathed/MathRow.h"
 
@@ -66,6 +70,7 @@
 #include "support/convert.h"
 #include "support/debug.h"
 #include "support/docstring.h"
+#include "support/docstring_list.h"
 #include "support/filetools.h"
 #include "support/gettext.h"
 #include "support/lassert.h"
@@ -170,18 +175,18 @@ bool findInset(DocIterator & dit, vector<InsetCode> const & codes,
 
 
 /// Moves cursor to the next inset with one of the given codes.
-void gotoInset(BufferView * bv, vector<InsetCode> const & codes,
+bool gotoInset(BufferView * bv, vector<InsetCode> const & codes,
               bool same_content)
 {
        Cursor tmpcur = bv->cursor();
        if (!findInset(tmpcur, codes, same_content)) {
                bv->cursor().message(_("No more insets"));
-               return;
+               return false;
        }
 
        tmpcur.clearSelection();
        bv->setCursor(tmpcur);
-       bv->showCursor();
+       return bv->scrollToCursor(bv->cursor(), SCROLL_TOP);
 }
 
 
@@ -227,7 +232,7 @@ struct BufferView::Private
        ///
        CoordCache coord_cache_;
        ///
-       typedef map<MathData const *, MathRow> MathRows;
+       typedef unordered_map<MathData const *, MathRow> MathRows;
        MathRows math_rows_;
 
        /// this is used to handle XSelection events in the right manner.
@@ -278,11 +283,11 @@ struct BufferView::Private
        ///
        map<string, Inset *> edited_insets_;
 
-       /// When the row where the cursor lies is scrolled, this
-       /// contains the scroll offset
        // cache for id of the paragraph which was edited the last time
        int bookmark_edit_position_;
 
+       /// When the row where the cursor lies is scrolled, this
+       /// contains the scroll offset
        int horiz_scroll_offset_;
        /// a slice pointing to the start of the row where the cursor
        /// is (at last draw time)
@@ -291,6 +296,15 @@ struct BufferView::Private
        bool clickable_inset_;
        /// shape of the caret
        frontend::CaretGeometry caret_geometry_;
+       ///
+       bool mouse_selecting_ = false;
+       /// Reference value for statistics (essentially subtract this from the actual value to see relative counts)
+       /// (words/chars/chars no blanks)
+       int stats_ref_value_w_ = 0;
+       int stats_ref_value_c_ = 0;
+       int stats_ref_value_nb_ = 0;
+       bool stats_update_trigger_ = false;
+
 };
 
 
@@ -330,16 +344,31 @@ BufferView::~BufferView()
 }
 
 
-int BufferView::rightMargin() const
+void BufferView::copySettingsFrom(BufferView const & bv)
+{
+       setCursor(bv.cursor());
+       d->anchor_pit_ = bv.d->anchor_pit_;
+       d->anchor_ypos_ = bv.d->anchor_ypos_;
+}
+
+
+int BufferView::defaultMargin() const
 {
        // The value used to be hardcoded to 10
-       int const default_margin = zoomedPixels(10);
-       // The additional test for the case the outliner is opened.
-       if (!full_screen_ || !lyxrc.full_screen_limit
-           || width_ < lyxrc.full_screen_width + 2 * default_margin)
-               return default_margin;
+       return zoomedPixels(20);
+}
 
-       return (width_ - lyxrc.full_screen_width) / 2;
+
+int BufferView::rightMargin() const
+{
+       const int screen_width = inPixels(lyxrc.screen_width);
+
+       // The additional test for the case the outliner is opened.
+       if (!lyxrc.screen_limit || width_ < screen_width + 2 * defaultMargin()) {
+               return defaultMargin();
+       } else {
+               return (width_ - screen_width) / 2;
+       }
 }
 
 
@@ -421,6 +450,12 @@ CoordCache const & BufferView::coordCache() const
 }
 
 
+bool BufferView::hasMathRow(MathData const * cell) const
+{
+       return d->math_rows_.find(cell) != d->math_rows_.end();
+}
+
+
 MathRow const & BufferView::mathRow(MathData const * cell) const
 {
        auto it = d->math_rows_.find(cell);
@@ -504,7 +539,7 @@ void BufferView::processUpdateFlags(Update::flags flags)
                   << flagsAsString(flags) << ")  buffer: " << &buffer_);
 
        // Case when no explicit update is requested.
-       if (flags == Update::None)
+       if (flags == Update::None || !ready())
                return;
 
        /* FIXME We would like to avoid doing this here, since it is very
@@ -517,10 +552,13 @@ void BufferView::processUpdateFlags(Update::flags flags)
 
        // First check whether the metrics and inset positions should be updated
        if (flags & Update::Force) {
-               // This will update the CoordCache items and replace Force
-               // with ForceDraw in flags.
-               updateMetrics(flags);
-       }
+               // This will compute all metrics and positions.
+               updateMetrics(true);
+               // metrics is done, full drawing is necessary now
+               flags = (flags & ~Update::Force) | Update::ForceDraw;
+       } else if (flags & Update::ForceDraw)
+               // This will compute only the needed metrics and update positions.
+               updateMetrics(false);
 
        // Detect whether we can only repaint a single paragraph (if we
        // are not already redrawing all).
@@ -529,22 +567,22 @@ void BufferView::processUpdateFlags(Update::flags flags)
        if (!(flags & Update::ForceDraw)
                        && (flags & Update::SinglePar)
                        && !singleParUpdate())
-               updateMetrics(flags);
+               updateMetrics(true);
 
        // Then make sure that the screen contains the cursor if needed
        if (flags & Update::FitCursor) {
                if (needsFitCursor()) {
                        // First try to make the selection start visible
                        // (which is just the cursor when there is no selection)
-                       scrollToCursor(d->cursor_.selectionBegin(), false);
+                       scrollToCursor(d->cursor_.selectionBegin(), SCROLL_VISIBLE);
                        // Metrics have to be recomputed (maybe again)
-                       updateMetrics();
+                       updateMetrics(true);
                        // Is the cursor visible? (only useful if cursor is at end of selection)
                        if (needsFitCursor()) {
                                // then try to make cursor visible instead
-                               scrollToCursor(d->cursor_, false);
+                               scrollToCursor(d->cursor_, SCROLL_VISIBLE);
                                // Metrics have to be recomputed (maybe again)
-                               updateMetrics(flags);
+                               updateMetrics(true);
                        }
                }
                flags = flags & ~Update::FitCursor;
@@ -578,9 +616,9 @@ void BufferView::processUpdateFlags(Update::flags flags)
 }
 
 
-void BufferView::updateScrollbar()
+void BufferView::updateScrollbarParameters()
 {
-       if (height_ == 0 && width_ == 0)
+       if (!ready())
                return;
 
        // We prefer fixed size line scrolling.
@@ -591,7 +629,7 @@ void BufferView::updateScrollbar()
        Text & t = buffer_.text();
        TextMetrics & tm = d->text_metrics_[&t];
 
-       LYXERR(Debug::GUI, " Updating scrollbar: height: "
+       LYXERR(Debug::SCROLLING, " Updating scrollbar: height: "
                << t.paragraphs().size()
                << " curr par: " << d->cursor_.bottom().pit()
                << " default height " << defaultRowHeight());
@@ -613,8 +651,8 @@ void BufferView::updateScrollbar()
                        << d->par_height_[pit]);
        }
 
-       int top_pos = first.second->position() - first.second->ascent();
-       int bottom_pos = last.second->position() + last.second->descent();
+       int top_pos = first.second->top();
+       int bottom_pos = last.second->bottom();
        bool first_visible = first.first == 0 && top_pos >= 0;
        bool last_visible = last.first + 1 == int(parsize) && bottom_pos <= height_;
        if (first_visible && last_visible) {
@@ -669,13 +707,53 @@ string BufferView::contextMenu(int x, int y) const
 
        // Get inset under mouse, if there is one.
        Inset const * covering_inset = getCoveringInset(buffer_.text(), x, y);
-       if (covering_inset)
+       if (covering_inset) {
+               if (covering_inset->asInsetMath()) {
+                       CoordCache::Insets const & inset_cache =
+                               coordCache().getInsets();
+                       Inset const * inner_inset = mathContextMenu(
+                               covering_inset->asInsetMath()->asNestInset(),
+                               inset_cache, x, y);
+                       if (inner_inset)
+                               return inner_inset->contextMenu(*this, x, y);
+               }
                return covering_inset->contextMenu(*this, x, y);
+       }
 
        return buffer_.inset().contextMenu(*this, x, y);
 }
 
 
+Inset const * BufferView::mathContextMenu(InsetMathNest const * inset,
+               CoordCache::Insets const & inset_cache, int x, int y) const
+{
+       for (size_t i = 0; i < inset->nargs(); ++i) {
+               MathData const & ar = inset->cell(i);
+               for (size_t j = 0; j < ar.size(); ++j) {
+                       string const name = lyxerr.debugging(Debug::MATHED)
+                               ? insetName(ar[j].nucleus()->lyxCode())
+                               : string();
+                       LYXERR(Debug::MATHED, "Examining inset: " << name);
+                       if (!ar[j].nucleus()->contextMenuName().empty()) {
+                               if (inset_cache.covers(ar[j].nucleus(), x, y)) {
+                                       LYXERR(Debug::MATHED, "Hit inset: "
+                                              << name);
+                                       return ar[j].nucleus();
+                               }
+                       }
+                       InsetMathNest const * imn =
+                               ar[j].nucleus()->asNestInset();
+                       if (imn) {
+                               Inset const * inner =
+                                       mathContextMenu(imn, inset_cache, x, y);
+                               if (inner)
+                                       return inner;
+                       }
+               }
+       }
+       return nullptr;
+}
+
 
 void BufferView::scrollDocView(int const pixels, bool update)
 {
@@ -686,17 +764,21 @@ void BufferView::scrollDocView(int const pixels, bool update)
        if (pixels == 0)
                return;
 
-       // If the offset is less than 2 screen height, prefer to scroll instead.
-       if (abs(pixels) <= 2 * height_) {
+       // If part of the existing paragraphs will remain visible, prefer to
+       // scroll
+       TextMetrics const & tm = textMetrics(&buffer_.text());
+       if (tm.first().second->top() - pixels <= height_
+            &&  tm.last().second->bottom() - pixels >= 0) {
+               LYXERR(Debug::SCROLLING, "small skip");
                d->anchor_ypos_ -= pixels;
-               processUpdateFlags(Update::Force);
+               processUpdateFlags(Update::ForceDraw);
                return;
        }
 
        // cut off at the top
        if (pixels <= d->scrollbarParameters_.min) {
                DocIterator dit = doc_iterator_begin(&buffer_);
-               showCursor(dit, false, update);
+               showCursor(dit, SCROLL_VISIBLE, update);
                LYXERR(Debug::SCROLLING, "scroll to top");
                return;
        }
@@ -705,11 +787,12 @@ void BufferView::scrollDocView(int const pixels, bool update)
        if (pixels >= d->scrollbarParameters_.max) {
                DocIterator dit = doc_iterator_end(&buffer_);
                dit.backwardPos();
-               showCursor(dit, false, update);
+               showCursor(dit, SCROLL_VISIBLE, update);
                LYXERR(Debug::SCROLLING, "scroll to bottom");
                return;
        }
 
+       LYXERR(Debug::SCROLLING, "search paragraph");
        // find paragraph at target position
        int par_pos = d->scrollbarParameters_.min;
        pit_type i = 0;
@@ -730,7 +813,7 @@ void BufferView::scrollDocView(int const pixels, bool update)
        DocIterator dit = doc_iterator_begin(&buffer_);
        dit.pit() = i;
        LYXERR(Debug::SCROLLING, "pixels = " << pixels << " -> scroll to pit " << i);
-       showCursor(dit, false, update);
+       showCursor(dit, SCROLL_VISIBLE, update);
 }
 
 
@@ -872,7 +955,7 @@ bool BufferView::moveToPosition(pit_type bottom_pit, pos_type bottom_pos,
        // restoration is inaccurate. If a bookmark was within an inset,
        // it will be restored to the left of the outmost inset that contains
        // the bookmark.
-       if (bottom_pit < int(buffer_.paragraphs().size())) {
+       if (!success && bottom_pit < int(buffer_.paragraphs().size())) {
                dit = doc_iterator_begin(&buffer_);
 
                dit.pit() = bottom_pit;
@@ -916,45 +999,39 @@ int BufferView::workWidth() const
 
 void BufferView::recenter()
 {
-       showCursor(d->cursor_, true, true);
+       showCursor(d->cursor_, SCROLL_CENTER, true);
 }
 
 
 void BufferView::showCursor()
 {
-       showCursor(d->cursor_, false, true);
+       showCursor(d->cursor_, SCROLL_VISIBLE, true);
 }
 
 
-void BufferView::showCursor(DocIterator const & dit,
-       bool recenter, bool update)
+void BufferView::showCursor(DocIterator const & dit, ScrollType how,
+       bool update)
 {
-       if (scrollToCursor(dit, recenter) && update)
+       if (scrollToCursor(dit, how) && update)
                processUpdateFlags(Update::Force);
 }
 
 
-void BufferView::scrollToCursor()
+bool BufferView::scrollToCursor(DocIterator const & dit, ScrollType how)
 {
-       if (scrollToCursor(d->cursor_, false))
-               processUpdateFlags(Update::Force);
-}
-
-
-bool BufferView::scrollToCursor(DocIterator const & dit, bool const recenter)
-{
-       // We are not properly started yet, delay until resizing is
-       // done.
+       // We are not properly started yet, delay until resizing is done.
        if (height_ == 0)
                return false;
 
-       if (recenter)
-         LYXERR(Debug::SCROLLING, "recentering and scrolling to cursor");
+       if (how == SCROLL_CENTER)
+               LYXERR(Debug::SCROLLING, "Centering cursor in workarea");
+       else if (how == SCROLL_TOP)
+               LYXERR(Debug::SCROLLING, "Setting cursor to top of workarea");
        else
-         LYXERR(Debug::SCROLLING, "scrolling to cursor");
+               LYXERR(Debug::SCROLLING, "Making sure cursor is visible in workarea");
 
        CursorSlice const & bot = dit.bottom();
-       TextMetrics & tm = d->text_metrics_[bot.text()];
+       TextMetrics & tm = textMetrics(bot.text());
 
        pos_type const max_pit = pos_type(bot.text()->paragraphs().size() - 1);
        pos_type bot_pit = bot.pit();
@@ -970,24 +1047,23 @@ bool BufferView::scrollToCursor(DocIterator const & dit, bool const recenter)
        else if (bot_pit == tm.last().first + 1)
                tm.newParMetricsDown();
 
-       if (tm.contains(bot_pit)) {
+       if (tm.contains(bot_pit) && how == SCROLL_VISIBLE) {
                ParagraphMetrics const & pm = tm.parMetrics(bot_pit);
                LBUFERR(!pm.rows().empty());
                // FIXME: smooth scrolling doesn't work in mathed.
                CursorSlice const & cs = dit.innerTextSlice();
-               int offset = coordOffset(dit).y_;
-               int ypos = pm.position() + offset;
+               int const ypos = pm.position() + coordOffset(dit).y_;
+               ParagraphMetrics const & inner_pm =
+                       textMetrics(cs.text()).parMetrics(cs.pit());
                Dimension const & row_dim =
-                       pm.getRow(cs.pos(), dit.boundary()).dim();
+                       inner_pm.getRow(cs.pos(), dit.boundary()).dim();
                int scrolled = 0;
-               if (recenter)
-                       scrolled = scroll(ypos - height_/2);
 
                // We try to visualize the whole row, if the row height is larger than
                // the screen height, we scroll to a heuristic value of height_ / 4.
                // FIXME: This heuristic value should be replaced by a recursive search
                // for a row in the inset that can be visualized completely.
-               else if (row_dim.height() > height_) {
+               if (row_dim.height() > height_) {
                        if (ypos < defaultRowHeight())
                                scrolled = scroll(ypos - height_ / 4);
                        else if (ypos > height_ - defaultRowHeight())
@@ -997,13 +1073,13 @@ bool BufferView::scrollToCursor(DocIterator const & dit, bool const recenter)
                // If the top part of the row falls of the screen, we scroll
                // up to align the top of the row with the top of the screen.
                else if (ypos - row_dim.ascent() < 0 && ypos < height_) {
-                       int ynew = row_dim.ascent();
+                       int const ynew = row_dim.ascent();
                        scrolled = scrollUp(ynew - ypos);
                }
 
                // If the bottom of the row falls of the screen, we scroll down.
                else if (ypos + row_dim.descent() > height_ && ypos > 0) {
-                       int ynew = height_ - row_dim.descent();
+                       int const ynew = height_ - row_dim.descent();
                        scrolled = scrollDown(ypos - ynew);
                }
 
@@ -1016,26 +1092,26 @@ bool BufferView::scrollToCursor(DocIterator const & dit, bool const recenter)
                d->inlineCompletionPos_ = DocIterator();
 
        tm.redoParagraph(bot_pit);
-       ParagraphMetrics const & pm = tm.parMetrics(bot_pit);
-       int offset = coordOffset(dit).y_;
-
+       int const offset = coordOffset(dit).y_;
+       pit_type const old_pit = d->anchor_pit_;
        d->anchor_pit_ = bot_pit;
+
        CursorSlice const & cs = dit.innerTextSlice();
+       ParagraphMetrics const & inner_pm =
+               textMetrics(cs.text()).parMetrics(cs.pit());
        Dimension const & row_dim =
-               pm.getRow(cs.pos(), dit.boundary()).dim();
-
-       if (recenter)
-               d->anchor_ypos_ = height_/2;
-       else if (d->anchor_pit_ == 0)
-               d->anchor_ypos_ = offset + pm.ascent();
-       else if (d->anchor_pit_ == max_pit)
-               d->anchor_ypos_ = height_ - offset - row_dim.descent();
+               inner_pm.getRow(cs.pos(), dit.boundary()).dim();
+
+       int const old_ypos = d->anchor_ypos_;
+       d->anchor_ypos_ = - offset + row_dim.ascent();
+       if (how == SCROLL_CENTER)
+               d->anchor_ypos_ += height_/2 - row_dim.height() / 2;
        else if (offset > height_)
                d->anchor_ypos_ = height_ - offset - defaultRowHeight();
        else
                d->anchor_ypos_ = defaultRowHeight() * 2;
 
-       return true;
+       return d->anchor_ypos_ != old_ypos || d->anchor_pit_ != old_pit;
 }
 
 
@@ -1049,8 +1125,6 @@ void BufferView::makeDocumentClass()
 
 void BufferView::updateDocumentClass(DocumentClassConstPtr olddc)
 {
-       message(_("Converting document to new document class..."));
-
        StableDocIterator backcur(d->cursor_);
        ErrorList & el = buffer_.errorList("Class Switch");
        cap::switchBetweenClasses(
@@ -1178,6 +1252,7 @@ bool BufferView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
        case LFUN_SCREEN_SHOW_CURSOR:
        case LFUN_BIBTEX_DATABASE_ADD:
        case LFUN_BIBTEX_DATABASE_DEL:
+       case LFUN_BIBTEX_DATABASE_LIST:
        case LFUN_STATISTICS:
        case LFUN_KEYMAP_OFF:
        case LFUN_KEYMAP_PRIMARY:
@@ -1202,7 +1277,8 @@ bool BufferView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
 
        case LFUN_LABEL_GOTO:
                flag.setEnabled(!cmd.argument().empty()
-                   || getInsetByCode<InsetRef>(cur, REF_CODE));
+                   || getInsetByCode<InsetRef>(cur, REF_CODE)
+                   || getInsetByCode<InsetMathRef>(cur, MATH_REF_CODE));
                break;
 
        case LFUN_CHANGES_MERGE:
@@ -1274,6 +1350,17 @@ bool BufferView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
                flag.setEnabled(cur.selection());
                break;
 
+       case LFUN_STATISTICS_REFERENCE_CLAMP: {
+               // disable optitem reset if clamp not used
+               if  (cmd.argument() == "reset" && d->stats_ref_value_c_ == 0) {
+                               flag.setEnabled(false);
+                               break;
+               }
+               flag.setEnabled(true);
+               break;
+
+       }
+
        default:
                return false;
        }
@@ -1440,6 +1527,7 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
                else {
                        dr.screenUpdate(Update::Force | Update::FitCursor);
                        dr.forceBufferUpdate();
+                       resetInlineCompletionPos();
                        if (buffer().params().citeEngine() != engine ||
                            buffer().params().citeEngineType() != enginetype)
                                buffer().invalidateCiteLabels();
@@ -1460,6 +1548,7 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
                else {
                        dr.screenUpdate(Update::Force | Update::FitCursor);
                        dr.forceBufferUpdate();
+                       resetInlineCompletionPos();
                        if (buffer().params().citeEngine() != engine ||
                            buffer().params().citeEngineType() != enginetype)
                                buffer().invalidateCiteLabels();
@@ -1493,6 +1582,12 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
                        // eventually call LFUN_PARAGRAPH_GOTO, but it seems best
                        // to have it here.
                        dr.screenUpdate(Update::Force | Update::FitCursor);
+               } else {
+                       InsetMathRef * minset =
+                               getInsetByCode<InsetMathRef>(cur, MATH_REF_CODE);
+                       if (minset)
+                               lyx::dispatch(FuncRequest(LFUN_LABEL_GOTO,
+                                                       minset->getTarget()));
                }
                break;
        }
@@ -1532,8 +1627,8 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
                                        success = setCursorFromEntries({id, pos},
                                                                       {id_end, pos_end});
                                }
-                               if (success)
-                                       dr.screenUpdate(Update::Force | Update::FitCursor);
+                               if (success && scrollToCursor(d->cursor_, SCROLL_TOP))
+                                               dr.screenUpdate(Update::Force);
                        } else {
                                // Switch to other buffer view and resend cmd
                                lyx::dispatch(FuncRequest(
@@ -1546,19 +1641,13 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
        }
 
        case LFUN_NOTE_NEXT:
-               gotoInset(this, { NOTE_CODE }, false);
-               // FIXME: if SinglePar is changed to act on the inner
-               // paragraph, this will not be OK anymore. The update is
-               // useful for auto-open collapsible insets.
-               dr.screenUpdate(Update::SinglePar | Update::FitCursor);
+               if (gotoInset(this, { NOTE_CODE }, false))
+                       dr.screenUpdate(Update::Force);
                break;
 
        case LFUN_REFERENCE_NEXT: {
-               gotoInset(this, { LABEL_CODE, REF_CODE }, true);
-               // FIXME: if SinglePar is changed to act on the inner
-               // paragraph, this will not be OK anymore. The update is
-               // useful for auto-open collapsible insets.
-               dr.screenUpdate(Update::SinglePar | Update::FitCursor);
+               if (gotoInset(this, { LABEL_CODE, REF_CODE }, true))
+                       dr.screenUpdate(Update::Force);
                break;
        }
 
@@ -1676,6 +1765,8 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
                }
                if (cur.selection())
                        pattern = cur.selectionAsString(false);
+               else if (!cur.inTexted())
+                       break; // not suitable for selectWord at cursor
                else {
                        pos_type spos = cur.pos();
                        cur.innerText()->selectWord(cur, WHOLE_WORD);
@@ -1712,6 +1803,84 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
                break;
        }
 
+       case LFUN_INDEX_TAG_ALL: {
+               if (cur.pos() == 0)
+                       // nothing precedes
+                       break;
+
+               Inset * ins = cur.nextInset();
+               if (!ins || ins->lyxCode() != INDEX_CODE)
+                       // not at index inset
+                       break;
+
+               // clone the index inset
+               InsetIndex * cins =
+                       new InsetIndex(static_cast<InsetIndex &>(*cur.nextInset()));
+               // In order to avoid duplication, we compare the
+               // LaTeX output if we find another index inset after
+               // the word
+               odocstringstream oilatex;
+               otexstream oits(oilatex);
+               OutputParams rp(&cur.buffer()->params().encoding());
+               ins->latex(oits, rp);
+               cap::copyInsetToTemp(cur, cins);
+
+               // move backwards into preceding word
+               // skip over other index insets
+               cur.backwardPosIgnoreCollapsed();
+               while (true) {
+                       if (cur.inset().lyxCode() == INDEX_CODE)
+                               cur.pop_back();
+                       else if (cur.prevInset() && cur.prevInset()->lyxCode() == INDEX_CODE)
+                               cur.backwardPosIgnoreCollapsed();
+                       else
+                               break;
+               }
+               if (!cur.inTexted()) {
+                       // Nothing to do here.
+                       setCursorFromInset(ins);
+                       break;
+               }
+               // Get word or selection
+               cur.text()->selectWord(cur, WHOLE_WORD);
+               docstring const searched_string = cur.selectionAsString(false);
+               if (searched_string.empty())
+                       break;
+               // Start from the beginning
+               lyx::dispatch(FuncRequest(LFUN_BUFFER_BEGIN));
+               while (findOne(this, searched_string,
+                              false,// case sensitive
+                              true,// match whole word only
+                              true,// forward
+                              false,//find deleted
+                              false,//check wrap
+                              false,// auto-wrap
+                              false,// instant
+                              false// only selection
+                              )) {
+                       cur.clearSelection();
+                       Inset * ains = cur.nextInset();
+                       if (ains && ains->lyxCode() == INDEX_CODE) {
+                               // We have an index inset.
+                               // Check whether it has the same
+                               // LaTeX content and move on if so.
+                               odocstringstream filatex;
+                               otexstream fits(filatex);
+                               ains->latex(fits, rp);
+                               if (oilatex.str() == filatex.str())
+                                       continue;
+                       }
+                       // Paste the inset and possibly continue
+                       cap::pasteFromTemp(cursor(), cursor().buffer()->errorList("Paste"));
+               }
+               // Go back to start position.
+               setCursorFromInset(ins);
+               dr.screenUpdate(cur.result().screenUpdate());
+               if (cur.result().needBufferUpdate())
+                       dr.forceBufferUpdate();
+               break;
+       }
+
        case LFUN_MARK_OFF:
                cur.clearSelection();
                dr.setMessage(from_utf8(N_("Mark off")));
@@ -1806,6 +1975,25 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
                break;
        }
 
+       case LFUN_BIBTEX_DATABASE_LIST: {
+               docstring_list const & files = buffer_.getBibfiles();
+               bool first = true;
+               docstring result;
+               char const separator(os::path_separator());
+               for (auto const & file : files) {
+                       if (first)
+                               first = false;
+                       else
+                               result += separator;
+
+                       FileName const fn = buffer_.getBibfilePath(file);
+                       string const path = fn.realPath();
+                       result += from_utf8(os::external_path(path));
+               }
+               dr.setMessage(result);
+               break;
+       }
+
        case LFUN_STATISTICS: {
                DocIterator from, to;
                if (cur.selection()) {
@@ -1831,21 +2019,38 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
                        message += _("One word");
                message += "\n";
                if (chars_blanks != 1)
-                       message += bformat(_("%1$d characters (including blanks)"),
-                                         chars_blanks);
+                       message += bformat(_("%1$d characters"), chars_blanks);
                else
-                       message += _("One character (including blanks)");
+                       message += _("One character");
                message += "\n";
                if (chars != 1)
-                       message += bformat(_("%1$d characters (excluding blanks)"),
-                                         chars);
+                       message += bformat(_("%1$d characters (no blanks)"), chars);
                else
-                       message += _("One character (excluding blanks)");
+                       message += _("One character (no blanks)");
 
                Alert::information(_("Statistics"), message);
        }
                break;
 
+       case LFUN_STATISTICS_REFERENCE_CLAMP: {
+               d->stats_update_trigger_ = true;
+               if  (cmd.argument() == "reset") {
+                       d->stats_ref_value_w_ = d->stats_ref_value_c_ = d->stats_ref_value_nb_ = 0;
+                       break;
+               }
+
+               DocIterator from, to;
+               from = doc_iterator_begin(&buffer_);
+               to = doc_iterator_end(&buffer_);
+               buffer_.updateStatistics(from, to);
+
+               d->stats_ref_value_w_ = buffer_.wordCount();
+               d->stats_ref_value_c_ = buffer_.charCount(true);
+               d->stats_ref_value_nb_ = buffer_.charCount(false);
+               break;
+       }
+
+
        case LFUN_SCREEN_UP:
        case LFUN_SCREEN_DOWN: {
                Point p = getPos(cur);
@@ -1866,7 +2071,8 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
                cur.setCursor(doc_iterator_begin(cur.buffer()));
                cur.selHandle(false);
                // Force an immediate computation of metrics because we need it below
-               updateMetrics();
+               if (scrolled)
+                       processUpdateFlags(Update::Force);
 
                d->text_metrics_[&buffer_.text()].editXY(cur, p.x_, p.y_,
                        true, act == LFUN_SCREEN_UP);
@@ -2022,21 +2228,22 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
                // an arbitrary number to limit number of iterations
                const int max_iter = 100000;
                int iterations = 0;
-               Cursor & curs = d->cursor_;
-               Cursor const savecur = curs;
-               curs.reset();
-               if (!curs.nextInset())
-                       curs.forwardInset();
-               curs.beginUndoGroup();
-               while(curs && iterations < max_iter) {
-                       Inset * const ins = curs.nextInset();
+               Cursor & bvcur = d->cursor_;
+               Cursor const savecur = bvcur;
+               bvcur.reset();
+               if (!bvcur.nextInset())
+                       bvcur.forwardInset();
+               bvcur.beginUndoGroup();
+               while(bvcur && iterations < max_iter) {
+                       Inset * const ins = bvcur.nextInset();
                        if (!ins)
                                break;
                        docstring insname = ins->layoutName();
                        while (!insname.empty()) {
                                if (insname == name || name == from_utf8("*")) {
-                                       curs.recordUndo();
                                        lyx::dispatch(fr, dr);
+                                       // we do not want to remember selection here
+                                       bvcur.clearSelection();
                                        ++iterations;
                                        break;
                                }
@@ -2046,11 +2253,11 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
                                insname = insname.substr(0, i);
                        }
                        // if we did not delete the inset, skip it
-                       if (!curs.nextInset() || curs.nextInset() == ins)
-                               curs.forwardInset();
+                       if (!bvcur.nextInset() || bvcur.nextInset() == ins)
+                               bvcur.forwardInset();
                }
-               curs = savecur;
-               curs.fixIfBroken();
+               bvcur = savecur;
+               bvcur.fixIfBroken();
                /** This is a dummy undo record only to remember the cursor
                 * that has just been set; this will be used on a redo action
                 * (see ticket #10097)
@@ -2058,8 +2265,8 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
                 * FIXME: a better fix would be to have a way to set the
                 * cursor value directly, but I am not sure it is worth it.
                 */
-               curs.recordUndo();
-               curs.endUndoGroup();
+               bvcur.recordUndo();
+               bvcur.endUndoGroup();
                dr.screenUpdate(Update::Force);
                dr.forceBufferUpdate();
 
@@ -2159,6 +2366,28 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
                string icstr = InsetCommand::params2string(icp);
                FuncRequest fr(LFUN_INSET_INSERT, icstr);
                lyx::dispatch(fr);
+
+               // if the request comes from the LyX server, then we
+               // return a list of the undefined keys, in case some
+               // action could be taken.
+               if (cmd.origin() != FuncRequest::LYXSERVER)
+                       break;
+
+               vector<docstring> keys = getVectorFromString(from_utf8(arg));
+               vector<docstring>::iterator it = keys.begin();
+               vector<docstring>::const_iterator end = keys.end();
+
+               BiblioInfo const & bibInfo = buffer_.masterBibInfo();
+               const BiblioInfo::const_iterator bibEnd = bibInfo.end();
+               while (it != end) {
+                       if (bibInfo.find(*it) != bibEnd) {
+                               it = keys.erase(it);
+                               end = keys.end();
+                       } else
+                               ++it;
+               }
+               dr.setMessage(getStringFromVector(keys));
+
                break;
        }
 
@@ -2214,6 +2443,13 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
        }
 
        case LFUN_COPY:
+               // With multi-cell table content, we pass down to the inset
+               if (cur.inTexted() && cur.selection()
+                   && cur.selectionBegin().idx() != cur.selectionEnd().idx()) {
+                       buffer_.dispatch(cmd, dr);
+                       dispatched = dr.dispatched();
+                       break;
+               }
                cap::copySelection(cur);
                cur.message(_("Copy"));
                break;
@@ -2285,14 +2521,19 @@ void BufferView::clearSelection()
 
 void BufferView::resize(int width, int height)
 {
-       // Update from work area
-       width_ = width;
        height_ = height;
+       // Update metrics only if width has changed
+       if (width != width_) {
+               width_ = width;
 
-       // Clear the paragraph height cache.
-       d->par_height_.clear();
-       // Redo the metrics.
-       updateMetrics();
+               // Clear the paragraph height cache.
+               d->par_height_.clear();
+               // Redo the metrics.
+               updateMetrics(true);
+       }
+       // metrics is OK, full drawing is necessary now
+       d->update_flags_ = (d->update_flags_ & ~Update::Force) | Update::ForceDraw;
+       d->update_strategy_ = FullScreenUpdate;
 }
 
 
@@ -2326,12 +2567,50 @@ Inset const * BufferView::getCoveringInset(Text const & text,
 }
 
 
+Inset const * BufferView::clickableMathInset(InsetMathNest const * inset,
+               CoordCache::Insets const & inset_cache, int x, int y) const
+{
+       for (size_t i = 0; i < inset->nargs(); ++i) {
+               MathData const & ar = inset->cell(i);
+               for (size_t j = 0; j < ar.size(); ++j) {
+                       string const name = lyxerr.debugging(Debug::MATHED)
+                               ? insetName(ar[j].nucleus()->lyxCode())
+                               : string();
+                       LYXERR(Debug::MATHED, "Checking inset: " << name);
+                       if (ar[j].nucleus()->clickable(*this, x, y)) {
+                               if (inset_cache.covers(ar[j].nucleus(), x, y)) {
+                                       LYXERR(Debug::MATHED, "Clickable inset: "
+                                              << name);
+                                       return ar[j].nucleus();
+                               }
+                       }
+                       InsetMathNest const * imn =
+                               ar[j].nucleus()->asNestInset();
+                       if (imn) {
+                               Inset const * inner =
+                                       clickableMathInset(imn, inset_cache, x, y);
+                               if (inner)
+                                       return inner;
+                       }
+               }
+       }
+       return nullptr;
+}
+
+
 void BufferView::updateHoveredInset() const
 {
        // Get inset under mouse, if there is one.
        int const x = d->mouse_position_cache_.x_;
        int const y = d->mouse_position_cache_.y_;
        Inset const * covering_inset = getCoveringInset(buffer_.text(), x, y);
+       if (covering_inset && covering_inset->asInsetMath()) {
+               Inset const * inner_inset = clickableMathInset(
+                               covering_inset->asInsetMath()->asNestInset(),
+                               coordCache().getInsets(), x, y);
+               if (inner_inset)
+                       covering_inset = inner_inset;
+       }
 
        d->clickable_inset_ = covering_inset && covering_inset->clickable(*this, x, y);
 
@@ -2378,19 +2657,41 @@ void BufferView::clearLastInset(Inset * inset) const
 }
 
 
+bool BufferView::mouseSelecting() const
+{
+       return d->mouse_selecting_;
+}
+
+
+int BufferView::stats_ref_value_w() const
+{
+       return d->stats_ref_value_w_;
+}
+
+
+int BufferView::stats_ref_value_c() const
+{
+       return d->stats_ref_value_c_;
+}
+
+
+int BufferView::stats_ref_value_nb() const
+{
+       return d->stats_ref_value_nb_;
+}
+
+
 void BufferView::mouseEventDispatch(FuncRequest const & cmd0)
 {
        //lyxerr << "[ cmd0 " << cmd0 << "]" << endl;
 
+       if (!ready())
+               return;
+
        // This is only called for mouse related events including
        // LFUN_FILE_OPEN generated by drag-and-drop.
        FuncRequest cmd = cmd0;
 
-       Cursor old = cursor();
-       Cursor cur(*this);
-       cur.push(buffer_.inset());
-       cur.selection(d->cursor_.selection());
-
        // Either the inset under the cursor or the
        // surrounding Text will handle this event.
 
@@ -2400,11 +2701,19 @@ void BufferView::mouseEventDispatch(FuncRequest const & cmd0)
        d->mouse_position_cache_.x_ = cmd.x();
        d->mouse_position_cache_.y_ = cmd.y();
 
+       d->mouse_selecting_ =
+               cmd.action() == LFUN_MOUSE_MOTION && cmd.button() == mouse_button::button1;
+
        if (cmd.action() == LFUN_MOUSE_MOTION && cmd.button() == mouse_button::none) {
                updateHoveredInset();
                return;
        }
 
+       Cursor old = cursor();
+       Cursor cur(*this);
+       cur.push(buffer_.inset());
+       cur.selection(d->cursor_.selection());
+
        // Build temporary cursor.
        Inset * inset = d->text_metrics_[&buffer_.text()].editXY(cur, cmd.x(), cmd.y());
        if (inset) {
@@ -2425,7 +2734,7 @@ void BufferView::mouseEventDispatch(FuncRequest const & cmd0)
        // Put anchor at the same position.
        cur.resetAnchor();
 
-       cur.beginUndoGroup();
+       old.beginUndoGroup();
 
        // Try to dispatch to an non-editable inset near this position
        // via the temp cursor. If the inset wishes to change the real
@@ -2441,13 +2750,13 @@ void BufferView::mouseEventDispatch(FuncRequest const & cmd0)
 
        // Notify left insets
        if (cur != old) {
-               bool badcursor = old.fixIfBroken() | cur.fixIfBroken();
-               badcursor |= notifyCursorLeavesOrEnters(old, cur);
+               bool badcursor = old.fixIfBroken() || cur.fixIfBroken();
+               badcursor = badcursor || notifyCursorLeavesOrEnters(old, cur);
                if (badcursor)
                        cursor().fixIfBroken();
        }
 
-       cur.endUndoGroup();
+       old.endUndoGroup();
 
        // Do we have a selection?
        theSelection().haveSelection(cursor().selection());
@@ -2486,7 +2795,7 @@ int BufferView::scrollDown(int pixels)
        int const ymax = height_ + pixels;
        while (true) {
                pair<pit_type, ParagraphMetrics const *> last = tm.last();
-               int bottom_pos = last.second->position() + last.second->descent();
+               int bottom_pos = last.second->bottom();
                if (lyxrc.scroll_below_document)
                        bottom_pos += height_ - minVisiblePart();
                if (last.first + 1 == int(text->paragraphs().size())) {
@@ -2511,7 +2820,7 @@ int BufferView::scrollUp(int pixels)
        int ymin = - pixels;
        while (true) {
                pair<pit_type, ParagraphMetrics const *> first = tm.first();
-               int top_pos = first.second->position() - first.second->ascent();
+               int top_pos = first.second->top();
                if (first.first == 0) {
                        if (top_pos >= 0)
                                return 0;
@@ -2531,7 +2840,7 @@ bool BufferView::setCursorFromRow(int row)
 {
        TexRow::TextEntry start, end;
        tie(start,end) = buffer_.texrow().getEntriesFromRow(row);
-       LYXERR(Debug::LATEX,
+       LYXERR(Debug::OUTFILE,
               "setCursorFromRow: for row " << row << ", TexRow has found "
               "start (id=" << start.id << ",pos=" << start.pos << "), "
               "end (id=" << end.id << ",pos=" << end.pos << ")");
@@ -2753,6 +3062,25 @@ void BufferView::putSelectionAt(DocIterator const & cur,
 }
 
 
+void BufferView::setSelection(DocIterator const & from,
+                             DocIterator const & to)
+{
+       if (from.pit() != to.pit()) {
+               // there are multiple paragraphs in selection
+               cursor().setCursor(from);
+               cursor().clearSelection();
+               cursor().selection(true);
+               cursor().setCursor(to);
+               cursor().selection(true);
+       } else {
+               // only single paragraph
+               int const size = to.pos() - from.pos();
+               putSelectionAt(from, size, false);
+       }
+       processUpdateFlags(Update::Force | Update::FitCursor);
+}
+
+
 bool BufferView::selectIfEmpty(DocIterator & cur)
 {
        if ((cur.inTexted() && !cur.paragraph().empty())
@@ -2804,24 +3132,36 @@ Cursor const & BufferView::cursor() const
 
 bool BufferView::singleParUpdate()
 {
-       Text & buftext = buffer_.text();
-       pit_type const bottom_pit = d->cursor_.bottom().pit();
-       TextMetrics & tm = textMetrics(&buftext);
-       Dimension const old_dim = tm.parMetrics(bottom_pit).dim();
+       CursorSlice const & its = d->cursor_.innerTextSlice();
+       pit_type const pit = its.pit();
+       TextMetrics & tm = textMetrics(its.text());
+       Dimension const old_dim = tm.parMetrics(pit).dim();
 
        // make sure inline completion pointer is ok
        if (d->inlineCompletionPos_.fixIfBroken())
                d->inlineCompletionPos_ = DocIterator();
 
-       // In Single Paragraph mode, rebreak only
-       // the (main text, not inset!) paragraph containing the cursor.
-       // (if this paragraph contains insets etc., rebreaking will
-       // recursively descend)
-       tm.redoParagraph(bottom_pit);
-       ParagraphMetrics & pm = tm.parMetrics(bottom_pit);
-       if (pm.height() != old_dim.height()) {
-               // Paragraph height has changed so we cannot proceed to
-               // the singlePar optimisation.
+       /* Try to rebreak only the paragraph containing the cursor (if
+        * this paragraph contains insets etc., rebreaking will
+        * recursively descend). We need a full redraw if either
+        * 1/ the height has changed
+        * or
+        * 2/ the width has changed and it was equal to the textmetrics
+        *    width; the goal is to catch the case of a one-row inset that
+        *    grows with its contents, but optimize the case of typing at
+        *    the end of a mmultiple-row paragraph.
+        *
+        * NOTE: if only the height has changed, then it should be
+        *   possible to update all metrics at minimal cost. However,
+        *   since this is risky, we do not try that right now.
+        */
+       tm.redoParagraph(pit);
+       ParagraphMetrics & pm = tm.parMetrics(pit);
+       if (pm.height() != old_dim.height()
+               || (pm.width() != old_dim.width() && old_dim.width() == tm.width())) {
+               // Paragraph height or width has changed so we cannot proceed
+               // to the singlePar optimisation.
+               LYXERR(Debug::PAINTING, "SinglePar optimization failed.");
                return false;
        }
        // Since position() points to the baseline of the first row, we
@@ -2829,39 +3169,43 @@ bool BufferView::singleParUpdate()
        // the height does not change but the ascent does.
        pm.setPosition(pm.position() - old_dim.ascent() + pm.ascent());
 
-       tm.updatePosCache(bottom_pit);
+       tm.updatePosCache(pit);
 
-       LYXERR(Debug::PAINTING, "\ny1: " << pm.position() - pm.ascent()
-               << " y2: " << pm.position() + pm.descent()
-               << " pit: " << bottom_pit
-               << " singlepar: 1");
+       LYXERR(Debug::PAINTING, "\ny1: " << pm.top() << " y2: " << pm.bottom()
+               << " pit: " << pit << " singlepar: 1");
        return true;
 }
 
 
 void BufferView::updateMetrics()
 {
-       updateMetrics(d->update_flags_);
+       updateMetrics(true);
+       // metrics is done, full drawing is necessary now
+       d->update_flags_ = (d->update_flags_ & ~Update::Force) | Update::ForceDraw;
        d->update_strategy_ = FullScreenUpdate;
 }
 
 
-void BufferView::updateMetrics(Update::flags & update_flags)
+void BufferView::updateMetrics(bool force)
 {
-       if (height_ == 0 || width_ == 0)
+       if (!ready())
                return;
 
+       //LYXERR0("updateMetrics " << _v_(force));
+
        Text & buftext = buffer_.text();
-       pit_type const npit = int(buftext.paragraphs().size());
+       pit_type const lastpit = int(buftext.paragraphs().size()) - 1;
 
-       // Clear out the position cache in case of full screen redraw,
-       d->coord_cache_.clear();
-       d->math_rows_.clear();
+       if (force) {
+               // Clear out the position cache in case of full screen redraw,
+               d->coord_cache_.clear();
+               d->math_rows_.clear();
 
-       // Clear out paragraph metrics to avoid having invalid metrics
-       // in the cache from paragraphs not relayouted below
-       // The complete text metrics will be redone.
-       d->text_metrics_.clear();
+               // Clear out paragraph metrics to avoid having invalid metrics
+               // in the cache from paragraphs not relayouted below. The
+               // complete text metrics will be redone.
+               d->text_metrics_.clear();
+       }
 
        TextMetrics & tm = textMetrics(&buftext);
 
@@ -2869,71 +3213,55 @@ void BufferView::updateMetrics(Update::flags & update_flags)
        if (d->inlineCompletionPos_.fixIfBroken())
                d->inlineCompletionPos_ = DocIterator();
 
-       if (d->anchor_pit_ >= npit)
+       if (d->anchor_pit_ > lastpit)
                // The anchor pit must have been deleted...
-               d->anchor_pit_ = npit - 1;
+               d->anchor_pit_ = lastpit;
 
-       // Rebreak anchor paragraph.
-       tm.redoParagraph(d->anchor_pit_);
-       ParagraphMetrics & anchor_pm = tm.parMetrics(d->anchor_pit_);
+       // Update metrics around the anchor
+       tm.updateMetrics(d->anchor_pit_, d->anchor_ypos_, height_);
 
-       // position anchor
-       if (d->anchor_pit_ == 0) {
-               int scrollRange = d->scrollbarParameters_.max - d->scrollbarParameters_.min;
+       // Check that the end of the document is not too high
+       int const min_visible = lyxrc.scroll_below_document ? minVisiblePart() : height_;
+       if (tm.last().first == lastpit && tm.last().second->bottom() < min_visible) {
+               d->anchor_ypos_ += min_visible - tm.last().second->bottom();
+               LYXERR(Debug::SCROLLING, "Too high, adjusting anchor ypos to " << d->anchor_ypos_);
+               tm.updateMetrics(d->anchor_pit_, d->anchor_ypos_, height_);
+       }
 
-               // Complete buffer visible? Then it's easy.
-               if (scrollRange == 0)
-                       d->anchor_ypos_ = anchor_pm.ascent();
-               else {
-                       // avoid empty space above the first row
-                       d->anchor_ypos_ = min(d->anchor_ypos_, anchor_pm.ascent());
-               }
-       }
-       anchor_pm.setPosition(d->anchor_ypos_);
-       tm.updatePosCache(d->anchor_pit_);
-
-       LYXERR(Debug::PAINTING, "metrics: "
-               << " anchor pit = " << d->anchor_pit_
-               << " anchor ypos = " << d->anchor_ypos_);
-
-       // Redo paragraphs above anchor if necessary.
-       int y1 = d->anchor_ypos_ - anchor_pm.ascent();
-       // We are now just above the anchor paragraph.
-       pit_type pit1 = d->anchor_pit_ - 1;
-       for (; pit1 >= 0 && y1 >= 0; --pit1) {
-               tm.redoParagraph(pit1);
-               ParagraphMetrics & pm = tm.parMetrics(pit1);
-               y1 -= pm.descent();
-               // Save the paragraph position in the cache.
-               pm.setPosition(y1);
-               tm.updatePosCache(pit1);
-               y1 -= pm.ascent();
-       }
-
-       // Redo paragraphs below the anchor if necessary.
-       int y2 = d->anchor_ypos_ + anchor_pm.descent();
-       // We are now just below the anchor paragraph.
-       pit_type pit2 = d->anchor_pit_ + 1;
-       for (; pit2 < npit && y2 <= height_; ++pit2) {
-               tm.redoParagraph(pit2);
-               ParagraphMetrics & pm = tm.parMetrics(pit2);
-               y2 += pm.ascent();
-               // Save the paragraph position in the cache.
-               pm.setPosition(y2);
-               tm.updatePosCache(pit2);
-               y2 += pm.descent();
-       }
-
-       LYXERR(Debug::PAINTING, "Metrics: "
-               << " anchor pit = " << d->anchor_pit_
-               << " anchor ypos = " << d->anchor_ypos_
-               << " y1 = " << y1
-               << " y2 = " << y2
-               << " pit1 = " << pit1
-               << " pit2 = " << pit2);
+       // Check that the start of the document is not too low
+       if (tm.first().first == 0 && tm.first().second->top() > 0) {
+               d->anchor_ypos_ -= tm.first().second->top();
+               LYXERR(Debug::SCROLLING, "Too low, adjusting anchor ypos to " << d->anchor_ypos_);
+               tm.updateMetrics(d->anchor_pit_, d->anchor_ypos_, height_);
+       }
 
-       // metrics is done, full drawing is necessary now
-       update_flags = (update_flags & ~Update::Force) | Update::ForceDraw;
+       /* FIXME: do we want that? It avoids potential issues with old
+        * paragraphs that should have been recomputed but have not, at
+        * the price of potential extra metrics computaiton. I do not
+        * think that the performance gain is high, so that for now the
+        * extra paragraphs are removed
+        */
+       // Remove paragraphs that are outside of screen
+       while(tm.first().second->bottom() <= 0) {
+               //LYXERR0("Forget pit: " << tm.first().first);
+               tm.forget(tm.first().first);
+       }
+       while(tm.last().second->top() > height_) {
+               //LYXERR0("Forget pit: " << tm.first().first);
+               tm.forget(tm.last().first);
+       }
+
+       /* FIXME: if paragraphs outside of the screen are not removed
+        * above, one has to search for the first visible one here */
+       // Normalize anchor for next time
+       if (d->anchor_pit_ != tm.first().first
+           || d->anchor_ypos_ != tm.first().second->position()) {
+               LYXERR(Debug::PAINTING, __func__ << ": Found new anchor pit = " << tm.first().first
+                               << "  anchor ypos = " << tm.first().second->position()
+                               << " (was " << d->anchor_pit_ << ", " << d->anchor_ypos_ << ")");
+               d->anchor_pit_ = tm.first().first;
+               d->anchor_ypos_ = tm.first().second->position();
+       }
 
        // Now update the positions of insets in the cache.
        updatePosCache();
@@ -2977,6 +3305,7 @@ void BufferView::insertLyXFile(FileName const & fname, bool const ignorelang)
                        // set main language of imported file to context language
                        buf.changeLanguage(buf.language(), d->cursor_.getFont().language());
                buffer_.undo().recordUndo(d->cursor_);
+               cap::replaceSelection(d->cursor_);
                cap::pasteParagraphList(d->cursor_, pars,
                                        buf.params().documentClassPtr(),
                                        buf.params().authors(), el);
@@ -3096,13 +3425,15 @@ bool BufferView::paragraphVisible(DocIterator const & dit) const
 void BufferView::caretPosAndDim(Point & p, Dimension & dim) const
 {
        Cursor const & cur = cursor();
-       if (cur.inMathed()) {
+       if (cur.inMathed() && hasMathRow(&cur.cell())) {
                MathRow const & mrow = mathRow(&cur.cell());
                dim = mrow.caret_dim;
        } else {
                Font const font = cur.real_current_font;
                frontend::FontMetrics const & fm = theFontMetrics(font);
-               dim.wid = fm.lineWidth();
+               // lineWidth() can be 0 to mean 'thin line' on HiDpi, but the
+               // caret drawing code is not prepared for that.
+               dim.wid = max(fm.lineWidth(), 1);
                dim.asc = fm.maxAscent();
                dim.des = fm.maxDescent();
        }
@@ -3137,8 +3468,8 @@ void BufferView::buildCaretGeometry(bool complet)
        bool const slant = fm.italic() && cur.inTexted() && !cur.selection();
        double const slope = slant ? fm.italicSlope() : 0;
        cg.shapes.push_back(
-               {{iround(p.x_ + dim.asc * slope), p.y_},
-                {iround(p.x_ - dim.des * slope), p.y_ + dim.height()},
+               {{iround(p.x_ + dim.asc * slope),                 p.y_},
+                {iround(p.x_ - dim.des * slope),                 p.y_ + dim.height()},
                 {iround(p.x_ + dir * dim.wid - dim.des * slope), p.y_ + dim.height()},
                 {iround(p.x_ + dir * dim.wid + dim.asc * slope), p.y_}}
                );
@@ -3151,10 +3482,10 @@ void BufferView::buildCaretGeometry(bool complet)
                int const xx = iround(p.x_ - dim.des * slope);
                int const yy = p.y_ + dim.height();
                cg.shapes.push_back(
-                       {{xx, yy - dim.wid},
+                       {{xx,                            yy - dim.wid},
                         {xx + dir * (dim.wid + lx - 1), yy - dim.wid},
                         {xx + dir * (dim.wid + lx - 1), yy},
-                        {xx, yy}}
+                        {xx,                            yy}}
                        );
        }
 
@@ -3167,12 +3498,12 @@ void BufferView::buildCaretGeometry(bool complet)
                // starting position x
                int const xx = p.x_ + dir * dim.wid + sx;
                cg.shapes.push_back(
-                       {{xx, m - d},
-                        {xx + dir * d, m},
-                        {xx, m + d},
-                        {xx, m + d - dim.wid},
+                       {{xx,                     m - d},
+                        {xx + dir * d,           m},
+                        {xx,                     m + d},
+                        {xx,                     m + d - dim.wid},
                         {xx + dir * d - dim.wid, m},
-                        {xx, m - d + dim.wid}}
+                        {xx,                     m - d + dim.wid}}
                        );
        }
 
@@ -3300,24 +3631,27 @@ void BufferView::checkCursorScrollOffset()
 
        //lyxerr << "cur_x=" << cur_x << ", offset=" << offset << ", row.wid=" << row.width() << ", margin=" << MARGIN << endl;
 
-       if (offset != d->horiz_scroll_offset_)
+       if (offset != d->horiz_scroll_offset_) {
                LYXERR(Debug::PAINTING, "Horiz. scroll offset changed from "
                       << d->horiz_scroll_offset_ << " to " << offset);
-
-       if (d->update_strategy_ == NoScreenUpdate
-           && offset != d->horiz_scroll_offset_) {
-               // FIXME: if one uses SingleParUpdate, then home/end
-               // will not work on long rows. Why?
-               d->update_strategy_ = FullScreenUpdate;
+               row.changed(true);
+               if (d->update_strategy_ == NoScreenUpdate)
+                       d->update_strategy_ = SingleParUpdate;
        }
 
        d->horiz_scroll_offset_ = offset;
 }
 
 
+bool BufferView::busy() const
+{
+       return buffer().undo().activeUndoGroup();
+}
+
+
 void BufferView::draw(frontend::Painter & pain, bool paint_caret)
 {
-       if (height_ == 0 || width_ == 0)
+       if (!ready())
                return;
        LYXERR(Debug::PAINTING, (pain.isNull() ? "\t\t--- START NODRAW ---"
                                 : "\t\t*** START DRAWING ***"));
@@ -3379,7 +3713,7 @@ void BufferView::draw(frontend::Painter & pain, bool paint_caret)
 
                // and possibly grey out below
                pair<pit_type, ParagraphMetrics const *> lastpm = tm.last();
-               int const y2 = lastpm.second->position() + lastpm.second->descent();
+               int const y2 = lastpm.second->bottom();
 
                if (y2 < height_) {
                        Color color = buffer().isInternal()
@@ -3393,23 +3727,27 @@ void BufferView::draw(frontend::Painter & pain, bool paint_caret)
 
        // The scrollbar needs an update.
        // FIXME: does it always? see ticket #11947.
-       updateScrollbar();
+       updateScrollbarParameters();
 
-       // Normalize anchor for next time
+       // Normalize anchor for next time (in case updateMetrics did not do it yet)
+       // FIXME: is this useful?
        pair<pit_type, ParagraphMetrics const *> firstpm = tm.first();
        pair<pit_type, ParagraphMetrics const *> lastpm = tm.last();
        for (pit_type pit = firstpm.first; pit <= lastpm.first; ++pit) {
                ParagraphMetrics const & pm = tm.parMetrics(pit);
-               if (pm.position() + pm.descent() > 0) {
+               if (pm.bottom() > 0) {
                        if (d->anchor_pit_ != pit
                            || d->anchor_ypos_ != pm.position())
-                               LYXERR(Debug::PAINTING, "Found new anchor pit = " << d->anchor_pit_
-                                      << "  anchor ypos = " << d->anchor_ypos_);
+                               LYXERR0(__func__ << ": Found new anchor pit = " << pit
+                                               << "  anchor ypos = " << pm.position()
+                                               << " (was " << d->anchor_pit_ << ", " << d->anchor_ypos_ << ")"
+                                                  "\nIf you see this message, please report.");
                        d->anchor_pit_ = pit;
                        d->anchor_ypos_ = pm.position();
                        break;
                }
        }
+
        if (!pain.isNull()) {
                // reset the update flags, everything has been done
                d->update_flags_ = Update::None;
@@ -3603,4 +3941,14 @@ bool BufferView::clickableInset() const
        return d->clickable_inset_;
 }
 
+
+bool BufferView::stats_update_trigger()
+{
+       if (d->stats_update_trigger_) {
+               d->stats_update_trigger_ = false;
+               return true;
+       }
+       return false;
+}
+
 } // namespace lyx