Files
neovim/test/old/testdir/test_cmdwin.vim
Justin M. Keyes b2bf7bcfb1 feat(cmdwin): implement cmdwin as a normal buf+win
Problem:

cmdwin (the `:q` cmdline buffer) has various limitations which require
special-casing all over the codebase.

Besides complicating the code, it also breaks async plugins if they try
to create buffers/windows after some work is done, if the user happens
to open cmdwin at the wrong the moment:

    Lua callback: …/guh.nvim/lua/guh/util.lua:531:
    E11: Invalid in command-line window; <CR> executes, CTRL-C quits
    stack traceback:
        [C]: in function 'nvim_buf_delete'
        …/guh.nvim/lua/guh/util.lua:531: in function <…/guh.nvim/lua/guh/util.lua:526>

Solution:

Just say no to "inception". Reimplement cmdwin as a normal buffer+window.

All of the cmdwin contortions (in both core, and innocent plugins) exist
literally only to support "inception": recursive
cmdwin-in-cmdline-things, like `<c-r>=`, `/`, search-during-substitute,
`:input()`, etc. So we just won't support that (though I have
a potential plan for that later, which I call "modal parking lot").

The benefit is that plugins, and core, no longer have to care about
cmdwin.

BONUS:
- mouse-drag on vertical separators works (it only worked for
  horizontal/statusline before)
