mirror of
https://github.com/neovim/neovim.git
synced 2026-07-23 01:12:49 +00:00
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).
234 lines
8.3 KiB
Lua
234 lines
8.3 KiB
Lua
local t = require('test.testutil')
|
|
local n = require('test.functional.testnvim')()
|
|
local Screen = require('test.functional.ui.screen')
|
|
|
|
local neq, eq, command = t.neq, t.eq, n.command
|
|
local clear = n.clear
|
|
local expect, eval = n.expect, n.eval
|
|
local exec_lua = n.exec_lua
|
|
local insert, pcall_err = n.insert, t.pcall_err
|
|
local matches = t.matches
|
|
local api = n.api
|
|
local feed = n.feed
|
|
|
|
describe('eval-API', function()
|
|
before_each(clear)
|
|
|
|
it('work', function()
|
|
command("call nvim_command('let g:test = 1')")
|
|
eq(1, eval("nvim_get_var('test')"))
|
|
|
|
local buf = eval('nvim_get_current_buf()')
|
|
command('call nvim_buf_set_lines(' .. buf .. ", 0, -1, v:true, ['aa', 'bb'])")
|
|
expect([[
|
|
aa
|
|
bb]])
|
|
|
|
command('call nvim_win_set_cursor(0, [1, 1])')
|
|
command("call nvim_input('ax<esc>')")
|
|
expect([[
|
|
aax
|
|
bb]])
|
|
end)
|
|
|
|
it('throw errors for invalid arguments', function()
|
|
local err = pcall_err(command, 'call nvim_get_current_buf("foo")')
|
|
eq('Vim(call):E118: Too many arguments for function: nvim_get_current_buf', err)
|
|
|
|
err = pcall_err(command, 'call nvim_set_option_value("hlsearch")')
|
|
eq('Vim(call):E119: Not enough arguments for function: nvim_set_option_value', err)
|
|
|
|
err = pcall_err(command, 'call nvim_buf_set_lines(1, 0, -1, [], ["list"])')
|
|
eq(
|
|
'Vim(call):E5555: API call: Wrong type for argument 4 when calling nvim_buf_set_lines, expecting Boolean',
|
|
err
|
|
)
|
|
|
|
err = pcall_err(command, 'call nvim_buf_set_lines(0, 0, -1, v:true, "string")')
|
|
eq(
|
|
'Vim(call):E5555: API call: Wrong type for argument 5 when calling nvim_buf_set_lines, expecting ArrayOf(String)',
|
|
err
|
|
)
|
|
|
|
err = pcall_err(command, 'call nvim_buf_get_number("0")')
|
|
eq(
|
|
'Vim(call):E5555: API call: Wrong type for argument 1 when calling nvim_buf_get_number, expecting Buffer',
|
|
err
|
|
)
|
|
|
|
err = pcall_err(command, 'call nvim_buf_line_count(17)')
|
|
eq('Vim(call):E5555: API call: Invalid buffer id: 17', err)
|
|
end)
|
|
|
|
it('cannot change text or window if textlocked', 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$',
|
|
pcall_err(command, 'normal! yy')
|
|
)
|
|
|
|
command('autocmd TextYankPost <buffer> ++once call nvim_open_term(0, {})')
|
|
matches(
|
|
'Vim%(call%):E5555: API call: E565: Not allowed to change text or change window$',
|
|
pcall_err(command, 'normal! yy')
|
|
)
|
|
|
|
-- Functions checking textlock should also not be usable from <expr> mappings.
|
|
command('inoremap <expr> <f2> nvim_win_close(0, 1)')
|
|
eq(
|
|
'Vim(normal):E5555: API call: E565: Not allowed to change text or change window',
|
|
pcall_err(command, [[execute "normal i\<f2>"]])
|
|
)
|
|
|
|
-- Text-changing functions gave a "Failed to save undo information" error when called from an
|
|
-- <expr> mapping outside do_cmdline() (msg_list == NULL), so use feed() to test this.
|
|
command("inoremap <expr> <f2> nvim_buf_set_text(0, 0, 0, 0, 0, ['hi'])")
|
|
api.nvim_set_vvar('errmsg', '')
|
|
feed('i<f2><esc>')
|
|
eq(
|
|
'E5555: API call: E565: Not allowed to change text or change window',
|
|
api.nvim_get_vvar('errmsg')
|
|
)
|
|
|
|
-- cmdwin behavior (changed since #40312).
|
|
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)
|
|
)
|
|
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)
|
|
eq(cmdwin_win, api.nvim_get_current_win())
|
|
|
|
-- nvim_buf_set_lines() in the cmdwin buffer is OK.
|
|
api.nvim_buf_set_lines(0, 0, -1, 1, { 'wow!' })
|
|
eq({ 'wow!' }, api.nvim_buf_get_lines(0, 0, -1, 1))
|
|
|
|
-- Turning the cmdwin buffer into a terminal buffer would be pretty weird.
|
|
eq(
|
|
'E11: Invalid in command-line window; <CR> executes, CTRL-C quits',
|
|
pcall_err(api.nvim_open_term, 0, {})
|
|
)
|
|
|
|
matches(
|
|
'E11: Invalid in command%-line window; <CR> executes, CTRL%-C quits$',
|
|
pcall_err(
|
|
exec_lua,
|
|
[[
|
|
local cmdwin_buf = vim.api.nvim_get_current_buf()
|
|
vim._with({buf = vim.api.nvim_create_buf(false, true)}, function()
|
|
vim.api.nvim_open_term(cmdwin_buf, {})
|
|
end)
|
|
]]
|
|
)
|
|
)
|
|
|
|
-- But turning a different buffer into a terminal from the cmdwin is OK.
|
|
local term_buf = api.nvim_create_buf(false, true)
|
|
api.nvim_open_term(term_buf, {})
|
|
eq('terminal', api.nvim_get_option_value('buftype', { buf = term_buf }))
|
|
end)
|
|
|
|
it('use buffer numbers and windows ids as handles', function()
|
|
local bnr = eval("bufnr('')")
|
|
local bhnd = eval('nvim_get_current_buf()')
|
|
local wid = eval('win_getid()')
|
|
local whnd = eval('nvim_get_current_win()')
|
|
eq(bnr, bhnd)
|
|
eq(wid, whnd)
|
|
|
|
command('new') -- creates new buffer and new window
|
|
local bnr2 = eval("bufnr('')")
|
|
local bhnd2 = eval('nvim_get_current_buf()')
|
|
local wid2 = eval('win_getid()')
|
|
local whnd2 = eval('nvim_get_current_win()')
|
|
eq(bnr2, bhnd2)
|
|
eq(wid2, whnd2)
|
|
neq(bnr, bnr2)
|
|
neq(wid, wid2)
|
|
-- 0 is synonymous to the current buffer
|
|
eq(bnr2, eval('nvim_buf_get_number(0)'))
|
|
|
|
command('bn') -- show old buffer in new window
|
|
eq(bnr, eval('nvim_get_current_buf()'))
|
|
eq(bnr, eval("bufnr('')"))
|
|
eq(bnr, eval('nvim_buf_get_number(0)'))
|
|
eq(wid2, eval('win_getid()'))
|
|
eq(whnd2, eval('nvim_get_current_win()'))
|
|
end)
|
|
|
|
it('get_lines and set_lines use NL to represent NUL', function()
|
|
api.nvim_buf_set_lines(0, 0, -1, true, { 'aa\0', 'b\0b' })
|
|
eq({ 'aa\n', 'b\nb' }, eval('nvim_buf_get_lines(0, 0, -1, 1)'))
|
|
|
|
command('call nvim_buf_set_lines(0, 1, 2, v:true, ["xx", "\\nyy"])')
|
|
eq({ 'aa\0', 'xx', '\0yy' }, api.nvim_buf_get_lines(0, 0, -1, 1))
|
|
end)
|
|
|
|
it('that are FUNC_ATTR_NOEVAL cannot be called', function()
|
|
-- Deprecated vim_ prefix is not exported.
|
|
local err = pcall_err(command, 'call vim_get_current_buffer("foo")')
|
|
eq('Vim(call):E117: Unknown function: vim_get_current_buffer', err)
|
|
|
|
-- Deprecated buffer_ prefix is not exported.
|
|
err = pcall_err(command, 'call buffer_line_count(0)')
|
|
eq('Vim(call):E117: Unknown function: buffer_line_count', err)
|
|
|
|
-- Functions deprecated before the api functions became available
|
|
-- in vimscript are not exported.
|
|
err = pcall_err(command, 'call buffer_get_line(0, 1)')
|
|
eq('Vim(call):E117: Unknown function: buffer_get_line', err)
|
|
|
|
-- some api functions are only useful from a msgpack-rpc channel
|
|
err = pcall_err(command, 'call nvim_set_client_info()')
|
|
eq('Vim(call):E117: Unknown function: nvim_set_client_info', err)
|
|
end)
|
|
|
|
it('have metadata accessible with api_info()', function()
|
|
local api_keys = eval('sort(keys(api_info()))')
|
|
eq({ 'error_types', 'functions', 'types', 'ui_events', 'ui_options', 'version' }, api_keys)
|
|
end)
|
|
|
|
it('are highlighted by vim.vim syntax file', function()
|
|
local screen = Screen.new(40, 8)
|
|
|
|
command('set ft=vim')
|
|
n.add_builddir_to_rtp()
|
|
command('syntax on')
|
|
insert([[
|
|
call bufnr('%')
|
|
call nvim_input('typing...')
|
|
call not_a_function(42)]])
|
|
|
|
screen:expect([[
|
|
{15:call} {25:bufnr}{16:(}{26:'%'}{16:)} |
|
|
{15:call} {25:nvim_input}{16:(}{26:'typing...'}{16:)} |
|
|
{15:call} {9:not_a_function}{16:(}{26:42}{16:^)} |
|
|
{1:~ }|*4
|
|
|
|
|
]])
|
|
end)
|
|
|
|
it('cannot be called from sandbox', function()
|
|
eq(
|
|
'Vim(call):E48: Not allowed in sandbox',
|
|
pcall_err(command, "sandbox call nvim_input('ievil')")
|
|
)
|
|
eq({ '' }, api.nvim_buf_get_lines(0, 0, -1, true))
|
|
end)
|
|
|
|
it('converts blobs to API strings', function()
|
|
command('let g:v1 = nvim__id(0z68656c6c6f)')
|
|
command('let g:v2 = nvim__id(v:_null_blob)')
|
|
eq(1, eval('type(g:v1)'))
|
|
eq(1, eval('type(g:v2)'))
|
|
eq('hello', eval('g:v1'))
|
|
eq('', eval('g:v2'))
|
|
end)
|
|
end)
|