From 89e29e8774ed953048ab746d4a83a6f08d850d17 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Mon, 10 May 2021 23:00:00 -0400 Subject: [PATCH 01/16] fixup! refactor nvim_buf_set_lines to use extmark_splice --- src/nvim/api/buffer.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index ebc9aeb75f..11a4647d1c 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -477,7 +477,7 @@ void nvim_buf_set_lines(uint64_t channel_id, goto end; } - inserted_bytes += STRLEN(lines[i]) + 1; + inserted_bytes += (bcount_t)strlen(lines[i]) + 1; // Mark lines that haven't been passed to the buffer as they need // to be freed later lines[i] = NULL; @@ -497,7 +497,7 @@ void nvim_buf_set_lines(uint64_t channel_id, goto end; } - inserted_bytes += STRLEN(lines[i]) + 1; + inserted_bytes += (bcount_t)strlen(lines[i]) + 1; // Same as with replacing, but we also need to free lines xfree(lines[i]); From dad725d5e578c5dbef5a0bf503fa1b41aaf3ff6b Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Mon, 10 May 2021 22:40:08 -0400 Subject: [PATCH 02/16] vim-patch:8.1.1967: line() only works for the current window Problem: Line() only works for the current window. Solution: Add an optional argument for the window to use. https://github.com/vim/vim/commit/8e0a8e7eb7c177807f44db6b76d8e52314248ab5 --- src/nvim/eval.c | 2 +- src/nvim/eval.lua | 2 +- src/nvim/eval/funcs.c | 30 ++++++++++++++++++++++++------ test/functional/eval/null_spec.lua | 1 + test/functional/ui/float_spec.lua | 5 ++++- 5 files changed, 31 insertions(+), 9 deletions(-) diff --git a/src/nvim/eval.c b/src/nvim/eval.c index d67db94339..5d624882a2 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -7617,7 +7617,7 @@ char *save_tv_as_string(typval_T *tv, ptrdiff_t *const len, bool endnl) /// @param[out] ret_fnum Set to fnum for marks. /// /// @return Pointer to position or NULL in case of error (e.g. invalid type). -pos_T *var2fpos(const typval_T *const tv, const int dollar_lnum, +pos_T *var2fpos(const typval_T *const tv, const bool dollar_lnum, int *const ret_fnum) FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index 148804e54c..5ad7781a41 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -217,7 +217,7 @@ return { len={args=1}, libcall={args=3}, libcallnr={args=3}, - line={args=1}, + line={args={1, 2}}, line2byte={args=1}, lispindent={args=1}, list2str={args={1, 2}}, diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 9d5707e649..c2e1639624 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -5540,18 +5540,36 @@ static void f_libcallnr(typval_T *argvars, typval_T *rettv, FunPtr fptr) libcall_common(argvars, rettv, VAR_NUMBER); } -/* - * "line(string)" function - */ +// "line(string, [winid])" function static void f_line(typval_T *argvars, typval_T *rettv, FunPtr fptr) { linenr_T lnum = 0; - pos_T *fp; + pos_T *fp = NULL; int fnum; - fp = var2fpos(&argvars[0], TRUE, &fnum); - if (fp != NULL) + if (argvars[1].v_type != VAR_UNKNOWN) { + tabpage_T *tp; + win_T *save_curwin; + tabpage_T *save_curtab; + + // use window specified in the second argument + win_T *wp = win_id2wp_tp(&argvars[1], &tp); + if (wp != NULL && tp != NULL) { + if (switch_win_noblock(&save_curwin, &save_curtab, wp, tp, true) + == OK) { + check_cursor(); + fp = var2fpos(&argvars[0], true, &fnum); + } + restore_win_noblock(save_curwin, save_curtab, true); + } + } else { + // use current window + fp = var2fpos(&argvars[0], true, &fnum); + } + + if (fp != NULL) { lnum = fp->lnum; + } rettv->vval.v_number = lnum; } diff --git a/test/functional/eval/null_spec.lua b/test/functional/eval/null_spec.lua index d403fbc878..4f29811095 100644 --- a/test/functional/eval/null_spec.lua +++ b/test/functional/eval/null_spec.lua @@ -73,6 +73,7 @@ describe('NULL', function() null_expr_test('does not crash col()', 'col(L)', 0, 0) null_expr_test('does not crash virtcol()', 'virtcol(L)', 0, 0) null_expr_test('does not crash line()', 'line(L)', 0, 0) + null_expr_test('does not crash line() with window id', 'line(L, 1000)', 0, 0) null_expr_test('does not crash count()', 'count(L, 1)', 0, 0) null_expr_test('does not crash cursor()', 'cursor(L)', 'E474: Invalid argument', -1) null_expr_test('does not crash map()', 'map(L, "v:val")', 0, {}) diff --git a/test/functional/ui/float_spec.lua b/test/functional/ui/float_spec.lua index 66aaf0c941..39904663ee 100644 --- a/test/functional/ui/float_spec.lua +++ b/test/functional/ui/float_spec.lua @@ -64,10 +64,13 @@ describe('float window', function() it('win_execute() should work' , function() local buf = meths.create_buf(false, false) - meths.buf_set_lines(buf, 0, -1, true, {'the floatwin'}) + meths.buf_set_lines(buf, 0, -1, true, {'the floatwin', 'abc', 'def'}) local win = meths.open_win(buf, false, {relative='win', width=16, height=1, row=0, col=10}) local line = funcs.win_execute(win, 'echo getline(1)') eq('\nthe floatwin', line) + eq('\n1', funcs.win_execute(win, 'echo line(".",'..win.id..')')) + eq('\n3', funcs.win_execute(win, 'echo line("$",'..win.id..')')) + eq('\n0', funcs.win_execute(win, 'echo line("$", 123456)')) funcs.win_execute(win, 'bwipe!') end) From d5b9a7d6e8891c8f456413b86ebfba44f92c713c Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Tue, 11 May 2021 21:00:54 -0400 Subject: [PATCH 03/16] vim-patch:8.2.0023: command line editing not sufficiently tested Problem: Command line editing not sufficiently tested. Solution: Add more tests. (Dominique Pelle, closes vim/vim#5374) https://github.com/vim/vim/commit/59cb041d0a56d8555857da7e063ec61504ee1fa7 Ported to commit test_ex_mode.vim for later patches. Test fails because feedkeys() filters out modifier keys on output. --- src/nvim/testdir/test_alot.vim | 1 + src/nvim/testdir/test_cmdline.vim | 48 ++++++++++++++++++-------- src/nvim/testdir/test_ex_mode.vim | 56 +++++++++++++++++++++++++++++++ 3 files changed, 91 insertions(+), 14 deletions(-) create mode 100644 src/nvim/testdir/test_ex_mode.vim diff --git a/src/nvim/testdir/test_alot.vim b/src/nvim/testdir/test_alot.vim index e50602ccad..b5c50b5894 100644 --- a/src/nvim/testdir/test_alot.vim +++ b/src/nvim/testdir/test_alot.vim @@ -10,6 +10,7 @@ source test_cursor_func.vim source test_ex_equal.vim source test_ex_undo.vim source test_ex_z.vim +source test_ex_mode.vim source test_execute_func.vim source test_expand_func.vim source test_feedkeys.vim diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim index a1968807ac..5c4f68c700 100644 --- a/src/nvim/testdir/test_cmdline.vim +++ b/src/nvim/testdir/test_cmdline.vim @@ -477,7 +477,7 @@ func Test_expand_star_star() call delete('a', 'rf') endfunc -func Test_paste_in_cmdline() +func Test_cmdline_paste() let @a = "def" call feedkeys(":abc \a ghi\\"\", 'tx') call assert_equal('"abc def ghi', @:) @@ -517,18 +517,38 @@ func Test_paste_in_cmdline() bwipe! endfunc -func Test_remove_char_in_cmdline() - call feedkeys(":abc def\\\\"\", 'tx') - call assert_equal('"abc ef', @:) +func Test_cmdline_remove_char() + let encoding_save = &encoding - call feedkeys(":abc def\\\\"\", 'tx') - call assert_equal('"abcdef', @:) + " for e in ['utf8', 'latin1'] + for e in ['utf8'] + exe 'set encoding=' . e - call feedkeys(":abc def ghi\\\\"\", 'tx') - call assert_equal('"abc ghi', @:) + call feedkeys(":abc def\\\\"\", 'tx') + call assert_equal('"abc ef', @:, e) - call feedkeys(":abc def\\\\"\", 'tx') - call assert_equal('"def', @:) + call feedkeys(":abc def\\\\"\", 'tx') + call assert_equal('"abcdef', @:) + + call feedkeys(":abc def ghi\\\\"\", 'tx') + call assert_equal('"abc ghi', @:, e) + + call feedkeys(":abc def\\\\"\", 'tx') + call assert_equal('"def', @:, e) + endfor + + let &encoding = encoding_save +endfunc + +func Test_cmdline_keymap_ctrl_hat() + if !has('keymap') + return + endif + + set keymap=esperanto + call feedkeys(":\"Jxauxdo \Jxauxdo \Jxauxdo\", 'tx') + call assert_equal('"Jxauxdo Ĵaŭdo Jxauxdo', @:) + set keymap= endfunc func Test_illegal_address1() @@ -863,20 +883,20 @@ func Test_cmdline_overstrike() " Test overstrike in the middle of the command line. call feedkeys(":\"01234\\\ab\\cd\", 'xt') - call assert_equal('"0ab1cd4', @:) + call assert_equal('"0ab1cd4', @:, e) " Test overstrike going beyond end of command line. call feedkeys(":\"01234\\\ab\\cdefgh\", 'xt') - call assert_equal('"0ab1cdefgh', @:) + call assert_equal('"0ab1cdefgh', @:, e) " Test toggling insert/overstrike a few times. call feedkeys(":\"01234\\ab\\cd\\ef\", 'xt') - call assert_equal('"ab0cd3ef4', @:) + call assert_equal('"ab0cd3ef4', @:, e) endfor " Test overstrike with multi-byte characters. call feedkeys(":\"テキストエディタ\\\ab\\cd\", 'xt') - call assert_equal('"テabキcdエディタ', @:) + call assert_equal('"テabキcdエディタ', @:, e) let &encoding = encoding_save endfunc diff --git a/src/nvim/testdir/test_ex_mode.vim b/src/nvim/testdir/test_ex_mode.vim new file mode 100644 index 0000000000..87c23a2fe8 --- /dev/null +++ b/src/nvim/testdir/test_ex_mode.vim @@ -0,0 +1,56 @@ +" Test editing line in Ex mode (see :help Q and :help gQ). + +" Helper function to test editing line in Q Ex mode +func Ex_Q(cmd) + " Is there a simpler way to test editing Ex line? + call feedkeys("Q" + \ .. "let s:test_ex =<< END\" + \ .. a:cmd .. "\" + \ .. "END\" + \ .. "visual\", 'tx') + return s:test_ex[0] +endfunc + +" Helper function to test editing line in gQ Ex mode +func Ex_gQ(cmd) + call feedkeys("gQ" .. a:cmd .. "\\"\", 'tx') + let ret = @:[1:] " Remove leading quote. + call feedkeys("visual\", 'tx') + return ret +endfunc + +" Helper function to test editing line with both Q and gQ Ex mode. +func Ex(cmd) + return [Ex_Q(a:cmd), Ex_gQ(a:cmd)] +endfunc + +" Test editing line in Ex mode (both Q and gQ) +func Test_ex_mode() + throw 'skipped: TODO: ' + let encoding_save = &encoding + set sw=2 + + " for e in ['utf8', 'latin1'] + for e in ['utf8'] + exe 'set encoding=' . e + + call assert_equal(['bar', 'bar'], Ex("foo bar\bar"), e) + call assert_equal(["1\2", "1\2"], Ex("1\\2"), e) + call assert_equal(["1\2\3", '213'], Ex("1\2\3"), e) + call assert_equal(['0123', '2013'], Ex("01\2\3"), e) + call assert_equal(['0123', '0213'], Ex("01\2\3"), e) + call assert_equal(['01234', '0342'], Ex("012\\\3\4"), e) + call assert_equal(["foo bar\", 'foo '], Ex("foo bar\"), e) + call assert_equal(['foo', 'foo'], Ex("fooba\\"), e) + call assert_equal(["foo\tbar", 'foobar'], Ex("foo\bar"), e) + call assert_equal(["abbrev\t", 'abbreviate'], Ex("abbrev\"), e) + call assert_equal([' 1', "1\\"], Ex("1\\"), e) + call assert_equal([' 1', "1\\"], Ex("1\\\"), e) + call assert_equal([' foo', ' foo'], Ex(" foo\"), e) + call assert_equal(['foo', ' foo0'], Ex(" foo0\"), e) + call assert_equal(['foo', ' foo^'], Ex(" foo^\"), e) + endfor + + set sw& + let &encoding = encoding_save +endfunc From 73392909006de74e628323cde915b107244a247b Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Tue, 11 May 2021 21:10:14 -0400 Subject: [PATCH 04/16] vim-patch:8.2.1000: get error when leaving Ex mode with :visual Problem: Get error when leaving Ex mode with :visual and a CmdLineEnter autocommand was used. Solution: Reset ex_pressedreturn. (closes vim/vim#6293) https://github.com/vim/vim/commit/158ea175a99fc23eae1b0a5ee9a81cdd973854a6 --- src/nvim/ex_docmd.c | 1 + src/nvim/testdir/test_ex_mode.vim | 26 ++++++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 555029c1fb..87e482620c 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -7305,6 +7305,7 @@ do_exedit( if (exmode_active && (eap->cmdidx == CMD_visual || eap->cmdidx == CMD_view)) { exmode_active = FALSE; + ex_pressedreturn = false; if (*eap->arg == NUL) { /* Special case: ":global/pat/visual\NLvi-commands" */ if (global_busy) { diff --git a/src/nvim/testdir/test_ex_mode.vim b/src/nvim/testdir/test_ex_mode.vim index 87c23a2fe8..f70cb261e0 100644 --- a/src/nvim/testdir/test_ex_mode.vim +++ b/src/nvim/testdir/test_ex_mode.vim @@ -54,3 +54,29 @@ func Test_ex_mode() set sw& let &encoding = encoding_save endfunc + +func Test_ex_mode_errors() + " Not allowed to enter ex mode when text is locked + au InsertCharPre normal! gQ + let caught_e523 = 0 + try + call feedkeys("ix\", 'xt') + catch /^Vim\%((\a\+)\)\=:E523/ " catch E523 + let caught_e523 = 1 + endtry + call assert_equal(1, caught_e523) + au! InsertCharPre + + new + au CmdLineEnter * call ExEnterFunc() + func ExEnterFunc() + + endfunc + call feedkeys("gQvi\r", 'xt') + + au! CmdLineEnter + delfunc ExEnterFunc + quit +endfunc + +" vim: shiftwidth=2 sts=2 expandtab From f54a938271a4006a05638956a46e0bbe114eedf7 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Tue, 11 May 2021 21:20:32 -0400 Subject: [PATCH 05/16] vim-patch:8.2.1059: crash when using :tabonly in an autocommand Problem: Crash when using :tabonly in an autocommand. (Yegappan Lakshmanan) Solution: Do not allow the autocommand window to be closed. https://github.com/vim/vim/commit/cf8441704d6e517bda1899f4afa82c6b4eecbaec E813 error message does not mention 'popup' window because Neovim floating window are regular windows, unlike Vim popups. https://github.com/neovim/neovim/pull/14532#discussion_r631731829 --- src/nvim/ex_docmd.c | 6 ++++++ src/nvim/globals.h | 2 ++ src/nvim/testdir/test_autocmd.vim | 20 ++++++++++++++++++++ src/nvim/window.c | 2 +- 4 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 87e482620c..1b44b92aa2 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -6520,6 +6520,12 @@ ex_win_close( int need_hide; buf_T *buf = win->w_buffer; + // Never close the autocommand window. + if (win == aucmd_win) { + EMSG(_(e_autocmd_close)); + return; + } + need_hide = (bufIsChanged(buf) && buf->b_nwindows <= 1); if (need_hide && !buf_hide(buf) && !forceit) { if ((p_confirm || cmdmod.confirm) && p_write) { diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 624b7c93f3..0ce2b586e3 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -987,6 +987,8 @@ EXTERN char_u e_dirnotf[] INIT(= N_( "E919: Directory not found in '%s': \"%s\"")); EXTERN char_u e_au_recursive[] INIT(= N_( "E952: Autocommand caused recursive behavior")); +EXTERN char_u e_autocmd_close[] INIT(= N_( + "E813: Cannot close autocmd window")); EXTERN char_u e_unsupportedoption[] INIT(= N_("E519: Option not supported")); EXTERN char_u e_fnametoolong[] INIT(= N_("E856: Filename too long")); EXTERN char_u e_float_as_string[] INIT(= N_("E806: using Float as a String")); diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim index 5611560b1b..fb3aa2b3b0 100644 --- a/src/nvim/testdir/test_autocmd.vim +++ b/src/nvim/testdir/test_autocmd.vim @@ -1949,6 +1949,26 @@ func Test_autocmd_window() %bw! endfunc +" Test for trying to close the tab that has the temporary window for exeucing +" an autocmd. +func Test_close_autocmd_tab() + edit one.txt + tabnew two.txt + augroup aucmd_win_test + au! + au BufEnter * if expand('') == 'one.txt' | tabfirst | tabonly | endif + augroup END + + call assert_fails('doautoall BufEnter', 'E813:') + + tabonly + augroup aucmd_win_test + au! + augroup END + augroup! aucmd_win_test + %bwipe! +endfunc + func Test_autocmd_closes_window() au BufNew,BufWinLeave * e %e file yyy diff --git a/src/nvim/window.c b/src/nvim/window.c index d4d00c0a71..30ce2ba9f4 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -2286,7 +2286,7 @@ int win_close(win_T *win, bool free_buf) return FAIL; // window is already being closed } if (win == aucmd_win) { - EMSG(_("E813: Cannot close autocmd window")); + EMSG(_(e_autocmd_close)); return FAIL; } if ((firstwin == aucmd_win || lastwin == aucmd_win) && one_window()) { From 433807763236427cbe0ff347c1c5b77877aa0aff Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Wed, 12 May 2021 02:29:35 -0400 Subject: [PATCH 06/16] Revert "XXX: ex_tabonly(): aucmd_win is not part of the window list." This reverts commit 7214d0bc846179a862e8d3061d00270a6caa0d7b. Cannot test patch v8.2.1059 without reverting this commit. --- src/nvim/ex_docmd.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 1b44b92aa2..02bbc90858 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -6595,9 +6595,6 @@ static void ex_tabonly(exarg_T *eap) // Repeat this up to a 1000 times, because autocommands may // mess up the lists. for (int done = 0; done < 1000; done++) { - FOR_ALL_TAB_WINDOWS(tp, wp) { - assert(wp != aucmd_win); - } FOR_ALL_TABS(tp) { if (tp->tp_topframe != topframe) { tabpage_close_other(tp, eap->forceit); From f98433d82a340008fdc51c2093e74400e71101dc Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Tue, 11 May 2021 22:17:12 -0400 Subject: [PATCH 07/16] vim-patch:8.2.2354: crash with a weird combination of autocommands Problem: Crash with a weird combination of autocommands. Solution: Increment b_nwindows when needed. (closes vim/vim#7674) https://github.com/vim/vim/commit/797e63b9f2baa1853e7063aac478d663cd02f207 --- src/nvim/buffer.c | 25 ++++++++++++++----------- src/nvim/ex_cmds.c | 16 +++++++++++++--- src/nvim/testdir/test_autocmd.vim | 21 +++++++++++++++++++++ 3 files changed, 48 insertions(+), 14 deletions(-) diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index ce4163fccf..5e700dea8a 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -407,7 +407,8 @@ bool buf_valid(buf_T *buf) /// there to be only one window with this buffer. e.g. when /// ":quit" is supposed to close the window but autocommands /// close all other windows. -void close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last) +/// @returns true when we got to the end and b_nwindows was decremented. +bool close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last) { bool unload_buf = (action != 0); bool del_buf = (action == DOBUF_DEL || action == DOBUF_WIPE); @@ -444,7 +445,7 @@ void close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last) // halfway a command that relies on it). Unloading is allowed. if (buf->b_locked > 0 && (del_buf || wipe_buf)) { EMSG(_("E937: Attempt to delete a buffer that is in use")); - return; + return false; } if (win != NULL // Avoid bogus clang warning. @@ -471,13 +472,13 @@ void close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last) buf) && !bufref_valid(&bufref)) { // Autocommands deleted the buffer. EMSG(_(e_auabort)); - return; + return false; } buf->b_locked--; if (abort_if_last && last_nonfloat(win)) { // Autocommands made this the only window. EMSG(_(e_auabort)); - return; + return false; } // When the buffer becomes hidden, but is not unloaded, trigger @@ -488,17 +489,17 @@ void close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last) buf) && !bufref_valid(&bufref)) { // Autocommands deleted the buffer. EMSG(_(e_auabort)); - return; + return false; } buf->b_locked--; if (abort_if_last && last_nonfloat(win)) { // Autocommands made this the only window. EMSG(_(e_auabort)); - return; + return false; } } if (aborting()) { // autocmds may abort script processing - return; + return false; } } @@ -525,7 +526,7 @@ void close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last) /* Return when a window is displaying the buffer or when it's not * unloaded. */ if (buf->b_nwindows > 0 || !unload_buf) { - return; + return false; } if (buf->terminal) { @@ -561,11 +562,11 @@ void close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last) if (!bufref_valid(&bufref)) { // Autocommands may have deleted the buffer. - return; + return false; } if (aborting()) { // Autocmds may abort script processing. - return; + return false; } /* @@ -576,7 +577,7 @@ void close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last) * deleted buffer. */ if (buf == curbuf && !is_curbuf) { - return; + return false; } if (win != NULL // Avoid bogus clang warning. @@ -636,6 +637,8 @@ void close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last) buf->b_p_bl = false; } } + // NOTE: at this point "curbuf" may be invalid! + return true; } /// Make buffer not contain a file. diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 349c7d6280..18530a7d58 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -2462,6 +2462,7 @@ int do_ecmd( auto_buf = true; } else { win_T *the_curwin = curwin; + buf_T *was_curbuf = curbuf; // Set w_closing to avoid that autocommands close the window. // Set b_locked for the same reason. @@ -2475,9 +2476,10 @@ int do_ecmd( // Close the link to the current buffer. This will set // oldwin->w_buffer to NULL. u_sync(false); - close_buffer(oldwin, curbuf, - (flags & ECMD_HIDE) || curbuf->terminal ? 0 : DOBUF_UNLOAD, - false); + const bool did_decrement = close_buffer( + oldwin, curbuf, + (flags & ECMD_HIDE) || curbuf->terminal ? 0 : DOBUF_UNLOAD, + false); // Autocommands may have closed the window. if (win_valid(the_curwin)) { @@ -2497,6 +2499,14 @@ int do_ecmd( goto theend; } if (buf == curbuf) { // already in new buffer + // close_buffer() has decremented the window count, + // increment it again here and restore w_buffer. + if (did_decrement && buf_valid(was_curbuf)) { + was_curbuf->b_nwindows++; + } + if (win_valid_any_tab(oldwin) && oldwin->w_buffer == NULL) { + oldwin->w_buffer = was_curbuf; + } auto_buf = true; } else { // We could instead free the synblock diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim index fb3aa2b3b0..a2aa1c32c5 100644 --- a/src/nvim/testdir/test_autocmd.vim +++ b/src/nvim/testdir/test_autocmd.vim @@ -452,6 +452,27 @@ func Test_autocmd_bufwipe_in_SessLoadPost() endfor endfunc +" Using :blast and :ball for many events caused a crash, because b_nwindows was +" not incremented correctly. +func Test_autocmd_blast_badd() + let content =<< trim [CODE] + au BufNew,BufAdd,BufWinEnter,BufEnter,BufLeave,BufWinLeave,BufUnload,VimEnter foo* blast + edit foo1 + au BufNew,BufAdd,BufWinEnter,BufEnter,BufLeave,BufWinLeave,BufUnload,VimEnter foo* ball + edit foo2 + call writefile(['OK'], 'Xerrors') + qall + [CODE] + + call writefile(content, 'XblastBall') + call system(GetVimCommand() .. ' --clean -S XblastBall') + " call assert_match('OK', readfile('Xerrors')->join()) + call assert_match('OK', join(readfile('Xerrors'))) + + call delete('XblastBall') + call delete('Xerrors') +endfunc + " SEGV occurs in older versions. func Test_autocmd_bufwipe_in_SessLoadPost2() tabnew From 1ca89a60d8b5027f7d4491d9da749c2184194f27 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Wed, 12 May 2021 02:50:51 -0400 Subject: [PATCH 08/16] vim-patch:8.2.2359: strange test failure with MS-Windows Problem: Strange test failure with MS-Windows. Solution: Skip the system() call for now. https://github.com/vim/vim/commit/e2924328c165f1fc549f91bf212c93c87bb1d9ed --- src/nvim/testdir/test_autocmd.vim | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim index a2aa1c32c5..c2abf71a95 100644 --- a/src/nvim/testdir/test_autocmd.vim +++ b/src/nvim/testdir/test_autocmd.vim @@ -455,6 +455,14 @@ endfunc " Using :blast and :ball for many events caused a crash, because b_nwindows was " not incremented correctly. func Test_autocmd_blast_badd() + " The system() here causes SetChangeMarks() to fail, when run in the GUI + " under Windows. No idea why. Happens with any external command, not + " related to the actual test. + " TODO: find the cause + if has('win32') + throw 'Skipped: calling system() causes problems' + endif + let content =<< trim [CODE] au BufNew,BufAdd,BufWinEnter,BufEnter,BufLeave,BufWinLeave,BufUnload,VimEnter foo* blast edit foo1 From 3f9fa730ceb059ab94dcd20b8f323faca1417110 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Wed, 12 May 2021 02:51:43 -0400 Subject: [PATCH 09/16] vim-patch:8.2.2419: autocmd test was failing on MS-Windows with GUI Problem: Autocmd test was failing on MS-Windows with GUI. Solution: Remove stray feedkeys(). https://github.com/vim/vim/commit/d697ddea1466d877a79f8c56d46361dc54e9baef --- src/nvim/testdir/test_autocmd.vim | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim index c2abf71a95..e0a04c94f8 100644 --- a/src/nvim/testdir/test_autocmd.vim +++ b/src/nvim/testdir/test_autocmd.vim @@ -190,7 +190,6 @@ func Test_autocmd_bufunload_avoiding_SEGV_02() normal! i1 call assert_fails('edit a.txt', 'E517:') - call feedkeys("\") autocmd! test_autocmd_bufunload augroup! test_autocmd_bufunload @@ -455,14 +454,6 @@ endfunc " Using :blast and :ball for many events caused a crash, because b_nwindows was " not incremented correctly. func Test_autocmd_blast_badd() - " The system() here causes SetChangeMarks() to fail, when run in the GUI - " under Windows. No idea why. Happens with any external command, not - " related to the actual test. - " TODO: find the cause - if has('win32') - throw 'Skipped: calling system() causes problems' - endif - let content =<< trim [CODE] au BufNew,BufAdd,BufWinEnter,BufEnter,BufLeave,BufWinLeave,BufUnload,VimEnter foo* blast edit foo1 From 07f6f12cf529da403ccb1f3df48d266fa2008db7 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Tue, 11 May 2021 22:18:48 -0400 Subject: [PATCH 10/16] vim-patch:8.2.2433: opening cmdline window gives error in BufLeave autocommand Problem: Opening cmdline window gives error in BufLeave autocommand. Solution: Reset cmdwin_type when triggering the autocommand. https://github.com/vim/vim/commit/b63f3ca66db20f7b135aae706d36be538f91f8cc --- src/nvim/ex_cmds.c | 6 ++++++ src/nvim/testdir/test_cmdline.vim | 19 +++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 18530a7d58..fde0abdd4b 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -2434,6 +2434,11 @@ int do_ecmd( * is returned by buflist_new(), nothing to do here. */ if (buf != curbuf) { + const int save_cmdwin_type = cmdwin_type; + + // BufLeave applies to the old buffer. + cmdwin_type = 0; + /* * Be careful: The autocommands may delete any buffer and change * the current buffer. @@ -2449,6 +2454,7 @@ int do_ecmd( } set_bufref(&au_new_curbuf, buf); apply_autocmds(EVENT_BUFLEAVE, NULL, NULL, false, curbuf); + cmdwin_type = save_cmdwin_type; if (!bufref_valid(&au_new_curbuf)) { // New buffer has been deleted. delbuf_msg(new_name); // Frees new_name. diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim index 5c4f68c700..b521a193c4 100644 --- a/src/nvim/testdir/test_cmdline.vim +++ b/src/nvim/testdir/test_cmdline.vim @@ -1005,6 +1005,25 @@ func Test_buffers_lastused() bwipeout bufc endfunc +" Test for CmdwinEnter autocmd +func Test_cmdwin_autocmd() + CheckFeature cmdwin + + augroup CmdWin + au! + autocmd BufLeave * if &buftype == '' | update | endif + autocmd CmdwinEnter * startinsert + augroup END + + call assert_fails('call feedkeys("q:xyz\", "xt")', 'E492:') + call assert_equal('xyz', @:) + + augroup CmdWin + au! + augroup END + augroup! CmdWin +endfunc + func Test_cmdlineclear_tabenter() " See test/functional/legacy/cmdline_spec.lua CheckScreendump From e9804aead6adf1fb0e3d16d5544e82fc0e566890 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Wed, 12 May 2021 02:28:15 -0400 Subject: [PATCH 11/16] lint --- src/nvim/ex_cmds.c | 18 ++++++++---------- src/nvim/ex_docmd.c | 4 ++-- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index fde0abdd4b..6a0a08eee8 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -2439,16 +2439,14 @@ int do_ecmd( // BufLeave applies to the old buffer. cmdwin_type = 0; - /* - * Be careful: The autocommands may delete any buffer and change - * the current buffer. - * - If the buffer we are going to edit is deleted, give up. - * - If the current buffer is deleted, prefer to load the new - * buffer when loading a buffer is required. This avoids - * loading another buffer which then must be closed again. - * - If we ended up in the new buffer already, need to skip a few - * things, set auto_buf. - */ + // Be careful: The autocommands may delete any buffer and change + // the current buffer. + // - If the buffer we are going to edit is deleted, give up. + // - If the current buffer is deleted, prefer to load the new + // buffer when loading a buffer is required. This avoids + // loading another buffer which then must be closed again. + // - If we ended up in the new buffer already, need to skip a few + // things, set auto_buf. if (buf->b_fname != NULL) { new_name = vim_strsave(buf->b_fname); } diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 02bbc90858..c557bb2438 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -212,7 +212,7 @@ void do_exmode(int improved) while (exmode_active) { /* Check for a ":normal" command and no more characters left. */ if (ex_normal_busy > 0 && typebuf.tb_len == 0) { - exmode_active = FALSE; + exmode_active = 0; break; } msg_scroll = true; @@ -7307,7 +7307,7 @@ do_exedit( */ if (exmode_active && (eap->cmdidx == CMD_visual || eap->cmdidx == CMD_view)) { - exmode_active = FALSE; + exmode_active = 0; ex_pressedreturn = false; if (*eap->arg == NUL) { /* Special case: ":global/pat/visual\NLvi-commands" */ From c9195a1273159bfd227bfa9a7b407888d0d18110 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Tue, 11 May 2021 22:27:21 -0400 Subject: [PATCH 12/16] vim-patch:8.2.2473: crash when leaving command line window triggers autocommand Problem: Crash when leaving command line window triggers autocommand. (houyunsong) Solution: Make sure not to close the current window or buffer. https://github.com/vim/vim/commit/8c6951fa2836a1ae3257770e7b927a9380439912 N/A patches for version.c: vim-patch:8.2.2414: using freed memory when closing the cmdline window Problem: Using freed memory when closing the cmdline window. Solution: Check the window is still valid. https://github.com/vim/vim/commit/b7e2670b6a1de02c772af5097ba24f2a15b26eec --- src/nvim/ex_getln.c | 8 +++++--- src/nvim/testdir/test_autocmd.vim | 9 +++++++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 53571ec8da..43762e8d6b 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -6635,11 +6635,13 @@ static int open_cmdwin(void) wp = curwin; set_bufref(&bufref, curbuf); win_goto(old_curwin); - win_close(wp, true); + if (win_valid(wp) && wp != curwin) { + win_close(wp, true); + } // win_close() may have already wiped the buffer when 'bh' is - // set to 'wipe'. - if (bufref_valid(&bufref)) { + // set to 'wipe', autocommands may have closed other windows + if (bufref_valid(&bufref) && bufref.br_buf != curbuf) { close_buffer(NULL, bufref.br_buf, DOBUF_WIPE, false); } diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim index e0a04c94f8..bb84fa498e 100644 --- a/src/nvim/testdir/test_autocmd.vim +++ b/src/nvim/testdir/test_autocmd.vim @@ -2000,4 +2000,13 @@ func Test_autocmd_closes_window() au! BufWinLeave endfunc +func Test_autocmd_closing_cmdwin() + au BufWinLeave * nested q + call assert_fails("norm 7q?\n", 'E855:') + + au! BufWinLeave + new + only +endfunc + " vim: shiftwidth=2 sts=2 expandtab From cc798c626abf8d5d60904285a978827fd32b8a8c Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Thu, 13 May 2021 20:27:57 -0400 Subject: [PATCH 13/16] vim-patch:8.0.1394: cannot intercept a yank command Problem: Cannot intercept a yank command. Solution: Add the TextYankPost autocommand event. (Philippe Vaucher et al., closes vim/vim#2333) https://github.com/vim/vim/commit/7e1652c63c96585b9e2235c195a3c322b1f11595 --- src/nvim/eval.c | 6 ++---- src/nvim/eval/typval.c | 10 ++++++++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 5d624882a2..04a9abe41a 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -377,11 +377,9 @@ void eval_init(void) msgpack_types_dict->dv_lock = VAR_FIXED; set_vim_var_dict(VV_MSGPACK_TYPES, msgpack_types_dict); - set_vim_var_dict(VV_COMPLETED_ITEM, tv_dict_alloc()); + set_vim_var_dict(VV_COMPLETED_ITEM, tv_dict_alloc_lock(VAR_FIXED)); - dict_T *v_event = tv_dict_alloc(); - v_event->dv_lock = VAR_FIXED; - set_vim_var_dict(VV_EVENT, v_event); + set_vim_var_dict(VV_EVENT, tv_dict_alloc_lock(VAR_FIXED)); set_vim_var_list(VV_ERRORS, tv_list_alloc(kListLenUnknown)); set_vim_var_nr(VV_STDERR, CHAN_STDERR); set_vim_var_nr(VV_SEARCHFORWARD, 1L); diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 71e4edc667..d01c597c83 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -2106,6 +2106,13 @@ list_T *tv_list_alloc_ret(typval_T *const ret_tv, const ptrdiff_t len) return l; } +dict_T *tv_dict_alloc_lock(VarLockStatus lock) +{ + dict_T *const d = tv_dict_alloc(); + d->dv_lock = lock; + return d; +} + /// Allocate an empty dictionary for a return value /// /// Also sets reference count. @@ -2114,9 +2121,8 @@ list_T *tv_list_alloc_ret(typval_T *const ret_tv, const ptrdiff_t len) void tv_dict_alloc_ret(typval_T *const ret_tv) FUNC_ATTR_NONNULL_ALL { - dict_T *const d = tv_dict_alloc(); + dict_T *const d = tv_dict_alloc_lock(VAR_UNLOCKED); tv_dict_set_ret(ret_tv, d); - ret_tv->v_lock = VAR_UNLOCKED; } //{{{3 Clear From 30987b9190459b0b16a644de5c04d7e5eecf472e Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Thu, 13 May 2021 20:51:47 -0400 Subject: [PATCH 14/16] fixup! vim-patch:8.0.1449: slow redrawing with DirectX --- src/nvim/edit.c | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 56b563cba0..1579f3ff98 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -3150,9 +3150,7 @@ static void ins_compl_clear(void) XFREE_CLEAR(compl_orig_text); compl_enter_selects = false; // clear v:completed_item - dict_T *const d = tv_dict_alloc(); - d->dv_lock = VAR_FIXED; - set_vim_var_dict(VV_COMPLETED_ITEM, d); + set_vim_var_dict(VV_COMPLETED_ITEM, tv_dict_alloc_lock(VAR_FIXED)); } /// Check that Insert completion is active. @@ -4497,9 +4495,7 @@ static void ins_compl_delete(void) // causes flicker, thus we can't do that. changed_cline_bef_curs(); // clear v:completed_item - dict_T *const d = tv_dict_alloc(); - d->dv_lock = VAR_FIXED; - set_vim_var_dict(VV_COMPLETED_ITEM, d); + set_vim_var_dict(VV_COMPLETED_ITEM, tv_dict_alloc_lock(VAR_FIXED)); } // Insert the new text being completed. @@ -4520,8 +4516,7 @@ static void ins_compl_insert(int in_compl_func) static dict_T *ins_compl_dict_alloc(compl_T *match) { // { word, abbr, menu, kind, info } - dict_T *dict = tv_dict_alloc(); - dict->dv_lock = VAR_FIXED; + dict_T *dict = tv_dict_alloc_lock(VAR_FIXED); tv_dict_add_str( dict, S_LEN("word"), (const char *)EMPTY_IF_NULL(match->cp_str)); From 5110ef9171ac63994a02ce2f215dbae308877b07 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Thu, 13 May 2021 20:54:12 -0400 Subject: [PATCH 15/16] fixup! vim-patch:8.1.1228: not possible to process tags with a function --- src/nvim/tag.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/nvim/tag.c b/src/nvim/tag.c index a6310344e9..f0e48013b2 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -1143,7 +1143,6 @@ static int find_tagfunc_tags( typval_T args[4]; typval_T rettv; char_u flagString[4]; - dict_T *d; taggy_T *tag = &curwin->w_tagstack[curwin->w_tagstackidx]; if (*curbuf->b_p_tfu == NUL) { @@ -1156,7 +1155,7 @@ static int find_tagfunc_tags( args[1].vval.v_string = flagString; // create 'info' dict argument - d = tv_dict_alloc(); + dict_T *const d = tv_dict_alloc_lock(VAR_FIXED); if (tag->user_data != NULL) { tv_dict_add_str(d, S_LEN("user_data"), (const char *)tag->user_data); } From 520b925627604a5c3030d4e59dc2caa2f6501736 Mon Sep 17 00:00:00 2001 From: Jan Edmund Lazo Date: Thu, 13 May 2021 23:56:34 -0400 Subject: [PATCH 16/16] vim-patch:8.2.2850: recalling commands from history is not tested Problem: Recalling commands from history is not tested. Solution: Add tests. (closes vim/vim#8194) https://github.com/vim/vim/commit/71c6f7a665eb467054fbf319989d97c252af28d2 --- src/nvim/testdir/test_cmdline.vim | 48 +++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim index b521a193c4..34126b49fa 100644 --- a/src/nvim/testdir/test_cmdline.vim +++ b/src/nvim/testdir/test_cmdline.vim @@ -1072,4 +1072,52 @@ func Test_read_shellcmd() endif endfunc +" Test for recalling newer or older cmdline from history with , , +" , , , , , or . +func Test_recalling_cmdline() + CheckFeature cmdline_hist + + let g:cmdlines = [] + cnoremap (save-cmdline) let g:cmdlines += [getcmdline()] + + let histories = [ + \ {'name': 'cmd', 'enter': ':', 'exit': "\"}, + \ {'name': 'search', 'enter': '/', 'exit': "\"}, + \ {'name': 'expr', 'enter': ":\=", 'exit': "\\"}, + \ {'name': 'input', 'enter': ":call input('')\", 'exit': "\"}, + "\ TODO: {'name': 'debug', ...} + \] + let keypairs = [ + \ {'older': "\", 'newer': "\", 'prefixmatch': v:true}, + \ {'older': "\", 'newer': "\", 'prefixmatch': v:false}, + \ {'older': "\", 'newer': "\", 'prefixmatch': v:false}, + \ {'older': "\", 'newer': "\", 'prefixmatch': v:false}, + \] + let prefix = 'vi' + for h in histories + call histadd(h.name, 'vim') + call histadd(h.name, 'virtue') + call histadd(h.name, 'Virgo') + call histadd(h.name, 'vogue') + call histadd(h.name, 'emacs') + for k in keypairs + let g:cmdlines = [] + let keyseqs = h.enter + \ .. prefix + \ .. repeat(k.older .. "\(save-cmdline)", 2) + \ .. repeat(k.newer .. "\(save-cmdline)", 2) + \ .. h.exit + call feedkeys(keyseqs, 'xt') + call histdel(h.name, -1) " delete the history added by feedkeys above + let expect = k.prefixmatch + \ ? ['virtue', 'vim', 'virtue', prefix] + \ : ['emacs', 'vogue', 'emacs', prefix] + call assert_equal(expect, g:cmdlines) + endfor + endfor + + unlet g:cmdlines + cunmap (save-cmdline) +endfunc + " vim: shiftwidth=2 sts=2 expandtab