- inccommand-in-cmdwin now works correctly, for free (thus don't need
  #40077).

POTENTIAL FOLLOWUPS
- Drop `CHECK_CMDWIN` ("E11: Invalid in command-line window"), allow chaos.
- Unify `BUFLOCK_OK` / `LOCK_OK` ?

DESIGN:
- Eliminate lots of C globals, `EX_CMDWIN`, etc.
- `text_locked()` no longer reports true for cmdwin.
- cmdwin = a normal window with 'winfixbuf', 'bufhidden=wipe',
  'buftype=nofile'. Invariants come from those options rather than
  special cases throughout the codebase.
- `nv_record` for q:/q//q? calls Lua
  `nlua_call_vimfn("vim._core.cmdwin", …)`. No `K_CMDWIN`
  / cmdline-reader detour.
- `cedit_key` (`c_CTRL-F`) schedules a deferred event that calls
  `vim._core.cmdwin.open(type, content, pos)` and returns `Ctrl_C` so
  the in-flight cmdline cancels. Reader state is not serialized; instead
  the captured `(type, line, col)` is replayed via
  `nvim_feedkeys(type..line.."<CR>", "nt", …)` after user confirms.
- On confirm/cancel: `<CR>` / `<C-C>` calls into Lua which closes the
  window and re-feeds the cmdline.

BREAKING CHANGES:
- Expression-register cmdline (`<C-R>=` from insert-mode) no longer
  supports cmdwin. Same applies to `input()` / `inputlist()` (already
  covered by `text_locked`).
- Usage of cmdwin in macros/mappings will probably break (assuming they
  ever worked).
2026-06-25 18:36:24 +02:00

278 lines
7.9 KiB
VimL
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

" Tests for editing the command line.
source check.vim
source screendump.vim
func Test_cant_open_cmdwin_in_cmdwin()
try
call feedkeys("q:q::q\<CR>", "x!")
catch
let caught = v:exception
endtry
call assert_match('E1292:', caught)
endfunc
func Test_cmdwin_virtual_edit()
enew!
set ve=all cpo+=$
silent normal q/s
set ve= cpo-=$
endfunc
" Check that a :normal command can be used to stop Visual mode without side
" effects.
func Test_normal_escape()
throw 'Skipped: Nvim supports cmdwin freedom #40312'
call feedkeys("q:i\" foo\<Esc>:normal! \<C-V>\<Esc>\<CR>:\" bar\<CR>", 'ntx')
call assert_equal('" bar', @:)
endfunc
" This was using a pointer to a freed buffer
func Test_cmdwin_freed_buffer_ptr()
throw "Skipped: Nvim supports cmdwin freedom #40312"
" this does not work on MS-Windows because renaming an open file fails
CheckNotMSWindows
au BufEnter * next 0| file
edit 0
silent! norm q/
au! BufEnter
bwipe!
endfunc
" This was resulting in a window with negative width.
" The test doesn't reproduce the illegal memory access though...
func Test_cmdwin_split_often()
let lines = &lines
let columns = &columns
set t_WS=
try
" set encoding=iso8859
set ruler
winsize 0 0
noremap 0 H
sil norm 0000000q:
catch /E36:/
endtry
bwipe!
set encoding=utf8
let &lines = lines
let &columns = columns
endfunc
func Test_cmdwin_restore_heights()
set showtabline=0 cmdheight=2 laststatus=0
call feedkeys("q::set cmdheight=1\<CR>:q\<CR>", 'ntx')
call assert_equal(&lines - 1, winheight(0))
set showtabline=2 cmdheight=3
call feedkeys("q::set showtabline=0\<CR>:q\<CR>", 'ntx')
call assert_equal(&lines - 3, winheight(0))
set cmdheight=1 laststatus=2
call feedkeys("q::set laststatus=0\<CR>:q\<CR>", 'ntx')
call assert_equal(&lines - 1, winheight(0))
set laststatus=2
call feedkeys("q::set laststatus=1\<CR>:q\<CR>", 'ntx')
call assert_equal(&lines - 1, winheight(0))
set laststatus=2
belowright vsplit
wincmd _
let restcmds = winrestcmd()
call feedkeys("q::set laststatus=1\<CR>:q\<CR>", 'ntx')
" As we have 2 windows, &ls = 1 should still have a statusline on the last
" window. As such, the number of available rows hasn't changed and the window
" sizes should be restored.
call assert_equal(restcmds, winrestcmd())
set cmdheight& showtabline& laststatus&
endfunc
func Test_cmdwin_temp_curwin()
throw "Skipped: Nvim supports cmdwin freedom #40312"
func CheckWraps(expect_wrap)
setlocal textwidth=0 wrapmargin=1
call deletebufline('', 1, '$')
let as = repeat('a', winwidth(0) - 2 - &wrapmargin)
call setline(1, as .. ' b')
normal! gww
setlocal textwidth& wrapmargin&
call assert_equal(a:expect_wrap ? [as, 'b'] : [as .. ' b'], getline(1, '$'))
endfunc
func CheckCmdWin()
call assert_equal('command', win_gettype())
" textoff and &wrapmargin formatting considers the cmdwin_type char.
call assert_equal(1, getwininfo(win_getid())[0].textoff)
call CheckWraps(1)
endfunc
func CheckOtherWin()
call assert_equal('', win_gettype())
call assert_equal(0, getwininfo(win_getid())[0].textoff)
call CheckWraps(0)
endfunc
call feedkeys("q::call CheckCmdWin()\<CR>:call win_execute(win_getid(winnr('#')), 'call CheckOtherWin()')\<CR>:q<CR>", 'ntx')
%bwipe!
delfunc CheckWraps
delfunc CheckCmdWin
delfunc CheckOtherWin
endfunc
func Test_cmdwin_interrupted()
throw "Skipped: Nvim supports cmdwin freedom #40312"
func CheckInterrupted()
call feedkeys("q::call assert_equal('', getcmdwintype())\<CR>:call assert_equal('', getcmdtype())\<CR>:q<CR>", 'ntx')
endfunc
augroup CmdWin
" While opening the cmdwin's split:
" Close the cmdwin's window.
au WinEnter * ++once quit
call CheckInterrupted()
" Close the old window.
au WinEnter * ++once execute winnr('#') 'quit'
call CheckInterrupted()
" Switch back to the old window.
au WinEnter * ++once wincmd p
call CheckInterrupted()
" Change the old window's buffer.
au WinEnter * ++once call win_execute(win_getid(winnr('#')), 'enew')
call CheckInterrupted()
" Using BufLeave autocmds as cmdwin restrictions do not apply to them when
" fired from opening the cmdwin...
" After opening the cmdwin's split, while creating the cmdwin's buffer:
" Delete the cmdwin's buffer.
au BufLeave * ++once bwipe
call CheckInterrupted()
" Close the cmdwin's window.
au BufLeave * ++once quit
call CheckInterrupted()
" Close the old window.
au BufLeave * ++once execute winnr('#') 'quit'
call CheckInterrupted()
" Switch to a different window.
au BufLeave * ++once split
call CheckInterrupted()
" Change the old window's buffer.
au BufLeave * ++once call win_execute(win_getid(winnr('#')), 'enew')
call CheckInterrupted()
" However, changing the current buffer is OK and does not interrupt.
au BufLeave * ++once edit other
call feedkeys("q::let t=getcmdwintype()\<CR>:let b=bufnr()\<CR>:clo<CR>", 'ntx')
call assert_equal(':', t)
call assert_equal(1, bufloaded('other'))
call assert_notequal(b, bufnr('other'))
augroup END
" No autocmds should remain, but clear the augroup to be sure.
augroup CmdWin
au!
augroup END
%bwipe!
delfunc CheckInterrupted
endfunc
func Test_cmdwin_existing_bufname()
throw "Skipped: Nvim supports cmdwin freedom #40312"
func CheckName()
call assert_equal(1, getbufinfo('')[0].command)
call assert_equal(0, getbufinfo('[Command Line]')[0].command)
call assert_match('#a\s*"\[Command Line\]"', execute('ls'))
call assert_match('%a\s*"\[Command Line\]"', execute('ls'))
endfunc
file [Command Line]
call feedkeys("q::call CheckName()\<CR>:q\<CR>", 'ntx')
0file
delfunc CheckName
endfunc
func Test_cmdwin_showcmd()
CheckScreendump
let lines =<< trim [SCRIPT]
augroup vimHints | au! | augroup END
set showcmd
[SCRIPT]
call writefile(lines, 'XTest_cmdwin_showcmd', 'D')
let buf = RunVimInTerminal('-S XTest_cmdwin_showcmd', {'rows': 18})
for keys in ['q:', ":\<C-F>"]
call term_sendkeys(buf, keys)
call VerifyScreenDump(buf, 'Test_cmdwin_showcmd_1', {})
call term_sendkeys(buf, '"')
call WaitForAssert({-> assert_match('^: \+" *$', term_getline(buf, 18))})
call term_sendkeys(buf, 'x')
call WaitForAssert({-> assert_match('^: \+"x *$', term_getline(buf, 18))})
call term_sendkeys(buf, 'y')
call WaitForAssert({-> assert_match('^: \+"xy *$', term_getline(buf, 18))})
call term_sendkeys(buf, 'y')
call WaitForAssert({-> assert_match('^: \+$', term_getline(buf, 18))})
call term_sendkeys(buf, "\<C-C>\<C-C>")
call VerifyScreenDump(buf, 'Test_cmdwin_showcmd_2', {})
endfor
" clean up
call StopVimInTerminal(buf)
endfunc
func Test_cmdwin_cursor_position()
throw "Skipped: Nvim supports cmdwin freedom #40312"
" When the cmdline fills the screen width exactly, pressing CTRL-F to open
" the cmdwin should place the cursor on the last character, not past it.
let cmd = 'echo "' .. repeat('a', &columns - 8) .. '"'
call assert_equal(&columns - 1, len(cmd))
let g:cmdwin_col = 0
let g:cmdwin_line = ''
call feedkeys(':' .. cmd .. "\<C-F>" ..
\ ":let g:cmdwin_col = col('.')\<CR>" ..
\ ":let g:cmdwin_line = getline('.')\<CR>" ..
\ ":q\<CR>", 'x!')
call assert_equal(len(cmd), g:cmdwin_col)
call assert_equal(cmd, g:cmdwin_line)
unlet g:cmdwin_col g:cmdwin_line
endfunc
func Test_cmdwin_no_prefix_on_wrapped_line()
CheckScreendump
let lines =<< trim END
augroup vimHints | au! | augroup END
END
call writefile(lines, 'Xcmdwin_wrap', 'D')
let buf = RunVimInTerminal('-S Xcmdwin_wrap', #{rows: 12, cols: 40})
let cmd = 'echo "' .. repeat('a', 40 - 8) .. '"XYZ'
call term_sendkeys(buf, ':' .. cmd .. "\<C-F>")
call TermWait(buf, 100)
call VerifyScreenDump(buf, 'Test_cmdwin_wrap_prefix', {})
call term_sendkeys(buf, ":q\<CR>")
call StopVimInTerminal(buf)
endfunc
" vim: shiftwidth=2 sts=2 expandtab