X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2FBufferView.cpp;h=bff44cdbf02ae98d4f8d15b789ea911cfa7bfc03;hb=05dd6614c4e3984f2c1ace3b4a59e47421e13f06;hp=95024f5bb6b1bb7a99f8840aeb41fe77e0bee8e6;hpb=2dc84b69d5a040e6343e21606f1c16a7c0957383;p=lyx.git diff --git a/src/BufferView.cpp b/src/BufferView.cpp index 95024f5bb6..bff44cdbf0 100644 --- a/src/BufferView.cpp +++ b/src/BufferView.cpp @@ -18,7 +18,6 @@ #include "BranchList.h" #include "Buffer.h" -#include "buffer_funcs.h" #include "BufferList.h" #include "BufferParams.h" #include "CoordCache.h" @@ -26,67 +25,59 @@ #include "CutAndPaste.h" #include "DispatchResult.h" #include "ErrorList.h" -#include "factory.h" -#include "FloatList.h" #include "FuncRequest.h" #include "FuncStatus.h" #include "Intl.h" -#include "InsetIterator.h" #include "Language.h" -#include "LaTeXFeatures.h" #include "LayoutFile.h" -#include "Length.h" #include "Lexer.h" #include "LyX.h" #include "LyXAction.h" #include "lyxfind.h" -#include "Layout.h" #include "LyXRC.h" #include "MetricsInfo.h" #include "Paragraph.h" -#include "ParagraphParameters.h" -#include "ParIterator.h" -#include "RowPainter.h" #include "Session.h" #include "Text.h" -#include "TextClass.h" #include "TextMetrics.h" #include "TexRow.h" #include "TocBackend.h" -#include "WordLangTuple.h" #include "insets/InsetBibtex.h" #include "insets/InsetCitation.h" #include "insets/InsetCommand.h" // ChangeRefs -#include "insets/InsetExternal.h" #include "insets/InsetGraphics.h" -#include "insets/InsetNote.h" #include "insets/InsetRef.h" #include "insets/InsetText.h" -#include "mathed/MathData.h" #include "mathed/InsetMathNest.h" +#include "mathed/InsetMathRef.h" +#include "mathed/MathData.h" +#include "mathed/MathRow.h" #include "frontends/alert.h" -#include "frontends/Application.h" +#include "frontends/CaretGeometry.h" #include "frontends/Delegates.h" #include "frontends/FontMetrics.h" #include "frontends/NullPainter.h" #include "frontends/Painter.h" #include "frontends/Selection.h" +#include "frontends/Clipboard.h" #include "support/convert.h" #include "support/debug.h" -#include "support/ExceptionMessage.h" +#include "support/docstring.h" #include "support/filetools.h" #include "support/gettext.h" #include "support/lassert.h" +#include "support/Length.h" #include "support/lstrings.h" #include "support/lyxlib.h" -#include "support/Package.h" #include "support/types.h" +#include #include +#include #include #include #include @@ -110,7 +101,7 @@ T * getInsetByCode(Cursor const & cur, InsetCode code) Inset * inset = it.nextInset(); if (inset && inset->lyxCode() == code) return static_cast(inset); - return 0; + return nullptr; } @@ -179,13 +170,6 @@ bool findInset(DocIterator & dit, vector const & codes, } -/// Looks for next inset with the given code -void findInset(DocIterator & dit, InsetCode code, bool same_content) -{ - findInset(dit, vector(1, code), same_content); -} - - /// Moves cursor to the next inset with one of the given codes. void gotoInset(BufferView * bv, vector const & codes, bool same_content) @@ -202,13 +186,6 @@ void gotoInset(BufferView * bv, vector const & codes, } -/// Moves cursor to the next inset with given code. -void gotoInset(BufferView * bv, InsetCode code, bool same_content) -{ - gotoInset(bv, vector(1, code), same_content); -} - - /// A map from a Text to the associated text metrics typedef map TextMetricsCache; @@ -233,14 +210,11 @@ struct BufferView::Private Private(BufferView & bv) : update_strategy_(FullScreenUpdate), update_flags_(Update::Force), - wh_(0), cursor_(bv), - anchor_pit_(0), anchor_ypos_(0), - inlineCompletionUniqueChars_(0), - last_inset_(0), clickable_inset_(false), - mouse_position_cache_(), - bookmark_edit_position_(-1), gui_(0), - horiz_scroll_offset_(0), - caret_ascent_(0), caret_descent_(0) + cursor_(bv), anchor_pit_(0), anchor_ypos_(0), + wh_(0), inlineCompletionUniqueChars_(0), + last_inset_(nullptr), mouse_position_cache_(), + gui_(nullptr), bookmark_edit_position_(-1), + horiz_scroll_offset_(0), clickable_inset_(false) { xsel_cache_.set = false; } @@ -253,9 +227,10 @@ struct BufferView::Private Update::flags update_flags_; /// CoordCache coord_cache_; + /// + typedef map MathRows; + MathRows math_rows_; - /// Estimated average par height for scrollbar. - int wh_; /// this is used to handle XSelection events in the right manner. struct { CursorSlice cursor; @@ -268,6 +243,8 @@ struct BufferView::Private pit_type anchor_pit_; /// int anchor_ypos_; + /// Estimated average par height for scrollbar. + int wh_; /// vector par_height_; @@ -286,17 +263,12 @@ struct BufferView::Private * Not owned, so don't delete. */ Inset const * last_inset_; - /// are we hovering something that we can click - bool clickable_inset_; /// position of the mouse at the time of the last mouse move /// This is used to update the hovering status of inset in /// cases where the buffer is scrolled, but the mouse didn't move. Point mouse_position_cache_; - // cache for id of the paragraph which was edited the last time - int bookmark_edit_position_; - mutable TextMetricsCache text_metrics_; /// Whom to notify. @@ -304,27 +276,22 @@ struct BufferView::Private */ frontend::GuiBufferViewDelegate * gui_; - /// Cache for Find Next - FuncRequest search_request_cache_; - /// map 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_; + int horiz_scroll_offset_; /// a slice pointing to the start of the row where the cursor /// is (at last draw time) CursorSlice current_row_slice_; - /// a slice pointing to the start of the row where cursor was - /// at previous draw event - CursorSlice last_row_slice_; - - // The vertical size of the blinking caret. Only used for math - // Using it for text could be bad when undo restores the cursor - // current font, since the caret size could become wrong. - int caret_ascent_; - int caret_descent_; + /// are we hovering something that we can click + bool clickable_inset_; + /// shape of the caret + frontend::CaretGeometry caret_geometry_; }; @@ -352,9 +319,10 @@ BufferView::~BufferView() // That is to say, if a cursor is in a nested inset, it will be // restore to the left of the top level inset. LastFilePosSection::FilePos fp; + fp.file = buffer_.fileName(); fp.pit = d->cursor_.bottom().pit(); fp.pos = d->cursor_.bottom().pos(); - theSession().lastFilePos().save(buffer_.fileName(), fp); + theSession().lastFilePos().save(fp); if (d->last_inset_) d->last_inset_->setMouseHover(this, false); @@ -363,16 +331,20 @@ BufferView::~BufferView() } -int BufferView::rightMargin() const +int BufferView::defaultMargin() const { // The value used to be hardcoded to 10 - int const default_margin = zoomedPixels(10); + return zoomedPixels(20); +} + + +int BufferView::rightMargin() const +{ // 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; + if (full_screen_ && lyxrc.full_screen_limit) + return max(defaultMargin(), (width_ - lyxrc.full_screen_width) / 2); - return (width_ - lyxrc.full_screen_width) / 2; + return defaultMargin(); } @@ -382,6 +354,20 @@ int BufferView::leftMargin() const } +int BufferView::topMargin() const +{ + // Original value was 20px at 100dpi. For internal buffers like in + // advanced search and replace, a value of 5px is enough. + return zoomedPixels(buffer().isInternal() ? 5 : 20); +} + + +int BufferView::bottomMargin() const +{ + return topMargin(); +} + + int BufferView::inPixels(Length const & len) const { Font const font = buffer().params().getFont(); @@ -440,6 +426,20 @@ CoordCache const & BufferView::coordCache() const } +MathRow const & BufferView::mathRow(MathData const * cell) const +{ + auto it = d->math_rows_.find(cell); + LATTEST(it != d->math_rows_.end()); + return it->second; +} + + +void BufferView::setMathRow(MathData const * cell, MathRow const & mrow) +{ + d->math_rows_[cell] = mrow; +} + + Buffer & BufferView::buffer() { return buffer_; @@ -452,6 +452,26 @@ Buffer const & BufferView::buffer() const } +docstring const & BufferView::searchRequestCache() const +{ + return theClipboard().getFindBuffer(); +} + + +void BufferView::setSearchRequestCache(docstring const & text) +{ + bool casesensitive; + bool matchword; + bool forward; + bool wrap; + bool instant; + bool onlysel; + docstring const search = string2find(text, casesensitive, matchword, + forward, wrap, instant, onlysel); + theClipboard().setFindBuffer(search); +} + + bool BufferView::needsFitCursor() const { if (cursorStatus(d->cursor_) == CUR_INSIDE) { @@ -500,11 +520,6 @@ void BufferView::processUpdateFlags(Update::flags flags) */ buffer_.updateMacros(); - // SinglePar is ignored for now (this should probably change). We - // set it ourselves below, at the price of always rebreaking the - // paragraph at cursor. This can be expensive for large tables. - flags = flags & ~Update::SinglePar; - // First check whether the metrics and inset positions should be updated if (flags & Update::Force) { // This will update the CoordCache items and replace Force @@ -512,22 +527,30 @@ void BufferView::processUpdateFlags(Update::flags flags) updateMetrics(flags); } - // Detect whether we can only repaint a single paragraph. + // Detect whether we can only repaint a single paragraph (if we + // are not already redrawing all). // We handle this before FitCursor because the later will require // correct metrics at cursor position. - if (!(flags & Update::ForceDraw)) { - if (singleParUpdate()) - flags = flags | Update::SinglePar; - else - updateMetrics(flags); - } + if (!(flags & Update::ForceDraw) + && (flags & Update::SinglePar) + && !singleParUpdate()) + updateMetrics(flags); // Then make sure that the screen contains the cursor if needed if (flags & Update::FitCursor) { if (needsFitCursor()) { - scrollToCursor(d->cursor_, false); + // First try to make the selection start visible + // (which is just the cursor when there is no selection) + scrollToCursor(d->cursor_.selectionBegin(), false); // Metrics have to be recomputed (maybe again) - updateMetrics(flags); + updateMetrics(); + // 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); + // Metrics have to be recomputed (maybe again) + updateMetrics(flags); + } } flags = flags & ~Update::FitCursor; } @@ -538,7 +561,7 @@ void BufferView::processUpdateFlags(Update::flags flags) LYXERR(Debug::PAINTING, "Cumulative flags: " << flagsAsString(flags)); // Now compute the update strategy - // Possibly values in flag are None, Decoration, ForceDraw + // Possibly values in flag are None, SinglePar, Decoration, ForceDraw LATTEST((d->update_flags_ & ~(Update::None | Update::SinglePar | Update::Decoration | Update::ForceDraw)) == 0); @@ -651,32 +674,72 @@ 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 value, bool update) +void BufferView::scrollDocView(int const pixels, bool update) { // The scrollbar values are relative to the top of the screen, therefore the // offset is equal to the target value. // No scrolling at all? No need to redraw anything - if (value == 0) + if (pixels == 0) return; // If the offset is less than 2 screen height, prefer to scroll instead. - if (abs(value) <= 2 * height_) { - d->anchor_ypos_ -= value; + if (abs(pixels) <= 2 * height_) { + d->anchor_ypos_ -= pixels; processUpdateFlags(Update::Force); return; } // cut off at the top - if (value <= d->scrollbarParameters_.min) { + if (pixels <= d->scrollbarParameters_.min) { DocIterator dit = doc_iterator_begin(&buffer_); showCursor(dit, false, update); LYXERR(Debug::SCROLLING, "scroll to top"); @@ -684,7 +747,7 @@ void BufferView::scrollDocView(int const value, bool update) } // cut off at the bottom - if (value >= d->scrollbarParameters_.max) { + if (pixels >= d->scrollbarParameters_.max) { DocIterator dit = doc_iterator_end(&buffer_); dit.backwardPos(); showCursor(dit, false, update); @@ -697,11 +760,11 @@ void BufferView::scrollDocView(int const value, bool update) pit_type i = 0; for (; i != int(d->par_height_.size()); ++i) { par_pos += d->par_height_[i]; - if (par_pos >= value) + if (par_pos >= pixels) break; } - if (par_pos < value) { + if (par_pos < pixels) { // It seems we didn't find the correct pit so stay on the safe side and // scroll to bottom. LYXERR0("scrolling position not found!"); @@ -711,7 +774,7 @@ void BufferView::scrollDocView(int const value, bool update) DocIterator dit = doc_iterator_begin(&buffer_); dit.pit() = i; - LYXERR(Debug::SCROLLING, "value = " << value << " -> scroll to pit " << i); + LYXERR(Debug::SCROLLING, "pixels = " << pixels << " -> scroll to pit " << i); showCursor(dit, false, update); } @@ -796,23 +859,24 @@ void BufferView::bookmarkEditPosition() void BufferView::saveBookmark(unsigned int idx) { + if (buffer().isInternal()) + return; + // tentatively save bookmark, id and pos will be used to // acturately locate a bookmark in a 'live' lyx session. // pit and pos will be updated with bottom level pit/pos // when lyx exits. - if (!buffer_.isInternal()) { - theSession().bookmarks().save( - buffer_.fileName(), - d->cursor_.bottom().pit(), - d->cursor_.bottom().pos(), - d->cursor_.paragraph().id(), - d->cursor_.pos(), - idx - ); - if (idx) - // emit message signal. - message(_("Save bookmark")); - } + theSession().bookmarks().save( + buffer_.fileName(), + d->cursor_.bottom().pit(), + d->cursor_.bottom().pos(), + d->cursor_.paragraph().id(), + d->cursor_.pos(), + idx + ); + if (idx) + // emit message signal. + message(_("Save bookmark")); } @@ -853,7 +917,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; @@ -924,18 +988,20 @@ void BufferView::scrollToCursor() 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; - LYXERR(Debug::SCROLLING, "recentering!"); + if (recenter) + LYXERR(Debug::SCROLLING, "recentering and scrolling to cursor"); + else + LYXERR(Debug::SCROLLING, "scrolling to cursor"); 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); - int bot_pit = bot.pit(); + pos_type bot_pit = bot.pit(); if (bot_pit > max_pit) { // FIXME: Why does this happen? LYXERR0("bottom pit is greater that max pit: " @@ -953,10 +1019,11 @@ bool BufferView::scrollToCursor(DocIterator const & dit, bool const recenter) 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()).dimension(); + inner_pm.getRow(cs.pos(), dit.boundary()).dim(); int scrolled = 0; if (recenter) scrolled = scroll(ypos - height_/2); @@ -975,13 +1042,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); } @@ -995,12 +1062,14 @@ bool BufferView::scrollToCursor(DocIterator const & dit, bool const recenter) tm.redoParagraph(bot_pit); ParagraphMetrics const & pm = tm.parMetrics(bot_pit); - int offset = coordOffset(dit).y_; + int const offset = coordOffset(dit).y_; 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()).dimension(); + inner_pm.getRow(cs.pos(), dit.boundary()).dim(); if (recenter) d->anchor_ypos_ = height_/2; @@ -1020,7 +1089,7 @@ bool BufferView::scrollToCursor(DocIterator const & dit, bool const recenter) void BufferView::makeDocumentClass() { DocumentClassConstPtr olddc = buffer_.params().documentClassPtr(); - buffer_.params().makeDocumentClass(); + buffer_.params().makeDocumentClass(buffer_.isClone(), buffer_.isInternal()); updateDocumentClass(olddc); } @@ -1124,7 +1193,7 @@ bool BufferView::getStatus(FuncRequest const & cmd, FuncStatus & flag) break; case LFUN_FILE_INSERT_PLAINTEXT_PARA: case LFUN_FILE_INSERT_PLAINTEXT: { - docstring const fname = cmd.argument(); + docstring const & fname = cmd.argument(); if (!FileName::isAbsolute(to_utf8(fname))) { flag.message(_("Absolute filename expected.")); return false; @@ -1151,6 +1220,7 @@ bool BufferView::getStatus(FuncRequest const & cmd, FuncStatus & flag) case LFUN_MARK_OFF: case LFUN_MARK_ON: case LFUN_MARK_TOGGLE: + case LFUN_SEARCH_STRING_SET: case LFUN_SCREEN_RECENTER: case LFUN_SCREEN_SHOW_CURSOR: case LFUN_BIBTEX_DATABASE_ADD: @@ -1179,7 +1249,8 @@ bool BufferView::getStatus(FuncRequest const & cmd, FuncStatus & flag) case LFUN_LABEL_GOTO: flag.setEnabled(!cmd.argument().empty() - || getInsetByCode(cur, REF_CODE)); + || getInsetByCode(cur, REF_CODE) + || getInsetByCode(cur, MATH_REF_CODE)); break; case LFUN_CHANGES_MERGE: @@ -1247,6 +1318,10 @@ bool BufferView::getStatus(FuncRequest const & cmd, FuncStatus & flag) break; } + case LFUN_COPY: + flag.setEnabled(cur.selection()); + break; + default: return false; } @@ -1258,13 +1333,16 @@ bool BufferView::getStatus(FuncRequest const & cmd, FuncStatus & flag) Inset * BufferView::editedInset(string const & name) const { map::const_iterator it = d->edited_insets_.find(name); - return it == d->edited_insets_.end() ? 0 : it->second; + return it == d->edited_insets_.end() ? nullptr : it->second; } void BufferView::editInset(string const & name, Inset * inset) { - d->edited_insets_[name] = inset; + if (inset) + d->edited_insets_[name] = inset; + else + d->edited_insets_.erase(name); } @@ -1442,6 +1520,7 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr) break; case LFUN_BOOKMARK_SAVE: + dr.screenUpdate(Update::Force); saveBookmark(convert(to_utf8(cmd.argument()))); break; @@ -1462,6 +1541,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(cur, MATH_REF_CODE); + if (minset) + lyx::dispatch(FuncRequest(LFUN_LABEL_GOTO, + minset->getTarget())); } break; } @@ -1515,25 +1600,36 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr) } case LFUN_NOTE_NEXT: - gotoInset(this, NOTE_CODE, false); + 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); break; case LFUN_REFERENCE_NEXT: { - vector tmp; - tmp.push_back(LABEL_CODE); - tmp.push_back(REF_CODE); - gotoInset(this, tmp, true); + 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); break; } case LFUN_CHANGE_NEXT: findNextChange(this); + if (cur.inset().isTable()) + // In tables, there might be whole changed rows or columns + cur.dispatch(cmd); // FIXME: Move this LFUN to Buffer so that we don't have to do this: dr.screenUpdate(Update::Force | Update::FitCursor); break; case LFUN_CHANGE_PREVIOUS: findPreviousChange(this); + if (cur.inset().isTable()) + // In tables, there might be whole changed rows or columns + cur.dispatch(cmd); // FIXME: Move this LFUN to Buffer so that we don't have to do this: dr.screenUpdate(Update::Force | Update::FitCursor); break; @@ -1546,92 +1642,112 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr) } break; - case LFUN_ALL_CHANGES_ACCEPT: + case LFUN_ALL_CHANGES_ACCEPT: { // select complete document cur.reset(); cur.selHandle(true); buffer_.text().cursorBottom(cur); // accept everything in a single step to support atomic undo + // temporarily disable track changes in order to end with really + // no new (e.g., DPSM-caused) changes (see #7487) + bool const track = buffer_.params().track_changes; + buffer_.params().track_changes = false; buffer_.text().acceptOrRejectChanges(cur, Text::ACCEPT); + buffer_.params().track_changes = track; cur.resetAnchor(); // FIXME: Move this LFUN to Buffer so that we don't have to do this: dr.screenUpdate(Update::Force | Update::FitCursor); dr.forceBufferUpdate(); break; + } - case LFUN_ALL_CHANGES_REJECT: + case LFUN_ALL_CHANGES_REJECT: { // select complete document cur.reset(); cur.selHandle(true); buffer_.text().cursorBottom(cur); // reject everything in a single step to support atomic undo - // Note: reject does not work recursively; the user may have to repeat the operation + // temporarily disable track changes in order to end with really + // no new (e.g., DPSM-caused) changes (see #7487) + bool const track = buffer_.params().track_changes; + buffer_.params().track_changes = false; buffer_.text().acceptOrRejectChanges(cur, Text::REJECT); + buffer_.params().track_changes = track; cur.resetAnchor(); // FIXME: Move this LFUN to Buffer so that we don't have to do this: dr.screenUpdate(Update::Force | Update::FitCursor); dr.forceBufferUpdate(); break; + } case LFUN_WORD_FIND_FORWARD: case LFUN_WORD_FIND_BACKWARD: { - // FIXME THREAD - // Would it maybe be better if this variable were view specific anyway? - static docstring last_search; docstring searched_string; if (!cmd.argument().empty()) { - last_search = cmd.argument(); + setSearchRequestCache(cmd.argument()); searched_string = cmd.argument(); } else { - searched_string = last_search; + searched_string = searchRequestCache(); } if (searched_string.empty()) break; - bool const fw = act == LFUN_WORD_FIND_FORWARD; docstring const data = - find2string(searched_string, true, false, fw); + find2string(searched_string, false, false, + act == LFUN_WORD_FIND_FORWARD, false, false, false); bool found = lyxfind(this, FuncRequest(LFUN_WORD_FIND, data)); if (found) dr.screenUpdate(Update::Force | Update::FitCursor); + else + dr.setMessage(_("Search string not found!")); break; } case LFUN_WORD_FIND: { - FuncRequest req = cmd; - if (cmd.argument().empty() && !d->search_request_cache_.argument().empty()) - req = d->search_request_cache_; - if (req.argument().empty()) { + docstring arg = cmd.argument(); + if (arg.empty()) + arg = searchRequestCache(); + if (arg.empty()) { lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "findreplace")); break; } - if (lyxfind(this, req)) + if (lyxfind(this, FuncRequest(act, arg))) dr.screenUpdate(Update::Force | Update::FitCursor); + else + dr.setMessage(_("Search string not found!")); - d->search_request_cache_ = req; + setSearchRequestCache(arg); break; } - case LFUN_WORD_REPLACE: { - bool has_deleted = false; - if (cur.selection()) { - DocIterator beg = cur.selectionBegin(); - DocIterator end = cur.selectionEnd(); - if (beg.pit() == end.pit()) { - for (pos_type p = beg.pos() ; p < end.pos() ; ++p) { - if (!cur.inMathed() && cur.paragraph().isDeleted(p)) { - has_deleted = true; - break; - } - } - } + case LFUN_SEARCH_STRING_SET: { + docstring pattern = cmd.argument(); + if (!pattern.empty()) { + setSearchRequestCache(pattern); + break; + } + if (cur.selection()) + pattern = cur.selectionAsString(false); + else { + pos_type spos = cur.pos(); + cur.innerText()->selectWord(cur, WHOLE_WORD); + pattern = cur.selectionAsString(false); + cur.selection(false); + cur.pos() = spos; } - if (lyxreplace(this, cmd, has_deleted)) { + setSearchRequestCache(pattern); + break; + } + + case LFUN_WORD_REPLACE: { + if (lyxreplace(this, cmd)) { dr.forceBufferUpdate(); dr.screenUpdate(Update::Force | Update::FitCursor); } + else + dr.setMessage(_("Search string not found!")); break; } @@ -1683,7 +1799,7 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr) case LFUN_BIBTEX_DATABASE_ADD: { Cursor tmpcur = cur; - findInset(tmpcur, BIBTEX_CODE, false); + findInset(tmpcur, { BIBTEX_CODE }, false); InsetBibtex * inset = getInsetByCode(tmpcur, BIBTEX_CODE); if (inset) { @@ -1695,7 +1811,7 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr) case LFUN_BIBTEX_DATABASE_DEL: { Cursor tmpcur = cur; - findInset(tmpcur, BIBTEX_CODE, false); + findInset(tmpcur, { BIBTEX_CODE }, false); InsetBibtex * inset = getInsetByCode(tmpcur, BIBTEX_CODE); if (inset) { @@ -1804,7 +1920,7 @@ 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 - processUpdateFlags(Update::Force); + updateMetrics(); d->text_metrics_[&buffer_.text()].editXY(cur, p.x_, p.y_, true, act == LFUN_SCREEN_UP); @@ -1901,10 +2017,12 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr) // At least one complete cell is selected and inset is a table. // Select all cells cur.idx() = 0; + cur.pit() = 0; cur.pos() = 0; cur.resetAnchor(); cur.selection(true); cur.idx() = cur.lastidx(); + cur.pit() = cur.lastpit(); cur.pos() = cur.lastpos(); } else { // select current cell @@ -2149,6 +2267,18 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr) break; } + 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; + default: // OK, so try the Buffer itself... buffer_.dispatch(cmd, dr); @@ -2176,7 +2306,7 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr) } -docstring const BufferView::requestSelection() +docstring BufferView::requestSelection() { Cursor & cur = d->cursor_; @@ -2233,7 +2363,7 @@ Inset const * BufferView::getCoveringInset(Text const & text, TextMetrics & tm = d->text_metrics_[&text]; Inset * inset = tm.checkInsetHit(x, y); if (!inset) - return 0; + return nullptr; if (!inset->descendable(*this)) // No need to go further down if the inset is not @@ -2257,12 +2387,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); @@ -2274,7 +2442,7 @@ void BufferView::updateHoveredInset() const if (d->last_inset_) { // Remove the hint on the last hovered inset (if any). need_redraw |= d->last_inset_->setMouseHover(this, false); - d->last_inset_ = 0; + d->last_inset_ = nullptr; } if (covering_inset && covering_inset->setMouseHover(this, true)) { @@ -2305,7 +2473,7 @@ void BufferView::clearLastInset(Inset * inset) const LYXERR0("Wrong last_inset!"); LATTEST(false); } - d->last_inset_ = 0; + d->last_inset_ = nullptr; } @@ -2383,7 +2551,7 @@ void BufferView::mouseEventDispatch(FuncRequest const & cmd0) // Do we have a selection? theSelection().haveSelection(cursor().selection()); - if (cur.needBufferUpdate()) { + if (cur.needBufferUpdate() || buffer().needUpdate()) { cur.clearBufferUpdate(); buffer().updateBuffer(); } @@ -2400,21 +2568,21 @@ int BufferView::minVisiblePart() } -int BufferView::scroll(int y) +int BufferView::scroll(int pixels) { - if (y > 0) - return scrollDown(y); - if (y < 0) - return scrollUp(-y); + if (pixels > 0) + return scrollDown(pixels); + if (pixels < 0) + return scrollUp(-pixels); return 0; } -int BufferView::scrollDown(int offset) +int BufferView::scrollDown(int pixels) { Text * text = &buffer_.text(); TextMetrics & tm = d->text_metrics_[text]; - int const ymax = height_ + offset; + int const ymax = height_ + pixels; while (true) { pair last = tm.last(); int bottom_pos = last.second->position() + last.second->descent(); @@ -2423,38 +2591,38 @@ int BufferView::scrollDown(int offset) if (last.first + 1 == int(text->paragraphs().size())) { if (bottom_pos <= height_) return 0; - offset = min(offset, bottom_pos - height_); + pixels = min(pixels, bottom_pos - height_); break; } if (bottom_pos > ymax) break; tm.newParMetricsDown(); } - d->anchor_ypos_ -= offset; - return -offset; + d->anchor_ypos_ -= pixels; + return -pixels; } -int BufferView::scrollUp(int offset) +int BufferView::scrollUp(int pixels) { Text * text = &buffer_.text(); TextMetrics & tm = d->text_metrics_[text]; - int ymin = - offset; + int ymin = - pixels; while (true) { pair first = tm.first(); int top_pos = first.second->position() - first.second->ascent(); if (first.first == 0) { if (top_pos >= 0) return 0; - offset = min(offset, - top_pos); + pixels = min(pixels, - top_pos); break; } if (top_pos < ymin) break; tm.newParMetricsUp(); } - d->anchor_ypos_ += offset; - return offset; + d->anchor_ypos_ += pixels; + return pixels; } @@ -2512,15 +2680,26 @@ bool BufferView::setCursorFromInset(Inset const * inset) void BufferView::gotoLabel(docstring const & label) { + FuncRequest action; + bool have_inactive = false; for (Buffer const * buf : buffer().allRelatives()) { // find label for (TocItem const & item : *buf->tocBackend().toc("label")) { - if (label == item.str()) { + if (label == item.str() && item.isOutput()) { lyx::dispatch(item.action()); return; } + // If we find an inactive label, save it for the case + // that no active one is there + if (label == item.str() && !have_inactive) { + have_inactive = true; + action = item.action(); + } } } + // We only found an inactive label. Go there. + if (have_inactive) + lyx::dispatch(action); } @@ -2535,8 +2714,9 @@ TextMetrics & BufferView::textMetrics(Text const * t) LBUFERR(t); TextMetricsCache::iterator tmc_it = d->text_metrics_.find(t); if (tmc_it == d->text_metrics_.end()) { - tmc_it = d->text_metrics_.insert( - make_pair(t, TextMetrics(this, const_cast(t)))).first; + tmc_it = d->text_metrics_.emplace(std::piecewise_construct, + std::forward_as_tuple(t), + std::forward_as_tuple(this, const_cast(t))).first; } return tmc_it->second; } @@ -2592,7 +2772,7 @@ bool BufferView::checkDepm(Cursor & cur, Cursor & old) return false; bool need_anchor_change = false; - bool changed = d->cursor_.text()->deleteEmptyParagraphMechanism(cur, old, + bool changed = Text::deleteEmptyParagraphMechanism(cur, old, need_anchor_change); if (need_anchor_change) @@ -2726,7 +2906,7 @@ bool BufferView::singleParUpdate() Text & buftext = buffer_.text(); pit_type const bottom_pit = d->cursor_.bottom().pit(); TextMetrics & tm = textMetrics(&buftext); - int old_height = tm.parMetrics(bottom_pit).height(); + Dimension const old_dim = tm.parMetrics(bottom_pit).dim(); // make sure inline completion pointer is ok if (d->inlineCompletionPos_.fixIfBroken()) @@ -2737,11 +2917,16 @@ bool BufferView::singleParUpdate() // (if this paragraph contains insets etc., rebreaking will // recursively descend) tm.redoParagraph(bottom_pit); - ParagraphMetrics const & pm = tm.parMetrics(bottom_pit); - if (pm.height() != old_height) + ParagraphMetrics & pm = tm.parMetrics(bottom_pit); + if (pm.height() != old_dim.height()) { // Paragraph height has changed so we cannot proceed to // the singlePar optimisation. return false; + } + // Since position() points to the baseline of the first row, we + // may have to update it. See ticket #11601 for an example where + // the height does not change but the ascent does. + pm.setPosition(pm.position() - old_dim.ascent() + pm.ascent()); tm.updatePosCache(bottom_pit); @@ -2770,6 +2955,7 @@ void BufferView::updateMetrics(Update::flags & update_flags) // 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 @@ -2788,7 +2974,7 @@ void BufferView::updateMetrics(Update::flags & update_flags) // Rebreak anchor paragraph. tm.redoParagraph(d->anchor_pit_); - ParagraphMetrics & anchor_pm = tm.par_metrics_[d->anchor_pit_]; + ParagraphMetrics & anchor_pm = tm.parMetrics(d->anchor_pit_); // position anchor if (d->anchor_pit_ == 0) { @@ -2815,7 +3001,7 @@ void BufferView::updateMetrics(Update::flags & update_flags) pit_type pit1 = d->anchor_pit_ - 1; for (; pit1 >= 0 && y1 >= 0; --pit1) { tm.redoParagraph(pit1); - ParagraphMetrics & pm = tm.par_metrics_[pit1]; + ParagraphMetrics & pm = tm.parMetrics(pit1); y1 -= pm.descent(); // Save the paragraph position in the cache. pm.setPosition(y1); @@ -2829,7 +3015,7 @@ void BufferView::updateMetrics(Update::flags & update_flags) pit_type pit2 = d->anchor_pit_ + 1; for (; pit2 < npit && y2 <= height_; ++pit2) { tm.redoParagraph(pit2); - ParagraphMetrics & pm = tm.par_metrics_[pit2]; + ParagraphMetrics & pm = tm.parMetrics(pit2); y2 += pm.ascent(); // Save the paragraph position in the cache. pm.setPosition(y2); @@ -2867,7 +3053,7 @@ void BufferView::updatePosCache() } -void BufferView::insertLyXFile(FileName const & fname) +void BufferView::insertLyXFile(FileName const & fname, bool const ignorelang) { LASSERT(d->cursor_.inTexted(), return); @@ -2885,9 +3071,14 @@ void BufferView::insertLyXFile(FileName const & fname) ErrorList & el = buffer_.errorList("Parse"); // Copy the inserted document error list into the current buffer one. el = buf.errorList("Parse"); + ParagraphList & pars = buf.paragraphs(); + if (ignorelang) + // set main language of imported file to context language + buf.changeLanguage(buf.language(), d->cursor_.getFont().language()); buffer_.undo().recordUndo(d->cursor_); - cap::pasteParagraphList(d->cursor_, buf.paragraphs(), - buf.params().documentClassPtr(), el); + cap::pasteParagraphList(d->cursor_, pars, + buf.params().documentClassPtr(), + buf.params().authors(), el); res = _("Document %1$s inserted."); } else { res = _("Could not insert document %1$s"); @@ -3001,37 +3192,122 @@ bool BufferView::paragraphVisible(DocIterator const & dit) const } -void BufferView::setCaretAscentDescent(int asc, int des) +void BufferView::caretPosAndDim(Point & p, Dimension & dim) const { - d->caret_ascent_ = asc; - d->caret_descent_ = des; -} - - -void BufferView::caretPosAndHeight(Point & p, int & h) const -{ - int asc, des; Cursor const & cur = cursor(); if (cur.inMathed()) { - asc = d->caret_ascent_; - des = d->caret_descent_; + MathRow const & mrow = mathRow(&cur.cell()); + dim = mrow.caret_dim; } else { Font const font = cur.real_current_font; frontend::FontMetrics const & fm = theFontMetrics(font); - asc = fm.maxAscent(); - des = fm.maxDescent(); + // 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(); } - h = asc + des; + if (lyxrc.cursor_width > 0) + dim.wid = lyxrc.cursor_width; + p = getPos(cur); - p.y_ -= asc; + // center fat carets horizontally + p.x_ -= dim.wid / 2; + // p is top-left + p.y_ -= dim.asc; +} + + +void BufferView::buildCaretGeometry(bool complet) +{ + Point p; + Dimension dim; + caretPosAndDim(p, dim); + + Cursor const & cur = d->cursor_; + Font const & realfont = cur.real_current_font; + frontend::FontMetrics const & fm = theFontMetrics(realfont.fontInfo()); + bool const isrtl = realfont.isVisibleRightToLeft(); + int const dir = isrtl ? -1 : 1; + + frontend::CaretGeometry & cg = d->caret_geometry_; + cg.shapes.clear(); + + // The caret itself, slanted for italics in text edit mode except + // for selections because the selection rect does not slant + 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_ + dir * dim.wid - dim.des * slope), p.y_ + dim.height()}, + {iround(p.x_ + dir * dim.wid + dim.asc * slope), p.y_}} + ); + + // The language indicator _| (if needed) + Language const * doclang = buffer().params().language; + if (!((realfont.language() == doclang && isrtl == doclang->rightToLeft()) + || realfont.language() == latex_language)) { + int const lx = dim.height() / 3; + 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 + dir * (dim.wid + lx - 1), yy - dim.wid}, + {xx + dir * (dim.wid + lx - 1), yy}, + {xx, yy}} + ); + } + + // The completion triangle |> (if needed) + if (complet) { + int const m = p.y_ + dim.height() / 2; + int const d = dim.height() / 8; + // offset for slanted carret + int const sx = iround((dim.asc - (dim.height() / 2 - d)) * slope); + // 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 + dir * d - dim.wid, m}, + {xx, m - d + dim.wid}} + ); + } + + // compute extremal x values + cg.left = 1000000; + cg.right = -1000000; + cg.top = 1000000; + cg.bottom = -1000000; + for (auto const & shape : cg.shapes) + for (Point const & p : shape) { + cg.left = min(cg.left, p.x_); + cg.right = max(cg.right, p.x_); + cg.top = min(cg.top, p.y_); + cg.bottom = max(cg.bottom, p.y_); + } } -bool BufferView::cursorInView(Point const & p, int h) const +frontend::CaretGeometry const & BufferView::caretGeometry() const { - Cursor const & cur = cursor(); + return d->caret_geometry_; +} + + +bool BufferView::caretInView() const +{ + if (!paragraphVisible(cursor())) + return false; + Point p; + Dimension dim; + caretPosAndDim(p, dim); + // does the cursor touch the screen ? - if (p.y_ + h < 0 || p.y_ >= workHeight() || !paragraphVisible(cur)) + if (p.y_ + dim.height() < 0 || p.y_ >= workHeight()) return false; return true; } @@ -3056,30 +3332,24 @@ int BufferView::horizScrollOffset(Text const * text, } -bool BufferView::hadHorizScrollOffset(Text const * text, - pit_type pit, pos_type pos) const -{ - return !d->last_row_slice_.empty() - && &text->inset() == d->last_row_slice_.inset().asInsetText() - && pit == d->last_row_slice_.pit() - && pos == d->last_row_slice_.pos(); -} - - void BufferView::setCurrentRowSlice(CursorSlice const & rowSlice) { // nothing to do if the cursor was already on this row - if (d->current_row_slice_ == rowSlice) { - d->last_row_slice_ = CursorSlice(); + if (d->current_row_slice_ == rowSlice) return; - } // if the (previous) current row was scrolled, we have to // remember it in order to repaint it next time. - if (d->horiz_scroll_offset_ != 0) - d->last_row_slice_ = d->current_row_slice_; - else - d->last_row_slice_ = CursorSlice(); + if (d->horiz_scroll_offset_ != 0) { + // search the old row in cache and mark it changed + for (auto & tm_pair : d->text_metrics_) { + if (&tm_pair.first->inset() == rowSlice.inset().asInsetText()) { + tm_pair.second.setRowChanged(rowSlice.pit(), rowSlice.pos()); + // We found it, no need to continue. + break; + } + } + } // Since we changed row, the scroll offset is not valid anymore d->horiz_scroll_offset_ = 0; @@ -3136,8 +3406,7 @@ void BufferView::checkCursorScrollOffset() << d->horiz_scroll_offset_ << " to " << offset); if (d->update_strategy_ == NoScreenUpdate - && (offset != d->horiz_scroll_offset_ - || !d->last_row_slice_.empty())) { + && offset != d->horiz_scroll_offset_) { // FIXME: if one uses SingleParUpdate, then home/end // will not work on long rows. Why? d->update_strategy_ = FullScreenUpdate; @@ -3169,7 +3438,6 @@ void BufferView::draw(frontend::Painter & pain, bool paint_caret) // however, the different coordinates of insets and paragraphs // needs to be updated. LYXERR(Debug::PAINTING, "Strategy: NoScreenUpdate"); - pi.full_repaint = false; if (pain.isNull()) { pi.full_repaint = true; tm.draw(pi, 0, y); @@ -3225,6 +3493,7 @@ void BufferView::draw(frontend::Painter & pain, bool paint_caret) : "\t\t *** END DRAWING ***")); // The scrollbar needs an update. + // FIXME: does it always? see ticket #11947. updateScrollbar(); // Normalize anchor for next time @@ -3253,8 +3522,17 @@ void BufferView::draw(frontend::Painter & pain, bool paint_caret) * move at all */ if (paint_caret) { - Row const & caret_row = d->cursor_.textRow(); - caret_row.changed(true); + Cursor cur(d->cursor_); + while (cur.depth() > 1) { + if (!cur.inTexted()) + break; + TextMetrics const & tm = textMetrics(cur.text()); + if (d->caret_geometry_.left >= tm.origin().x_ + && d->caret_geometry_.right <= tm.origin().x_ + tm.dim().width()) + break; + cur.pop(); + } + cur.textRow().changed(true); } } @@ -3356,7 +3634,7 @@ docstring const & BufferView::inlineCompletion() const } -size_t const & BufferView::inlineCompletionUniqueChars() const +size_t BufferView::inlineCompletionUniqueChars() const { return d->inlineCompletionUniqueChars_; }