diff options
-rw-r--r-- | Makefile | 1 | ||||
-rwxr-xr-x | src/clint.py | 1 | ||||
-rw-r--r-- | src/nvim/api/vim.c | 1 | ||||
-rw-r--r-- | src/nvim/bufwrite.c | 1 | ||||
-rw-r--r-- | src/nvim/drawscreen.c | 1 | ||||
-rw-r--r-- | src/nvim/ex_cmds_defs.h | 2 | ||||
-rw-r--r-- | src/nvim/mouse.h | 9 | ||||
-rw-r--r-- | src/nvim/normal.h | 81 | ||||
-rw-r--r-- | src/nvim/normal_defs.h | 69 | ||||
-rw-r--r-- | src/nvim/option.h | 2 | ||||
-rw-r--r-- | src/nvim/os/shell.c | 1 | ||||
-rw-r--r-- | src/nvim/search.h | 5 | ||||
-rw-r--r-- | src/nvim/spell.c | 1 | ||||
-rw-r--r-- | src/nvim/spellsuggest.c | 1 | ||||
-rw-r--r-- | src/nvim/tag.c | 1 | ||||
-rw-r--r-- | src/nvim/textformat.c | 1 | ||||
-rw-r--r-- | src/nvim/textformat.h | 2 | ||||
-rw-r--r-- | src/nvim/textobject.c | 1 | ||||
-rw-r--r-- | src/nvim/textobject.h | 4 | ||||
-rw-r--r-- | src/nvim/window.c | 9 | ||||
-rw-r--r-- | test/functional/autocmd/autocmd_oldtest_spec.lua | 42 | ||||
-rw-r--r-- | test/old/testdir/samples/box.txt | 601 | ||||
-rw-r--r-- | test/old/testdir/samples/matchparen.vim | 234 | ||||
-rw-r--r-- | test/old/testdir/test_delete.vim | 23 |
24 files changed, 996 insertions, 98 deletions
@@ -224,7 +224,6 @@ iwyu: build/.ran-cmake |src/nvim/search.h\ |src/nvim/spell.h\ |src/nvim/syntax.h\ - |src/nvim/textformat.h\ |src/nvim/textobject.h\ |src/nvim/tui/input.h\ |src/nvim/tui/tui.h\ diff --git a/src/clint.py b/src/clint.py index 91fee4917a..ef2ac57a87 100755 --- a/src/clint.py +++ b/src/clint.py @@ -979,7 +979,6 @@ def CheckIncludes(filename, lines, error): "src/nvim/search.h", "src/nvim/spell.h", "src/nvim/syntax.h", - "src/nvim/textformat.h", "src/nvim/textobject.h", "src/nvim/tui/input.h", "src/nvim/tui/tui.h", diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index fdf12cba9e..d631b10af9 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -55,7 +55,6 @@ #include "nvim/option_vars.h" #include "nvim/optionstr.h" #include "nvim/os/input.h" -#include "nvim/os/os_defs.h" #include "nvim/os/process.h" #include "nvim/popupmenu.h" #include "nvim/pos_defs.h" diff --git a/src/nvim/bufwrite.c b/src/nvim/bufwrite.c index 0525e9ccc4..178218729a 100644 --- a/src/nvim/bufwrite.c +++ b/src/nvim/bufwrite.c @@ -38,7 +38,6 @@ #include "nvim/option_vars.h" #include "nvim/os/fs.h" #include "nvim/os/input.h" -#include "nvim/os/os.h" #include "nvim/path.h" #include "nvim/pos_defs.h" #include "nvim/sha256.h" diff --git a/src/nvim/drawscreen.c b/src/nvim/drawscreen.c index 8873dc01b1..ea33c39178 100644 --- a/src/nvim/drawscreen.c +++ b/src/nvim/drawscreen.c @@ -92,7 +92,6 @@ #include "nvim/normal.h" #include "nvim/option.h" #include "nvim/option_vars.h" -#include "nvim/os/os_defs.h" #include "nvim/plines.h" #include "nvim/popupmenu.h" #include "nvim/pos_defs.h" diff --git a/src/nvim/ex_cmds_defs.h b/src/nvim/ex_cmds_defs.h index ffdb4ba6af..00363884ec 100644 --- a/src/nvim/ex_cmds_defs.h +++ b/src/nvim/ex_cmds_defs.h @@ -5,7 +5,7 @@ #include "nvim/eval/typval_defs.h" #include "nvim/ex_eval_defs.h" -#include "nvim/normal.h" +#include "nvim/normal_defs.h" #include "nvim/pos_defs.h" #include "nvim/regexp_defs.h" diff --git a/src/nvim/mouse.h b/src/nvim/mouse.h index 5d2475005e..928b3e360b 100644 --- a/src/nvim/mouse.h +++ b/src/nvim/mouse.h @@ -1,11 +1,10 @@ #pragma once -#include <stdbool.h> - -#include "nvim/buffer_defs.h" -#include "nvim/normal.h" +#include "nvim/buffer_defs.h" // IWYU pragma: keep +#include "nvim/eval/typval_defs.h" // IWYU pragma: keep +#include "nvim/normal_defs.h" // IWYU pragma: keep +#include "nvim/types_defs.h" // IWYU pragma: keep #include "nvim/vim_defs.h" -#include "nvim/window.h" /// jump_to_mouse() returns one of first five these values, possibly with /// some of the other five added. diff --git a/src/nvim/normal.h b/src/nvim/normal.h index 9fb332d320..dbe74712fc 100644 --- a/src/nvim/normal.h +++ b/src/nvim/normal.h @@ -1,81 +1,18 @@ #pragma once -#include <stdbool.h> - #include "nvim/buffer_defs.h" // IWYU pragma: keep #include "nvim/macros_defs.h" -#include "nvim/pos_defs.h" - -// Values for find_ident_under_cursor() -#define FIND_IDENT 1 // find identifier (word) -#define FIND_STRING 2 // find any string (WORD) -#define FIND_EVAL 4 // include "->", "[]" and "." - -/// Motion types, used for operators and for yank/delete registers. -/// -/// The three valid numerical values must not be changed, as they -/// are used in external communication and serialization. -typedef enum { - kMTCharWise = 0, ///< character-wise movement/register - kMTLineWise = 1, ///< line-wise movement/register - kMTBlockWise = 2, ///< block-wise movement/register - kMTUnknown = -1, ///< Unknown or invalid motion type -} MotionType; - -// Arguments for operators. -typedef struct oparg_S { - int op_type; // current pending operator type - int regname; // register to use for the operator - MotionType motion_type; // type of the current cursor motion - int motion_force; // force motion type: 'v', 'V' or CTRL-V - bool use_reg_one; // true if delete uses reg 1 even when not - // linewise - bool inclusive; // true if char motion is inclusive (only - // valid when motion_type is kMTCharWise) - bool end_adjusted; // backuped b_op_end one char (only used by - // do_format()) - pos_T start; // start of the operator - pos_T end; // end of the operator - pos_T cursor_start; // cursor position before motion for "gw" - - linenr_T line_count; // number of lines from op_start to op_end - // (inclusive) - bool empty; // op_start and op_end the same (only used by - // op_change()) - bool is_VIsual; // operator on Visual area - colnr_T start_vcol; // start col for block mode operator - colnr_T end_vcol; // end col for block mode operator - int prev_opcount; // ca.opcount saved for K_EVENT - int prev_count0; // ca.count0 saved for K_EVENT - bool excl_tr_ws; // exclude trailing whitespace for yank of a - // block -} oparg_T; - -// Arguments for Normal mode commands. -typedef struct cmdarg_S { - oparg_T *oap; // Operator arguments - int prechar; // prefix character (optional, always 'g') - int cmdchar; // command character - int nchar; // next command character (optional) - int ncharC1; // first composing character (optional) - int ncharC2; // second composing character (optional) - int extra_char; // yet another character (optional) - int opcount; // count before an operator - int count0; // count before command, default 0 - int count1; // count before command, default 1 - int arg; // extra argument from nv_cmds[] - int retval; // return: CA_* values - char *searchbuf; // return: pointer to search pattern or NULL -} cmdarg_T; +#include "nvim/normal_defs.h" // IWYU pragma: export +#include "nvim/pos_defs.h" // IWYU pragma: keep -// values for retval: -#define CA_COMMAND_BUSY 1 // skip restarting edit() once -#define CA_NO_ADJ_OP_END 2 // don't adjust operator end +/// Values for find_ident_under_cursor() +enum { + FIND_IDENT = 1, ///< find identifier (word) + FIND_STRING = 2, ///< find any string (WORD) + FIND_EVAL = 4, ///< include "->", "[]" and "." +}; -// columns needed by shown command -#define SHOWCMD_COLS 10 -// 'showcmd' buffer shared between normal.c and statusline.c -#define SHOWCMD_BUFLEN (SHOWCMD_COLS + 1 + 30) +/// 'showcmd' buffer shared between normal.c and statusline.c EXTERN char showcmd_buf[SHOWCMD_BUFLEN]; #ifdef INCLUDE_GENERATED_DECLARATIONS diff --git a/src/nvim/normal_defs.h b/src/nvim/normal_defs.h new file mode 100644 index 0000000000..dcb7aa6186 --- /dev/null +++ b/src/nvim/normal_defs.h @@ -0,0 +1,69 @@ +#pragma once + +#include <stdbool.h> + +#include "nvim/pos_defs.h" + +/// Motion types, used for operators and for yank/delete registers. +/// +/// The three valid numerical values must not be changed, as they +/// are used in external communication and serialization. +typedef enum { + kMTCharWise = 0, ///< character-wise movement/register + kMTLineWise = 1, ///< line-wise movement/register + kMTBlockWise = 2, ///< block-wise movement/register + kMTUnknown = -1, ///< Unknown or invalid motion type +} MotionType; + +/// Arguments for operators. +typedef struct oparg_S { + int op_type; ///< current pending operator type + int regname; ///< register to use for the operator + MotionType motion_type; ///< type of the current cursor motion + int motion_force; ///< force motion type: 'v', 'V' or CTRL-V + bool use_reg_one; ///< true if delete uses reg 1 even when not + ///< linewise + bool inclusive; ///< true if char motion is inclusive (only + ///< valid when motion_type is kMTCharWise) + bool end_adjusted; ///< backuped b_op_end one char (only used by + ///< do_format()) + pos_T start; ///< start of the operator + pos_T end; ///< end of the operator + pos_T cursor_start; ///< cursor position before motion for "gw" + + linenr_T line_count; ///< number of lines from op_start to op_end (inclusive) + bool empty; ///< op_start and op_end the same (only used by op_change()) + bool is_VIsual; ///< operator on Visual area + colnr_T start_vcol; ///< start col for block mode operator + colnr_T end_vcol; ///< end col for block mode operator + int prev_opcount; ///< ca.opcount saved for K_EVENT + int prev_count0; ///< ca.count0 saved for K_EVENT + bool excl_tr_ws; ///< exclude trailing whitespace for yank of a block +} oparg_T; + +/// Arguments for Normal mode commands. +typedef struct cmdarg_S { + oparg_T *oap; ///< Operator arguments + int prechar; ///< prefix character (optional, always 'g') + int cmdchar; ///< command character + int nchar; ///< next command character (optional) + int ncharC1; ///< first composing character (optional) + int ncharC2; ///< second composing character (optional) + int extra_char; ///< yet another character (optional) + int opcount; ///< count before an operator + int count0; ///< count before command, default 0 + int count1; ///< count before command, default 1 + int arg; ///< extra argument from nv_cmds[] + int retval; ///< return: CA_* values + char *searchbuf; ///< return: pointer to search pattern or NULL +} cmdarg_T; + +/// values for retval: +enum { + CA_COMMAND_BUSY = 1, ///< skip restarting edit() once + CA_NO_ADJ_OP_END = 2, ///< don't adjust operator end +}; + +/// columns needed by shown command +enum { SHOWCMD_COLS = 10, }; +enum { SHOWCMD_BUFLEN = SHOWCMD_COLS + 1 + 30, }; diff --git a/src/nvim/option.h b/src/nvim/option.h index 4d89d354f9..ebf8e0417d 100644 --- a/src/nvim/option.h +++ b/src/nvim/option.h @@ -1,6 +1,7 @@ #pragma once #include <stdint.h> +#include <stdio.h> // IWYU pragma: keep #include "nvim/api/private/defs.h" // IWYU pragma: keep #include "nvim/api/private/helpers.h" @@ -8,7 +9,6 @@ #include "nvim/eval/typval_defs.h" #include "nvim/ex_cmds_defs.h" // IWYU pragma: keep #include "nvim/option_defs.h" // IWYU pragma: export -#include "nvim/search.h" #include "nvim/types_defs.h" // IWYU pragma: keep /// The options that are local to a window or buffer have "indir" set to one of diff --git a/src/nvim/os/shell.c b/src/nvim/os/shell.c index 0aaa7be2b8..994c67ae4c 100644 --- a/src/nvim/os/shell.c +++ b/src/nvim/os/shell.c @@ -31,7 +31,6 @@ #include "nvim/message.h" #include "nvim/option_vars.h" #include "nvim/os/fs.h" -#include "nvim/os/os_defs.h" #include "nvim/os/shell.h" #include "nvim/os/signal.h" #include "nvim/os/time.h" diff --git a/src/nvim/search.h b/src/nvim/search.h index c86bb2700f..48ca555e7f 100644 --- a/src/nvim/search.h +++ b/src/nvim/search.h @@ -5,9 +5,10 @@ #include "nvim/buffer_defs.h" #include "nvim/eval/typval_defs.h" -#include "nvim/normal.h" -#include "nvim/os/time.h" +#include "nvim/normal_defs.h" // IWYU pragma: keep +#include "nvim/os/time_defs.h" #include "nvim/pos_defs.h" +#include "nvim/regexp_defs.h" // IWYU pragma: keep #include "nvim/types_defs.h" #include "nvim/vim_defs.h" diff --git a/src/nvim/spell.c b/src/nvim/spell.c index 0b4f363d64..905f5c25b4 100644 --- a/src/nvim/spell.c +++ b/src/nvim/spell.c @@ -91,7 +91,6 @@ #include "nvim/option_vars.h" #include "nvim/os/fs.h" #include "nvim/os/input.h" -#include "nvim/os/os_defs.h" #include "nvim/path.h" #include "nvim/pos_defs.h" #include "nvim/regexp.h" diff --git a/src/nvim/spellsuggest.c b/src/nvim/spellsuggest.c index 0a48e613ed..d9dd28527e 100644 --- a/src/nvim/spellsuggest.c +++ b/src/nvim/spellsuggest.c @@ -36,7 +36,6 @@ #include "nvim/option_vars.h" #include "nvim/os/fs.h" #include "nvim/os/input.h" -#include "nvim/os/os_defs.h" #include "nvim/pos_defs.h" #include "nvim/profile.h" #include "nvim/spell.h" diff --git a/src/nvim/tag.c b/src/nvim/tag.c index 3b6a1bfe85..f99f0dc612 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -44,7 +44,6 @@ #include "nvim/optionstr.h" #include "nvim/os/fs.h" #include "nvim/os/input.h" -#include "nvim/os/os_defs.h" #include "nvim/os/time.h" #include "nvim/path.h" #include "nvim/pos_defs.h" diff --git a/src/nvim/textformat.c b/src/nvim/textformat.c index 25a197d401..64c698f48f 100644 --- a/src/nvim/textformat.c +++ b/src/nvim/textformat.c @@ -26,7 +26,6 @@ #include "nvim/memory.h" #include "nvim/message.h" #include "nvim/move.h" -#include "nvim/normal.h" #include "nvim/ops.h" #include "nvim/option.h" #include "nvim/option_vars.h" diff --git a/src/nvim/textformat.h b/src/nvim/textformat.h index b86df20f13..25e7152f1b 100644 --- a/src/nvim/textformat.h +++ b/src/nvim/textformat.h @@ -1,6 +1,6 @@ #pragma once -#include "nvim/normal.h" +#include "nvim/normal_defs.h" // IWYU pragma: keep #include "nvim/pos_defs.h" // IWYU pragma: keep #ifdef INCLUDE_GENERATED_DECLARATIONS diff --git a/src/nvim/textobject.c b/src/nvim/textobject.c index 3a0a3cc659..d4310d47a4 100644 --- a/src/nvim/textobject.c +++ b/src/nvim/textobject.c @@ -20,7 +20,6 @@ #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/move.h" -#include "nvim/normal.h" #include "nvim/option_vars.h" #include "nvim/pos_defs.h" #include "nvim/search.h" diff --git a/src/nvim/textobject.h b/src/nvim/textobject.h index 2ca5be43b7..a540c7c98f 100644 --- a/src/nvim/textobject.h +++ b/src/nvim/textobject.h @@ -1,7 +1,7 @@ #pragma once -#include "nvim/normal.h" -#include "nvim/pos_defs.h" +#include "nvim/normal_defs.h" // IWYU pragma: keep +#include "nvim/pos_defs.h" // IWYU pragma: keep #include "nvim/vim_defs.h" #ifdef INCLUDE_GENERATED_DECLARATIONS diff --git a/src/nvim/window.c b/src/nvim/window.c index ff62531dc7..bcf245ef93 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -57,7 +57,6 @@ #include "nvim/option_defs.h" #include "nvim/option_vars.h" #include "nvim/os/fs.h" -#include "nvim/os/os_defs.h" #include "nvim/path.h" #include "nvim/plines.h" #include "nvim/pos_defs.h" @@ -7102,8 +7101,9 @@ void reset_lnums(void) { FOR_ALL_TAB_WINDOWS(tp, wp) { if (wp->w_buffer == curbuf) { - // Restore the value if the autocommand didn't change it and it was - // set. + // 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 (equalpos(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; @@ -7112,6 +7112,9 @@ void reset_lnums(void) && 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; + } } } } diff --git a/test/functional/autocmd/autocmd_oldtest_spec.lua b/test/functional/autocmd/autocmd_oldtest_spec.lua index 29a6171269..dfd746a06e 100644 --- a/test/functional/autocmd/autocmd_oldtest_spec.lua +++ b/test/functional/autocmd/autocmd_oldtest_spec.lua @@ -1,10 +1,12 @@ local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') local clear = helpers.clear local eq = helpers.eq local meths = helpers.meths local funcs = helpers.funcs local exec = helpers.exec +local feed = helpers.feed describe('oldtests', function() before_each(clear) @@ -81,4 +83,44 @@ describe('oldtests', function() funcs.delete(fname) funcs.delete('Xout') end) + + -- oldtest: Test_delete_ml_get_errors() + it('no ml_get error with TextChanged autocommand and delete', function() + local screen = Screen.new(75, 10) + screen:attach() + screen:set_default_attr_ids({ + [1] = {background = Screen.colors.Cyan}; + }) + exec([[ + set noshowcmd noruler scrolloff=0 + source test/old/testdir/samples/matchparen.vim + edit test/old/testdir/samples/box.txt + ]]) + feed('249GV<C-End>d') + screen:expect{grid=[[ + const auto themeEmoji = _forPeer->themeEmoji(); | + if (themeEmoji.isEmpty()) { | + return nonCustom; | + } | + const auto &themes = _forPeer->owner().cloudThemes(); | + const auto theme = themes.themeForEmoji(themeEmoji); | + if (!theme) {1:{} | + return nonCustom; | + {1:^}} | + 353 fewer lines | + ]]} + feed('<PageUp>') + screen:expect{grid=[[ + | + auto BackgroundBox::Inner::resolveResetCustomPaper() const | + -> std::optional<Data::WallPaper> { | + if (!_forPeer) { | + return {}; | + } | + const auto nonCustom = Window::Theme::Background()->paper(); | + const auto themeEmoji = _forPeer->themeEmoji(); | + ^if (themeEmoji.isEmpty()) { | + 353 fewer lines | + ]]} + end) end) diff --git a/test/old/testdir/samples/box.txt b/test/old/testdir/samples/box.txt new file mode 100644 index 0000000000..21c7d4f3d5 --- /dev/null +++ b/test/old/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<Ui::VerticalLayout>(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<Info::Profile::FloatingIcon>( + button, + st::infoIconMediaPhoto, + st::infoSharedMediaButtonIconPosition); + + button->setClickedCallback([=] { + chooseFromFile(); + }); + + Settings::AddSkip(container); + Settings::AddDivider(container); + + _inner = container->add( + object_ptr<Inner>(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<Image>( + std::move(image))); + _controller->show(Box<BackgroundPreviewBox>( + _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<void()> 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<BackgroundPreviewBox>( + _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<void()> &&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<Main::Session*> session, + PeerData *forPeer) +: RpWidget(parent) +, _session(session) +, _forPeer(forPeer) +, _api(&_session->mtp()) +, _check(std::make_unique<Ui::RoundCheckbox>(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<Data::WallPaper> { + 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<DeleteSelected>(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<DeleteSelected>(&_over)) { + _backgroundRemove.fire_copy(_papers[index].data); + } else if (std::get_if<Selected>(&_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<Data::WallPaper> BackgroundBox::Inner::chooseEvents() const { + return _backgroundChosen.events(); +} + +auto BackgroundBox::Inner::removeRequests() const +-> rpl::producer<Data::WallPaper> { + 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/test/old/testdir/samples/matchparen.vim b/test/old/testdir/samples/matchparen.vim new file mode 100644 index 0000000000..4235a0d39b --- /dev/null +++ b/test/old/testdir/samples/matchparen.vim @@ -0,0 +1,234 @@ +" Vim plugin for showing matching parens +" Maintainer: The Vim Project <https://github.com/vim/vim> +" Last Change: 2023 Oct 20 +" Former Maintainer: Bram Moolenaar <Bram@vim.org> + +" 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/test/old/testdir/test_delete.vim b/test/old/testdir/test_delete.vim index b05c27d626..c00b8ca47d 100644 --- a/test/old/testdir/test_delete.vim +++ b/test/old/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\<C-End>d") + call TermWait(buf) + " The following used to trigger ml_get errors + call term_sendkeys(buf, "\<PageUp>") + call TermWait(buf) + call term_sendkeys(buf, ":mess\<cr>") + call VerifyScreenDump(buf, 'Test_delete_ml_get_errors_1', {}) + call term_sendkeys(buf, ":q!\<cr>") + call StopVimInTerminal(buf) +endfunc + " vim: shiftwidth=2 sts=2 expandtab |