Files
neovim/test/functional/ui/cmdline2_spec.lua
luukvbaal 5f7237f54b fix(ui2): unable to route by message ID #39734
Problem:  - "bufwrite" message identifier is encoded in the message ID
            of a "progress" kind message (since ff68fd6b); UI2 does not
            allow routing by message ID.
          - No documented way to set a default message target for all
            but a few kinds (without copying all of |ui-messages| kinds
            to cfg.msg.targets).
          - A user adding a message route for the documented empty ""
            kind can result in unexpected behavior.
          - Showing duplicate message (x) indicator in msg and cmd
            targets simultaneously is unsupported.
          - Manually triggering CursorMoved autocommand to add matchparen
            highlighting in the cmdline.
Solution: - Match cfg.msg.targets keys as Lua pattern to a message ID.
          - Recognize "default" as key in cfg.msg.targets, drop the
            undocumented cfg.msg.target field.
          - Don't try to get configured target for "" message kind/trigger.
          - Maintain msg indicator virtual text for the cmd and msg target.
          - Add matchparen highlighting by directly calling the Lua module
            (possible since b813c7e0).
2026-05-11 18:17:04 -04:00

334 lines
14 KiB
Lua

-- Tests for (protocol-driven) ui2, intended to replace the legacy message grid UI.
local t = require('test.testutil')
local n = require('test.functional.testnvim')()
local Screen = require('test.functional.ui.screen')
local clear, exec, exec_lua, feed = n.clear, n.exec, n.exec_lua, n.feed
describe('cmdline2', function()
local screen
before_each(function()
clear()
screen = Screen.new()
screen:add_extra_attr_ids({
[100] = { foreground = Screen.colors.Magenta1, bold = true },
[101] = { background = Screen.colors.Yellow, foreground = Screen.colors.Grey0 },
[102] = { background = Screen.colors.Cyan1, foreground = Screen.colors.SlateBlue },
})
exec_lua(function()
require('vim._core.ui2').enable({})
end)
end)
it("no crash for invalid grid after 'cmdheight' OptionSet", function()
exec('tabnew | tabprev')
feed(':set ch=0')
screen:expect([[
{5: [No Name] }{24: [No Name] }{2: }{24:X}|
|
{1:~ }|*11
{16::}{15:set} {16:ch}{15:=}0^ |
]])
feed('<CR>')
exec('tabnext')
screen:expect([[
{24: [No Name] }{5: [No Name] }{2: }{24:X}|
^ |
{1:~ }|*11
{16::}{15:set} {16:ch}{15:=}0 |
]])
exec('tabnext')
screen:expect([[
{5: [No Name] }{24: [No Name] }{2: }{24:X}|
^ |
{1:~ }|*12
]])
n.assert_alive()
end)
it("redraw does not clear 'incsearch' highlight with conceal", function()
exec('call setline(1, ["foo", "foobar"]) | set conceallevel=1 concealcursor=c')
feed('/foo')
screen:expect([[
{10:foo} |
{2:foo}bar |
{1:~ }|*11
/foo^ |
]])
end)
it('block mode', function()
feed(':if 1<CR>')
screen:expect([[
|
{1:~ }|*11
{16::}{15:if} {26:1} |
{16::} ^ |
]])
feed('echo "foo"')
screen:expect([[
|
{1:~ }|*11
{16::}{15:if} {26:1} |
{16::} {15:echo} {26:"foo"}^ |
]])
feed('<CR>')
screen:expect([[
|
{1:~ }|*9
{16::}{15:if} {26:1} |
{16::} {15:echo} {26:"foo"} |
foo |
{16::} ^ |
]])
feed([[echo input("foo\nbar:")<CR>]])
screen:expect([[
|
{1:~ }|*7
{16::}{15:if} {26:1} |
{16::} {15:echo} {26:"foo"} |
foo |
{16::} {15:echo} {25:input}{16:(}{26:"foo\nbar:"}{16:)} |
foo |
bar:^ |
]])
feed('baz')
screen:expect([[
|
{1:~ }|*7
{16::}{15:if} {26:1} |
{16::} {15:echo} {26:"foo"} |
foo |
{16::} {15:echo} {25:input}{16:(}{26:"foo\nbar:"}{16:)} |
foo |
bar:baz^ |
]])
feed('<CR>')
screen:expect([[
|
{1:~ }|*5
{16::}{15:if} {26:1} |
{16::} {15:echo} {26:"foo"} |
foo |
{16::} {15:echo} {25:input}{16:(}{26:"foo\nbar:"}{16:)} |
foo |
bar:baz |
baz |
{16::} ^ |
]])
feed('endif')
screen:expect([[
|
{1:~ }|*5
{16::}{15:if} {26:1} |
{16::} {15:echo} {26:"foo"} |
foo |
{16::} {15:echo} {25:input}{16:(}{26:"foo\nbar:"}{16:)} |
foo |
bar:baz |
baz |
{16::} {15:endif}^ |
]])
feed('<CR>')
screen:expect([[
^ |
{1:~ }|*12
|
]])
end)
it('handles empty prompt', function()
feed(":call input('')<CR>")
screen:expect([[
|
{1:~ }|*12
^ |
]])
end)
it('highlights after deleting buffer', function()
feed(':sil %bw!<CR>:call foo()')
screen:expect([[
|
{1:~ }|*12
{16::}{15:call} {25:foo}{16:()}^ |
]])
end)
it('can change cmdline buffer during textlock', function()
exec([[
func Foo(a, b)
redrawstatus!
endfunc
set wildoptions=pum findfunc=Foo wildmode=noselect:lastused,full
au CmdlineChanged * call wildtrigger()
]])
feed(':find ')
screen:expect([[
|
{1:~ }|*12
{16::}{15:find} ^ |
]])
t.eq(n.eval('v:errmsg'), "E1514: 'findfunc' did not return a List type")
end)
it('substitution match, empty message does not clear active cmdline', function()
exec('call setline(1, "foo")')
feed(':s/f')
screen:expect([[
{10:f}oo |
{1:~ }|*12
{16::}{15:s}{16:/f^ } |
]])
feed('<Esc>:foo')
screen:expect([[
foo |
{1:~ }|*12
{16::}{15:foo}^ |
]])
exec('echo')
screen:expect_unchanged(true)
end)
it('dialog position is adjusted for toggled non-pum wildmenu', function()
exec([[
set wildmode=list:full,full wildoptions-=pum
func Foo()
endf
func Fooo()
endf
]])
feed(':call Fo<C-Z>')
screen:expect([[
|
{1:~ }|*9
{3: }|
Foo() Fooo() |
|
{16::}{15:call} Fo^ |
]])
feed('<C-Z>')
screen:expect([[
|
{1:~ }|*8
{3: }|
Foo() Fooo() |
|
{101:Foo()}{3: Fooo() }|
{16::}{15:call} {25:Foo}{16:()}^ |
]])
feed('<BS><BS>')
exec('set wildoptions+=pum laststatus=2')
screen:expect([[
|
{1:~ }|*9
{3: }|
Foo() Fooo() |
|
{16::}{15:call} Foo^ |
]])
feed('<C-Z><C-Z>')
screen:expect([[
|
{1:~ }|*9
{3: }|
Foo(){12: Foo() } |
{4: Fooo() } |
{16::}{15:call} {25:Foo}{16:()}^ |
]])
end)
it('updated after setcmdline() #38764', function()
-- Also check that command-preview is updated.
exec('call setline(1, "foo")')
feed(':%s/foo')
screen:expect([[
{10:foo} |
{1:~ }|*12
{16::}%{15:s}{16:/foo^ } |
]])
exec_lua(function()
_G.events = {}
vim.api.nvim_create_autocmd({ 'CmdlineChanged', 'CursorMovedC' }, {
callback = function(ev)
_G.events[ev.event] = (_G.events[ev.event] or 0) + 1
end,
})
end)
exec('call setcmdline("%s/fo")')
screen:expect([[
{10:fo}o |
{1:~ }|*12
{16::}%{15:s}{16:/fo^ } |
]])
t.eq({ CmdlineChanged = 1, CursorMovedC = 1 }, exec_lua('return _G.events'))
end)
it("no 'incsearch' recursion with 'verbose' regex message", function()
exec('set verbose=1')
feed([[:%s/.\{//}]])
screen:expect([[
|
{1:~ }|*9
{3: }|
Switching to backtracking RE engine for pattern: .\{ |*2
{16::}%{15:s}{16:/.\{//}^ } |
]])
end)
it('matchparen highlights', function()
exec('source $VIMRUNTIME/plugin/matchparen.lua')
feed(':call foo(bar())')
screen:expect([[
|
{1:~ }|*12
{16::}{15:call} {25:foo}{102:(}{25:bar}{16:()}{102:)}^ |
]])
feed('<Left>')
screen:expect([[
|
{1:~ }|*12
{16::}{15:call} {25:foo}{16:(}{25:bar}{102:()}{16:^)} |
]])
feed('<Right><BS><BS>')
screen:expect([[
|
{1:~ }|*12
{16::}{15:call} {25:foo}{16:(}{25:bar}{16:(}^ |
]])
end)
end)
describe('cmdline2', function()
it('resizing during startup shows confirm prompt #36439', function()
clear({
args = {
'--clean',
'+lua require("vim._core.ui2").enable({})',
"+call feedkeys(':')",
},
})
local screen = Screen.new()
feed('call confirm("Ok?")<CR>')
screen:try_resize(screen._width + 1, screen._height)
screen:expect([[
|*10
{3: }|
|
{6:Ok?} |
{6:[O]k: }^ |
]])
-- And resizing the next event loop iteration also works.
feed('k')
screen:try_resize(screen._width, screen._height + 1)
screen:expect([[
|*11
{3: }|
|
{6:Ok?} |
{6:[O]k: }^ |
]])
end)
end)