diff --git a/runtime/doc/cmdline.txt b/runtime/doc/cmdline.txt index 332786f391..a95437ba24 100644 --- a/runtime/doc/cmdline.txt +++ b/runtime/doc/cmdline.txt @@ -1205,9 +1205,13 @@ There are two ways to open the command-line window: When the window opens it is filled with the command-line history. The editor opens in Normal mode. The last line contains the command as typed so far. The |cmdwin-char| is shown in 'statuscolumn', indicating the type of command-line. +The window is full width, positioned just above the command-line; its height +is decided by 'cmdwinheight' (if there is room). -The window height is decided by 'cmdwinheight' (if there is room). The window -is full width and is positioned just above the command-line. +Window and tabpage commands work as usual while cmdwin is open: you can +navigate, resize, close and open windows/tabpages. The mouse works as in any +window: clicks focus other windows, and statuslines and vertical separators +can be dragged to resize. *E1292* Only one command-line window can be open at a time. @@ -1223,17 +1227,17 @@ but it's not possible to open another cmdwin from there. There is no nesting. *E11* -|CTRL-W| navigation/split commands and some operations (e.g. |:terminal|) emit -E11. But mouse clicks may focus other windows. The cmdwin buffer is pinned by -'winfixbuf', so the window cannot switch to a different buffer. +The cmdwin buffer cannot be used as a |:terminal|. Its window cannot switch to +a different buffer ('winfixbuf'). CLOSE There are several ways to leave the command-line window: - Execute the command-line under the cursor. Works both in - Insert and in Normal mode. + Execute the command-line under the cursor, then close the + window(s) where cmdwin is visible. Works both in Insert and + in Normal mode. CTRL-C Continue in Command-line mode. The command-line under the cursor is pre-filled in the command-line. Works both in Insert and in Normal mode. @@ -1245,8 +1249,8 @@ CTRL-C Continue in Command-line mode. The command-line under the When the cmdwin closes, focus returns to the window where the command-line was started from (if possible). The cmdwin scratch buffer is destroyed -(`bufhidden=wipe`). Any text edits other than the line executed by - are lost. +(`bufhidden=wipe`). Any text edits other than the line executed by +are lost. If you would like to execute the command under the cursor and then have the command-line window open again, you may find this mapping useful: > @@ -1281,9 +1285,6 @@ If you don't want these mappings, disable them with: > au CmdwinEnter [:>] nunmap You could put these lines in your vimrc file. -The mouse works as in any window: clicks focus other windows, and statuslines -and vertical separators can be dragged to resize. - The |getcmdwintype()| function returns the type of the command-line being edited as described in |cmdwin-char|. diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 34ec454817..d369034de2 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -188,11 +188,11 @@ EDITOR kind/menu/info/abbr for the popup menu. • |cmdwin| (|q:|, |q/|, |q?|, |c_CTRL-F|) is implemented as a "normal" buffer+window instead of a nested-state modal loop: + • You can create/navigate windows/tabpages while cmdwin is active. Chaos! • 'inccommand' works in cmdwin! • API calls (e.g. |nvim_buf_delete()|) that previously failed with "E11: Invalid in command-line window" while cmdwin was open, now work normally. Async plugins no longer need special |CmdwinLeave| workarounds. - • Removed most cmdwin-specific |E11| guards, except for window navigation. • The |cmdwin-char| is shown via 'statuscolumn'. • |gf| and || support `file://…` URIs. • |:log| opens log files. diff --git a/runtime/lua/vim/_core/cmdwin.lua b/runtime/lua/vim/_core/cmdwin.lua index 699b72e8db..05bc112b33 100644 --- a/runtime/lua/vim/_core/cmdwin.lua +++ b/runtime/lua/vim/_core/cmdwin.lua @@ -101,15 +101,22 @@ function M.open(type, init_line, init_col) caller_win = caller, } - -- Clean up if the window is closed by other means (`:q`, `:close`, etc.). + -- Clean up when the (last-visible) cmdwin is closed by other means (`:q`, `:close`, etc.). vim.api.nvim_create_autocmd({ 'WinClosed' }, { buffer = buf, - once = true, nested = true, - callback = function() - if state ~= nil then - M._cleanup() + callback = function(ev) + if state == nil then + return end + local closing = tonumber(ev.match) + for _, w in ipairs(vim.fn.win_findbuf(buf)) do + if w ~= closing then + return -- Still visible elsewhere; keep cmdwin (and this autocmd) active. + end + end + M._cleanup() + return true -- Last cmdwin window gone; delete this autocmd. end, }) @@ -125,8 +132,8 @@ function M._cleanup() state = nil pcall(vim.api.nvim__cmdwin_set, '', 0) -- Clear the C-side globals. pcall(vim.api.nvim_exec_autocmds, 'CmdwinLeave', { pattern = s.type, modeline = false }) - if vim.api.nvim_win_is_valid(s.win) then - pcall(vim.api.nvim_win_close, s.win, true) + if vim.api.nvim_buf_is_valid(s.buf) then + pcall(vim.api.nvim_buf_delete, s.buf, { force = true }) end if vim.api.nvim_win_is_valid(s.caller_win) then pcall(vim.api.nvim_set_current_win, s.caller_win) diff --git a/src/nvim/window.c b/src/nvim/window.c index 994111d195..3e5d437177 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -271,20 +271,11 @@ void do_window(int nchar, int Prenum, int xchar) int Prenum1 = Prenum == 0 ? 1 : Prenum; -#define CHECK_CMDWIN \ - do { \ - if (cmdwin_buf != NULL) { \ - emsg(_(e_cmdwin)); \ - return; \ - } \ - } while (0) - switch (nchar) { // split current window in two parts, horizontally case 'S': case Ctrl_S: case 's': - CHECK_CMDWIN; reset_VIsual_and_resel(); // stop Visual mode // When splitting the quickfix window open a new buffer in it, // don't replicate the quickfix buffer. @@ -297,7 +288,6 @@ void do_window(int nchar, int Prenum, int xchar) // split current window in two parts, vertically case Ctrl_V: case 'v': - CHECK_CMDWIN; reset_VIsual_and_resel(); // stop Visual mode // When splitting the quickfix window open a new buffer in it, // don't replicate the quickfix buffer. @@ -310,7 +300,6 @@ void do_window(int nchar, int Prenum, int xchar) // split current window and edit alternate file case Ctrl_HAT: case '^': - CHECK_CMDWIN; reset_VIsual_and_resel(); // stop Visual mode if (buflist_findnr(Prenum == 0 ? curwin->w_alt_fnum : Prenum) == NULL) { @@ -331,7 +320,6 @@ void do_window(int nchar, int Prenum, int xchar) // open new window case Ctrl_N: case 'n': - CHECK_CMDWIN; reset_VIsual_and_resel(); // stop Visual mode newwindow: if (Prenum) { @@ -366,7 +354,6 @@ newwindow: // close preview window case Ctrl_Z: case 'z': - CHECK_CMDWIN; reset_VIsual_and_resel(); // stop Visual mode do_cmdline_cmd("pclose"); break; @@ -391,7 +378,6 @@ newwindow: // close all but current window case Ctrl_O: case 'o': - CHECK_CMDWIN; reset_VIsual_and_resel(); // stop Visual mode cmd_with_count("only", cbuf, sizeof(cbuf), Prenum); do_cmdline_cmd(cbuf); @@ -402,7 +388,6 @@ newwindow: case 'w': // cursor to previous window with wrap around case 'W': - CHECK_CMDWIN; if (ONE_WINDOW && Prenum != 1) { // just one window beep_flush(); } else { @@ -454,7 +439,6 @@ newwindow: case 'j': case K_DOWN: case Ctrl_J: - CHECK_CMDWIN; win_goto_ver(false, Prenum1); break; @@ -462,7 +446,6 @@ newwindow: case 'k': case K_UP: case Ctrl_K: - CHECK_CMDWIN; win_goto_ver(true, Prenum1); break; @@ -471,7 +454,6 @@ newwindow: case K_LEFT: case Ctrl_H: case K_BS: - CHECK_CMDWIN; win_goto_hor(true, Prenum1); break; @@ -479,13 +461,11 @@ newwindow: case 'l': case K_RIGHT: case Ctrl_L: - CHECK_CMDWIN; win_goto_hor(false, Prenum1); break; // move window to new tab page case 'T': - CHECK_CMDWIN; if (one_window(curwin, NULL)) { msg(_(m_onlyone), 0); } else { @@ -533,21 +513,18 @@ newwindow: // exchange current and next window case 'x': case Ctrl_X: - CHECK_CMDWIN; win_exchange(Prenum); break; // rotate windows downwards case Ctrl_R: case 'r': - CHECK_CMDWIN; reset_VIsual_and_resel(); // stop Visual mode win_rotate(false, Prenum1); // downwards break; // rotate windows upwards case 'R': - CHECK_CMDWIN; reset_VIsual_and_resel(); // stop Visual mode win_rotate(true, Prenum1); // upwards break; @@ -557,7 +534,6 @@ newwindow: case 'J': case 'H': case 'L': - CHECK_CMDWIN; if (one_window(curwin, NULL)) { beep_flush(); } else { @@ -608,7 +584,6 @@ newwindow: // jump to tag and split window if tag exists (in preview window) case '}': - CHECK_CMDWIN; if (Prenum) { g_do_tagpreview = Prenum; } else { @@ -617,7 +592,6 @@ newwindow: FALLTHROUGH; case ']': case Ctrl_RSB: - CHECK_CMDWIN; // Keep visual mode, can select words to use as a tag. if (Prenum) { postponed_split = Prenum; @@ -640,7 +614,6 @@ newwindow: case 'F': case Ctrl_F: { wingotofile: - CHECK_CMDWIN; if (check_text_or_curbuf_locked(NULL)) { break; } @@ -698,7 +671,6 @@ wingotofile: FALLTHROUGH; case 'd': // Go to definition, using 'define' case Ctrl_D: { - CHECK_CMDWIN; size_t len; char *ptr; if ((len = find_ident_under_cursor(&ptr, FIND_IDENT, NULL)) == 0) { @@ -726,7 +698,6 @@ wingotofile: // CTRL-W g extended commands case 'g': case Ctrl_G: - CHECK_CMDWIN; no_mapping++; allow_keys++; // no mapping for xchar, but allow key codes if (xchar == NUL) { @@ -4498,10 +4469,6 @@ tabpage_T *win_new_tabpage(int after, char *filename, bool enter, win_T **first) { tabpage_T *old_curtab = curtab; - if (enter && cmdwin_buf != NULL) { - emsg(_(e_cmdwin)); - return NULL; - } if (window_layout_locked(CMD_tabnew)) { return NULL; } @@ -4936,10 +4903,6 @@ void goto_tabpage(int n) /// @param trigger_leave_autocmds when true trigger *Leave autocommands. void goto_tabpage_tp(tabpage_T *tp, bool trigger_enter_autocmds, bool trigger_leave_autocmds) { - if (trigger_enter_autocmds || trigger_leave_autocmds) { - CHECK_CMDWIN; - } - // Don't repeat a message in another tab page. set_keep_msg(NULL, 0); diff --git a/test/functional/autocmd/tabnewentered_spec.lua b/test/functional/autocmd/tabnewentered_spec.lua index 93031a6b1c..5ed585a32d 100644 --- a/test/functional/autocmd/tabnewentered_spec.lua +++ b/test/functional/autocmd/tabnewentered_spec.lua @@ -618,7 +618,7 @@ describe('tabpage/previous', function() -- it('does not switch to previous via :tabn # after entering operator pending', -- does_not_switch_to_previous_after_entering_operator_pending(':tabn #')) - local function cmdwin_prevents_tab_switch(characters, completion_visible) + local function cmdwin_allows_tab_switch(characters) return function() -- Add three tabs for a total of four command('tabnew') @@ -633,25 +633,19 @@ describe('tabpage/previous', function() local cmdline_win_id = eval('win_getid()') - -- At this point switching to the previous tab should have no effect. + -- Switching to the previous tab now works. feed(characters) - -- Attempting to switch tabs maintains the current window. - eq(cmdline_win_id, eval('win_getid()')) - eq(completion_visible, eval('complete_info().pum_visible')) - - -- The current tab is still the fourth. - eq(4, eval('tabpagenr()')) - - -- The previous tab is still the third. - eq(3, eval("tabpagenr('#')")) + -- Focus moved out of the cmdwin to the previous (third) tab. + t.neq(cmdline_win_id, eval('win_getid()')) + eq(3, eval('tabpagenr()')) end end - it('cmdwin prevents tab switch via g', cmdwin_prevents_tab_switch('g', 0)) - it('cmdwin prevents tab switch via g', cmdwin_prevents_tab_switch('g', 0)) - it('cmdwin prevents tab switch via ', cmdwin_prevents_tab_switch('', 0)) - it('cmdwin prevents tab switch via :tabn #', cmdwin_prevents_tab_switch(':tabn #', 0)) + it('cmdwin allows tab switch via g', cmdwin_allows_tab_switch('g')) + it('cmdwin allows tab switch via g', cmdwin_allows_tab_switch('g')) + it('cmdwin allows tab switch via ', cmdwin_allows_tab_switch('')) + it('cmdwin allows tab switch via :tabn #', cmdwin_allows_tab_switch(':tabn #')) it(':tabs indicates correct prevtab curwin', function() -- Add three tabs for a total of four diff --git a/test/functional/editor/cmdwin_spec.lua b/test/functional/editor/cmdwin_spec.lua index c34acea192..d43cb7d4fa 100644 --- a/test/functional/editor/cmdwin_spec.lua +++ b/test/functional/editor/cmdwin_spec.lua @@ -32,6 +32,35 @@ describe('cmdwin', function() eq('', fn.getcmdwintype()) end) + it(' closes all cmdwin (split) windows #40484', function() + feed('q:') + feed('ilet g:cmdwin_result = 7') + feed('s') -- split: two windows now show the cmdwin buffer. + local cmdwin_buf = api.nvim_get_current_buf() + eq(2, #fn.win_findbuf(cmdwin_buf)) -- sanity: the split happened + + feed('') + eq(7, api.nvim_get_var('cmdwin_result')) -- the cmdline executed + eq('', fn.getcmdwintype()) + eq(0, #fn.win_findbuf(cmdwin_buf)) -- All cmdwin windows are closed. + eq(1, #api.nvim_list_wins()) -- Back to the single original window. + end) + + it(' executes when cmdwin was moved to another tabpage #40484', function() + feed('q:') + feed('ilet g:cmdwin_result = 9') + feed('T') -- Move the cmdwin to its own new tabpage. + eq(2, fn.tabpagenr('$')) -- sanity: the new tabpage exists + local cmdwin_buf = api.nvim_get_current_buf() + + feed('') + eq(9, api.nvim_get_var('cmdwin_result')) -- the cmdline executed. + eq('', fn.getcmdwintype()) + eq(0, #fn.win_findbuf(cmdwin_buf)) -- cmdwin window closed. + eq(1, fn.tabpagenr('$')) -- cmdwin's tabpage is gone. + eq(1, #api.nvim_list_wins()) + end) + it('q/ opens cmdwin with search history', function() fn.histadd('/', 'foo') fn.histadd('/', 'bar') diff --git a/test/functional/vimscript/api_functions_spec.lua b/test/functional/vimscript/api_functions_spec.lua index b341c191d6..cc08108d15 100644 --- a/test/functional/vimscript/api_functions_spec.lua +++ b/test/functional/vimscript/api_functions_spec.lua @@ -60,7 +60,7 @@ describe('eval-API', function() eq('Vim(call):E5555: API call: Invalid buffer id: 17', err) end) - it('cannot change text or window if textlocked', function() + it('cannot change text or window if textlock', function() command('autocmd TextYankPost ++once call nvim_buf_set_lines(0, 0, -1, v:false, [])') matches( 'Vim%(call%):E5555: API call: E565: Not allowed to change text or change window$', @@ -90,19 +90,16 @@ describe('eval-API', function() api.nvim_get_vvar('errmsg') ) - -- cmdwin behavior (changed since #40312). + -- cmdwin (#40312, #40484): E11 "Invalid in command-line window" restriction was removed. local old_win = api.nvim_get_current_win() feed('q:') local cmdwin_win = api.nvim_get_current_win() - eq( - 'Vim:E11: Invalid in command-line window; executes, CTRL-C quits', - pcall_err(api.nvim_set_current_win, old_win) - ) + neq(old_win, cmdwin_win) + -- Switching to another window from the cmdwin now works (previously E11). + api.nvim_set_current_win(old_win) eq(old_win, api.nvim_get_current_win()) - -- TODO(justinmk): awkward: E11 is raised but the focus switch happens anyway; inherited bug in - -- goto_tabpage_win (calls win_enter unconditionally even when goto_tabpage_tp's CHECK_CMDWIN - -- emsg'd). We should probably just remove CHECK_CMDWIN from goto_tabpage_tp. #40407 - pcall(api.nvim_set_current_win, cmdwin_win) + -- ...and back into the cmdwin. + api.nvim_set_current_win(cmdwin_win) eq(cmdwin_win, api.nvim_get_current_win()) -- nvim_buf_set_lines() in the cmdwin buffer is OK. diff --git a/test/old/testdir/test_normal.vim b/test/old/testdir/test_normal.vim index 4f57251036..1f25bf7c3c 100644 --- a/test/old/testdir/test_normal.vim +++ b/test/old/testdir/test_normal.vim @@ -3242,11 +3242,8 @@ func Test_normal50_commandline() func! DoTimerWork(id) call assert_equal(1, getbufinfo('')[0].command) - " should fail, with E11, but does fail with E23? - "call feedkeys("\", 'tm') - - " should fail with E11 - "Invalid in command-line window" - call assert_fails(":wincmd p", 'E11') + " Nvim removed the E11 "Invalid in command-line window" restriction (#40312, #40484). + "call assert_fails(":wincmd p", 'E11') " Return from commandline window. call feedkeys("\", 't')