mirror of
https://github.com/neovim/neovim.git
synced 2026-07-09 10:59:38 +00:00
Merge #40484 from justinmk/cmdwinchaos
This commit is contained in:
@@ -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:
|
||||
|
||||
<CR> Execute the command-line under the cursor. Works both in
|
||||
Insert and in Normal mode.
|
||||
<Enter> 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
|
||||
<CR> are lost.
|
||||
(`bufhidden=wipe`). Any text edits other than the line executed by <Enter>
|
||||
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 <buffer> <Tab>
|
||||
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|.
|
||||
|
||||
|
||||
@@ -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 |<cfile>| support `file://…` URIs.
|
||||
• |:log| opens log files.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -618,7 +618,7 @@ describe('tabpage/previous', function()
|
||||
-- it('does not switch to previous via :tabn #<CR> after entering operator pending',
|
||||
-- does_not_switch_to_previous_after_entering_operator_pending(':tabn #<CR>'))
|
||||
|
||||
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<Tab>', cmdwin_prevents_tab_switch('g<Tab>', 0))
|
||||
it('cmdwin prevents tab switch via <C-W>g<Tab>', cmdwin_prevents_tab_switch('<C-W>g<Tab>', 0))
|
||||
it('cmdwin prevents tab switch via <C-Tab>', cmdwin_prevents_tab_switch('<C-Tab>', 0))
|
||||
it('cmdwin prevents tab switch via :tabn #<CR>', cmdwin_prevents_tab_switch(':tabn #<CR>', 0))
|
||||
it('cmdwin allows tab switch via g<Tab>', cmdwin_allows_tab_switch('g<Tab>'))
|
||||
it('cmdwin allows tab switch via <C-W>g<Tab>', cmdwin_allows_tab_switch('<C-W>g<Tab>'))
|
||||
it('cmdwin allows tab switch via <C-Tab>', cmdwin_allows_tab_switch('<C-Tab>'))
|
||||
it('cmdwin allows tab switch via :tabn #<CR>', cmdwin_allows_tab_switch(':tabn #<CR>'))
|
||||
|
||||
it(':tabs indicates correct prevtab curwin', function()
|
||||
-- Add three tabs for a total of four
|
||||
|
||||
@@ -32,6 +32,35 @@ describe('cmdwin', function()
|
||||
eq('', fn.getcmdwintype())
|
||||
end)
|
||||
|
||||
it('<CR> closes all cmdwin (split) windows #40484', function()
|
||||
feed('q:')
|
||||
feed('ilet g:cmdwin_result = 7<Esc>')
|
||||
feed('<C-w>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('<CR>')
|
||||
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('<CR> executes when cmdwin was moved to another tabpage #40484', function()
|
||||
feed('q:')
|
||||
feed('ilet g:cmdwin_result = 9<Esc>')
|
||||
feed('<C-w>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('<CR>')
|
||||
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')
|
||||
|
||||
@@ -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 <buffer> ++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; <CR> 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.
|
||||
|
||||
@@ -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("\<c-^>", '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("\<CR>", 't')
|
||||
|
||||
Reference in New Issue
Block a user