Files
neovim/test/functional/ui/messages2_spec.lua
Volodymyr Chernetskyi 06bff27929 feat(ui2): highlight the [+x] spill indicator with MoreMsg #41679
Problem:
The "spill" indicator that ui2 appends when messages overflow the
available height is drawn with whatever highlight the message tail
happens to have, so it is indistinguishable from the message text.

Solution:
Give the [+x] chunks an explicit `MoreMsg` highlight, and only fall back
to the message tail highlight for chunks that don't carry one of their
own.

AI-assisted
2026-09-04 14:28:22 -04:00

1443 lines
65 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 describe, it, before_each, after_each, finally =
t.describe, t.it, t.before_each, t.after_each, t.finally
local api, clear, command, exec_lua, feed = n.api, n.clear, n.command, n.exec_lua, n.feed
local msg_timeout = 400
local function set_msg_target_zero_ch()
exec_lua(function()
vim.o.messagesopt = 'hit-enter,history:500,progress:c,timeout:' .. msg_timeout
require('vim._core.ui2').enable({ msg = { targets = 'msg' } })
vim.o.cmdheight = 0
end)
end
describe('messages2', function()
local screen
before_each(function()
clear()
screen = Screen.new()
screen:add_extra_attr_ids({
[100] = { foreground = Screen.colors.Magenta1, bold = true },
})
exec_lua(function()
require('vim._core.ui2').enable({})
end)
end)
after_each(function()
-- Since ui2 module lasts until Nvim exits, there may be unfinished timers.
-- Close unfinished timers to avoid 2s delay on exit with ASAN or TSAN.
exec_lua(function()
vim.uv.walk(function(handle)
if not handle:is_closing() then
handle:close()
end
end)
end)
end)
it('multiline messages and pager', function()
command('set ruler showcmd noshowmode | echo "foo\nbar"')
screen:expect([[
^ |
{1:~ }|*10
{3: }|
foo |
bar |
]])
feed('g')
screen:expect([[
^ |
{1:~ }|*10
{3: }|
foo |
bar g |
]])
feed('<lt>')
screen:expect([[
|
{1:~ }|*9
{3: }|
^foo |
bar |
1,1 All|
]])
-- Multiple messages in same event loop iteration are appended and shown in full.
feed([[q:echo "foo" | echo "bar\nbaz\n"->repeat(&lines)<CR>]])
screen:expect([[
^ |
{1:~ }|*5
{3: }|
foo |
bar |
baz |
bar |
baz |
bar |
baz{6: [+23]} |
]])
-- Any key press resizes the cmdline and updates the spill indicator.
feed('j')
screen:expect({ any = { '+29' } })
command('echo "foo"')
-- New message clears spill indicator.
screen:expect([[
^ |
{1:~ }|*12
foo 0,0-1 All|
]])
command('echo "foo"')
-- Ruler still positioned correctly after dupe message.
screen:expect([[
^ |
{1:~ }|*12
foo(1) 0,0-1 All|
]])
command('echo "foo"')
-- Dupe counter increases beyond 1
screen:expect([[
^ |
{1:~ }|*12
foo(2) 0,0-1 All|
]])
-- No error for ruler virt_text msg_row exceeding buffer length.
command([[map Q <cmd>echo "foo\nbar" <bar> ls<CR>]])
feed('Q')
screen:expect([[
^ |
{1:~ }|*9
{3: }|
foo |
bar |
1 %a "[No Name]" line 1 |
]])
feed('<C-L>')
screen:expect([[
^ |
{1:~ }|*12
0,0-1 All|
]])
-- g< shows messages from last command
feed('g<lt>')
screen:expect([[
|
{1:~ }|*8
{3: }|
^foo |
bar |
1 %a "[No Name]" line 1 |
1,1 All|
]])
-- edit_unputchar() does not clear already updated screen #34515.
feed('qix<Esc>dwi<C-r>')
screen:expect([[
{18:^"} |
{1:~ }|*12
^R 1,1 All|
]])
feed('-<Esc>')
screen:expect([[
^x |
{1:~ }|*12
1,1 All|
]])
-- Switching tabpage closes expanded cmdline #37659.
command('tabnew | echo "foo\nbar"')
screen:expect([[
{24: + [No Name] }{5: [No Name] }{2: }{24:X}|
^ |
{1:~ }|*9
{3: }|
foo |
bar |
]])
feed('gt')
screen:expect([[
{5: + [No Name] }{24: [No Name] }{2: }{24:X}|
^x |
{1:~ }|*11
foo{6: [+1]} 1,1 All|
]])
-- Don't enter the pager in insert mode.
command('tabonly | call nvim_echo([["foo\n"]]->repeat(&lines), 1, {}) | startinsert')
screen:expect([[
^x |
{1:~ }|*5
{3: }|
foo |*6
foo{6: [+8]} |
]])
feed('<CR>')
screen:expect([[
|
^x |
{1:~ }|*11
foo{6: [+14]} 2,1 All|
]])
feed('<BS><Esc>')
-- First multiline message expands cmdline, additional message updates spill indicator.
command('call nvim_echo([["foo\n"]]->repeat(&lines), 1, {}) | echo "bar"')
screen:expect([[
^x |
{1:~ }|*5
{3: }|
foo |*6
foo{6: [+9]} |
]])
-- Do enter the pager in normal mode (with keybinding setup).
-- Also checks that "messagesopt=pager:…" is normalized to the keytrans() form.
command('set messagesopt+=pager:<cr>')
command('nmap <Esc> <Cmd>fclose<CR>')
feed('<CR>')
screen:expect([[
^foo |
foo |*12
1,1 Top|
]])
command('set messagesopt-=pager:<cr>')
-- Changing 'laststatus' reveals the global statusline with a pager height
-- exceeding the available lines: #38008.
command('set laststatus=3')
screen:expect([[
^foo |
foo |*11
{3:[Pager] 1,1 Top}|
|
]])
feed(':<C-F>')
screen:expect([[
x |
{1:~ }|*3
─────────────────────────────────────────────────────|
{1::}echo "foo" | echo "bar\nbaz\n"->repeat(&lines) |
{1::}^ |
{1:~ }|*5
{3:[Command Line] 2,0-1 All}|
|
]])
command('wincmd +')
screen:expect([[
x |
{1:~ }|*2
─────────────────────────────────────────────────────|
{1::}echo "foo" | echo "bar\nbaz\n"->repeat(&lines) |
{1::}^ |
{1:~ }|*6
{3:[Command Line] 2,0-1 All}|
|
]])
command('echo "foo"')
screen:expect([[
x |
{1:~ }|*2
─────────────────────────────────────────────────────|
{1::}echo "foo" | echo "bar\nbaz\n"->repeat(&lines) |
{1::}^ |
{1:~ }|*6
{3:[Command Line] 2,0-1 All}|
foo |
]])
-- <C-C> closes the cmdwin and drops back into pre-filled cmdline. The pager remains visible.
feed('<C-C>')
screen:expect([[
x |
{1:~ }|*11
{3:[Pager] 1,1 Top}|
{16::}^ |
]])
feed('<Esc><Esc>')
-- Can enter pager from cmdwin (use cmdwin.open() since "q" is consumed by the pager on_key
-- callback before nv_record can read it).
exec_lua([[require('vim._core.cmdwin').open(':')]])
screen:expect([[
x |
{1:~ }|*3
─────────────────────────────────────────────────────|
{1::}echo "foo" | echo "bar\nbaz\n"->repeat(&lines) |
{1::}^ |
{1:~ }|*5
{3:[Command Line] 2,0-1 All}|
|
]])
feed(':messages<CR>')
-- Cmdwin stays open behind the pager: it is a regular window (#40312).
screen:expect([[
^foo |
foo |*4
{1::}echo "foo" | echo "bar\nbaz\n"->repeat(&lines) |
{1::} |
{1:~ }|*5
{3:[Pager] 1,1 Top}|
{16::}{15:messages} |
]])
-- Closing the pager returns to the cmdwin, unchanged (it was never closed).
feed('q')
screen:expect([[
x |
{1:~ }|*3
─────────────────────────────────────────────────────|
{1::}echo "foo" | echo "bar\nbaz\n"->repeat(&lines) |
{1::}^ |
{1:~ }|*5
{3:[Command Line] 2,0-1 All}|
{16::}{15:messages} |
]])
-- Configured maximum height.
command('quit | lua require("vim._core.ui2").enable({msg = {pager = {height = 2 } } })')
command('messages')
screen:expect([[
x |
{1:~ }|*8
{3: }|
^foo |
foo |
{3:[Pager] 1,1 Top}|
{16::}{15:messages} |
]])
end)
it('new buffer, window and options after closing a buffer or switching tabpage', function()
command('set nomodifiable | echom "foo" | messages')
screen:expect([[
|
{1:~ }|*10
{3: }|
^foo |
foo |
]])
command('bdelete | messages')
screen:expect_unchanged()
set_msg_target_zero_ch()
command('quit | echo "foo\nbar" | tabnew')
screen:expect([[
{24: [No Name] }{5: [No Name] }{2: }{24:X}|
^ |
{1:~ }|*10
{1:~ }{4:foo}|
{1:~ }{4:bar}|
]])
end)
it('screenclear and empty message clears messages', function()
command('echo "foo"')
screen:expect([[
^ |
{1:~ }|*12
foo |
]])
command('mode')
screen:expect([[
^ |
{1:~ }|*12
|
]])
command('echo "foo"')
screen:expect([[
^ |
{1:~ }|*12
foo |
]])
command('echo ""')
screen:expect([[
^ |
{1:~ }|*12
|
]])
-- A redraw indicates the start of messages in the cmdline, which empty should clear.
command('echo "foo" | redraw | echo "bar"')
screen:expect([[
^ |
{1:~ }|*12
bar |
]])
command('echo "foo" | redraw | echo ""')
screen:expect([[
^ |
{1:~ }|*12
|
]])
set_msg_target_zero_ch()
command('echo "foo"')
screen:expect([[
^ |
{1:~ }|*12
{1:~ }{4:foo}|
]])
command('mode')
screen:expect([[
^ |
{1:~ }|*13
]])
-- But not with target='msg'
command('echo "foo"')
screen:expect([[
^ |
{1:~ }|*12
{1:~ }{4:foo}|
]])
command('echo ""')
screen:expect_unchanged()
-- Or a screen resize
screen:try_resize(screen._width, screen._height - 1)
screen:expect([[
^ |
{1:~ }|*11
{1:~ }{4:foo}|
]])
-- Moved up when opening cmdline
feed(':')
screen:expect([[
|
{1:~ }|*10
{1:~ }{4:foo}|
{16::}^ |
]])
-- Highlighter disabled when message is moved to cmdline #34884
feed([[echo "bar\n"->repeat(&lines)<CR>]])
screen:expect([[
^ |
{1:~ }|*5
foo |
bar |*5
bar{6: [+8]} |
]])
end)
it('does not draw the message seperator with cmdheight=0', function()
command('set laststatus=2 statusline=%f')
command('set cmdheight=0')
command('echo "hello"')
screen:expect([[
^ |
{1:~ }|*12
hello |
]])
end)
it('no prompt and newlines with Visual filter command #38273', function()
set_msg_target_zero_ch()
feed('V:w !printf foo<CR>')
screen:expect([[
^ |
{1:~ }|*12
{1:~ }{4:foo}|
]])
end)
it('empty kind after message that does not flush immediately', function()
command('echon "foo" | echo')
screen:expect([[
^ |
{1:~ }|*12
foo |
]])
end)
it("deleting buffer restores 'buftype'", function()
feed(':%bdelete<CR>')
screen:expect([[
^ |
{1:~ }|*12
5 buffers deleted |
]])
-- Would trigger changed dialog if 'buftype' was not restored.
command('%bdelete')
screen:expect_unchanged()
end)
it('showmode does not overwrite important messages', function()
command('set readonly')
feed('i')
screen:expect([[
^ |
{1:~ }|*12
{19:W10: Warning: Changing a readonly file} |
]])
feed('<Esc>@@i')
screen:expect([[
^ |
{1:~ }|*12
{9:E748: No previously used register} |
]])
end)
it('showmode does not overwrite Visual word count #40824', function()
if t.is_os('win') then
t.pending('FIXME #40843')
end
api.nvim_buf_set_lines(0, 0, -1, true, { 'one two', 'three four' })
feed('Vj')
screen:expect([[
{17:one two} |
^t{17:hree four} |
{1:~ }|*11
{5:-- VISUAL LINE --} |
]])
feed('g<C-G>')
screen:expect([[
{17:one two} |
^t{17:hree four} |
{1:~ }|*11
Selected 2 of 2 Lines; 4 of 4 Words; 19 of 19 Bytes |
]])
end)
it('hit-enter prompt does not error for invalid window #35095', function()
command('echo "foo\nbar"')
screen:expect([[
^ |
{1:~ }|*10
{3: }|
foo |
bar |
]])
feed('<C-w>o')
screen:expect([[
^ |
{1:~ }|*12
foo{6: [+1]} |
]])
end)
it('entering cmdline below expanded messages', function()
command('echo "foo\n"->repeat(&lines)')
screen:expect([[
^ |
{1:~ }|*5
{3: }|
foo |*6
foo{6: [+8]} |
]])
-- Place cmdline below expanded messages: #37653, without "more" title #38481.
feed(':call setline(1, "foo")')
screen:expect([[
|
{1:~ }|*4
{3: }|
foo |*6
foo{6: [+8]} |
{16::}{15:call} {25:setline}{16:(}{26:1}{16:,} {26:"foo"}{16:)}^ |
]])
-- No message closes expanded cmdline and keeps the entered command.
feed('<CR>')
screen:expect([[
^foo |
{1:~ }|*12
{16::}{15:call} {25:setline}{16:(}{26:1}{16:,} {26:"foo"}{16:)} |
]])
-- If command emits another message it is opened in the pager without focusing
-- it, to closely mimic useful UI1 behavior. #41061
command('echo "foo\nbar"')
feed(':echo "baz"<CR>')
screen:expect([[
^foo |
{1:~ }|*8
{3: }|
foo |
bar |
baz |
{16::}{15:echo} {26:"baz"} |
]])
-- Subsequent typed commands are appended to the pager.
feed(':echo "typed append"<CR>')
screen:expect([[
^foo |
{1:~ }|*7
{3: }|
foo |
bar |
baz |
typed append |
{16::}{15:echo} {26:"typed append"} |
]])
-- Any other typed key dismisses the pager.
feed('n')
screen:expect([[
^foo |
{1:~ }|*12
{9:E35: No previous regular expression} |
]])
-- Non-typed key doesn't dismiss expanded cmdline #39221
command('nnoremap b :ls!<cr>:b<space>')
feed('b')
screen:expect([[
foo |
{1:~ }|*6
{3: }|
1 %a + "[No Name]" line 1 |
2u a "[Cmd]" line 0 |
3u a "[Dialog]" line 0 |
4u a "[Msg]" line 0 |
5u a "[Pager]" line 0 |
{16::}{15:b} ^ |
]])
end)
it('pager for consecutive command messages is not focused #41061', function()
local win = api.nvim_get_current_win()
command('nnoremap j gj')
command('echo "foo\nbar"')
feed(':echo "baz"<CR>')
screen:expect([[
^ |
{1:~ }|*8
{3: }|
foo |
bar |
baz |
{16::}{15:echo} {26:"baz"} |
]])
t.eq(win, api.nvim_get_current_win())
-- "g<" enters the pager (showing the previous command output).
feed('g<lt>')
screen:expect([[
|
{1:~ }|*10
{3: }|
^baz |
|
]])
t.neq(win, api.nvim_get_current_win())
-- "q" closes the entered pager.
feed('q')
t.eq(win, api.nvim_get_current_win())
-- ":messages" from inside the pager does not make the next unfocused pager steal focus.
command('echo "foo\nbar"')
feed(':echo "baz"<CR>')
feed('g<lt>')
n.poke_eventloop()
t.neq(win, api.nvim_get_current_win())
feed(':messages<CR>')
n.poke_eventloop()
feed('q')
n.poke_eventloop()
t.eq(win, api.nvim_get_current_win())
command('echo "foo\nbar"')
feed(':echo "baz"<CR>')
n.poke_eventloop()
t.eq(win, api.nvim_get_current_win())
feed('j')
screen:expect([[
^ |
{1:~ }|*12
{16::}{15:echo} {26:"baz"} |
]])
-- A typed command that emits no message keeps the pager; the next key dismisses it.
command('echo "foo\nbar"')
feed(':echo "baz"<CR>')
feed(':let g:x = 1<CR>')
screen:expect([[
^ |
{1:~ }|*8
{3: }|
foo |
bar |
baz |
{16::}{15:let} {25:g:x} {15:=} {26:1} |
]])
feed('j')
screen:expect([[
^ |
{1:~ }|*12
{16::}{15:let} {25:g:x} {15:=} {26:1} |
]])
end)
it('paging prompt dialog #35191', function()
screen:try_resize(71, screen._height)
-- Don't consume <Esc> when paging is not necessary.
feed(':call confirm("Ok?")<CR>')
screen:expect([[
|
{1:~ }|*10
{3: }|
{6:Ok?} |
{6:[O]k: }^ |
]])
feed('<Esc>')
screen:expect([[
^ |
{1:~ }|*12
|
]])
local top = [[
|
{1:~ }|*4
{3: }|
0 |
1 |
2 |
3 |
4 |
5 |
6{6: [+93]} |
Type number and <Enter> (q or empty cancels): ^ |
]]
command('set mousescroll=ver:2')
feed(':call inputlist(range(100))<CR>')
screen:expect(top)
feed('<Down>')
screen:expect([[
|
{1:~ }|*4
{3: }|
1{6: [+1]} |
2 |
3 |
4 |
5 |
6 |
7{6: [+92]} |
Type number and <Enter> (q or empty cancels): ^ |
]])
feed('<Up>')
screen:expect(top)
feed('<PageDown>')
screen:expect([[
|
{1:~ }|*4
{3: }|
5{6: [+5]} |
6 |
7 |
8 |
9 |
10 |
11{6: [+88]} |
Type number and <Enter> (q or empty cancels): ^ |
]])
feed('<PageUp>')
screen:expect(top)
feed('<End>')
screen:expect([[
|
{1:~ }|*4
{3: }|
93{6: [+93]} |
94 |
95 |
96 |
97 |
98 |
99 |
Type number and <Enter> (q or empty cancels): ^ |
]])
-- No scrolling beyond end of buffer #36114
feed('<PageDown>')
screen:expect_unchanged()
feed('<Home>')
screen:expect(top)
-- The wheel scrolls by 'mousescroll'; the arrow keys above still scrolled by one #39172
feed('<ScrollWheelDown><0,0>')
screen:expect([[
|
{1:~ }|*4
{3: }|
2{6: [+2]} |
3 |
4 |
5 |
6 |
7 |
8{6: [+91]} |
Type number and <Enter> (q or empty cancels): ^ |
]])
feed('<Up>')
screen:expect([[
|
{1:~ }|*4
{3: }|
1{6: [+1]} |
2 |
3 |
4 |
5 |
6 |
7{6: [+92]} |
Type number and <Enter> (q or empty cancels): ^ |
]])
feed('<ScrollWheelUp><0,0>')
screen:expect(top)
end)
it('FileType is fired after default options are set', function()
n.exec([[
let g:set = {}
au FileType pager set nowrap
au OptionSet * let g:set[expand('<amatch>')] = g:set->get(expand('<amatch>'), 0) + 1
echom 'foo'->repeat(&columns)
messages
]])
screen:expect([[
|
{1:~ }|*10
{3: }|
^foofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofoofo|
|
]])
t.eq(5, n.eval('g:set').filetype) -- still fires for 'filetype'
end)
it('Search highlights only apply to pager', function()
screen:add_extra_attr_ids({
[100] = { background = Screen.colors.Blue1, foreground = Screen.colors.Red },
[101] = { background = Screen.colors.Red1, foreground = Screen.colors.Blue1 },
[102] = {
background = Screen.colors.Blue,
foreground = Screen.colors.Red1,
reverse = true,
bold = true,
},
})
command('hi MsgArea guifg=Red guibg=Blue')
command('hi Search guifg=Blue guibg=Red')
command('set hlsearch shortmess+=s')
feed('/foo<CR>')
screen:expect([[
^ |
{1:~ }|*12
{9:E486: Pattern not found: foo}{100: }|
]])
set_msg_target_zero_ch()
command('echo "foo"')
screen:expect([[
^ |
{1:~ }|*12
{1:~ }{4:foo}|
]])
feed('g<lt>')
screen:expect([[
|
{1:~ }|*11
{102: }|
{101:^foo}{100: }|
]])
end)
it(':echon appends message', function()
command([[echo 1 | echon 2]])
screen:expect([[
^ |
{1:~ }|*12
12 |
]])
feed('g<lt>')
screen:expect([[
|
{1:~ }|*10
{3: }|
^12 |
|
]])
feed([[q:echo 1 | echon 2 | echon 2 | echon 3<CR>]])
screen:expect([[
^ |
{1:~ }|*12
1223 |
]])
feed('g<lt>')
screen:expect([[
|
{1:~ }|*10
{3: }|
^1223 |
|
]])
end)
it('shows message from still running command', function()
exec_lua(function()
vim.schedule(function()
print('foo')
vim.fn.getchar()
end)
end)
screen:expect([[
^ |
{1:~ }|*12
foo |
]])
feed('<CR>')
-- Fast context is not determined by message kind #39666
exec_lua(function()
vim.schedule(function()
vim.api.nvim_echo({ { 'bar' } }, false, { kind = 'search_cmd' })
vim.fn.getchar()
end)
end)
screen:expect([[
^ |
{1:~ }|*12
bar |
]])
end)
it('properly formatted carriage return messages', function()
screen:try_resize(screen._width, 20)
command([[echon "\r" | echon "Hello" | echon " " | echon "World"]])
screen:expect([[
^ |
{1:~ }|*18
Hello World |
]])
exec_lua(function()
vim.api.nvim_echo({ { 'fooo\nbarbaz\n\nlol', 'statement' }, { '\rbar' } }, true, {})
vim.api.nvim_echo({ { 'foooooooo', 'statement' }, { 'baz\rb', 'error' } }, true, {})
vim.api.nvim_echo({ { 'fooobar', 'statement' }, { '\rbaz\n' } }, true, {})
vim.api.nvim_echo({ { 'fooobar', 'statement' }, { '\rbaz\rb', 'error' } }, true, {})
vim.api.nvim_echo({ { 'fooo\rbar', 'statement' }, { 'baz', 'error' } }, true, {})
end)
screen:expect([[
^ |
{1:~ }|*9
{3: }|
{15:fooo} |
{15:barbaz} |
|
bar |
{9:b}{15:oooooooo}{9:baz} |
baz{15:obar} |
|
{9:baz}{15:obar} |
{15:bar}{9:baz} |
]])
end)
it('can show message during textlock', function()
exec_lua(function()
_G.omnifunc = function()
print('x!')
vim.cmd.sleep('100m')
end
vim.bo.omnifunc = 'v:lua.omnifunc'
end)
feed('i<C-X>')
screen:expect([[
^ |
{1:~ }|*12
{5:-- ^X mode (^]^D^E^F^I^K^L^N^O^P^Rs^U^V^Y)} |
]])
feed('<C-O>')
screen:expect([[
^ |
{1:~ }|*12
x! |
]])
screen:expect([[
^ |
{1:~ }|*12
{5:-- Omni completion (^O^N^P) }{9:Pattern not found} |
]])
exec_lua(function()
vim.keymap.set('n', '<F1>', function()
print('i hate locks so much!!!!')
vim.cmd.messages()
end, { expr = true })
end)
feed('<Esc><F1>')
screen:expect([[
|
{1:~ }|*8
{3: }|
^x! |
x! |
i hate locks so much!!!! |*2
]])
end)
it('replace by message ID', function()
exec_lua(function()
assert(1 == vim.api.nvim_echo({ { 'foo' } }, true, {}))
assert(2 == vim.api.nvim_echo({ { 'bar\nbaz' } }, true, {}))
assert(3 == vim.api.nvim_echo({ { 'foo' } }, true, {}))
vim.keymap.set('n', 'Q', function()
vim.api.nvim_echo({ { 'Syntax', 23 }, { '\n - ', 0 }, { 'cCommentL', 439 } }, false, {})
end)
end)
screen:expect([[
^ |
{1:~ }|*8
{3: }|
foo |
bar |
baz |
foo |
]])
api.nvim_echo({ { 'foo' } }, true, { id = 2 })
screen:expect([[
^ |
{1:~ }|*9
{3: }|
foo |*3
]])
api.nvim_echo({ { 'bar\nbaz' } }, true, { id = 1 })
screen:expect([[
^ |
{1:~ }|*8
{3: }|
bar |
baz |
foo |*2
]])
-- Pressing a key immediately dismisses an expanded cmdline, and
-- replacing a multiline, multicolored message doesn't error due
-- to unnecessarily inserted lines #37994.
feed('Q')
screen:expect([[
^ |
{1:~ }|*10
{3: }|
{100:Syntax} |
- cCommentL |
]])
feed('Q')
screen:expect_unchanged(true)
feed('<C-L>') -- close expanded cmdline
set_msg_target_zero_ch()
api.nvim_echo({ { 'foo' } }, true, { id = 1 })
api.nvim_echo({ { 'bar\nbaz' } }, true, { id = 2 })
api.nvim_echo({ { 'foo' } }, true, { id = 3 })
screen:expect([[
^ |
{1:~ }|*9
{1:~ }{4:foo}|
{1:~ }{4:bar}|
{1:~ }{4:baz}|
{1:~ }{4:foo}|
]])
api.nvim_echo({ { 'foo' } }, true, { id = 2 })
screen:expect([[
^ |
{1:~ }|*10
{1:~ }{4:foo}|*3
]])
api.nvim_echo({ { 'f', 'Conceal' }, { 'oo\nbar' } }, true, { id = 3 })
screen:expect([[
^ |
{1:~ }|*9
{1:~ }{4:foo}|*2
{1:~ }{14:f}{4:oo}|
{1:~ }{4:bar}|
]])
-- No error expanding the cmdline when trying to copy over message span marks #37672.
screen:try_resize(screen._width, 6)
command('ls!')
screen:expect([[
^ |
{1:~ }|*2
foo |*2
{14:f}oo{6: [+6]} |
]])
feed('<Esc>')
screen:expect([[
^ |
{1:~ }|*5
]])
end)
it('while cmdline is open', function()
command('cnoremap <C-A> <Cmd>lua error("foo")<CR>')
feed(':echo "bar"<C-A>')
screen:expect([[
|
{1:~ }|*7
{3: }|
{9:E5108: Lua: [string ":lua"]:1: foo} |
{9:stack traceback:} |
{9: [C]: in function 'error'} |
{9: [string ":lua"]:1: in main chunk} |
{16::}{15:echo} {26:"bar"}^ |
]])
feed('<CR>')
screen:expect([[
^ |
{1:~ }|*12
bar |
]])
set_msg_target_zero_ch()
feed([[:call confirm("foo\nbar")<C-A>]])
screen:expect([[
|
{1:~ }|*8
{1:~ }{9:E5108: Lua: [string ":lua"]:1: foo}{4: }|
{1:~ }{9:stack traceback:}{4: }|
{1:~ }{9: [C]: in function 'error'}{4: }|
{1:~ }{9: [string ":lua"]:1: in main chunk}|
{16::}{15:call} {25:confirm}{16:(}{26:"foo\nbar"}{16:)}^ |
]])
feed('<CR>')
screen:expect([[
|
{1:~ }|*8
{1:~ }{9:E5108: Lua: [string ":lua"]:1: foo}{4: }|
{3: }|
{6:foo} |
{6:bar} |
{6:[O]k: }^ |
]])
end)
it('no search_cmd with cmdheight=0', function()
set_msg_target_zero_ch()
feed('ifoo<Esc>?foo<CR>')
screen:expect([[
{10:^foo} |
{1:~ }|*13
]])
end)
it('closed msg window timer removes empty lines', function()
set_msg_target_zero_ch()
command('echo "foo" | echo "bar\n"')
screen:expect([[
^ |
{1:~ }|*10
{1:~ }{4:foo}|
{1:~ }{4:bar}|
{1:~ }{4: }|
]])
command('fclose!')
screen:sleep(msg_timeout + 50)
command('echo "baz"')
screen:expect([[
^ |
{1:~ }|*12
{1:~ }{4:baz}|
]])
-- Last message line is at bottom of window after closing it.
screen:try_resize(screen._width, 8)
command('mode | echo "1\n" | echo "2\n" | echo "3\n" | echo "4\n"')
screen:expect([[
^ |
{1:~ }|*3
{1:~ }{4:3}|
{1:~ }{4: }|
{1:~ }{4:4}|
{1:~ }{4: }|
]])
command('fclose!')
screen:expect([[
^ |
{1:~ }|*7
]])
command('echo "5\n"')
screen:expect([[
^ |
{1:~ }|*3
{1:~ }{4:4}|
{1:~ }{4: }|
{1:~ }{4:5}|
{1:~ }{4: }|
]])
end)
it('msg window timer does not trigger ModeChanged #40780', function()
exec_lua(function()
vim.o.messagesopt = 'hit-enter,history:500,progress:c,timeout:50'
require('vim._core.ui2').enable({ msg = { targets = 'msg' } })
end)
command('let g:modechanged = []')
command([[autocmd ModeChanged i:n call add(g:modechanged, copy(v:event))]])
command([[echo "a" | echo "b" | startinsert]])
screen:sleep(100)
t.eq({}, n.eval('g:modechanged'))
feed('<Esc>')
t.eq({ { old_mode = 'i', new_mode = 'n' } }, n.eval('g:modechanged'))
end)
it('configured targets per kind', function()
exec_lua(function()
local targets = { echo = 'msg', list_cmd = 'pager', bufwrite = 'cmd', lua_print = 'cmd' }
require('vim._core.ui2').enable({ msg = { targets = targets } })
print('foo') -- "lua_print" kind goes to cmd
vim.cmd.echo('"bar"') -- "echo" kind goes to msg
vim.cmd.highlight('VisualNC') -- "list_cmd" kind goes to pager
end)
screen:expect([[
^ |
{1:~ }|*10
{3: }|
VisualNC xxx cleared {4:bar}|
foo |
]])
command('hi VisualNC') -- appended to the pager without focusing it
screen:expect([[
^ |
{1:~ }|*9
{3: }|
VisualNC xxx cleared |
VisualNC xxx cleared {4:bar}|
foo |
]])
-- Any typed key dismisses the unfocused pager.
feed('<Esc>')
-- Duplicate indicator in msg and cmd target simultaneously
command('echo "bar" | lua print("foo")')
command('echo "bar" | lua print("foo")')
screen:expect([[
^ |
{1:~ }|*11
{1:~ }{4:bar(2)}|
foo(1) |
]])
finally(function()
os.remove('Xfile')
end)
-- Route bufwrite as a pattern to the message ID #39341
command('edit Xfile | write')
screen:expect([[
^ |
{1:~ }|*11
{1:~ }{4:bar(2)}|
"Xfile" [New] 0L, 0B written |
]])
end)
it('message survives after closing tabpage without error #39055', function()
set_msg_target_zero_ch()
command('tabnew')
command('echo "hello"')
screen:expect([[
{24: [No Name] }{5: [No Name] }{2: }{24:X}|
^ |
{1:~ }|*11
{1:~ }{4:hello}|
]])
command('quit!')
screen:expect([[
^ |
{1:~ }|*12
{1:~ }{4:hello}|
]])
end)
it('no crash for resized grid during redraw #39075', function()
exec_lua(function()
vim.api.nvim_set_decoration_provider(vim.api.nvim_create_namespace(''), {
on_win = function()
print('\n')
end,
})
end)
feed(':')
screen:expect([[
|
{1:~ }|*12
{16::}^ |
]])
feed('f')
screen:expect([[
|
{1:~ }|*9
{3: }|
|*2
{16::}{15:f}^ |
]])
end)
it('configured cmd window height prevents expanded message #39375', function()
command('set messagesopt+=maxheight:1') -- Rounds up to a single row.
command('echo "foo\nbar"')
screen:expect([[
^ |
{1:~ }|*12
foo{6: [+1]} |
]])
end)
it('search count is cleared', function()
command('set ruler showcmd shortmess-=S | call setline(1, ["foo", "bar"])')
feed('/foo<CR>')
local search_count = [[
{10:^foo} |
bar |
{1:~ }|*11
/foo W [1/1] 1,1 All|
]]
screen:expect(search_count)
feed('<C-L>j')
screen:expect([[
{10:foo} |
^bar |
{1:~ }|*11
2,1 All|
]])
feed('n')
screen:expect(search_count)
command('echo "-"->repeat(&columns)')
screen:expect([[
{10:^foo} |
bar |
{1:~ }|*11
-----------------------------------1,1 All|
]])
end)
it('crops long messages to make place for ruler', function()
command('set noruler | echo "-"->repeat(&columns)')
screen:expect([[
^ |
{1:~ }|*12
-----------------------------------------------------|
]])
feed('<C-L>')
command('set ruler showcmd | echo "-"->repeat(&columns)')
screen:expect([[
^ |
{1:~ }|*12
-----------------------------------0,0-1 All|
]])
feed('<C-L>')
-- message with multibyte characters
command('echo "∙"->repeat(&columns)')
screen:expect([[
^ |
{1:~ }|*12
∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙∙0,0-1 All|
]])
feed('<C-L>')
-- message with multicell characters
command('echo "-".."🙂"->repeat(&columns/2-1)')
screen:expect([[
^ |
{1:~ }|*12
-🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂0,0-1 All|
]])
feed('<C-L>')
command('echo "🙂"->repeat(&columns/2)')
screen:expect([[
^ |
{1:~ }|*12
🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂🙂 0,0-1 All|
]])
feed('<C-L>')
-- when cmdheight>1, only the last line is cropped
command('set cmdheight=2 | redraw | echo "-"->repeat(2*&columns)')
screen:expect([[
^ |
{1:~ }|*11
-----------------------------------------------------|
-----------------------------------0,0-1 All|
]])
feed('<C-L>')
command('set cmdheight=2 showbreak=> | redraw | echo "-"->repeat(2*&columns-1)')
screen:expect([[
^ |
{1:~ }|*11
-----------------------------------------------------|
{1:>}----------------------------------0,0-1 All|
]])
command('set showbreak&')
feed('<C-L>')
-- ruler is not shown after dismissing expanded cmdline with key press -> no need to crop
command('set cmdheight=1 | redraw | echo "-"->repeat(2*&columns)')
feed('k')
screen:expect([[
^ |
{1:~ }|*12
-----------------------------------------------------|
]])
feed('<C-L>')
-- messages are deferred until showcmd is cleared -> no need to crop for showcmd
command('lua vim.defer_fn(function() vim.cmd[[echo "-"->repeat(&columns)]] end, 50)')
feed('g')
screen:expect([[
^ |
{1:~ }|*12
g 0,0-1 All|
]])
screen:sleep(100)
feed('<Esc>')
screen:expect([[
^ |
{1:~ }|*12
-----------------------------------0,0-1 All|
]])
feed('<C-L>')
-- message with repetition indicator keeps ruler intact
command('echo "-"->repeat(&columns)')
command('echo "-"->repeat(&columns)')
screen:expect([[
^ |
{1:~ }|*12
--------------------------------(1)0,0-1 All|
]])
feed('<C-L>')
-- when the ruler is configured smaller, there is more space for messages
command('set rulerformat=%l | redraw | echo "-"->repeat(&columns)')
screen:expect([[
^ |
{1:~ }|*12
----------------------------------------------------0|
]])
end)
it('pager statusline is not clobbered by DiagnosticChanged redraw #41130', function()
exec_lua(function()
vim.o.laststatus = 3
vim.print('message')
vim.cmd.mes()
end)
screen:expect([[
|
{1:~ }|*9
{3: }|
^message |
{3:[Pager] }|
message |
]])
exec_lua(function()
vim.diagnostic.config({ signs = false, virtual_text = false, underline = false })
local ns = vim.api.nvim_create_namespace('repro-41130')
vim.diagnostic.set(ns, 1, { { lnum = 0, message = 'random error' } }, {})
end)
screen:expect_unchanged()
end)
it('correct end_row for highlights copied to pager #41419', function()
command('echo "a" | echo "b"') -- Two lines; end_row should be offset by 2
screen:expect([[
^ |
{1:~ }|*10
{3: }|
a |
b |
]])
feed('g<lt>')
screen:expect([[
|
{1:~ }|*9
{3: }|
^a |
b |
|
]])
command('echohl WarningMsg | echo "c" | echohl ErrorMsg | echo "de"')
screen:expect([[
|
{1:~ }|*7
{3: }|
^a |
b |
{19:c} |
{9:de} |
|
]])
feed('g<lt>')
screen:expect([[
|
{1:~ }|*9
{3: }|
{19:^c} |
{9:de} |
|
]])
end)
end)