From f6518e55164be3fd2d10bf7738c5d76970fb8d79 Mon Sep 17 00:00:00 2001 From: jing Date: Sat, 24 Apr 2021 23:09:31 +0800 Subject: [PATCH 1/8] vim-patch:8.1.1418: win_execute() is not implemented yet Problem: Win_execute() is not implemented yet. Solution: Implement it. https://github.com/vim/vim/commit/868b7b6712ea4f2232eeeae18c5cbbbddf2ee84d --- runtime/doc/eval.txt | 10 +++++++ src/nvim/eval.lua | 1 + src/nvim/eval/funcs.c | 40 ++++++++++++++++++++------ src/nvim/testdir/test_execute_func.vim | 24 ++++++++++++++++ test/functional/ui/float_spec.lua | 18 ++++++++++++ 5 files changed, 85 insertions(+), 8 deletions(-) diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index b8dcfd0ff4..4d6de151f9 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -2498,6 +2498,8 @@ visualmode([expr]) String last visual mode used wait({timeout}, {condition}[, {interval}]) Number Wait until {condition} is satisfied wildmenumode() Number whether 'wildmenu' mode is active +win_execute({id}, {command} [, {silent}]) + String execute {command} in window {id} win_findbuf({bufnr}) List find windows containing {bufnr} win_getid([{win} [, {tab}]]) Number get |window-ID| for {win} in {tab} win_gettype([{nr}]) String type of window {nr} @@ -3616,6 +3618,8 @@ execute({command} [, {silent}]) *execute()* Note: If nested, an outer execute() will not observe output of the inner calls. Note: Text attributes (highlights) are not captured. + To execute a command in another window than the current one + use `win_execute()`. exepath({expr}) *exepath()* Returns the full path of {expr} if it is an executable and @@ -9477,6 +9481,12 @@ wildmenumode() *wildmenumode()* < (Note, this needs the 'wildcharm' option set appropriately). +win_execute({id}, {command} [, {silent}]) *win_execute()* + Like `execute()` but in the context of window {id}. + The window will temporarily be made the current window, + without triggering autocommands. + Example: > + call win_execute(winid, 'syntax enable') win_findbuf({bufnr}) *win_findbuf()* Returns a list with |window-ID|s for windows that contain diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index 77e7c7b3a9..148804e54c 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -390,6 +390,7 @@ return { visualmode={args={0, 1}}, wait={args={2,3}}, wildmenumode={}, + win_execute={args={2, 3}}, win_findbuf={args=1}, win_getid={args={0,2}}, win_gettype={args={0,1}}, diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 6d328953f6..fe0a4c29a2 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -1953,8 +1953,8 @@ static char_u *get_list_line(int c, void *cookie, int indent, bool do_concat) return (char_u *)(s == NULL ? NULL : xstrdup(s)); } -// "execute(command)" function -static void f_execute(typval_T *argvars, typval_T *rettv, FunPtr fptr) +static void execute_common(typval_T *argvars, typval_T *rettv, FunPtr fptr, + int arg_off) { const int save_msg_silent = msg_silent; const int save_emsg_silent = emsg_silent; @@ -1968,9 +1968,9 @@ static void f_execute(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } - if (argvars[1].v_type != VAR_UNKNOWN) { + if (argvars[arg_off + 1].v_type != VAR_UNKNOWN) { char buf[NUMBUFLEN]; - const char *const s = tv_get_string_buf_chk(&argvars[1], buf); + const char *const s = tv_get_string_buf_chk(&argvars[arg_off + 1], buf); if (s == NULL) { return; @@ -1997,10 +1997,10 @@ static void f_execute(typval_T *argvars, typval_T *rettv, FunPtr fptr) msg_col = 0; // prevent leading spaces } - if (argvars[0].v_type != VAR_LIST) { - do_cmdline_cmd(tv_get_string(&argvars[0])); - } else if (argvars[0].vval.v_list != NULL) { - list_T *const list = argvars[0].vval.v_list; + if (argvars[arg_off].v_type != VAR_LIST) { + do_cmdline_cmd(tv_get_string(&argvars[arg_off])); + } else if (argvars[arg_off].vval.v_list != NULL) { + list_T *const list = argvars[arg_off].vval.v_list; tv_list_ref(list); GetListLineCookie cookie = { .l = list, @@ -2032,6 +2032,30 @@ static void f_execute(typval_T *argvars, typval_T *rettv, FunPtr fptr) capture_ga = save_capture_ga; } +// "execute(command)" function +static void f_execute(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + execute_common(argvars, rettv, fptr, 0); +} + +// "win_execute(win_id, command)" function +static void f_win_execute(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + win_T *wp = win_id2wp(argvars); + win_T *save_curwin = curwin; + + if (wp != NULL) { + curwin = wp; + curbuf = curwin->w_buffer; + check_cursor(); + execute_common(argvars, rettv, fptr, 1); + if (win_valid(save_curwin)) { + curwin = save_curwin; + curbuf = curwin->w_buffer; + } + } +} + /// "exepath()" function static void f_exepath(typval_T *argvars, typval_T *rettv, FunPtr fptr) { diff --git a/src/nvim/testdir/test_execute_func.vim b/src/nvim/testdir/test_execute_func.vim index eb84a6739d..98e0ea4e09 100644 --- a/src/nvim/testdir/test_execute_func.vim +++ b/src/nvim/testdir/test_execute_func.vim @@ -82,3 +82,27 @@ func Test_execute_not_silent() endfor call assert_equal('xyz ', text2) endfunc + +func Test_win_execute() + let thiswin = win_getid() + new + let otherwin = win_getid() + call setline(1, 'the new window') + call win_gotoid(thiswin) + let line = win_execute(otherwin, 'echo getline(1)') + call assert_match('the new window', line) + + if has('textprop') + let popupwin = popup_create('the popup win', {'line': 2, 'col': 3}) + redraw + let line = win_execute(popupwin, 'echo getline(1)') + call assert_match('the popup win', line) + + call assert_fails('call win_execute(popupwin, "bwipe!")', 'E937:') + + call popup_close(popupwin) + endif + + call win_gotoid(otherwin) + bwipe! +endfunc diff --git a/test/functional/ui/float_spec.lua b/test/functional/ui/float_spec.lua index 3e73d8b3de..6b75803727 100644 --- a/test/functional/ui/float_spec.lua +++ b/test/functional/ui/float_spec.lua @@ -62,6 +62,24 @@ describe('float window', function() eq(1000, funcs.win_getid()) end) + it('win_execute() should work' , function() + local buf = meths.create_buf(false, false) + meths.buf_set_lines(buf, 0, -1, true, {'the floatwin'}) + 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) + funcs.win_execute(win, 'bwipe!') + end) + + it('win_execute() call commands that not allowed' , function() + local buf = meths.create_buf(false, false) + meths.buf_set_lines(buf, 0, -1, true, {'the floatwin'}) + local win = meths.open_win(buf, true, {relative='win', width=16, height=1, row=0, col=10}) + eq(pcall_err(funcs.win_execute, win, 'close'), 'Vim(close):E37: No write since last change (add ! to override)') + eq(pcall_err(funcs.win_execute, win, 'bdelete'), 'Vim(bdelete):E89: No write since last change for buffer 2 (add ! to override)') + funcs.win_execute(win, 'bwipe!') + end) + it('closed immediately by autocmd #11383', function() eq('Error executing lua: [string ""]:0: Window was closed immediately', pcall_err(exec_lua, [[ From 97abf8ee63994c19c2119e34bc7a7aa66457a7f2 Mon Sep 17 00:00:00 2001 From: jing Date: Sat, 1 May 2021 10:00:41 +0800 Subject: [PATCH 2/8] vim-patch:8.1.1425: win_execute() does not set window pointers properly Problem: Win_execute() does not set window pointers properly. Solution: Use switch_win_noblock(). Also execute autocommands in a popup window. https://github.com/vim/vim/commit/89adc3a1371d211f7766f3dbc0975ecb2f862327 --- src/nvim/eval/funcs.c | 15 +++++++-------- src/nvim/window.c | 16 +++++++++++++++- 2 files changed, 22 insertions(+), 9 deletions(-) diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index fe0a4c29a2..3a202759b6 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -2042,17 +2042,16 @@ static void f_execute(typval_T *argvars, typval_T *rettv, FunPtr fptr) static void f_win_execute(typval_T *argvars, typval_T *rettv, FunPtr fptr) { win_T *wp = win_id2wp(argvars); - win_T *save_curwin = curwin; + win_T *save_curwin; + tabpage_T *save_curtab; if (wp != NULL) { - curwin = wp; - curbuf = curwin->w_buffer; - check_cursor(); - execute_common(argvars, rettv, fptr, 1); - if (win_valid(save_curwin)) { - curwin = save_curwin; - curbuf = curwin->w_buffer; + if (switch_win_noblock(&save_curwin, &save_curtab, wp, curtab, true) == + OK) { + check_cursor(); + execute_common(argvars, rettv, fptr, 1); } + restore_win_noblock(save_curwin, save_curtab, true); } } diff --git a/src/nvim/window.c b/src/nvim/window.c index c482d265ff..ddfb4e6ef3 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -6335,6 +6335,13 @@ static win_T *get_snapshot_focus(int idx) int switch_win(win_T **save_curwin, tabpage_T **save_curtab, win_T *win, tabpage_T *tp, int no_display) { block_autocmds(); + return switch_win_noblock(save_curwin, save_curtab, win, tp, no_display); +} + +// As switch_win() but without blocking autocommands. +int switch_win_noblock(win_T **save_curwin, tabpage_T **save_curtab, + win_T *win, tabpage_T *tp, int no_display) +{ *save_curwin = curwin; if (tp != NULL) { *save_curtab = curtab; @@ -6359,6 +6366,14 @@ int switch_win(win_T **save_curwin, tabpage_T **save_curtab, win_T *win, tabpage // When "no_display" is true the display won't be affected, no redraw is // triggered. void restore_win(win_T *save_curwin, tabpage_T *save_curtab, bool no_display) +{ + restore_win_noblock(save_curwin, save_curtab, no_display); + unblock_autocmds(); +} + +// As restore_win() but without unblocking autocommands. +void restore_win_noblock(win_T *save_curwin, tabpage_T *save_curtab, + bool no_display) { if (save_curtab != NULL && valid_tabpage(save_curtab)) { if (no_display) { @@ -6374,7 +6389,6 @@ void restore_win(win_T *save_curwin, tabpage_T *save_curtab, bool no_display) curwin = save_curwin; curbuf = curwin->w_buffer; } - unblock_autocmds(); } /// Make "buf" the current buffer. From 3c00252248daf54c5ba4ef2c983256cf81c71ded Mon Sep 17 00:00:00 2001 From: jing Date: Sat, 1 May 2021 10:19:21 +0800 Subject: [PATCH 3/8] vim-patch:8.1.1440: win_execute() test fails Problem: Win_execute() test fails. Solution: Adjust the expected error number. Move to popup test. https://github.com/vim/vim/commit/2d247849ce612050ba1085df806746b23be1f0a3 --- src/nvim/testdir/test_execute_func.vim | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/nvim/testdir/test_execute_func.vim b/src/nvim/testdir/test_execute_func.vim index 98e0ea4e09..a634f9fc91 100644 --- a/src/nvim/testdir/test_execute_func.vim +++ b/src/nvim/testdir/test_execute_func.vim @@ -98,8 +98,6 @@ func Test_win_execute() let line = win_execute(popupwin, 'echo getline(1)') call assert_match('the popup win', line) - call assert_fails('call win_execute(popupwin, "bwipe!")', 'E937:') - call popup_close(popupwin) endif From 73154bbae033513a937af8092320920a49684ce6 Mon Sep 17 00:00:00 2001 From: jing Date: Sat, 1 May 2021 10:21:14 +0800 Subject: [PATCH 4/8] vim-patch:8.1.1832: win_execute() does not work in other tab Problem: Win_execute() does not work in other tab. (Rick Howe) Solution: Take care of the tab. (closes vim/vim#4792) https://github.com/vim/vim/commit/820680b9ff1de8699156c7b060f97e5c0b87ad15 --- src/nvim/eval/funcs.c | 7 ++++--- src/nvim/testdir/test_execute_func.vim | 9 +++++++++ src/nvim/window.c | 9 +++++++++ 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 3a202759b6..0b50f41de3 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -2041,12 +2041,13 @@ static void f_execute(typval_T *argvars, typval_T *rettv, FunPtr fptr) // "win_execute(win_id, command)" function static void f_win_execute(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - win_T *wp = win_id2wp(argvars); + tabpage_T *tp; + win_T *wp = win_id2wp_tp(argvars, &tp); win_T *save_curwin; tabpage_T *save_curtab; - if (wp != NULL) { - if (switch_win_noblock(&save_curwin, &save_curtab, wp, curtab, true) == + if (wp != NULL && tp != NULL) { + if (switch_win_noblock(&save_curwin, &save_curtab, wp, tp, true) == OK) { check_cursor(); execute_common(argvars, rettv, fptr, 1); diff --git a/src/nvim/testdir/test_execute_func.vim b/src/nvim/testdir/test_execute_func.vim index a634f9fc91..9efed76eda 100644 --- a/src/nvim/testdir/test_execute_func.vim +++ b/src/nvim/testdir/test_execute_func.vim @@ -104,3 +104,12 @@ func Test_win_execute() call win_gotoid(otherwin) bwipe! endfunc + +func Test_win_execute_other_tab() + let thiswin = win_getid() + tabnew + call win_execute(thiswin, 'let xyz = 1') + call assert_equal(1, xyz) + tabclose + unlet xyz +endfunc diff --git a/src/nvim/window.c b/src/nvim/window.c index ddfb4e6ef3..bcd955d1b0 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -6820,11 +6820,20 @@ void win_id2tabwin(typval_T *const argvars, typval_T *const rettv) } win_T * win_id2wp(typval_T *argvars) +{ + return win_id2wp_tp(argvars, NULL); +} + +// Return the window and tab pointer of window "id". +win_T * win_id2wp_tp(typval_T *argvars, tabpage_T **tpp) { int id = tv_get_number(&argvars[0]); FOR_ALL_TAB_WINDOWS(tp, wp) { if (wp->handle == id) { + if (tpp != NULL) { + *tpp = tp; + } return wp; } } From ec3524da29b4d3c6cdccb3ab3608f88e8c4ba183 Mon Sep 17 00:00:00 2001 From: jing Date: Sat, 1 May 2021 10:31:15 +0800 Subject: [PATCH 5/8] vim-patch:8.1.2124: ruler is not updated if win_execute() moves cursor Problem: Ruler is not updated if win_execute() moves cursor. Solution: Update the status line. (closes vim/vim#5022) https://github.com/vim/vim/commit/345f28df5482cd35f5fa74b06443376379f113b0 --- src/nvim/eval/funcs.c | 6 ++++++ src/nvim/testdir/test_execute_func.vim | 20 ++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 0b50f41de3..2a046efc0b 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -2047,12 +2047,18 @@ static void f_win_execute(typval_T *argvars, typval_T *rettv, FunPtr fptr) tabpage_T *save_curtab; if (wp != NULL && tp != NULL) { + pos_T curpos = wp->w_cursor; if (switch_win_noblock(&save_curwin, &save_curtab, wp, tp, true) == OK) { check_cursor(); execute_common(argvars, rettv, fptr, 1); } restore_win_noblock(save_curwin, save_curtab, true); + + // Update the status line if the cursor moved. + if (win_valid(wp) && !equalpos(curpos, wp->w_cursor)) { + wp->w_redr_status = true; + } } } diff --git a/src/nvim/testdir/test_execute_func.vim b/src/nvim/testdir/test_execute_func.vim index 9efed76eda..51df61d762 100644 --- a/src/nvim/testdir/test_execute_func.vim +++ b/src/nvim/testdir/test_execute_func.vim @@ -1,5 +1,7 @@ " test execute() +source view_util.vim + func NestedEval() let nested = execute('echo "nested\nlines"') echo 'got: "' . nested . '"' @@ -105,6 +107,24 @@ func Test_win_execute() bwipe! endfunc +func Test_win_execute_update_ruler() + enew + call setline(1, range(500)) + 20 + split + let winid = win_getid() + set ruler + wincmd w + let height = winheight(winid) + redraw + call assert_match('20,1', Screenline(height + 1)) + let line = win_execute(winid, 'call cursor(100, 1)') + redraw + call assert_match('100,1', Screenline(height + 1)) + + bwipe! +endfunc + func Test_win_execute_other_tab() let thiswin = win_getid() tabnew From 8fecc5fab89c5141186d4ca2937f9aed8a361aa0 Mon Sep 17 00:00:00 2001 From: jing Date: Sat, 1 May 2021 10:38:43 +0800 Subject: [PATCH 6/8] vim-patch:8.2.0137: crash when using win_execute() from a new tab Problem: Crash when using win_execute() from a new tab. Solution: Set the tp_*win pointers. (Ozaki Kiichi, closes vim/vim#5512) https://github.com/vim/vim/commit/a44b3eeafa57d4904a3de86b132008b93404f0fd --- src/nvim/testdir/test_winbuf_close.vim | 14 ++++++++++++-- src/nvim/window.c | 5 +++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/nvim/testdir/test_winbuf_close.vim b/src/nvim/testdir/test_winbuf_close.vim index ee43540fdd..7f5b80e8d3 100644 --- a/src/nvim/testdir/test_winbuf_close.vim +++ b/src/nvim/testdir/test_winbuf_close.vim @@ -160,7 +160,7 @@ func Test_winfixwidth_on_close() endfunction " Test that 'winfixheight' will be respected even there is non-leaf frame -fun! Test_winfixheight_non_leaf_frame() +func Test_winfixheight_non_leaf_frame() vsplit botright 11new let l:wid = win_getid() @@ -173,7 +173,7 @@ fun! Test_winfixheight_non_leaf_frame() endf " Test that 'winfixwidth' will be respected even there is non-leaf frame -fun! Test_winfixwidth_non_leaf_frame() +func Test_winfixwidth_non_leaf_frame() split topleft 11vnew let l:wid = win_getid() @@ -184,3 +184,13 @@ fun! Test_winfixwidth_non_leaf_frame() call assert_equal(11, winwidth(l:wid)) %bwipe! endf + +func Test_tabwin_close() + enew + let l:wid = win_getid() + tabedit + call win_execute(l:wid, 'close') + " Should not crash. + call assert_true(v:true) + %bwipe! +endfunc diff --git a/src/nvim/window.c b/src/nvim/window.c index bcd955d1b0..4fa6d8e63c 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -3459,6 +3459,9 @@ int win_alloc_first(void) first_tabpage = alloc_tabpage(); first_tabpage->tp_topframe = topframe; curtab = first_tabpage; + curtab->tp_firstwin = firstwin; + curtab->tp_lastwin = lastwin; + curtab->tp_curwin = curwin; return OK; } @@ -3627,6 +3630,8 @@ int win_new_tabpage(int after, char_u *filename) newtp->tp_next = tp->tp_next; tp->tp_next = newtp; } + newtp->tp_firstwin = newtp->tp_lastwin = newtp->tp_curwin = curwin; + win_init_size(); firstwin->w_winrow = tabline_height(); win_comp_scroll(curwin); From 03b3ff861082ff09f2903565d928187489225306 Mon Sep 17 00:00:00 2001 From: jing Date: Sat, 1 May 2021 10:44:21 +0800 Subject: [PATCH 7/8] vim-patch:8.2.2340: win_execute() unexpectedly returns number zero when failing Problem: win_execute() unexpectedly returns number zero when failing. Solution: Return an empty string. (closes vim/vim#7665) https://github.com/vim/vim/commit/37487e16da7877129edee8d11b9b7f5c8df312c6 --- src/nvim/eval/funcs.c | 3 +++ src/nvim/testdir/test_execute_func.vim | 2 ++ 2 files changed, 5 insertions(+) diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index 2a046efc0b..04e6a73f37 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -2045,6 +2045,9 @@ static void f_win_execute(typval_T *argvars, typval_T *rettv, FunPtr fptr) win_T *wp = win_id2wp_tp(argvars, &tp); win_T *save_curwin; tabpage_T *save_curtab; + // Return an empty string if something fails. + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; if (wp != NULL && tp != NULL) { pos_T curpos = wp->w_cursor; diff --git a/src/nvim/testdir/test_execute_func.vim b/src/nvim/testdir/test_execute_func.vim index 51df61d762..15ba894dbe 100644 --- a/src/nvim/testdir/test_execute_func.vim +++ b/src/nvim/testdir/test_execute_func.vim @@ -93,6 +93,8 @@ func Test_win_execute() call win_gotoid(thiswin) let line = win_execute(otherwin, 'echo getline(1)') call assert_match('the new window', line) + let line = win_execute(134343, 'echo getline(1)') + call assert_equal('', line) if has('textprop') let popupwin = popup_create('the popup win', {'line': 2, 'col': 3}) From 1def3d1542d6a65f057e743faea39a760b50db87 Mon Sep 17 00:00:00 2001 From: jing Date: Sat, 9 Jan 2021 13:15:23 +0800 Subject: [PATCH 8/8] api/window: use the "noblock" variants in nvim_win_set_buf after commit 92c6383cdca977("vim-patch:8.1.1425: win_execute() does not set window pointers properly"), nvim_win_set_buf can use switch_win_noblock and restore_win_noblock. It makes nvim_win_set_buf don't block autocmds so that it will be more "without side-effects" as said in help text. Signed-off-by: jing --- src/nvim/api/window.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c index 89fa2f86fb..5e2f03f007 100644 --- a/src/nvim/api/window.c +++ b/src/nvim/api/window.c @@ -54,7 +54,7 @@ void nvim_win_set_buf(Window window, Buffer buffer, Error *err) return; } - if (switch_win(&save_curwin, &save_curtab, win, tab, false) == FAIL) { + if (switch_win_noblock(&save_curwin, &save_curtab, win, tab, false) == FAIL) { api_set_error(err, kErrorTypeException, "Failed to switch to window %d", @@ -74,7 +74,7 @@ void nvim_win_set_buf(Window window, Buffer buffer, Error *err) // So do it now. validate_cursor(); - restore_win(save_curwin, save_curtab, false); + restore_win_noblock(save_curwin, save_curtab, false); } /// Gets the (1,0)-indexed cursor position in the window. |api-indexing|