# HG changeset patch # User Christian Brabandt # Date 1701124204 -3600 # Node ID 58c9f11eae5bf6179bd807a2a19716ceba6657cc # Parent dc37b35ded98232d3ea9bb8a116a63ce8a6dfc42 patch 9.0.2134: ml_get error when scrolling Commit: https://github.com/vim/vim/commit/c4ffeddfe5bd1824650e9b911ed9245bf56c69e3 Author: Christian Brabandt Date: Mon Nov 27 23:25:03 2023 +0100 patch 9.0.2134: ml_get error when scrolling Problem: ml_get error when scrolling after delete Solution: mark topline to be validated in main_loop if it is larger than current buffers line count reset_lnums() is called after e.g. TextChanged autocommands and it may accidentally cause curwin->w_topline to become invalid, e.g. if the autocommand has deleted some lines. So verify that curwin->w_topline points to a valid line and if not, mark the window to have w_topline recalculated in main_loop() in update_topline() after reset_lnums() returns. fixes: #13568 fixes: #13578 Signed-off-by: Christian Brabandt diff --git a/src/testdir/dumps/Test_delete_ml_get_errors_1.dump b/src/testdir/dumps/Test_delete_ml_get_errors_1.dump new file mode 100644 --- /dev/null +++ b/src/testdir/dumps/Test_delete_ml_get_errors_1.dump @@ -0,0 +1,10 @@ +|-+0&#ffffff0|>| |s|t|d|:@1|o|p|t|i|o|n|a|l|<|D|a|t|a|:@1|W|a|l@1|P|a|p|e|r|>| |{| @39 +@8|i|f| |(|!|_|f|o|r|P|e@1|r|)| |{| @50 +@16|r|e|t|u|r|n| |{|}|;| @48 +@8|}| @65 +@8|c|o|n|s|t| |a|u|t|o| |n|o|n|C|u|s|t|o|m| |=| |W|i|n|d|o|w|:@1|T|h|e|m|e|:@1|B|a|c|k|g|r|o|u|n|d|(|)|-|>|p|a|p|e|r|(|)|;| @6 +@8|c|o|n|s|t| |a|u|t|o| |t|h|e|m|e|E|m|o|j|i| |=| |_|f|o|r|P|e@1|r|-|>|t|h|e|m|e|E|m|o|j|i|(|)|;| @19 +@8|i|f| |(|t|h|e|m|e|E|m|o|j|i|.|i|s|E|m|p|t|y|(|)@1| |{| @39 +|"|s|a|m|p|l|e|s|/|b|o|x|.|t|x|t|"| |6|0|1|L|,| |1|7@1|3@1|B| @44 +|3|5|3| |f|e|w|e|r| |l|i|n|e|s| @59 +|P+0#00e0003&|r|e|s@1| |E|N|T|E|R| |o|r| |t|y|p|e| |c|o|m@1|a|n|d| |t|o| |c|o|n|t|i|n|u|e> +0#0000000&@35 diff --git a/src/testdir/samples/box.txt b/src/testdir/samples/box.txt new file mode 100644 --- /dev/null +++ b/src/testdir/samples/box.txt @@ -0,0 +1,601 @@ +void BackgroundBox::prepare() { + setTitle(tr::lng_backgrounds_header()); + + addButton(tr::lng_close(), [=] { closeBox(); }); + + setDimensions(st::boxWideWidth, st::boxMaxListHeight); + + auto wrap = object_ptr(this); + const auto container = wrap.data(); + + Settings::AddSkip(container); + + const auto button = container->add(Settings::CreateButton( + container, + tr::lng_settings_bg_from_file(), + st::infoProfileButton)); + object_ptr( + button, + st::infoIconMediaPhoto, + st::infoSharedMediaButtonIconPosition); + + button->setClickedCallback([=] { + chooseFromFile(); + }); + + Settings::AddSkip(container); + Settings::AddDivider(container); + + _inner = container->add( + object_ptr(this, &_controller->session(), _forPeer)); + + container->resizeToWidth(st::boxWideWidth); + + setInnerWidget(std::move(wrap), st::backgroundScroll); + setInnerTopSkip(st::lineWidth); + + _inner->chooseEvents( + ) | rpl::start_with_next([=](const Data::WallPaper &paper) { + chosen(paper); + }, _inner->lifetime()); + + _inner->removeRequests( + ) | rpl::start_with_next([=](const Data::WallPaper &paper) { + removePaper(paper); + }, _inner->lifetime()); +} + +void BackgroundBox::chooseFromFile() { + const auto filterStart = _forPeer + ? u"Image files (*"_q + : u"Theme files (*.tdesktop-theme *.tdesktop-palette *"_q; + auto filters = QStringList( + filterStart + + Ui::ImageExtensions().join(u" *"_q) + + u")"_q); + filters.push_back(FileDialog::AllFilesFilter()); + const auto callback = [=](const FileDialog::OpenResult &result) { + if (result.paths.isEmpty() && result.remoteContent.isEmpty()) { + return; + } + + if (!_forPeer && !result.paths.isEmpty()) { + const auto filePath = result.paths.front(); + const auto hasExtension = [&](QLatin1String extension) { + return filePath.endsWith(extension, Qt::CaseInsensitive); + }; + if (hasExtension(qstr(".tdesktop-theme")) + || hasExtension(qstr(".tdesktop-palette"))) { + Window::Theme::Apply(filePath); + return; + } + } + + auto image = Images::Read({ + .path = result.paths.isEmpty() ? QString() : result.paths.front(), + .content = result.remoteContent, + .forceOpaque = true, + }).image; + if (image.isNull() || image.width() <= 0 || image.height() <= 0) { + return; + } + auto local = Data::CustomWallPaper(); + local.setLocalImageAsThumbnail(std::make_shared( + std::move(image))); + _controller->show(Box( + _controller, + local, + BackgroundPreviewArgs{ _forPeer })); + }; + FileDialog::GetOpenPath( + this, + tr::lng_choose_image(tr::now), + filters.join(u";;"_q), + crl::guard(this, callback)); +} + +bool BackgroundBox::hasDefaultForPeer() const { + Expects(_forPeer != nullptr); + + const auto paper = _forPeer->wallPaper(); + if (!paper) { + return true; + } + const auto reset = _inner->resolveResetCustomPaper(); + Assert(reset.has_value()); + return (paper->id() == reset->id()); +} + +bool BackgroundBox::chosenDefaultForPeer( + const Data::WallPaper &paper) const { + if (!_forPeer) { + return false; + } + + const auto reset = _inner->resolveResetCustomPaper(); + Assert(reset.has_value()); + return (paper.id() == reset->id()); +} + +void BackgroundBox::chosen(const Data::WallPaper &paper) { + if (chosenDefaultForPeer(paper)) { + if (!hasDefaultForPeer()) { + const auto reset = crl::guard(this, [=](Fn close) { + resetForPeer(); + close(); + }); + _controller->show(Ui::MakeConfirmBox({ + .text = tr::lng_background_sure_reset_default(), + .confirmed = reset, + .confirmText = tr::lng_background_reset_default(), + })); + } else { + closeBox(); + } + return; + } + _controller->show(Box( + _controller, + paper, + BackgroundPreviewArgs{ _forPeer })); +} + +void BackgroundBox::resetForPeer() { + const auto api = &_controller->session().api(); + api->request(MTPmessages_SetChatWallPaper( + MTP_flags(0), + _forPeer->input, + MTPInputWallPaper(), + MTPWallPaperSettings(), + MTPint() + )).done([=](const MTPUpdates &result) { + api->applyUpdates(result); + }).send(); + + const auto weak = Ui::MakeWeak(this); + _forPeer->setWallPaper(std::nullopt); + if (weak) { + _controller->finishChatThemeEdit(_forPeer); + } +} + +void BackgroundBox::removePaper(const Data::WallPaper &paper) { + const auto session = &_controller->session(); + const auto remove = [=, weak = Ui::MakeWeak(this)](Fn &&close) { + close(); + if (weak) { + weak->_inner->removePaper(paper); + } + session->data().removeWallpaper(paper); + session->api().request(MTPaccount_SaveWallPaper( + paper.mtpInput(session), + MTP_bool(true), + paper.mtpSettings() + )).send(); + }; + _controller->show(Ui::MakeConfirmBox({ + .text = tr::lng_background_sure_delete(), + .confirmed = remove, + .confirmText = tr::lng_selected_delete(), + })); +} + +BackgroundBox::Inner::Inner( + QWidget *parent, + not_null session, + PeerData *forPeer) +: RpWidget(parent) +, _session(session) +, _forPeer(forPeer) +, _api(&_session->mtp()) +, _check(std::make_unique(st::overviewCheck, [=] { update(); })) { + _check->setChecked(true, anim::type::instant); + resize(st::boxWideWidth, 2 * (st::backgroundSize.height() + st::backgroundPadding) + st::backgroundPadding); + Window::Theme::IsNightModeValue( + ) | rpl::start_with_next([=] { + updatePapers(); + }, lifetime()); + requestPapers(); + + _session->downloaderTaskFinished( + ) | rpl::start_with_next([=] { + update(); + }, lifetime()); + + style::PaletteChanged( + ) | rpl::start_with_next([=] { + _check->invalidateCache(); + }, lifetime()); + + using Update = Window::Theme::BackgroundUpdate; + Window::Theme::Background()->updates( + ) | rpl::start_with_next([=](const Update &update) { + if (update.type == Update::Type::New) { + sortPapers(); + requestPapers(); + this->update(); + } + }, lifetime()); + + + setMouseTracking(true); +} + +void BackgroundBox::Inner::requestPapers() { + _api.request(MTPaccount_GetWallPapers( + MTP_long(_session->data().wallpapersHash()) + )).done([=](const MTPaccount_WallPapers &result) { + if (_session->data().updateWallpapers(result)) { + updatePapers(); + } + }).send(); +} + +auto BackgroundBox::Inner::resolveResetCustomPaper() const +-> std::optional { + if (!_forPeer) { + return {}; + } + const auto nonCustom = Window::Theme::Background()->paper(); + const auto themeEmoji = _forPeer->themeEmoji(); + if (themeEmoji.isEmpty()) { + return nonCustom; + } + const auto &themes = _forPeer->owner().cloudThemes(); + const auto theme = themes.themeForEmoji(themeEmoji); + if (!theme) { + return nonCustom; + } + using Type = Data::CloudTheme::Type; XXXXX + const auto dark = Window::Theme::IsNightMode(); + const auto i = theme->settings.find(dark ? Type::Dark : Type::Light); + if (i != end(theme->settings) && i->second.paper) { + return *i->second.paper; + } + return nonCustom; +} + +void BackgroundBox::Inner::pushCustomPapers() { + auto customId = uint64(); + if (const auto custom = _forPeer ? _forPeer->wallPaper() : nullptr) { + customId = custom->id(); + const auto j = ranges::find( + _papers, + custom->id(), + [](const Paper &paper) { return paper.data.id(); }); + if (j != end(_papers)) { + j->data = j->data.withParamsFrom(*custom); + } else { + _papers.insert(begin(_papers), Paper{ *custom }); + } + } + if (const auto reset = resolveResetCustomPaper()) { + _insertedResetId = reset->id(); + const auto j = ranges::find( + _papers, + _insertedResetId, + [](const Paper &paper) { return paper.data.id(); }); + if (j != end(_papers)) { + if (_insertedResetId != customId) { + j->data = j->data.withParamsFrom(*reset); + } + } else { + _papers.insert(begin(_papers), Paper{ *reset }); + } + } +} + +void BackgroundBox::Inner::sortPapers() { + const auto currentCustom = _forPeer ? _forPeer->wallPaper() : nullptr; + _currentId = currentCustom + ? currentCustom->id() + : _insertedResetId + ? _insertedResetId + : Window::Theme::Background()->id(); + const auto dark = Window::Theme::IsNightMode(); + ranges::stable_sort(_papers, std::greater<>(), [&](const Paper &paper) { + const auto &data = paper.data; + return std::make_tuple( + _insertedResetId && (_insertedResetId == data.id()), + data.id() == _currentId, + dark ? data.isDark() : !data.isDark(), + Data::IsDefaultWallPaper(data), + !data.isDefault() && !Data::IsLegacy1DefaultWallPaper(data), + Data::IsLegacy3DefaultWallPaper(data), + Data::IsLegacy2DefaultWallPaper(data), + Data::IsLegacy1DefaultWallPaper(data)); + }); + if (!_papers.empty() + && _papers.front().data.id() == _currentId + && !currentCustom + && !_insertedResetId) { + _papers.front().data = _papers.front().data.withParamsFrom( + Window::Theme::Background()->paper()); + } +} + +void BackgroundBox::Inner::updatePapers() { + if (_session->data().wallpapers().empty()) { + return; + } + _over = _overDown = Selection(); + + _papers = _session->data().wallpapers( + ) | ranges::views::filter([&](const Data::WallPaper &paper) { + return (!paper.isPattern() || !paper.backgroundColors().empty()) + && (!_forPeer + || (!Data::IsDefaultWallPaper(paper) + && (Data::IsCloudWallPaper(paper) + || Data::IsCustomWallPaper(paper)))); + }) | ranges::views::transform([](const Data::WallPaper &paper) { + return Paper{ paper }; + }) | ranges::to_vector; + pushCustomPapers(); + sortPapers(); + resizeToContentAndPreload(); +} + +void BackgroundBox::Inner::resizeToContentAndPreload() { + const auto count = _papers.size(); + const auto rows = (count / kBackgroundsInRow) + + (count % kBackgroundsInRow ? 1 : 0); + + resize( + st::boxWideWidth, + (rows * (st::backgroundSize.height() + st::backgroundPadding) + + st::backgroundPadding)); + + const auto preload = kBackgroundsInRow * 3; + for (const auto &paper : _papers | ranges::views::take(preload)) { + if (!paper.data.localThumbnail() && !paper.dataMedia) { + if (const auto document = paper.data.document()) { + paper.dataMedia = document->createMediaView(); + paper.dataMedia->thumbnailWanted(paper.data.fileOrigin()); + } + } + } + update(); +} + +void BackgroundBox::Inner::paintEvent(QPaintEvent *e) { + QRect r(e->rect()); + auto p = QPainter(this); + + if (_papers.empty()) { + p.setFont(st::noContactsFont); + p.setPen(st::noContactsColor); + p.drawText(QRect(0, 0, width(), st::noContactsHeight), tr::lng_contacts_loading(tr::now), style::al_center); + return; + } + auto row = 0; + auto column = 0; + for (const auto &paper : _papers) { + const auto increment = gsl::finally([&] { + ++column; + if (column == kBackgroundsInRow) { + column = 0; + ++row; + } + }); + if ((st::backgroundSize.height() + st::backgroundPadding) * (row + 1) <= r.top()) { + continue; + } else if ((st::backgroundSize.height() + st::backgroundPadding) * row >= r.top() + r.height()) { + break; + } + paintPaper(p, paper, column, row); + } +} + +void BackgroundBox::Inner::validatePaperThumbnail( + const Paper &paper) const { + if (!paper.thumbnail.isNull()) { + return; + } + const auto localThumbnail = paper.data.localThumbnail(); + if (!localThumbnail) { + if (const auto document = paper.data.document()) { + if (!paper.dataMedia) { + paper.dataMedia = document->createMediaView(); + paper.dataMedia->thumbnailWanted(paper.data.fileOrigin()); + } + if (!paper.dataMedia->thumbnail()) { + return; + } + } else if (!paper.data.backgroundColors().empty()) { + paper.thumbnail = Ui::PixmapFromImage( + Ui::GenerateBackgroundImage( + st::backgroundSize * cIntRetinaFactor(), + paper.data.backgroundColors(), + paper.data.gradientRotation())); + paper.thumbnail.setDevicePixelRatio(cRetinaFactor()); + return; + } else { + return; + } + } + const auto thumbnail = localThumbnail + ? localThumbnail + : paper.dataMedia->thumbnail(); + auto original = thumbnail->original(); + if (paper.data.isPattern()) { + original = Ui::PreparePatternImage( + std::move(original), + paper.data.backgroundColors(), + paper.data.gradientRotation(), + paper.data.patternOpacity()); + } + paper.thumbnail = Ui::PixmapFromImage(TakeMiddleSample( + original, + st::backgroundSize)); + paper.thumbnail.setDevicePixelRatio(cRetinaFactor()); +} + +void BackgroundBox::Inner::paintPaper( + QPainter &p, + const Paper &paper, + int column, + int row) const { + const auto x = st::backgroundPadding + column * (st::backgroundSize.width() + st::backgroundPadding); + const auto y = st::backgroundPadding + row * (st::backgroundSize.height() + st::backgroundPadding); + validatePaperThumbnail(paper); + if (!paper.thumbnail.isNull()) { + p.drawPixmap(x, y, paper.thumbnail); + } + + const auto over = !v::is_null(_overDown) ? _overDown : _over; + if (paper.data.id() == _currentId) { + const auto checkLeft = x + st::backgroundSize.width() - st::overviewCheckSkip - st::overviewCheck.size; + const auto checkTop = y + st::backgroundSize.height() - st::overviewCheckSkip - st::overviewCheck.size; + _check->paint(p, checkLeft, checkTop, width()); + } else if (Data::IsCloudWallPaper(paper.data) + && !Data::IsDefaultWallPaper(paper.data) + && !Data::IsLegacy2DefaultWallPaper(paper.data) + && !Data::IsLegacy3DefaultWallPaper(paper.data) + && !v::is_null(over) + && (&paper == &_papers[getSelectionIndex(over)])) { + const auto deleteSelected = v::is(over); + const auto deletePos = QPoint(x + st::backgroundSize.width() - st::stickerPanDeleteIconBg.width(), y); + p.setOpacity(deleteSelected ? st::stickerPanDeleteOpacityBgOver : st::stickerPanDeleteOpacityBg); + st::stickerPanDeleteIconBg.paint(p, deletePos, width()); + p.setOpacity(deleteSelected ? st::stickerPanDeleteOpacityFgOver : st::stickerPanDeleteOpacityFg); + st::stickerPanDeleteIconFg.paint(p, deletePos, width()); + p.setOpacity(1.); + } +} + +void BackgroundBox::Inner::mouseMoveEvent(QMouseEvent *e) { + const auto newOver = [&] { + const auto x = e->pos().x(); + const auto y = e->pos().y(); + const auto width = st::backgroundSize.width(); + const auto height = st::backgroundSize.height(); + const auto skip = st::backgroundPadding; + const auto row = int((y - skip) / (height + skip)); + const auto column = int((x - skip) / (width + skip)); + const auto result = row * kBackgroundsInRow + column; + if (y - row * (height + skip) > skip + height) { + return Selection(); + } else if (x - column * (width + skip) > skip + width) { + return Selection(); + } else if (result >= _papers.size()) { + return Selection(); + } + auto &data = _papers[result].data; + const auto deleteLeft = (column + 1) * (width + skip) + - st::stickerPanDeleteIconBg.width(); + const auto deleteBottom = row * (height + skip) + skip + + st::stickerPanDeleteIconBg.height(); + const auto inDelete = (x >= deleteLeft) + && (y < deleteBottom) + && Data::IsCloudWallPaper(data) + && !Data::IsDefaultWallPaper(data) + && !Data::IsLegacy2DefaultWallPaper(data) + && !Data::IsLegacy3DefaultWallPaper(data) + && (_currentId != data.id()); + return (result >= _papers.size()) + ? Selection() + : inDelete + ? Selection(DeleteSelected{ result }) + : Selection(Selected{ result }); + }(); + if (_over != newOver) { + repaintPaper(getSelectionIndex(_over)); + _over = newOver; + repaintPaper(getSelectionIndex(_over)); + setCursor((!v::is_null(_over) || !v::is_null(_overDown)) + ? style::cur_pointer + : style::cur_default); + } +} + +void BackgroundBox::Inner::repaintPaper(int index) { + if (index < 0 || index >= _papers.size()) { + return; + } + const auto row = (index / kBackgroundsInRow); + const auto column = (index % kBackgroundsInRow); + const auto width = st::backgroundSize.width(); + const auto height = st::backgroundSize.height(); + const auto skip = st::backgroundPadding; + update( + (width + skip) * column + skip, + (height + skip) * row + skip, + width, + height); +} + +void BackgroundBox::Inner::mousePressEvent(QMouseEvent *e) { + _overDown = _over; +} + +int BackgroundBox::Inner::getSelectionIndex( + const Selection &selection) const { + return v::match(selection, [](const Selected &data) { + return data.index; + }, [](const DeleteSelected &data) { + return data.index; + }, [](v::null_t) { + return -1; + }); +} + +void BackgroundBox::Inner::mouseReleaseEvent(QMouseEvent *e) { + if (base::take(_overDown) == _over && !v::is_null(_over)) { + const auto index = getSelectionIndex(_over); + if (index >= 0 && index < _papers.size()) { + if (std::get_if(&_over)) { + _backgroundRemove.fire_copy(_papers[index].data); + } else if (std::get_if(&_over)) { + auto &paper = _papers[index]; + if (!paper.dataMedia) { + if (const auto document = paper.data.document()) { + // Keep it alive while it is on the screen. + paper.dataMedia = document->createMediaView(); + } + } + _backgroundChosen.fire_copy(paper.data); + } + } + } else if (v::is_null(_over)) { + setCursor(style::cur_default); + } +} + +void BackgroundBox::Inner::visibleTopBottomUpdated( + int visibleTop, + int visibleBottom) { + for (auto i = 0, count = int(_papers.size()); i != count; ++i) { + const auto row = (i / kBackgroundsInRow); + const auto height = st::backgroundSize.height(); + const auto skip = st::backgroundPadding; + const auto top = skip + row * (height + skip); + const auto bottom = top + height; + if ((bottom <= visibleTop || top >= visibleBottom) + && !_papers[i].thumbnail.isNull()) { + _papers[i].dataMedia = nullptr; + } + } +} + +rpl::producer BackgroundBox::Inner::chooseEvents() const { + return _backgroundChosen.events(); +} + +auto BackgroundBox::Inner::removeRequests() const +-> rpl::producer { + return _backgroundRemove.events(); +} + +void BackgroundBox::Inner::removePaper(const Data::WallPaper &data) { + const auto i = ranges::find( + _papers, + data.id(), + [](const Paper &paper) { return paper.data.id(); }); + if (i != end(_papers)) { + _papers.erase(i); + _over = _overDown = Selection(); + resizeToContentAndPreload(); + } +} + +BackgroundBox::Inner::~Inner() = default; diff --git a/src/testdir/samples/matchparen.vim b/src/testdir/samples/matchparen.vim new file mode 100644 --- /dev/null +++ b/src/testdir/samples/matchparen.vim @@ -0,0 +1,234 @@ +" Vim plugin for showing matching parens +" Maintainer: The Vim Project +" Last Change: 2023 Oct 20 +" Former Maintainer: Bram Moolenaar + +" Exit quickly when: +" - this plugin was already loaded (or disabled) +" - when 'compatible' is set +if exists("g:loaded_matchparen") || &cp + finish +endif +let g:loaded_matchparen = 1 + +if !exists("g:matchparen_timeout") + let g:matchparen_timeout = 300 +endif +if !exists("g:matchparen_insert_timeout") + let g:matchparen_insert_timeout = 60 +endif + +let s:has_matchaddpos = exists('*matchaddpos') + +augroup matchparen + " Replace all matchparen autocommands + autocmd! CursorMoved,CursorMovedI,WinEnter,BufWinEnter,WinScrolled * call s:Highlight_Matching_Pair() + autocmd! WinLeave,BufLeave * call s:Remove_Matches() + if exists('##TextChanged') + autocmd! TextChanged,TextChangedI * call s:Highlight_Matching_Pair() + autocmd! TextChangedP * call s:Remove_Matches() + endif +augroup END + +" Skip the rest if it was already done. +if exists("*s:Highlight_Matching_Pair") + finish +endif + +let s:cpo_save = &cpo +set cpo-=C + +" The function that is invoked (very often) to define a ":match" highlighting +" for any matching paren. +func s:Highlight_Matching_Pair() + if !exists("w:matchparen_ids") + let w:matchparen_ids = [] + endif + " Remove any previous match. + call s:Remove_Matches() + + " Avoid that we remove the popup menu. + " Return when there are no colors (looks like the cursor jumps). + if pumvisible() || (&t_Co < 8 && !has("gui_running")) + return + endif + + " Get the character under the cursor and check if it's in 'matchpairs'. + let c_lnum = line('.') + let c_col = col('.') + let before = 0 + + let text = getline(c_lnum) + let matches = matchlist(text, '\(.\)\=\%'.c_col.'c\(.\=\)') + if empty(matches) + let [c_before, c] = ['', ''] + else + let [c_before, c] = matches[1:2] + endif + let plist = split(&matchpairs, '.\zs[:,]') + let i = index(plist, c) + if i < 0 + " not found, in Insert mode try character before the cursor + if c_col > 1 && (mode() == 'i' || mode() == 'R') + let before = strlen(c_before) + let c = c_before + let i = index(plist, c) + endif + if i < 0 + " not found, nothing to do + return + endif + endif + + " Figure out the arguments for searchpairpos(). + if i % 2 == 0 + let s_flags = 'nW' + let c2 = plist[i + 1] + else + let s_flags = 'nbW' + let c2 = c + let c = plist[i - 1] + endif + if c == '[' + let c = '\[' + let c2 = '\]' + endif + + " Find the match. When it was just before the cursor move it there for a + " moment. + if before > 0 + let has_getcurpos = exists("*getcurpos") + if has_getcurpos + " getcurpos() is more efficient but doesn't exist before 7.4.313. + let save_cursor = getcurpos() + else + let save_cursor = winsaveview() + endif + call cursor(c_lnum, c_col - before) + endif + + if !has("syntax") || !exists("g:syntax_on") + let s_skip = "0" + else + " Build an expression that detects whether the current cursor position is + " in certain syntax types (string, comment, etc.), for use as + " searchpairpos()'s skip argument. + " We match "escape" for special items, such as lispEscapeSpecial, and + " match "symbol" for lispBarSymbol. + let s_skip = 'synstack(".", col("."))' + \ . '->indexof({_, id -> synIDattr(id, "name") =~? ' + \ . '"string\\|character\\|singlequote\\|escape\\|symbol\\|comment"}) >= 0' + " If executing the expression determines that the cursor is currently in + " one of the syntax types, then we want searchpairpos() to find the pair + " within those syntax types (i.e., not skip). Otherwise, the cursor is + " outside of the syntax types and s_skip should keep its value so we skip + " any matching pair inside the syntax types. + " Catch if this throws E363: pattern uses more memory than 'maxmempattern'. + try + execute 'if ' . s_skip . ' | let s_skip = "0" | endif' + catch /^Vim\%((\a\+)\)\=:E363/ + " We won't find anything, so skip searching, should keep Vim responsive. + return + endtry + endif + + " Limit the search to lines visible in the window. + let stoplinebottom = line('w$') + let stoplinetop = line('w0') + if i % 2 == 0 + let stopline = stoplinebottom + else + let stopline = stoplinetop + endif + + " Limit the search time to 300 msec to avoid a hang on very long lines. + " This fails when a timeout is not supported. + if mode() == 'i' || mode() == 'R' + let timeout = exists("b:matchparen_insert_timeout") ? b:matchparen_insert_timeout : g:matchparen_insert_timeout + else + let timeout = exists("b:matchparen_timeout") ? b:matchparen_timeout : g:matchparen_timeout + endif + try + let [m_lnum, m_col] = searchpairpos(c, '', c2, s_flags, s_skip, stopline, timeout) + catch /E118/ + " Can't use the timeout, restrict the stopline a bit more to avoid taking + " a long time on closed folds and long lines. + " The "viewable" variables give a range in which we can scroll while + " keeping the cursor at the same position. + " adjustedScrolloff accounts for very large numbers of scrolloff. + let adjustedScrolloff = min([&scrolloff, (line('w$') - line('w0')) / 2]) + let bottom_viewable = min([line('$'), c_lnum + &lines - adjustedScrolloff - 2]) + let top_viewable = max([1, c_lnum-&lines+adjustedScrolloff + 2]) + " one of these stoplines will be adjusted below, but the current values are + " minimal boundaries within the current window + if i % 2 == 0 + if has("byte_offset") && has("syntax_items") && &smc > 0 + let stopbyte = min([line2byte("$"), line2byte(".") + col(".") + &smc * 2]) + let stopline = min([bottom_viewable, byte2line(stopbyte)]) + else + let stopline = min([bottom_viewable, c_lnum + 100]) + endif + let stoplinebottom = stopline + else + if has("byte_offset") && has("syntax_items") && &smc > 0 + let stopbyte = max([1, line2byte(".") + col(".") - &smc * 2]) + let stopline = max([top_viewable, byte2line(stopbyte)]) + else + let stopline = max([top_viewable, c_lnum - 100]) + endif + let stoplinetop = stopline + endif + let [m_lnum, m_col] = searchpairpos(c, '', c2, s_flags, s_skip, stopline) + endtry + + if before > 0 + if has_getcurpos + call setpos('.', save_cursor) + else + call winrestview(save_cursor) + endif + endif + + " If a match is found setup match highlighting. + if m_lnum > 0 && m_lnum >= stoplinetop && m_lnum <= stoplinebottom + if s:has_matchaddpos + call add(w:matchparen_ids, matchaddpos('MatchParen', [[c_lnum, c_col - before], [m_lnum, m_col]], 10)) + else + exe '3match MatchParen /\(\%' . c_lnum . 'l\%' . (c_col - before) . + \ 'c\)\|\(\%' . m_lnum . 'l\%' . m_col . 'c\)/' + call add(w:matchparen_ids, 3) + endif + let w:paren_hl_on = 1 + endif +endfunction + +func s:Remove_Matches() + if exists('w:paren_hl_on') && w:paren_hl_on + while !empty(w:matchparen_ids) + silent! call remove(w:matchparen_ids, 0)->matchdelete() + endwhile + let w:paren_hl_on = 0 + endif +endfunc + +" Define commands that will disable and enable the plugin. +command DoMatchParen call s:DoMatchParen() +command NoMatchParen call s:NoMatchParen() + +func s:NoMatchParen() + let w = winnr() + noau windo silent! call matchdelete(3) + unlet! g:loaded_matchparen + exe "noau ". w . "wincmd w" + au! matchparen +endfunc + +func s:DoMatchParen() + runtime plugin/matchparen.vim + let w = winnr() + silent windo doau CursorMoved + exe "noau ". w . "wincmd w" +endfunc + +let &cpo = s:cpo_save +unlet s:cpo_save diff --git a/src/testdir/test_delete.vim b/src/testdir/test_delete.vim --- a/src/testdir/test_delete.vim +++ b/src/testdir/test_delete.vim @@ -1,6 +1,8 @@ " Test for delete(). source check.vim +source term_util.vim +source screendump.vim func Test_file_delete() split Xfdelfile @@ -107,4 +109,25 @@ func Test_delete_errors() call assert_fails('call delete(''foo'', 0)', 'E15:') endfunc +" This should no longer trigger ml_get errors +func Test_delete_ml_get_errors() + CheckRunVimInTerminal + let lines =<< trim END + set noshowcmd noruler scrolloff=0 + source samples/matchparen.vim + END + call writefile(lines, 'XDelete_ml_get_error', 'D') + let buf = RunVimInTerminal('-S XDelete_ml_get_error samples/box.txt', #{rows: 10, wait_for_ruler: 0}) + call TermWait(buf) + call term_sendkeys(buf, "249GV\d") + call TermWait(buf) + " The following used to trigger ml_get errors + call term_sendkeys(buf, "\") + call TermWait(buf) + call term_sendkeys(buf, ":mess\") + call VerifyScreenDump(buf, 'Test_delete_ml_get_errors_1', {}) + call term_sendkeys(buf, ":q!\") + call StopVimInTerminal(buf) +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -705,6 +705,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 2134, +/**/ 2133, /**/ 2132, diff --git a/src/window.c b/src/window.c --- a/src/window.c +++ b/src/window.c @@ -7441,12 +7441,16 @@ reset_lnums(void) { // Restore the value if the autocommand didn't change it and it was // set. + // Note: This triggers e.g. on BufReadPre, when the buffer is not yet + // loaded, so cannot validate the buffer line if (EQUAL_POS(wp->w_save_cursor.w_cursor_corr, wp->w_cursor) && wp->w_save_cursor.w_cursor_save.lnum != 0) wp->w_cursor = wp->w_save_cursor.w_cursor_save; if (wp->w_save_cursor.w_topline_corr == wp->w_topline - && wp->w_save_cursor.w_topline_save != 0) + && wp->w_save_cursor.w_topline_save != 0) wp->w_topline = wp->w_save_cursor.w_topline_save; + if (wp->w_save_cursor.w_topline_save > wp->w_buffer->b_ml.ml_line_count) + wp->w_valid &= ~VALID_TOPLINE; } }