diff options
Diffstat (limited to 'test/functional')
120 files changed, 8006 insertions, 2693 deletions
diff --git a/test/functional/api/autocmd_spec.lua b/test/functional/api/autocmd_spec.lua index 3f9883f43f..64b28e7bf7 100644 --- a/test/functional/api/autocmd_spec.lua +++ b/test/functional/api/autocmd_spec.lua @@ -273,54 +273,72 @@ describe('autocmd api', function() eq({}, api.nvim_get_autocmds({ event = 'User', pattern = 'Test' })) end) - it('receives an args table', function() + local function test_autocmd_args(event) + local function get_amatch(pat) + return event == 'User' and pat or vim.fs.normalize(n.fn.fnamemodify(pat, ':p')) + end + local group_id = api.nvim_create_augroup('TestGroup', {}) -- Having an existing autocmd calling expand("<afile>") shouldn't change args #18964 - api.nvim_create_autocmd('User', { + api.nvim_create_autocmd(event, { group = 'TestGroup', pattern = 'Te*', command = 'call expand("<afile>")', }) - local autocmd_id = exec_lua [[ - return vim.api.nvim_create_autocmd("User", { + local autocmd_id = exec_lua(([[ + return vim.api.nvim_create_autocmd(%q, { group = "TestGroup", pattern = "Te*", callback = function(args) vim.g.autocmd_args = args end, }) - ]] + ]]):format(event)) - api.nvim_exec_autocmds('User', { pattern = 'Test pattern' }) + local exec_pat = 'Test pattern' + local amatch = get_amatch(exec_pat) + api.nvim_exec_autocmds(event, { pattern = exec_pat }) eq({ id = autocmd_id, group = group_id, - event = 'User', - match = 'Test pattern', - file = 'Test pattern', + event = event, + match = amatch, + file = exec_pat, buf = 1, }, api.nvim_get_var('autocmd_args')) -- Test without a group - autocmd_id = exec_lua [[ - return vim.api.nvim_create_autocmd("User", { + autocmd_id = exec_lua(([[ + return vim.api.nvim_create_autocmd(%q, { pattern = "*", callback = function(args) vim.g.autocmd_args = args end, }) - ]] + ]]):format(event)) - api.nvim_exec_autocmds('User', { pattern = 'some_pat' }) + exec_pat = 'some_pat' + amatch = get_amatch(exec_pat) + api.nvim_exec_autocmds(event, { pattern = exec_pat }) eq({ id = autocmd_id, group = nil, - event = 'User', - match = 'some_pat', - file = 'some_pat', + event = event, + match = amatch, + file = exec_pat, buf = 1, }, api.nvim_get_var('autocmd_args')) + end + + describe('receives correct args table', function() + it('for event that takes non-file pattern', function() + test_autocmd_args('User') + end) + + it('for event that takes file pattern', function() + test_autocmd_args('BufEnter') + end) end) it('can receive arbitrary data', function() @@ -881,6 +899,89 @@ describe('autocmd api', function() eq([[:echo "Buffer"]], normalized_aus[1].command) end) end) + + describe('id', function() + it('gets events by ID', function() + local id = api.nvim_create_autocmd('BufEnter', { + command = 'echo "hello"', + }) + eq({ + { + buflocal = false, + command = 'echo "hello"', + event = 'BufEnter', + id = id, + once = false, + pattern = '*', + }, + }, api.nvim_get_autocmds({ id = id })) + end) + + it('gets events by ID by other filters', function() + local group_name = 'NVIM_GET_AUTOCMDS_ID' + local group = api.nvim_create_augroup(group_name, { clear = true }) + local id = api.nvim_create_autocmd('BufEnter', { + command = 'set number', + group = group, + }) + api.nvim_create_autocmd('WinEnter', { + group = group, + command = 'set cot&', + }) + eq({ + { + buflocal = false, + command = 'set number', + event = 'BufEnter', + group = group, + group_name = group_name, + id = id, + once = false, + pattern = '*', + }, + }, api.nvim_get_autocmds({ id = id, group = group })) + end) + + it('gets events by ID and a specific event', function() + local id = api.nvim_create_autocmd('InsertEnter', { command = 'set number' }) + api.nvim_create_autocmd('InsertEnter', { command = 'set wrap' }) + eq({ + { + buflocal = false, + command = 'set number', + event = 'InsertEnter', + id = id, + once = false, + pattern = '*', + }, + }, api.nvim_get_autocmds({ id = id, event = 'InsertEnter' })) + end) + + it('gets events by ID and a specific pattern', function() + local id = api.nvim_create_autocmd('InsertEnter', { + pattern = '*.c', + command = 'set number', + }) + api.nvim_create_autocmd('InsertEnter', { + pattern = '*.c', + command = 'set wrap', + }) + eq({ + { + buflocal = false, + command = 'set number', + event = 'InsertEnter', + id = id, + once = false, + pattern = '*.c', + }, + }, api.nvim_get_autocmds({ id = id, pattern = '*.c' })) + end) + + it('empty result when id does not found', function() + eq({}, api.nvim_get_autocmds({ id = 255 })) + end) + end) end) describe('nvim_exec_autocmds', function() diff --git a/test/functional/api/buffer_updates_spec.lua b/test/functional/api/buffer_updates_spec.lua index 527394bfd1..489f601d31 100644 --- a/test/functional/api/buffer_updates_spec.lua +++ b/test/functional/api/buffer_updates_spec.lua @@ -879,7 +879,8 @@ describe('API: buffer events:', function() it('when :terminal lines change', function() local buffer_lines = {} local expected_lines = {} - fn.termopen({ nvim_prog, '-u', 'NONE', '-i', 'NONE', '-n', '-c', 'set shortmess+=A' }, { + fn.jobstart({ nvim_prog, '-u', 'NONE', '-i', 'NONE', '-n', '-c', 'set shortmess+=A' }, { + term = true, env = { VIMRUNTIME = os.getenv('VIMRUNTIME') }, }) diff --git a/test/functional/api/command_spec.lua b/test/functional/api/command_spec.lua index a16c6a88e3..fabd9be6d6 100644 --- a/test/functional/api/command_spec.lua +++ b/test/functional/api/command_spec.lua @@ -651,6 +651,11 @@ describe('nvim_create_user_command', function() api.nvim_set_current_buf(bufnr) command('Hello') assert_alive() + eq( + 'Invalid buffer id: 1234', + pcall_err(api.nvim_buf_create_user_command, 1234, 'Hello', '', {}) + ) + assert_alive() end) it('can use a Lua complete function', function() @@ -771,5 +776,9 @@ describe('nvim_del_user_command', function() command('Hello') api.nvim_buf_del_user_command(0, 'Hello') matches('Not an editor command: Hello', pcall_err(command, 'Hello')) + eq('Invalid command (not found): Hello', pcall_err(api.nvim_buf_del_user_command, 0, 'Hello')) + eq('Invalid command (not found): Bye', pcall_err(api.nvim_buf_del_user_command, 0, 'Bye')) + eq('Invalid buffer id: 1234', pcall_err(api.nvim_buf_del_user_command, 1234, 'Hello')) + assert_alive() end) end) diff --git a/test/functional/api/deprecated_spec.lua b/test/functional/api/deprecated_spec.lua new file mode 100644 index 0000000000..2efcfda873 --- /dev/null +++ b/test/functional/api/deprecated_spec.lua @@ -0,0 +1,21 @@ +-- Island of misfit toys. +--- @diagnostic disable: deprecated + +local t = require('test.testutil') +local n = require('test.functional.testnvim')() + +describe('deprecated', function() + before_each(n.clear) + + describe('nvim_notify', function() + it('can notify a info message', function() + n.api.nvim_notify('hello world', 2, {}) + end) + + it('can be overridden', function() + n.command('lua vim.notify = function(...) return 42 end') + t.eq(42, n.api.nvim_exec_lua("return vim.notify('Hello world')", {})) + n.api.nvim_notify('hello world', 4, {}) + end) + end) +end) diff --git a/test/functional/api/extmark_spec.lua b/test/functional/api/extmark_spec.lua index 43be0c0e43..8a4aea1efe 100644 --- a/test/functional/api/extmark_spec.lua +++ b/test/functional/api/extmark_spec.lua @@ -249,7 +249,7 @@ describe('API/extmarks', function() set_extmark(ns, 2, 1, 0, { right_gravity = false }) eq({ { 1, 0, 0 }, { 2, 1, 0 } }, get_extmarks(ns, { 0, 0 }, { -1, -1 })) feed('u') - eq({ { 1, 0, 0 }, { 2, 1, 0 } }, get_extmarks(ns, { 0, 0 }, { -1, -1 })) + eq({ { 1, 0, 0 }, { 2, 0, 0 } }, get_extmarks(ns, { 0, 0 }, { -1, -1 })) api.nvim_buf_clear_namespace(0, ns, 0, -1) end) @@ -1731,7 +1731,7 @@ describe('API/extmarks', function() -- mark with invalidate is removed command('d2') screen:expect([[ - S2^aaa bbb ccc | + {7:S2}^aaa bbb ccc | {7: }aaa bbb ccc |*3 {7: } | | @@ -1739,9 +1739,9 @@ describe('API/extmarks', function() -- mark is restored with undo_restore == true command('silent undo') screen:expect([[ - S1{7: }^aaa bbb ccc | - S2S1aaa bbb ccc | - S2{7: }aaa bbb ccc | + {7:S1 }^aaa bbb ccc | + {7:S2S1}aaa bbb ccc | + {7:S2 }aaa bbb ccc | {7: }aaa bbb ccc |*2 | ]]) @@ -1794,6 +1794,16 @@ describe('API/extmarks', function() eq({}, get_extmark_by_id(ns, 4, {})) end) + it('no crash checking invalidated flag of sign pair end key #31856', function() + api.nvim_buf_set_lines(0, 0, 1, false, { '', '' }) + api.nvim_set_option_value('signcolumn', 'auto:2', {}) + set_extmark(ns, 1, 0, 0, { sign_text = 'S1', invalidate = true, end_row = 0 }) + set_extmark(ns, 2, 1, 0, { sign_text = 'S2', end_row = 1 }) + command('d') + api.nvim_buf_clear_namespace(0, ns, 0, -1) + n.assert_alive() + end) + it('can set a URL', function() local url1 = 'https://example.com' local url2 = 'http://127.0.0.1' diff --git a/test/functional/api/highlight_spec.lua b/test/functional/api/highlight_spec.lua index dd0611f184..8f98ae3650 100644 --- a/test/functional/api/highlight_spec.lua +++ b/test/functional/api/highlight_spec.lua @@ -309,6 +309,15 @@ describe('API: set highlight', function() eq({ underdotted = true }, api.nvim_get_hl_by_name('Test_hl', true)) end) + it('can set all underline cterm attributes #31385', function() + local ns = get_ns() + local attrs = { 'underline', 'undercurl', 'underdouble', 'underdotted', 'underdashed' } + for _, attr in ipairs(attrs) do + api.nvim_set_hl(ns, 'Test_' .. attr, { cterm = { [attr] = true } }) + eq({ [attr] = true }, api.nvim_get_hl_by_name('Test_' .. attr, false)) + end + end) + it('can set a highlight in the global namespace', function() api.nvim_set_hl(0, 'Test_hl', highlight2_config) eq( @@ -710,4 +719,18 @@ describe('API: set/get highlight namespace', function() api.nvim_win_set_hl_ns(0, ns) eq(ns, api.nvim_get_hl_ns({ winid = 0 })) end) + + it('setting namespace takes priority over &winhighlight', function() + command('set winhighlight=Visual:Search') + n.insert('foobar') + local ns = api.nvim_create_namespace('') + api.nvim_win_set_hl_ns(0, ns) + eq(ns, api.nvim_get_hl_ns({ winid = 0 })) + command('enew') -- switching buffer keeps namespace #30904 + eq(ns, api.nvim_get_hl_ns({ winid = 0 })) + command('set winhighlight=') + eq(ns, api.nvim_get_hl_ns({ winid = 0 })) + command('set winhighlight=Visual:Search') + eq(ns, api.nvim_get_hl_ns({ winid = 0 })) + end) end) diff --git a/test/functional/api/server_requests_spec.lua b/test/functional/api/server_requests_spec.lua index bdd340f6c6..c022ba28de 100644 --- a/test/functional/api/server_requests_spec.lua +++ b/test/functional/api/server_requests_spec.lua @@ -9,7 +9,6 @@ local nvim_prog, command, fn = n.nvim_prog, n.command, n.fn local source, next_msg = n.source, n.next_msg local ok = t.ok local api = n.api -local spawn, merge_args = n.spawn, n.merge_args local set_session = n.set_session local pcall_err = t.pcall_err local assert_alive = n.assert_alive @@ -281,10 +280,9 @@ describe('server -> client', function() end) describe('connecting to another (peer) nvim', function() - local nvim_argv = merge_args(n.nvim_argv, { '--headless' }) local function connect_test(server, mode, address) local serverpid = fn.getpid() - local client = spawn(nvim_argv, false, nil, true) + local client = n.new_session(true) set_session(client) local clientpid = fn.getpid() @@ -312,7 +310,7 @@ describe('server -> client', function() end it('via named pipe', function() - local server = spawn(nvim_argv) + local server = n.new_session(false) set_session(server) local address = fn.serverlist()[1] local first = string.sub(address, 1, 1) @@ -321,7 +319,7 @@ describe('server -> client', function() end) it('via ipv4 address', function() - local server = spawn(nvim_argv) + local server = n.new_session(false) set_session(server) local status, address = pcall(fn.serverstart, '127.0.0.1:') if not status then @@ -332,7 +330,7 @@ describe('server -> client', function() end) it('via ipv6 address', function() - local server = spawn(nvim_argv) + local server = n.new_session(false) set_session(server) local status, address = pcall(fn.serverstart, '::1:') if not status then @@ -343,7 +341,7 @@ describe('server -> client', function() end) it('via hostname', function() - local server = spawn(nvim_argv) + local server = n.new_session(false) set_session(server) local address = fn.serverstart('localhost:') eq('localhost:', string.sub(address, 1, 10)) @@ -351,10 +349,10 @@ describe('server -> client', function() end) it('does not crash on receiving UI events', function() - local server = spawn(nvim_argv) + local server = n.new_session(false) set_session(server) local address = fn.serverlist()[1] - local client = spawn(nvim_argv, false, nil, true) + local client = n.new_session(true) set_session(client) local id = fn.sockconnect('pipe', address, { rpc = true }) diff --git a/test/functional/api/version_spec.lua b/test/functional/api/version_spec.lua index 617786eb2d..68c4ef7503 100644 --- a/test/functional/api/version_spec.lua +++ b/test/functional/api/version_spec.lua @@ -43,7 +43,7 @@ describe("api_info()['version']", function() eq(0, fn.has('nvim-' .. major .. '.' .. minor .. '.' .. (patch + 1))) eq(0, fn.has('nvim-' .. major .. '.' .. (minor + 1) .. '.' .. patch)) eq(0, fn.has('nvim-' .. (major + 1) .. '.' .. minor .. '.' .. patch)) - assert(build == nil or type(build) == 'string') + assert(build == vim.NIL or type(build) == 'string') end) end) @@ -93,28 +93,28 @@ describe('api metadata', function() local function clean_level_0(metadata) for _, f in ipairs(metadata.functions) do f.can_fail = nil - f.async = nil + f.async = nil -- XXX: renamed to "fast". f.receives_channel_id = nil f.since = 0 end end - local api_info, compat, stable, api_level + local api_info --[[@type table]] + local compat --[[@type integer]] + local stable --[[@type integer]] + local api_level --[[@type integer]] local old_api = {} setup(function() clear() -- Ensure a session before requesting api_info. + --[[@type { version: {api_compatible: integer, api_level: integer, api_prerelease: boolean} }]] api_info = api.nvim_get_api_info()[2] compat = api_info.version.api_compatible api_level = api_info.version.api_level - if api_info.version.api_prerelease then - stable = api_level - 1 - else - stable = api_level - end + stable = api_info.version.api_prerelease and api_level - 1 or api_level for level = compat, stable do local path = ('test/functional/fixtures/api_level_' .. tostring(level) .. '.mpack') - old_api[level] = read_mpack_file(path) + old_api[level] = read_mpack_file(path) --[[@type table]] if old_api[level] == nil then local errstr = 'missing metadata fixture for stable level ' .. level .. '. ' if level == api_level and not api_info.version.api_prerelease then diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index 3f1e378bc1..3aa9ba49d5 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -96,12 +96,19 @@ describe('API', function() assert_alive() end) - it('input is processed first when followed immediately by non-fast events', function() + it('input is processed first if followed immediately by non-fast events', function() api.nvim_set_current_line('ab') async_meths.nvim_input('x') async_meths.nvim_exec_lua('_G.res1 = vim.api.nvim_get_current_line()', {}) async_meths.nvim_exec_lua('_G.res2 = vim.api.nvim_get_current_line()', {}) eq({ 'b', 'b' }, exec_lua('return { _G.res1, _G.res2 }')) + -- Also test with getchar() + async_meths.nvim_command('let g:getchar = 1 | call getchar() | let g:getchar = 0') + eq(1, api.nvim_get_var('getchar')) + async_meths.nvim_input('x') + async_meths.nvim_exec_lua('_G.res1 = vim.g.getchar', {}) + async_meths.nvim_exec_lua('_G.res2 = vim.g.getchar', {}) + eq({ 0, 0 }, exec_lua('return { _G.res1, _G.res2 }')) end) it('does not set CA_COMMAND_BUSY #7254', function() @@ -695,7 +702,7 @@ describe('API', function() pcall_err(request, 'nvim_call_dict_function', 42, 'f', { 1, 2 }) ) eq( - 'Failed to evaluate dict expression', + 'Vim:E121: Undefined variable: foo', pcall_err(request, 'nvim_call_dict_function', 'foo', 'f', { 1, 2 }) ) eq('dict not found', pcall_err(request, 'nvim_call_dict_function', '42', 'f', { 1, 2 })) @@ -781,18 +788,6 @@ describe('API', function() end) end) - describe('nvim_notify', function() - it('can notify a info message', function() - api.nvim_notify('hello world', 2, {}) - end) - - it('can be overridden', function() - command('lua vim.notify = function(...) return 42 end') - eq(42, api.nvim_exec_lua("return vim.notify('Hello world')", {})) - api.nvim_notify('hello world', 4, {}) - end) - end) - describe('nvim_input', function() it('Vimscript error: does NOT fail, updates v:errmsg', function() local status, _ = pcall(api.nvim_input, ':call bogus_fn()<CR>') @@ -1770,6 +1765,11 @@ describe('API', function() end) it('validation', function() + eq("Unknown option 'foobar'", pcall_err(api.nvim_set_option_value, 'foobar', 'baz', {})) + eq( + "Unknown option 'foobar'", + pcall_err(api.nvim_set_option_value, 'foobar', 'baz', { win = api.nvim_get_current_win() }) + ) eq( "Invalid 'scope': expected 'local' or 'global'", pcall_err(api.nvim_get_option_value, 'scrolloff', { scope = 'bogus' }) @@ -1952,6 +1952,16 @@ describe('API', function() api.nvim_set_current_win(api.nvim_list_wins()[2]) eq(api.nvim_list_wins()[2], api.nvim_get_current_win()) end) + + it('failure modes', function() + n.command('split') + + eq('Invalid window id: 9999', pcall_err(api.nvim_set_current_win, 9999)) + + -- XXX: force nvim_set_current_win to fail somehow. + n.command("au WinLeave * throw 'foo'") + eq('WinLeave Autocommands for "*": foo', pcall_err(api.nvim_set_current_win, 1000)) + end) end) describe('nvim_{get,set}_current_tabpage, nvim_list_tabpages', function() @@ -1971,6 +1981,16 @@ describe('API', function() eq(api.nvim_list_tabpages()[2], api.nvim_get_current_tabpage()) eq(api.nvim_list_wins()[2], api.nvim_get_current_win()) end) + + it('failure modes', function() + n.command('tabnew') + + eq('Invalid tabpage id: 999', pcall_err(api.nvim_set_current_tabpage, 999)) + + -- XXX: force nvim_set_current_tabpage to fail somehow. + n.command("au TabLeave * throw 'foo'") + eq('TabLeave Autocommands for "*": foo', pcall_err(api.nvim_set_current_tabpage, 1)) + end) end) describe('nvim_get_mode', function() @@ -2679,7 +2699,8 @@ describe('API', function() -- :terminal with args + running process. command('enew') local progpath_esc = eval('shellescape(v:progpath)') - fn.termopen(('%s -u NONE -i NONE'):format(progpath_esc), { + fn.jobstart(('%s -u NONE -i NONE'):format(progpath_esc), { + term = true, env = { VIMRUNTIME = os.getenv('VIMRUNTIME') }, }) eq(-1, eval('jobwait([&channel], 0)[0]')) -- Running? @@ -3654,6 +3675,30 @@ describe('API', function() async_meths.nvim_echo({ { 'msg\nmsg' }, { 'msg' } }, false, {}) eq('', exec_capture('messages')) end) + + it('can print error message', function() + async_meths.nvim_echo({ { 'Error\nMessage' } }, false, { err = true }) + screen:expect([[ + | + {1:~ }|*3 + {3: }| + {9:Error} | + {9:Message} | + {6:Press ENTER or type command to continue}^ | + ]]) + feed(':messages<CR>') + screen:expect([[ + ^ | + {1:~ }|*6 + | + ]]) + async_meths.nvim_echo({ { 'Error' }, { 'Message', 'Special' } }, false, { err = true }) + screen:expect([[ + ^ | + {1:~ }|*6 + {9:Error}{16:Message} | + ]]) + end) end) describe('nvim_open_term', function() @@ -3760,7 +3805,7 @@ describe('API', function() screen:expect { grid = [[ | - {1:~}{102: }{4: }{1: }| + {1:~}{4:^ }{1: }| {1:~}{4: }{1: }|*4 {1:~ }|*3 {5:-- TERMINAL --} | @@ -3776,7 +3821,7 @@ describe('API', function() screen:expect { grid = [[ | - {1:~}{4:herrejösses!}{102: }{4: }{1: }| + {1:~}{4:herrejösses!^ }{1: }| {1:~}{4: }{1: }|*4 {1:~ }|*3 {5:-- TERMINAL --} | @@ -3955,8 +4000,8 @@ describe('API', function() str = 'TextWithWarningHighlightTextWithUserHighlight', width = 45, highlights = { - { start = 0, group = 'WarningMsg' }, - { start = 24, group = 'User1' }, + { start = 0, group = 'WarningMsg', groups = { 'StatusLine', 'WarningMsg' } }, + { start = 24, group = 'User1', groups = { 'StatusLine', 'User1' } }, }, }, api.nvim_eval_statusline( @@ -3971,7 +4016,7 @@ describe('API', function() str = 'TextWithNoHighlight', width = 19, highlights = { - { start = 0, group = 'StatusLine' }, + { start = 0, group = 'StatusLine', groups = { 'StatusLine' } }, }, }, api.nvim_eval_statusline('TextWithNoHighlight', { highlights = true })) end) @@ -3983,8 +4028,8 @@ describe('API', function() str = 'TextWithNoHighlightTextWithWarningHighlight', width = 43, highlights = { - { start = 0, group = 'StatusLineNC' }, - { start = 19, group = 'WarningMsg' }, + { start = 0, group = 'StatusLineNC', groups = { 'StatusLineNC' } }, + { start = 19, group = 'WarningMsg', groups = { 'StatusLineNC', 'WarningMsg' } }, }, }, api.nvim_eval_statusline( @@ -4000,8 +4045,8 @@ describe('API', function() str = 'TextWithNoHighlightTextWithWarningHighlight', width = 43, highlights = { - { start = 0, group = 'TabLineFill' }, - { start = 19, group = 'WarningMsg' }, + { start = 0, group = 'TabLineFill', groups = { 'TabLineFill' } }, + { start = 19, group = 'WarningMsg', groups = { 'TabLineFill', 'WarningMsg' } }, }, }, api.nvim_eval_statusline( @@ -4017,8 +4062,8 @@ describe('API', function() str = 'TextWithNoHighlightTextWithWarningHighlight', width = 43, highlights = { - { start = 0, group = 'WinBar' }, - { start = 19, group = 'WarningMsg' }, + { start = 0, group = 'WinBar', groups = { 'WinBar' } }, + { start = 19, group = 'WarningMsg', groups = { 'WinBar', 'WarningMsg' } }, }, }, api.nvim_eval_statusline( @@ -4045,11 +4090,11 @@ describe('API', function() str = '││bbaa 4 ', width = 9, highlights = { - { group = 'CursorLineFold', start = 0 }, - { group = 'Normal', start = 6 }, - { group = 'ErrorMsg', start = 6 }, - { group = 'IncSearch', start = 8 }, - { group = 'Normal', start = 10 }, + { group = 'CursorLineFold', start = 0, groups = { 'CursorLineFold' } }, + { group = 'Normal', start = 6, groups = { 'Normal' } }, + { group = 'ErrorMsg', start = 6, groups = { 'CursorLineSign', 'ErrorMsg' } }, + { group = 'IncSearch', start = 8, groups = { 'CursorLineSign', 'IncSearch' } }, + { group = 'Normal', start = 10, groups = { 'Normal' } }, }, }, api.nvim_eval_statusline( '%C%s%=%l ', @@ -4060,8 +4105,8 @@ describe('API', function() str = ' 3 ', width = 9, highlights = { - { group = 'LineNr', start = 0 }, - { group = 'ErrorMsg', start = 8 }, + { group = 'LineNr', start = 0, groups = { 'LineNr' } }, + { group = 'ErrorMsg', start = 8, groups = { 'LineNr', 'ErrorMsg' } }, }, }, api.nvim_eval_statusline('%l%#ErrorMsg# ', { use_statuscol_lnum = 3, highlights = true }) @@ -5359,8 +5404,53 @@ describe('API', function() 13 | ]], }) - -- takes buffer line count from correct buffer with "win" and {0, -1} "range" - api.nvim__redraw({ win = 0, range = { 0, -1 } }) + end) + + it('nvim__redraw range parameter', function() + Screen.new(10, 5) + fn.setline(1, fn.range(4)) + + exec_lua([[ + _G.lines_list = {} + ns = vim.api.nvim_create_namespace('') + vim.api.nvim_set_decoration_provider(ns, { + on_win = function() + end, + on_line = function(_, _, _, line) + table.insert(_G.lines_list, line) + end, + }) + function _G.get_lines() + local lines = _G.lines_list + _G.lines_list = {} + return lines + end + ]]) + + api.nvim__redraw({ flush = true, valid = false }) + exec_lua('_G.get_lines()') + + local actual_lines = {} + local function test(range) + api.nvim__redraw({ win = 0, range = range }) + table.insert(actual_lines, exec_lua('return _G.get_lines()')) + end + + test({ 0, -1 }) + test({ 2, 2 ^ 31 }) + test({ 2, 2 ^ 32 }) + test({ 2 ^ 31 - 1, 2 }) + test({ 2 ^ 32 - 1, 2 }) + + local expected_lines = { + { 0, 1, 2, 3 }, + { 2, 3 }, + { 2, 3 }, + {}, + {}, + } + eq(expected_lines, actual_lines) + n.assert_alive() end) end) diff --git a/test/functional/api/window_spec.lua b/test/functional/api/window_spec.lua index 92999f383a..028f0beb38 100644 --- a/test/functional/api/window_spec.lua +++ b/test/functional/api/window_spec.lua @@ -359,6 +359,15 @@ describe('API/win', function() eq(2, api.nvim_win_get_height(api.nvim_list_wins()[2])) end) + it('failure modes', function() + command('split') + eq('Invalid window id: 999999', pcall_err(api.nvim_win_set_height, 999999, 10)) + eq( + 'Wrong type for argument 2 when calling nvim_win_set_height, expecting Integer', + pcall_err(api.nvim_win_set_height, 0, 0.9) + ) + end) + it('correctly handles height=1', function() command('split') api.nvim_set_current_win(api.nvim_list_wins()[1]) @@ -409,6 +418,15 @@ describe('API/win', function() eq(2, api.nvim_win_get_width(api.nvim_list_wins()[2])) end) + it('failure modes', function() + command('vsplit') + eq('Invalid window id: 999999', pcall_err(api.nvim_win_set_width, 999999, 10)) + eq( + 'Wrong type for argument 2 when calling nvim_win_set_width, expecting Integer', + pcall_err(api.nvim_win_set_width, 0, 0.9) + ) + end) + it('do not cause ml_get errors with foldmethod=expr #19989', function() insert([[ aaaaa @@ -488,6 +506,48 @@ describe('API/win', function() assert_alive() end) + describe('after closing', function() + local buf, win0, win1, win2 + + before_each(function() + win0 = api.nvim_get_current_win() + command('new') + buf = api.nvim_get_current_buf() + win1 = api.nvim_get_current_win() + command('set numberwidth=10') + command('split') + win2 = api.nvim_get_current_win() + command('set numberwidth=15') + command('enew') + api.nvim_set_current_win(win1) + command('normal ix') + command('enew') + api.nvim_set_current_win(win0) + eq(4, api.nvim_get_option_value('numberwidth', {})) + end) + + -- at this point buffer `buf` is current in no windows. Closing shouldn't affect its defaults + it('0 windows', function() + api.nvim_set_current_buf(buf) + eq(10, api.nvim_get_option_value('numberwidth', {})) + end) + + it('1 window', function() + api.nvim_win_close(win1, false) + + api.nvim_set_current_buf(buf) + eq(10, api.nvim_get_option_value('numberwidth', {})) + end) + + it('2 windows', function() + api.nvim_win_close(win1, false) + api.nvim_win_close(win2, false) + + api.nvim_set_current_buf(buf) + eq(10, api.nvim_get_option_value('numberwidth', {})) + end) + end) + it('returns values for unset local options', function() eq(-1, api.nvim_get_option_value('scrolloff', { win = 0, scope = 'local' })) end) @@ -1664,7 +1724,7 @@ describe('API/win', function() autocmd BufWinEnter * ++once let fired = v:true ]]) eq( - 'Failed to set buffer 2', + 'Vim:E37: No write since last change (add ! to override)', pcall_err(api.nvim_open_win, api.nvim_create_buf(true, true), false, { split = 'left' }) ) eq(false, eval('fired')) diff --git a/test/functional/autocmd/autocmd_spec.lua b/test/functional/autocmd/autocmd_spec.lua index c62e4752e0..1b7275ebf6 100644 --- a/test/functional/autocmd/autocmd_spec.lua +++ b/test/functional/autocmd/autocmd_spec.lua @@ -160,7 +160,7 @@ describe('autocmd', function() it('++once', function() -- :help autocmd-once -- - -- ":autocmd ... ++once" executes its handler once, then removes the handler. + -- ":autocmd … ++once" executes its handler once, then removes the handler. -- local expected = { 'Many1', @@ -206,7 +206,7 @@ describe('autocmd', function() ) -- - -- ":autocmd ... ++once" handlers can be deleted. + -- ":autocmd … ++once" handlers can be deleted. -- expected = {} command('let g:foo = []') @@ -216,7 +216,7 @@ describe('autocmd', function() eq(expected, eval('g:foo')) -- - -- ":autocmd ... <buffer> ++once ++nested" + -- ":autocmd … <buffer> ++once ++nested" -- expected = { 'OptionSet-Once', @@ -250,6 +250,24 @@ describe('autocmd', function() --- Autocommands ---]]), fn.execute('autocmd Tabnew') ) + + -- + -- :autocmd does not recursively call ++once Lua handlers. + -- + exec_lua [[vim.g.count = 0]] + eq(0, eval('g:count')) + exec_lua [[ + vim.api.nvim_create_autocmd('User', { + once = true, + pattern = nil, + callback = function() + vim.g.count = vim.g.count + 1 + vim.api.nvim_exec_autocmds('User', { pattern = nil }) + end, + }) + vim.api.nvim_exec_autocmds('User', { pattern = nil }) + ]] + eq(1, eval('g:count')) end) it('internal `aucmd_win` window', function() diff --git a/test/functional/autocmd/completedone_spec.lua b/test/functional/autocmd/completedone_spec.lua index 33beb16db2..36dc73842d 100644 --- a/test/functional/autocmd/completedone_spec.lua +++ b/test/functional/autocmd/completedone_spec.lua @@ -32,7 +32,7 @@ describe('CompleteDone', function() feed('<Esc>') eq('cancel', eval('g:donereason')) end) - it('when overriden by another complete()', function() + it('when overridden by another complete()', function() call('complete', call('col', '.'), { 'bar', 'baz' }) eq('cancel', eval('g:donereason')) end) diff --git a/test/functional/autocmd/dirchanged_spec.lua b/test/functional/autocmd/dirchanged_spec.lua index 1cde0e0552..9b572df568 100644 --- a/test/functional/autocmd/dirchanged_spec.lua +++ b/test/functional/autocmd/dirchanged_spec.lua @@ -351,11 +351,10 @@ describe('autocmd DirChanged and DirChangedPre', function() eq(2, eval('g:cdprecount')) eq(2, eval('g:cdcount')) - local status, err = pcall(function() - request('nvim_set_current_dir', '/doesnotexist') - end) - eq(false, status) - eq('Failed to change directory', string.match(err, ': (.*)')) + eq( + 'Vim:E344: Can\'t find directory "/doesnotexist" in cdpath', + t.pcall_err(request, 'nvim_set_current_dir', '/doesnotexist') + ) eq({ directory = '/doesnotexist', scope = 'global', changed_window = false }, eval('g:evpre')) eq(3, eval('g:cdprecount')) eq(2, eval('g:cdcount')) diff --git a/test/functional/autocmd/focus_spec.lua b/test/functional/autocmd/focus_spec.lua index 7f6092bf48..177e8997cf 100644 --- a/test/functional/autocmd/focus_spec.lua +++ b/test/functional/autocmd/focus_spec.lua @@ -47,7 +47,7 @@ describe('autoread TUI FocusGained/FocusLost', function() screen:expect { grid = [[ - {1: } | + ^ | {4:~ }|*3 {5:[No Name] }| | @@ -57,7 +57,7 @@ describe('autoread TUI FocusGained/FocusLost', function() feed_command('edit ' .. path) screen:expect { grid = [[ - {1: } | + ^ | {4:~ }|*3 {5:xtest-foo }| :edit xtest-foo | @@ -68,7 +68,7 @@ describe('autoread TUI FocusGained/FocusLost', function() feed_data('\027[O') screen:expect { grid = [[ - {1: } | + ^ | {4:~ }|*3 {5:xtest-foo }| :edit xtest-foo | @@ -83,7 +83,7 @@ describe('autoread TUI FocusGained/FocusLost', function() screen:expect { grid = [[ - {1:l}ine 1 | + ^line 1 | line 2 | line 3 | line 4 | diff --git a/test/functional/autocmd/termxx_spec.lua b/test/functional/autocmd/termxx_spec.lua index baf2bb6071..950ef2f58a 100644 --- a/test/functional/autocmd/termxx_spec.lua +++ b/test/functional/autocmd/termxx_spec.lua @@ -213,9 +213,11 @@ describe('autocmd TextChangedT', function() end) it('cannot delete terminal buffer', function() - command([[autocmd TextChangedT * call nvim_input('<CR>') | bwipe!]]) + command('autocmd TextChangedT * bwipe!') tt.feed_data('a') screen:expect({ any = 'E937: ' }) + feed('<CR>') + command('autocmd! TextChangedT') matches( '^E937: Attempt to delete a buffer that is in use: term://', api.nvim_get_vvar('errmsg') diff --git a/test/functional/core/channels_spec.lua b/test/functional/core/channels_spec.lua index dee13d19ae..7b10eb05ef 100644 --- a/test/functional/core/channels_spec.lua +++ b/test/functional/core/channels_spec.lua @@ -5,7 +5,6 @@ local clear, eq, eval, next_msg, ok, source = n.clear, t.eq, n.eval, n.next_msg, local command, fn, api = n.command, n.fn, n.api local matches = t.matches local sleep = vim.uv.sleep -local spawn, nvim_argv = n.spawn, n.nvim_argv local get_session, set_session = n.get_session, n.set_session local nvim_prog = n.nvim_prog local is_os = t.is_os @@ -33,10 +32,10 @@ describe('channels', function() end) pending('can connect to socket', function() - local server = spawn(nvim_argv, nil, nil, true) + local server = n.new_session(true) set_session(server) local address = fn.serverlist()[1] - local client = spawn(nvim_argv, nil, nil, true) + local client = n.new_session(true) set_session(client) source(init) @@ -63,7 +62,7 @@ describe('channels', function() it('dont crash due to garbage in rpc #23781', function() local client = get_session() - local server = spawn(nvim_argv, nil, nil, true) + local server = n.new_session(true) set_session(server) local address = fn.serverlist()[1] set_session(client) diff --git a/test/functional/core/exit_spec.lua b/test/functional/core/exit_spec.lua index 34c3eedbd2..65f6bc28a6 100644 --- a/test/functional/core/exit_spec.lua +++ b/test/functional/core/exit_spec.lua @@ -8,8 +8,6 @@ local feed = n.feed local eval = n.eval local eq = t.eq local run = n.run -local fn = n.fn -local nvim_prog = n.nvim_prog local pcall_err = t.pcall_err local exec_capture = n.exec_capture local poke_eventloop = n.poke_eventloop @@ -69,8 +67,8 @@ describe(':cquit', function() poke_eventloop() assert_alive() else - fn.system({ nvim_prog, '-u', 'NONE', '-i', 'NONE', '--headless', '--cmd', cmdline }) - eq(exit_code, eval('v:shell_error')) + local p = n.spawn_wait('--cmd', cmdline) + eq(exit_code, p.status) end end diff --git a/test/functional/core/fileio_spec.lua b/test/functional/core/fileio_spec.lua index cf9715f848..b1a8e21762 100644 --- a/test/functional/core/fileio_spec.lua +++ b/test/functional/core/fileio_spec.lua @@ -31,7 +31,6 @@ local feed_command = n.feed_command local skip = t.skip local is_os = t.is_os local is_ci = t.is_ci -local spawn = n.spawn local set_session = n.set_session describe('fileio', function() @@ -51,12 +50,11 @@ describe('fileio', function() rmdir('Xtest_backupdir with spaces') end) - local args = { nvim_prog, '--clean', '--cmd', 'set nofsync directory=Xtest_startup_swapdir' } + local args = { '--clean', '--cmd', 'set nofsync directory=Xtest_startup_swapdir' } --- Starts a new nvim session and returns an attached screen. - local function startup(extra_args) - extra_args = extra_args or {} - local argv = vim.iter({ args, '--embed', extra_args }):flatten():totable() - local screen_nvim = spawn(argv) + local function startup() + local argv = vim.iter({ args, '--embed' }):flatten():totable() + local screen_nvim = n.new_session(false, { args = argv, merge = false }) set_session(screen_nvim) local screen = Screen.new(70, 10) screen:set_default_attr_ids({ @@ -100,7 +98,8 @@ describe('fileio', function() eq('foozubbaz', trim(read_file('Xtest_startup_file1'))) -- 4. Exit caused by deadly signal (+ 'swapfile'). - local j = fn.jobstart(vim.iter({ args, '--embed' }):flatten():totable(), { rpc = true }) + local j = + fn.jobstart(vim.iter({ nvim_prog, args, '--embed' }):flatten():totable(), { rpc = true }) fn.rpcrequest( j, 'nvim_exec2', diff --git a/test/functional/core/job_spec.lua b/test/functional/core/job_spec.lua index 618c294566..e833b5127d 100644 --- a/test/functional/core/job_spec.lua +++ b/test/functional/core/job_spec.lua @@ -65,6 +65,39 @@ describe('jobs', function() ]]) end) + it('validation', function() + matches( + "E475: Invalid argument: job cannot have both 'pty' and 'rpc' options set", + pcall_err(command, "call jobstart(['cat', '-'], { 'pty': v:true, 'rpc': v:true })") + ) + matches( + 'E475: Invalid argument: expected valid directory', + pcall_err(command, "call jobstart(['cat', '-'], { 'cwd': 9313843 })") + ) + matches( + 'E475: Invalid argument: expected valid directory', + pcall_err(command, "call jobstart(['cat', '-'], { 'cwd': 'bogusssssss/bogus' })") + ) + matches( + "E475: Invalid argument: 'term' must be Boolean", + pcall_err(command, "call jobstart(['cat', '-'], { 'term': 'bogus' })") + ) + matches( + "E475: Invalid argument: 'term' must be Boolean", + pcall_err(command, "call jobstart(['cat', '-'], { 'term': 1 })") + ) + command('set modified') + matches( + vim.pesc('jobstart(...,{term=true}) requires unmodified buffer'), + pcall_err(command, "call jobstart(['cat', '-'], { 'term': v:true })") + ) + + -- Non-failure cases: + command('set nomodified') + command("call jobstart(['cat', '-'], { 'term': v:true })") + command("call jobstart(['cat', '-'], { 'term': v:false })") + end) + it('must specify env option as a dict', function() command('let g:job_opts.env = v:true') local _, err = pcall(function() @@ -940,6 +973,39 @@ describe('jobs', function() feed('<CR>') fn.jobstop(api.nvim_get_var('id')) end) + + it('does not set UI busy with zero timeout #31712', function() + local screen = Screen.new(50, 6) + command([[let g:id = jobstart(['sleep', '0.3'])]]) + local busy = 0 + screen._handle_busy_start = (function(orig) + return function() + orig(screen) + busy = busy + 1 + end + end)(screen._handle_busy_start) + source([[ + func PrintAndPoll() + echon "aaa\nbbb" + call jobwait([g:id], 0) + echon "\nccc" + endfunc + ]]) + feed_command('call PrintAndPoll()') + screen:expect { + grid = [[ + | + {3: }| + aaa | + bbb | + ccc | + {6:Press ENTER or type command to continue}^ | + ]], + } + feed('<CR>') + fn.jobstop(api.nvim_get_var('id')) + eq(0, busy) + end) end) pending('exit event follows stdout, stderr', function() @@ -969,13 +1035,6 @@ describe('jobs', function() eq({ 'notification', 'exit', { 0, 143 } }, next_msg()) end) - it('cannot have both rpc and pty options', function() - command('let g:job_opts.pty = v:true') - command('let g:job_opts.rpc = v:true') - local _, err = pcall(command, "let j = jobstart(['cat', '-'], g:job_opts)") - matches("E475: Invalid argument: job cannot have both 'pty' and 'rpc' options set", err) - end) - it('does not crash when repeatedly failing to start shell', function() source([[ set shell=nosuchshell @@ -1198,7 +1257,7 @@ describe('jobs', function() }) -- Wait for startup to complete, so that all terminal responses are received. screen:expect([[ - {1: } | + ^ | ~ |*3 {1:[No Name] 0,0-1 All}| | @@ -1208,7 +1267,7 @@ describe('jobs', function() feed(':q<CR>') screen:expect([[ | - [Process exited 0]{1: } | + [Process exited 0]^ | |*4 {3:-- TERMINAL --} | ]]) @@ -1230,7 +1289,7 @@ describe('pty process teardown', function() it('does not prevent/delay exit. #4798 #4900', function() skip(fn.executable('sleep') == 0, 'missing "sleep" command') -- Use a nested nvim (in :term) to test without --headless. - fn.termopen({ + fn.jobstart({ n.nvim_prog, '-u', 'NONE', @@ -1243,7 +1302,10 @@ describe('pty process teardown', function() '+terminal', '+!(sleep 300 &)', '+qa', - }, { env = { VIMRUNTIME = os.getenv('VIMRUNTIME') } }) + }, { + term = true, + env = { VIMRUNTIME = os.getenv('VIMRUNTIME') }, + }) -- Exiting should terminate all descendants (PTY, its children, ...). screen:expect([[ diff --git a/test/functional/core/log_spec.lua b/test/functional/core/log_spec.lua index 57dfd6364c..571bece833 100644 --- a/test/functional/core/log_spec.lua +++ b/test/functional/core/log_spec.lua @@ -46,7 +46,7 @@ describe('log', function() env = env, }) screen:expect([[ - {1: } | + ^ | ~ |*4 | {3:-- TERMINAL --} | diff --git a/test/functional/core/main_spec.lua b/test/functional/core/main_spec.lua index a445423efc..65a7c556b8 100644 --- a/test/functional/core/main_spec.lua +++ b/test/functional/core/main_spec.lua @@ -9,7 +9,6 @@ local feed = n.feed local eval = n.eval local clear = n.clear local fn = n.fn -local nvim_prog_abs = n.nvim_prog_abs local write_file = t.write_file local is_os = t.is_os local skip = t.skip @@ -35,7 +34,7 @@ describe('command-line option', function() it('treats - as stdin', function() eq(nil, uv.fs_stat(fname)) fn.system({ - nvim_prog_abs(), + n.nvim_prog, '-u', 'NONE', '-i', @@ -56,41 +55,39 @@ describe('command-line option', function() eq(nil, uv.fs_stat(fname)) eq(true, not not dollar_fname:find('%$%w+')) write_file(dollar_fname, ':call setline(1, "100500")\n:wqall!\n') - fn.system({ - nvim_prog_abs(), - '-u', - 'NONE', - '-i', - 'NONE', - '--headless', + local p = n.spawn_wait( '--cmd', 'set noswapfile shortmess+=IFW fileformats=unix', '-s', dollar_fname, - fname, - }) - eq(0, eval('v:shell_error')) + fname + ) + eq(0, p.status) local attrs = uv.fs_stat(fname) eq(#'100500\n', attrs.size) end) - it('does not crash when run completion in ex mode', function() - fn.system({ - nvim_prog_abs(), - '--clean', - '-e', - '-s', - '--cmd', - 'exe "norm! i\\<C-X>\\<C-V>"', - }) - eq(0, eval('v:shell_error')) + it('does not crash when running completion in Ex mode', function() + local p = + n.spawn_wait('--clean', '-e', '-s', '--cmd', 'exe "norm! i\\<C-X>\\<C-V>"', '--cmd', 'qa!') + eq(0, p.status) + end) + + it('does not crash when running completion from -l script', function() + local lua_fname = 'Xinscompl.lua' + write_file(lua_fname, [=[vim.cmd([[exe "norm! i\<C-X>\<C-V>"]])]=]) + finally(function() + os.remove(lua_fname) + end) + local p = n.spawn_wait('--clean', '-l', lua_fname) + eq(0, p.status) end) it('does not crash after reading from stdin in non-headless mode', function() skip(is_os('win')) local screen = Screen.new(40, 8) local args = { - nvim_prog_abs(), + n.nvim_prog, '-u', 'NONE', '-i', @@ -103,7 +100,8 @@ describe('command-line option', function() -- Need to explicitly pipe to stdin so that the embedded Nvim instance doesn't try to read -- data from the terminal #18181 - fn.termopen(string.format([[echo "" | %s]], table.concat(args, ' ')), { + fn.jobstart(string.format([[echo "" | %s]], table.concat(args, ' ')), { + term = true, env = { VIMRUNTIME = os.getenv('VIMRUNTIME') }, }) screen:expect( @@ -120,7 +118,7 @@ describe('command-line option', function() feed('i:cq<CR>') screen:expect([[ | - [Process exited 1]{2: } | + [Process exited 1]^ | |*5 {5:-- TERMINAL --} | ]]) @@ -137,60 +135,49 @@ describe('command-line option', function() ]=] end) - it('errors out when trying to use nonexistent file with -s', function() + it('fails when trying to use nonexistent file with -s', function() + local p = n.spawn_wait( + '--cmd', + 'set noswapfile shortmess+=IFW fileformats=unix', + '--cmd', + 'language C', + '-s', + nonexistent_fname + ) eq( 'Cannot open for reading: "' .. nonexistent_fname .. '": no such file or directory\n', - fn.system({ - nvim_prog_abs(), - '-u', - 'NONE', - '-i', - 'NONE', - '--headless', - '--cmd', - 'set noswapfile shortmess+=IFW fileformats=unix', - '--cmd', - 'language C', - '-s', - nonexistent_fname, - }) + --- TODO(justinmk): using `p.output` because Nvim emits CRLF even on non-Win. Emit LF instead? + p:output() ) - eq(2, eval('v:shell_error')) + eq(2, p.status) end) it('errors out when trying to use -s twice', function() write_file(fname, ':call setline(1, "1")\n:wqall!\n') write_file(dollar_fname, ':call setline(1, "2")\n:wqall!\n') - eq( - 'Attempt to open script file again: "-s ' .. dollar_fname .. '"\n', - fn.system({ - nvim_prog_abs(), - '-u', - 'NONE', - '-i', - 'NONE', - '--headless', - '--cmd', - 'set noswapfile shortmess+=IFW fileformats=unix', - '--cmd', - 'language C', - '-s', - fname, - '-s', - dollar_fname, - fname_2, - }) + local p = n.spawn_wait( + '--cmd', + 'set noswapfile shortmess+=IFW fileformats=unix', + '--cmd', + 'language C', + '-s', + fname, + '-s', + dollar_fname, + fname_2 ) - eq(2, eval('v:shell_error')) + --- TODO(justinmk): using `p.output` because Nvim emits CRLF even on non-Win. Emit LF instead? + eq('Attempt to open script file again: "-s ' .. dollar_fname .. '"\n', p:output()) + eq(2, p.status) eq(nil, uv.fs_stat(fname_2)) end) end) it('nvim -v, :version', function() matches('Run ":verbose version"', fn.execute(':version')) - matches('Compilation: .*Run :checkhealth', fn.execute(':verbose version')) - matches('Run "nvim %-V1 %-v"', fn.system({ nvim_prog_abs(), '-v' })) - matches('Compilation: .*Run :checkhealth', fn.system({ nvim_prog_abs(), '-V1', '-v' })) + matches('fall%-back for %$VIM: .*Run :checkhealth', fn.execute(':verbose version')) + matches('Run "nvim %-V1 %-v"', n.spawn_wait('-v').stdout) + matches('fall%-back for %$VIM: .*Run :checkhealth', n.spawn_wait('-V1', '-v').stdout) end) if is_os('win') then @@ -204,7 +191,7 @@ describe('command-line option', function() eq( 'some text', fn.system({ - nvim_prog_abs(), + n.nvim_prog, '-es', '+%print', '+q', diff --git a/test/functional/core/remote_spec.lua b/test/functional/core/remote_spec.lua index 6cc28ddeef..1cfa0535f6 100644 --- a/test/functional/core/remote_spec.lua +++ b/test/functional/core/remote_spec.lua @@ -10,10 +10,8 @@ local expect = n.expect local fn = n.fn local insert = n.insert local nvim_prog = n.nvim_prog -local new_argv = n.new_argv local neq = t.neq local set_session = n.set_session -local spawn = n.spawn local tmpname = t.tmpname local write_file = t.write_file @@ -32,8 +30,7 @@ describe('Remote', function() describe('connect to server and', function() local server before_each(function() - server = spawn(new_argv(), true) - set_session(server) + server = n.clear() end) after_each(function() @@ -49,7 +46,7 @@ describe('Remote', function() -- to wait for the remote instance to exit and calling jobwait blocks -- the event loop. If the server event loop is blocked, it can't process -- our incoming --remote calls. - local client_starter = spawn(new_argv(), false, nil, true) + local client_starter = n.new_session(true) set_session(client_starter) -- Call jobstart() and jobwait() in the same RPC request to reduce flakiness. eq( @@ -144,15 +141,8 @@ describe('Remote', function() describe('exits with error on', function() local function run_and_check_exit_code(...) - local bogus_argv = new_argv(...) - - -- Create an nvim instance just to run the remote-invoking nvim. We want - -- to wait for the remote instance to exit and calling jobwait blocks - -- the event loop. If the server event loop is blocked, it can't process - -- our incoming --remote calls. - clear() - -- Call jobstart() and jobwait() in the same RPC request to reduce flakiness. - eq({ 2 }, exec_lua([[return vim.fn.jobwait({ vim.fn.jobstart(...) })]], bogus_argv)) + local p = n.spawn_wait { args = { ... } } + eq(2, p.status) end it('bogus subcommand', function() run_and_check_exit_code('--remote-bogus') diff --git a/test/functional/core/startup_spec.lua b/test/functional/core/startup_spec.lua index 7062211187..8ecd3dca97 100644 --- a/test/functional/core/startup_spec.lua +++ b/test/functional/core/startup_spec.lua @@ -55,7 +55,10 @@ describe('startup', function() clear() local screen screen = Screen.new(84, 3) - fn.termopen({ nvim_prog, '-u', 'NONE', '--server', eval('v:servername'), '--remote-ui' }) + fn.jobstart( + { nvim_prog, '-u', 'NONE', '--server', eval('v:servername'), '--remote-ui' }, + { term = true } + ) screen:expect([[ ^Cannot attach UI of :terminal child to its parent. (Unset $NVIM to skip this check) | |*2 @@ -74,22 +77,9 @@ describe('startup', function() end) it('--startuptime does not crash on error #31125', function() - eq( - "E484: Can't open file .", - fn.system({ - nvim_prog, - '-u', - 'NONE', - '-i', - 'NONE', - '--headless', - '--startuptime', - '.', - '-c', - '42cquit', - }) - ) - eq(42, api.nvim_get_vvar('shell_error')) + local p = n.spawn_wait('--startuptime', '.', '-c', '42cquit') + eq("E484: Can't open file .", p.stderr) + eq(42, p.status) end) it('-D does not hang #12647', function() @@ -98,7 +88,7 @@ describe('startup', function() screen = Screen.new(60, 7) -- not the same colors on windows for some reason screen._default_attr_ids = nil - local id = fn.termopen({ + local id = fn.jobstart({ nvim_prog, '-u', 'NONE', @@ -108,6 +98,7 @@ describe('startup', function() 'set noruler', '-D', }, { + term = true, env = { VIMRUNTIME = os.getenv('VIMRUNTIME'), }, @@ -145,13 +136,18 @@ describe('startup', function() vim.list_extend(args, { '-l', (script or 'test/functional/fixtures/startup.lua') }) vim.list_extend(args, lua_args or {}) local out = fn.system(args, input):gsub('\r\n', '\n') - return eq(dedent(expected), out) + if type(expected) == 'function' then + return expected(out) + else + return eq(dedent(expected), out) + end end it('failure modes', function() -- nvim -l <empty> - matches('nvim%.?e?x?e?: Argument missing after: "%-l"', fn.system({ nvim_prog, '-l' })) - eq(1, eval('v:shell_error')) + local proc = n.spawn_wait('-l') + matches('nvim%.?e?x?e?: Argument missing after: "%-l"', proc.stderr) + eq(1, proc.status) end) it('os.exit() sets Nvim exitcode', function() @@ -178,13 +174,13 @@ describe('startup', function() end) it('Lua-error sets Nvim exitcode', function() + local proc = n.spawn_wait('-l', 'test/functional/fixtures/startup-fail.lua') + matches('E5113: .* my pearls!!', proc:output()) + eq(0, proc.signal) + eq(1, proc.status) + eq(0, eval('v:shell_error')) matches( - 'E5113: .* my pearls!!', - fn.system({ nvim_prog, '-l', 'test/functional/fixtures/startup-fail.lua' }) - ) - eq(1, eval('v:shell_error')) - matches( 'E5113: .* %[string "error%("whoa"%)"%]:1: whoa', fn.system({ nvim_prog, '-l', '-' }, 'error("whoa")') ) @@ -291,14 +287,30 @@ describe('startup', function() eq(0, eval('v:shell_error')) end) - it('disables swapfile/shada/config/plugins', function() + it('disables swapfile/shada/config/plugins unless overridden', function() + local script = [[print(('updatecount=%d shadafile=%s loadplugins=%s scripts=%d'):format( + vim.o.updatecount, vim.o.shadafile, tostring(vim.o.loadplugins), math.max(1, #vim.fn.getscriptinfo())))]] + finally(function() + os.remove('Xtest_shada') + end) + assert_l_out( 'updatecount=0 shadafile=NONE loadplugins=false scripts=1\n', nil, nil, '-', - [[print(('updatecount=%d shadafile=%s loadplugins=%s scripts=%d'):format( - vim.o.updatecount, vim.o.shadafile, tostring(vim.o.loadplugins), math.max(1, #vim.fn.getscriptinfo())))]] + script + ) + + -- User can override. + assert_l_out( + function(out) + return matches('updatecount=99 shadafile=Xtest_shada loadplugins=true scripts=2%d\n', out) + end, + { '+set updatecount=99', '-i', 'Xtest_shada', '+set loadplugins', '-u', 'NORC' }, + nil, + '-', + script ) end) end) @@ -343,7 +355,7 @@ describe('startup', function() local screen = Screen.new(25, 3) -- Remote UI connected by --embed. -- TODO: a lot of tests in this file already use the new default color scheme. - -- once we do the batch update of tests to use it, remove this workarond + -- once we do the batch update of tests to use it, remove this workaround screen._default_attr_ids = nil command([[echo has('ttyin') has('ttyout')]]) screen:expect([[ @@ -360,7 +372,7 @@ describe('startup', function() command([[set shellcmdflag=/s\ /c shellxquote=\"]]) end -- Running in :terminal - fn.termopen({ + fn.jobstart({ nvim_prog, '-u', 'NONE', @@ -371,6 +383,7 @@ describe('startup', function() '-c', 'echo has("ttyin") has("ttyout")', }, { + term = true, env = { VIMRUNTIME = os.getenv('VIMRUNTIME'), }, @@ -393,13 +406,14 @@ describe('startup', function() os.remove('Xtest_startup_ttyout') end) -- Running in :terminal - fn.termopen( + fn.jobstart( ( [["%s" -u NONE -i NONE --cmd "%s"]] .. [[ -c "call writefile([has('ttyin'), has('ttyout')], 'Xtest_startup_ttyout')"]] .. [[ -c q | cat -v]] ):format(nvim_prog, nvim_set), { + term = true, env = { VIMRUNTIME = os.getenv('VIMRUNTIME'), }, @@ -424,7 +438,7 @@ describe('startup', function() os.remove('Xtest_startup_ttyout') end) -- Running in :terminal - fn.termopen( + fn.jobstart( ( [[echo foo | ]] -- Input from a pipe. .. [["%s" -u NONE -i NONE --cmd "%s"]] @@ -432,6 +446,7 @@ describe('startup', function() .. [[ -c q -- -]] ):format(nvim_prog, nvim_set), { + term = true, env = { VIMRUNTIME = os.getenv('VIMRUNTIME'), }, @@ -454,13 +469,14 @@ describe('startup', function() command([[set shellcmdflag=/s\ /c shellxquote=\"]]) end -- Running in :terminal - fn.termopen( + fn.jobstart( ( [[echo foo | ]] .. [["%s" -u NONE -i NONE --cmd "%s"]] .. [[ -c "echo has('ttyin') has('ttyout')"]] ):format(nvim_prog, nvim_set), { + term = true, env = { VIMRUNTIME = os.getenv('VIMRUNTIME'), }, @@ -577,19 +593,21 @@ describe('startup', function() eq(' encoding=utf-8\n', fn.system({ nvim_prog, '-n', '-es' }, { 'set encoding', '' })) end) - it('-es/-Es disables swapfile, user config #8540', function() + it('-es/-Es disables swapfile/shada/config #8540', function() for _, arg in ipairs({ '-es', '-Es' }) do local out = fn.system({ nvim_prog, arg, - '+set swapfile? updatecount? shadafile?', + '+set updatecount? shadafile? loadplugins?', '+put =map(getscriptinfo(), {-> v:val.name})', '+%print', }) local line1 = string.match(out, '^.-\n') -- updatecount=0 means swapfile was disabled. - eq(' swapfile updatecount=0 shadafile=\n', line1) - -- Standard plugins were loaded, but not user config. + eq(' updatecount=0 shadafile=NONE loadplugins\n', line1) + -- Standard plugins were loaded, but not user config. #31878 + local nrlines = #vim.split(out, '\n') + ok(nrlines > 20, '>20', nrlines) ok(string.find(out, 'man.lua') ~= nil) ok(string.find(out, 'init.vim') == nil) end @@ -598,15 +616,15 @@ describe('startup', function() it('fails on --embed with -es/-Es/-l', function() matches( 'nvim[.exe]*: %-%-embed conflicts with %-es/%-Es/%-l', - fn.system({ nvim_prog, '--embed', '-es' }) + n.spawn_wait('--embed', '-es').stderr ) matches( 'nvim[.exe]*: %-%-embed conflicts with %-es/%-Es/%-l', - fn.system({ nvim_prog, '--embed', '-Es' }) + n.spawn_wait('--embed', '-Es').stderr ) matches( 'nvim[.exe]*: %-%-embed conflicts with %-es/%-Es/%-l', - fn.system({ nvim_prog, '--embed', '-l', 'foo.lua' }) + n.spawn_wait('--embed', '-l', 'foo.lua').stderr ) end) @@ -614,7 +632,7 @@ describe('startup', function() local screen screen = Screen.new(60, 6) screen._default_attr_ids = nil - local id = fn.termopen({ + local id = fn.jobstart({ nvim_prog, '-u', 'NONE', @@ -625,6 +643,7 @@ describe('startup', function() '--cmd', 'let g:foo = g:bar', }, { + term = true, env = { VIMRUNTIME = os.getenv('VIMRUNTIME'), }, @@ -689,20 +708,8 @@ describe('startup', function() end) it('get command line arguments from v:argv', function() - local out = fn.system({ - nvim_prog, - '-u', - 'NONE', - '-i', - 'NONE', - '--headless', - '--cmd', - nvim_set, - '-c', - [[echo v:argv[-1:] len(v:argv) > 1]], - '+q', - }) - eq("['+q'] 1", out) + local p = n.spawn_wait('--cmd', nvim_set, '-c', [[echo v:argv[-1:] len(v:argv) > 1]], '+q') + eq("['+q'] 1", p.stderr) end) end) @@ -1144,7 +1151,8 @@ describe('user config init', function() local screen = Screen.new(50, 8) screen._default_attr_ids = nil - fn.termopen({ nvim_prog }, { + fn.jobstart({ nvim_prog }, { + term = true, env = { VIMRUNTIME = os.getenv('VIMRUNTIME'), }, @@ -1153,7 +1161,7 @@ describe('user config init', function() -- `i` to enter Terminal mode, `a` to allow feed('ia') screen:expect([[ - | + ^ | ~ |*4 [No Name] 0,0-1 All| | @@ -1162,7 +1170,7 @@ describe('user config init', function() feed(':echo g:exrc_file<CR>') screen:expect(string.format( [[ - | + ^ | ~ |*4 [No Name] 0,0-1 All| %s%s| @@ -1418,7 +1426,7 @@ describe('inccommand on ex mode', function() clear() local screen screen = Screen.new(60, 10) - local id = fn.termopen({ + local id = fn.jobstart({ nvim_prog, '-u', 'NONE', @@ -1429,6 +1437,7 @@ describe('inccommand on ex mode', function() '-E', 'test/README.md', }, { + term = true, env = { VIMRUNTIME = os.getenv('VIMRUNTIME') }, }) fn.chansend(id, '%s/N') diff --git a/test/functional/editor/completion_spec.lua b/test/functional/editor/completion_spec.lua index 030181764d..7c68de272b 100644 --- a/test/functional/editor/completion_spec.lua +++ b/test/functional/editor/completion_spec.lua @@ -10,6 +10,7 @@ local fn = n.fn local command = n.command local api = n.api local poke_eventloop = n.poke_eventloop +local exec_lua = n.exec_lua describe('completion', function() local screen @@ -1023,18 +1024,18 @@ describe('completion', function() it("'ignorecase' 'infercase' CTRL-X CTRL-N #6451", function() feed_command('set ignorecase infercase') - feed_command('edit runtime/doc/backers.txt') + feed_command('edit runtime/doc/credits.txt') feed('oX<C-X><C-N>') screen:expect { grid = [[ - *backers.txt* Nvim | - Xnull^ | - {12:Xnull }{101: } | - {4:Xoxomoon }{101: } | - {4:Xu }{101: } NVIM REFERENCE MANUAL | - {4:Xpayn }{12: } | - {4:Xinity }{12: } | - {5:-- Keyword Local completion (^N^P) }{6:match 1 of 7} | + *credits.txt* Nvim | + Xvi^ | + {12:Xvi }{101: } | + {4:Xvim }{101: } | + {4:X11 }{12: } NVIM REFERENCE MANUAL | + {4:Xnull }{12: } | + {4:Xoxomoon }{12: } | + {5:-- Keyword Local completion (^N^P) }{6:match 1 of 10} | ]], } end) @@ -1265,7 +1266,7 @@ describe('completion', function() command([[ call setline(1, ['aaaa']) let ns_id = nvim_create_namespace('extmark') - let mark_id = nvim_buf_set_extmark(0, ns_id, 0, 0, { 'end_col':2, 'hl_group':'Error'}) + let mark_id = nvim_buf_set_extmark(0, ns_id, 0, 0, { 'end_col':2, 'hl_group':'Error' }) let mark = nvim_buf_get_extmark_by_id(0, ns_id, mark_id, { 'details':1 }) inoremap <C-x> <C-r>=Complete()<CR> function Complete() abort @@ -1302,5 +1303,53 @@ describe('completion', function() aaaaa | {5:-- INSERT --} | ]]) + -- Also when completion leader is changed #31384 + feed('<Esc>hi<C-N><C-P>a') + screen:expect({ + grid = [[ + {9:aa}a^aa | + {4:aaaa } | + {4:aaaaa } | + {5:-- Keyword completion (^N^P) }{19:Back at original} | + ]], + }) + -- But still grows with end_right_gravity #31437 + command( + "call nvim_buf_set_extmark(0, ns_id, 1, 0, { 'end_col':2, 'hl_group':'Error', 'end_right_gravity': 1 })" + ) + feed('<Esc>ji<C-N>a') + screen:expect({ + grid = [[ + {9:aa}aaa | + {9:aaa}^aa | + aaaaa | + {5:-- INSERT --} | + ]], + }) + end) + + describe('nvim__complete_set', function() + it("fails when 'completeopt' does not include popup", function() + exec_lua([[ + function _G.omni_test(findstart, base) + if findstart == 1 then + return vim.fn.col('.') - 1 + end + return { { word = 'one' } } + end + vim.api.nvim_create_autocmd('CompleteChanged', { + callback = function() + local ok, err = pcall(vim.api.nvim__complete_set, 0, { info = '1info' }) + if not ok then + vim.g.err_msg = err + end + end, + }) + vim.opt.completeopt = 'menu,menuone' + vim.opt.omnifunc = 'v:lua.omni_test' + ]]) + feed('S<C-X><C-O>') + eq('completeopt option does not include popup', api.nvim_get_var('err_msg')) + end) end) end) diff --git a/test/functional/editor/defaults_spec.lua b/test/functional/editor/defaults_spec.lua index 82d285cc9a..ee6bfa1be9 100644 --- a/test/functional/editor/defaults_spec.lua +++ b/test/functional/editor/defaults_spec.lua @@ -32,7 +32,7 @@ describe('default', function() describe('popupmenu', function() it('can be disabled by user', function() n.clear { - args = { '+autocmd! nvim_popupmenu', '+aunmenu PopUp' }, + args = { '+autocmd! nvim.popupmenu', '+aunmenu PopUp' }, } local screen = Screen.new(40, 8) n.insert([[ @@ -94,8 +94,103 @@ describe('default', function() end) describe('key mappings', function() + describe('Visual mode search mappings', function() + it('handle various chars properly', function() + n.clear({ args_rm = { '--cmd' } }) + local screen = Screen.new(60, 8) + screen:set_default_attr_ids({ + [1] = { foreground = Screen.colors.NvimDarkGray4 }, + [2] = { + foreground = Screen.colors.NvimDarkGray3, + background = Screen.colors.NvimLightGray3, + }, + [3] = { + foreground = Screen.colors.NvimLightGrey1, + background = Screen.colors.NvimDarkYellow, + }, + [4] = { + foreground = Screen.colors.NvimDarkGrey1, + background = Screen.colors.NvimLightYellow, + }, + }) + n.api.nvim_buf_set_lines(0, 0, -1, true, { + [[testing <CR> /?\!1]], + [[testing <CR> /?\!2]], + [[testing <CR> /?\!3]], + [[testing <CR> /?\!4]], + }) + n.feed('gg0vf!o*') + screen:expect([[ + {3:testing <CR> /?\!}1 | + {4:^testing <CR> /?\!}2 | + {3:testing <CR> /?\!}3 | + {3:testing <CR> /?\!}4 | + {1:~ }|*2 + {2:[No Name] [+] 2,1 All}| + /\Vtesting <CR> \/?\\! [2/4] | + ]]) + n.feed('n') + screen:expect([[ + {3:testing <CR> /?\!}1 | + {3:testing <CR> /?\!}2 | + {4:^testing <CR> /?\!}3 | + {3:testing <CR> /?\!}4 | + {1:~ }|*2 + {2:[No Name] [+] 3,1 All}| + /\Vtesting <CR> \/?\\! [3/4] | + ]]) + n.feed('G0vf!o#') + screen:expect([[ + {3:testing <CR> /?\!}1 | + {3:testing <CR> /?\!}2 | + {4:^testing <CR> /?\!}3 | + {3:testing <CR> /?\!}4 | + {1:~ }|*2 + {2:[No Name] [+] 3,1 All}| + ?\Vtesting <CR> /?\\! [3/4] | + ]]) + n.feed('n') + screen:expect([[ + {3:testing <CR> /?\!}1 | + {4:^testing <CR> /?\!}2 | + {3:testing <CR> /?\!}3 | + {3:testing <CR> /?\!}4 | + {1:~ }|*2 + {2:[No Name] [+] 2,1 All}| + ?\Vtesting <CR> /?\\! [2/4] | + ]]) + end) + end) + describe('unimpaired-style mappings', function() - it('do not show a full stack trace #30625', function() + it('show the command ouptut when successful', function() + n.clear({ args_rm = { '--cmd' } }) + local screen = Screen.new(40, 8) + n.fn.setqflist({ + { filename = 'file1', text = 'item1' }, + { filename = 'file2', text = 'item2' }, + }) + + n.feed(']q') + + screen:set_default_attr_ids({ + [1] = { foreground = Screen.colors.NvimDarkGrey4 }, + [2] = { + background = Screen.colors.NvimLightGray3, + foreground = Screen.colors.NvimDarkGrey3, + }, + }) + screen:expect({ + grid = [[ + ^ | + {1:~ }|*5 + {2:file2 0,0-1 All}| + (2 of 2): item2 | + ]], + }) + end) + + it('do not show a full stack trace when unsuccessful #30625', function() n.clear({ args_rm = { '--cmd' } }) local screen = Screen.new(40, 8) screen:set_default_attr_ids({ diff --git a/test/functional/editor/mode_normal_spec.lua b/test/functional/editor/mode_normal_spec.lua index 5237f51237..353a261edb 100644 --- a/test/functional/editor/mode_normal_spec.lua +++ b/test/functional/editor/mode_normal_spec.lua @@ -26,10 +26,10 @@ describe('Normal mode', function() it('&showcmd does not crash with :startinsert #28419', function() local screen = Screen.new(60, 17) - fn.termopen( - { n.nvim_prog, '--clean', '--cmd', 'startinsert' }, - { env = { VIMRUNTIME = os.getenv('VIMRUNTIME') } } - ) + fn.jobstart({ n.nvim_prog, '--clean', '--cmd', 'startinsert' }, { + term = true, + env = { VIMRUNTIME = os.getenv('VIMRUNTIME') }, + }) screen:expect({ grid = [[ ^ | diff --git a/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua b/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua index 2820cc9663..f84fc140d2 100644 --- a/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua +++ b/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua @@ -12,12 +12,10 @@ local fn = n.fn local nvim_prog = n.nvim_prog local ok = t.ok local rmdir = n.rmdir -local new_argv = n.new_argv local new_pipename = n.new_pipename local pesc = vim.pesc local os_kill = n.os_kill local set_session = n.set_session -local spawn = n.spawn local async_meths = n.async_meths local expect_msg_seq = n.expect_msg_seq local pcall_err = t.pcall_err @@ -56,7 +54,7 @@ describe("preserve and (R)ecover with custom 'directory'", function() local nvim0 before_each(function() - nvim0 = spawn(new_argv()) + nvim0 = n.new_session(false) set_session(nvim0) rmdir(swapdir) mkdir(swapdir) @@ -76,7 +74,8 @@ describe("preserve and (R)ecover with custom 'directory'", function() local function test_recover(swappath1) -- Start another Nvim instance. - local nvim2 = spawn({ nvim_prog, '-u', 'NONE', '-i', 'NONE', '--embed' }, true) + local nvim2 = + n.new_session(false, { args = { '-u', 'NONE', '-i', 'NONE', '--embed' }, merge = false }) set_session(nvim2) exec(init) @@ -115,7 +114,8 @@ describe("preserve and (R)ecover with custom 'directory'", function() t.skip(t.is_os('win')) local screen0 = Screen.new() local child_server = new_pipename() - fn.termopen({ nvim_prog, '-u', 'NONE', '-i', 'NONE', '--listen', child_server }, { + fn.jobstart({ nvim_prog, '-u', 'NONE', '-i', 'NONE', '--listen', child_server }, { + term = true, env = { VIMRUNTIME = os.getenv('VIMRUNTIME') }, }) screen0:expect({ any = pesc('[No Name]') }) -- Wait for the child process to start. @@ -140,7 +140,7 @@ describe('swapfile detection', function() set swapfile fileformat=unix nomodified undolevels=-1 nohidden ]] before_each(function() - nvim0 = spawn(new_argv()) + nvim0 = n.new_session(false) set_session(nvim0) rmdir(swapdir) mkdir(swapdir) @@ -167,12 +167,13 @@ describe('swapfile detection', function() command('preserve') -- Start another Nvim instance. - local nvim2 = spawn({ nvim_prog, '-u', 'NONE', '-i', 'NONE', '--embed' }, true, nil, true) + local nvim2 = + n.new_session(true, { args = { '-u', 'NONE', '-i', 'NONE', '--embed' }, merge = false }) set_session(nvim2) local screen2 = Screen.new(256, 40) screen2._default_attr_ids = nil exec(init) - command('autocmd! nvim_swapfile') -- Delete the default handler (which skips the dialog). + command('autocmd! nvim.swapfile') -- Delete the default handler (which skips the dialog). -- With shortmess+=F command('set shortmess+=F') @@ -219,6 +220,7 @@ describe('swapfile detection', function() .. [[%.swp"]], } feed('e') -- Chose "Edit" at the swap dialog. + screen2:expect({ any = pesc('E5555: API call: Vim(edit):E325: ATTENTION') }) feed('<c-c>') screen2:expect(expected_no_dialog) @@ -249,7 +251,7 @@ describe('swapfile detection', function() command('preserve') -- Make sure the swap file exists. local nvimpid = fn.getpid() - local nvim1 = spawn(new_argv(), true, nil, true) + local nvim1 = n.new_session(true) set_session(nvim1) local screen = Screen.new(75, 18) exec(init) @@ -271,11 +273,11 @@ describe('swapfile detection', function() [1] = { bold = true, foreground = Screen.colors.SeaGreen }, -- MoreMsg }) - local nvim1 = spawn(new_argv(), true, nil, true) + local nvim1 = n.new_session(true) set_session(nvim1) screen:attach() exec(init) - command('autocmd! nvim_swapfile') -- Delete the default handler (which skips the dialog). + command('autocmd! nvim.swapfile') -- Delete the default handler (which skips the dialog). feed(':split Xfile1\n') -- The default SwapExists handler does _not_ skip this prompt. screen:expect({ @@ -290,11 +292,11 @@ describe('swapfile detection', function() ]]) nvim1:close() - local nvim2 = spawn(new_argv(), true, nil, true) + local nvim2 = n.new_session(true) set_session(nvim2) screen:attach() exec(init) - command('autocmd! nvim_swapfile') -- Delete the default handler (which skips the dialog). + command('autocmd! nvim.swapfile') -- Delete the default handler (which skips the dialog). command('set more') command('au bufadd * let foo_w = wincol()') feed(':e Xfile1<CR>') @@ -325,7 +327,7 @@ describe('swapfile detection', function() exec(init) if not swapexists then - command('autocmd! nvim_swapfile') -- Delete the default handler (which skips the dialog). + command('autocmd! nvim.swapfile') -- Delete the default handler (which skips the dialog). end command('set nohidden') @@ -446,9 +448,10 @@ describe('quitting swapfile dialog on startup stops TUI properly', function() end) it('(Q)uit at first file argument', function() - local chan = fn.termopen( + local chan = fn.jobstart( { nvim_prog, '-u', 'NONE', '-i', 'NONE', '--cmd', init_dir, '--cmd', init_set, testfile }, { + term = true, env = { VIMRUNTIME = os.getenv('VIMRUNTIME') }, } ) @@ -468,7 +471,7 @@ describe('quitting swapfile dialog on startup stops TUI properly', function() end) it('(A)bort at second file argument with -p', function() - local chan = fn.termopen({ + local chan = fn.jobstart({ nvim_prog, '-u', 'NONE', @@ -482,6 +485,7 @@ describe('quitting swapfile dialog on startup stops TUI properly', function() otherfile, testfile, }, { + term = true, env = { VIMRUNTIME = os.getenv('VIMRUNTIME') }, }) retry(nil, nil, function() @@ -508,7 +512,7 @@ describe('quitting swapfile dialog on startup stops TUI properly', function() second %s /^ \zssecond$/ third %s /^ \zsthird$/]]):format(testfile, testfile, testfile) ) - local chan = fn.termopen({ + local chan = fn.jobstart({ nvim_prog, '-u', 'NONE', @@ -522,6 +526,7 @@ describe('quitting swapfile dialog on startup stops TUI properly', function() 'set tags=' .. otherfile, '-tsecond', }, { + term = true, env = { VIMRUNTIME = os.getenv('VIMRUNTIME') }, }) retry(nil, nil, function() @@ -532,10 +537,6 @@ describe('quitting swapfile dialog on startup stops TUI properly', function() end) api.nvim_chan_send(chan, 'q') retry(nil, nil, function() - eq('Press ENTER or type command to continue', eval("getline('$')->trim(' ', 2)")) - end) - api.nvim_chan_send(chan, '\r') - retry(nil, nil, function() eq( { '', '[Process exited 1]', '' }, eval("[1, 2, '$']->map({_, lnum -> getline(lnum)->trim(' ', 2)})") diff --git a/test/functional/ex_cmds/wundo_spec.lua b/test/functional/ex_cmds/wundo_spec.lua index 2299f33f06..9b81c6d06d 100644 --- a/test/functional/ex_cmds/wundo_spec.lua +++ b/test/functional/ex_cmds/wundo_spec.lua @@ -5,8 +5,6 @@ local n = require('test.functional.testnvim')() local command = n.command local clear = n.clear local eval = n.eval -local spawn = n.spawn -local nvim_prog = n.nvim_prog local set_session = n.set_session describe(':wundo', function() @@ -24,15 +22,11 @@ end) describe('u_* functions', function() it('safely fail on new, non-empty buffer', function() - local session = spawn({ - nvim_prog, - '-u', - 'NONE', - '-i', - 'NONE', - '--embed', - '-c', - 'set undodir=. undofile', + local session = n.new_session(false, { + args = { + '-c', + 'set undodir=. undofile', + }, }) set_session(session) command('echo "True"') -- Should not error out due to crashed Neovim diff --git a/test/functional/fixtures/CMakeLists.txt b/test/functional/fixtures/CMakeLists.txt index 150407fe46..a388f9cb33 100644 --- a/test/functional/fixtures/CMakeLists.txt +++ b/test/functional/fixtures/CMakeLists.txt @@ -1,7 +1,4 @@ add_library(test_lib INTERFACE) -if(MINGW) - target_link_libraries(test_lib INTERFACE -municode) -endif() if(WIN32) target_compile_definitions(test_lib INTERFACE MSWIN) endif() diff --git a/test/functional/fixtures/fake-lsp-server.lua b/test/functional/fixtures/fake-lsp-server.lua index f813927f77..5d7ab2ad12 100644 --- a/test/functional/fixtures/fake-lsp-server.lua +++ b/test/functional/fixtures/fake-lsp-server.lua @@ -386,6 +386,21 @@ function tests.check_forward_content_modified() } end +function tests.check_forward_server_cancelled() + skeleton { + on_init = function() + return { capabilities = {} } + end, + body = function() + expect_request('error_code_test', function() + return { code = -32802 }, nil, { method = 'error_code_test', client_id = 1 } + end) + expect_notification('finish') + notify('finish') + end, + } +end + function tests.check_pending_request_tracked() skeleton { on_init = function(_) diff --git a/test/functional/fixtures/printargs-test.c b/test/functional/fixtures/printargs-test.c index 2c25cf8447..a1d3fdf76e 100644 --- a/test/functional/fixtures/printargs-test.c +++ b/test/functional/fixtures/printargs-test.c @@ -2,7 +2,7 @@ int main(int argc, char **argv) { - for (int i=1; i<argc; i++) { + for (int i = 1; i < argc; i++) { printf("arg%d=%s;", i, argv[i]); } return 0; diff --git a/test/functional/fixtures/shell-test.c b/test/functional/fixtures/shell-test.c index bd71e7d11b..f3e94a28da 100644 --- a/test/functional/fixtures/shell-test.c +++ b/test/functional/fixtures/shell-test.c @@ -1,11 +1,12 @@ +#include <stdbool.h> +#include <stdint.h> #include <stdio.h> #include <string.h> -#include <stdint.h> #ifdef _MSC_VER -#include <Windows.h> -#define usleep(usecs) Sleep(usecs/1000) +# include <Windows.h> +# define usleep(usecs) Sleep(usecs/1000) #else -#include <unistd.h> +# include <unistd.h> #endif static void flush_wait(void) @@ -56,7 +57,7 @@ int main(int argc, char **argv) if (argc >= 2) { if (strcmp(argv[1], "-t") == 0) { if (argc < 3) { - fprintf(stderr,"Missing prompt text for -t option\n"); + fprintf(stderr, "Missing prompt text for -t option\n"); return 5; } else { fprintf(stderr, "%s $ ", argv[2]); @@ -107,18 +108,18 @@ int main(int argc, char **argv) char cmd[100]; int arg; - while (1) { + while (true) { fprintf(stderr, "interact $ "); if (fgets(input, sizeof(input), stdin) == NULL) { break; // EOF } - if(1 == sscanf(input, "%99s %d", cmd, &arg)) { + if (1 == sscanf(input, "%99s %d", cmd, &arg)) { arg = 0; } if (strcmp(cmd, "exit") == 0) { - return arg; + return arg; } else { fprintf(stderr, "command not found: %s\n", cmd); } diff --git a/test/functional/fixtures/streams-test.c b/test/functional/fixtures/streams-test.c index 5a59abb33b..68e668d5fa 100644 --- a/test/functional/fixtures/streams-test.c +++ b/test/functional/fixtures/streams-test.c @@ -1,6 +1,5 @@ /// Helper program to exit and keep stdout open (like "xclip -i -loops 1"). #include <stdio.h> - #include <uv.h> int main(int argc, char **argv) @@ -8,7 +7,7 @@ int main(int argc, char **argv) uv_loop_t *loop = uv_default_loop(); uv_process_t child_req; - char * args[3]; + char *args[3]; args[0] = "sleep"; args[1] = "10"; args[2] = NULL; diff --git a/test/functional/legacy/cmdline_spec.lua b/test/functional/legacy/cmdline_spec.lua index bf146e1322..819b40323a 100644 --- a/test/functional/legacy/cmdline_spec.lua +++ b/test/functional/legacy/cmdline_spec.lua @@ -159,6 +159,50 @@ describe('cmdline', function() endfunc ]]) + feed(':resize -3<CR>') + screen:expect([[ + ^ | + {1:~ }|*2 + {3:[No Name] }| + |*4 + ]]) + + -- :resize now also changes 'cmdheight' accordingly + feed(':set cmdheight+=1<CR>') + screen:expect([[ + ^ | + {1:~ }| + {3:[No Name] }| + |*5 + ]]) + + -- using more space moves the status line up + feed(':set cmdheight+=1<CR>') + screen:expect([[ + ^ | + {3:[No Name] }| + |*6 + ]]) + + -- reducing cmdheight moves status line down + feed(':set cmdheight-=3<CR>') + screen:expect([[ + ^ | + {1:~ }|*3 + {3:[No Name] }| + |*3 + ]]) + + -- reducing window size and then setting cmdheight + feed(':resize -1<CR>') + feed(':set cmdheight=1<CR>') + screen:expect([[ + ^ | + {1:~ }|*5 + {3:[No Name] }| + | + ]]) + -- setting 'cmdheight' works after outputting two messages feed(':call EchoTwo()') screen:expect([[ @@ -185,6 +229,16 @@ describe('cmdline', function() bar | {6:Press ENTER or type command to continue}^ | ]]) + + -- window commands do not reduce 'cmdheight' to value lower than :set by user + feed('<CR>:wincmd _<CR>') + screen:expect([[ + ^ | + {1:~ }|*4 + {3:[No Name] }| + :wincmd _ | + | + ]]) end) -- oldtest: Test_cmdheight_tabline() @@ -216,6 +270,22 @@ describe('cmdline', function() longish | ]] end) + + -- oldtest: Test_rulerformat_function() + it("'rulerformat' can use %!", function() + local screen = Screen.new(40, 2) + exec([[ + func TestRulerFn() + return '10,20%=30%%' + endfunc + ]]) + api.nvim_set_option_value('ruler', true, {}) + api.nvim_set_option_value('rulerformat', '%!TestRulerFn()', {}) + screen:expect([[ + ^ | + 10,20 30% | + ]]) + end) end) describe('cmdwin', function() diff --git a/test/functional/legacy/highlight_spec.lua b/test/functional/legacy/highlight_spec.lua index e663931bd7..24d6abcc6b 100644 --- a/test/functional/legacy/highlight_spec.lua +++ b/test/functional/legacy/highlight_spec.lua @@ -29,8 +29,8 @@ describe(':highlight', function() | TermCursor {2:xxx} {18:cterm=}reverse | {18:gui=}reverse | - TermCursorNC xxx cleared | NonText {1:xxx} {18:ctermfg=}12 | + {18:gui=}bold | {6:-- More --}^ | ]]) feed('q') diff --git a/test/functional/legacy/messages_spec.lua b/test/functional/legacy/messages_spec.lua index bc58551a34..db5e3f6857 100644 --- a/test/functional/legacy/messages_spec.lua +++ b/test/functional/legacy/messages_spec.lua @@ -82,7 +82,7 @@ describe('messages', function() NoSuchFil^e | three | {1:~ }|*5 - from DebugSilent visual | + | {9:E447: Can't find file "NoSuchFile" in path} | ]]) end) @@ -689,4 +689,43 @@ describe('messages', function() ]]) os.remove('b.txt') end) + + -- oldtest: Test_messagesopt_wait() + it('&messagesopt "wait"', function() + screen = Screen.new(45, 6) + command('set cmdheight=1') + + -- Check hit-enter prompt + command('set messagesopt=hit-enter,history:500') + feed(":echo 'foo' | echo 'bar' | echo 'baz'\n") + screen:expect([[ + | + {3: }| + foo | + bar | + baz | + {6:Press ENTER or type command to continue}^ | + ]]) + feed('<CR>') + + -- Check no hit-enter prompt when "wait:" is set + command('set messagesopt=wait:500,history:500') + feed(":echo 'foo' | echo 'bar' | echo 'baz'\n") + screen:expect({ + grid = [[ + | + {1:~ }| + {3: }| + foo | + bar | + baz | + ]], + timeout = 500, + }) + screen:expect([[ + ^ | + {1:~ }|*4 + | + ]]) + end) end) diff --git a/test/functional/legacy/signs_spec.lua b/test/functional/legacy/signs_spec.lua index ac7c8cd0bc..af355f27e6 100644 --- a/test/functional/legacy/signs_spec.lua +++ b/test/functional/legacy/signs_spec.lua @@ -26,6 +26,9 @@ describe('signs', function() -- oldtest: Test_sign_cursor_position() it('are drawn correctly', function() local screen = Screen.new(75, 6) + screen:add_extra_attr_ids({ + [100] = { foreground = Screen.colors.Blue4, background = Screen.colors.Yellow }, + }) exec([[ call setline(1, [repeat('x', 75), 'mmmm', 'yyyy']) call cursor(2,1) @@ -37,7 +40,7 @@ describe('signs', function() screen:expect([[ {7: }xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx| {7: }xx | - {10:=>}^mmmm | + {100:=>}^mmmm | {7: }yyyy | {1:~ }| | @@ -48,7 +51,7 @@ describe('signs', function() screen:expect([[ {7: }xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx| {7: }xx | - {10:-)}^mmmm | + {100:-)}^mmmm | {7: }yyyy | {1:~ }| | @@ -59,7 +62,7 @@ describe('signs', function() screen:expect([[ {7: }xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx| {7: }xx | - {10:-)}{4:^mmmm }| + {100:-)}{4:^mmmm }| {7: }yyyy | {1:~ }| | diff --git a/test/functional/legacy/substitute_spec.lua b/test/functional/legacy/substitute_spec.lua index 0081371f7f..30ff13140f 100644 --- a/test/functional/legacy/substitute_spec.lua +++ b/test/functional/legacy/substitute_spec.lua @@ -220,8 +220,10 @@ describe(':substitute', function() {2:o}ne | two | three | - {1:~ }|*4 - {6:replace with (y/n/a/q/l/^E/^Y)?}^ | + {1:~ }|*2 + {3: }| + {6:replace with ? (y)es/(n)o/(a)ll/(q)uit/(l)ast/scroll up(^}| + {6:E)/down(^Y)}^ | ]]) end) end) diff --git a/test/functional/legacy/window_cmd_spec.lua b/test/functional/legacy/window_cmd_spec.lua index d68c780f49..fac982354c 100644 --- a/test/functional/legacy/window_cmd_spec.lua +++ b/test/functional/legacy/window_cmd_spec.lua @@ -120,7 +120,7 @@ describe('splitkeep', function() row = 0, col = 0, })) - vim.cmd("call termopen([&sh, &shcf, 'true'], { 'on_exit': 'C2' })") + vim.cmd("call jobstart([&sh, &shcf, 'true'], { 'term': v:true, 'on_exit': 'C2' })") end })]]) feed('j') @@ -299,7 +299,7 @@ describe('splitkeep', function() c | {1:~ }| {3:[No Name] }| - | + :call win_move_statusline(win, 1) | ]]) end) diff --git a/test/functional/lua/diagnostic_spec.lua b/test/functional/lua/diagnostic_spec.lua index eb1ac3e6a1..a19f558ef2 100644 --- a/test/functional/lua/diagnostic_spec.lua +++ b/test/functional/lua/diagnostic_spec.lua @@ -5,6 +5,7 @@ local command = n.command local clear = n.clear local exec_lua = n.exec_lua local eq = t.eq +local neq = t.neq local matches = t.matches local api = n.api local pcall_err = t.pcall_err @@ -112,6 +113,18 @@ describe('vim.diagnostic', function() ) end + function _G.get_virt_lines_extmarks(ns) + ns = vim.diagnostic.get_namespace(ns) + local virt_lines_ns = ns.user_data.virt_lines_ns + return vim.api.nvim_buf_get_extmarks( + _G.diagnostic_bufnr, + virt_lines_ns, + 0, + -1, + { details = true } + ) + end + ---@param ns integer function _G.get_underline_extmarks(ns) ---@type integer @@ -160,6 +173,11 @@ describe('vim.diagnostic', function() 'DiagnosticUnderlineOk', 'DiagnosticUnderlineWarn', 'DiagnosticUnnecessary', + 'DiagnosticVirtualLinesError', + 'DiagnosticVirtualLinesHint', + 'DiagnosticVirtualLinesInfo', + 'DiagnosticVirtualLinesOk', + 'DiagnosticVirtualLinesWarn', 'DiagnosticVirtualTextError', 'DiagnosticVirtualTextHint', 'DiagnosticVirtualTextInfo', @@ -368,6 +386,9 @@ describe('vim.diagnostic', function() end) it('handles one namespace clearing highlights while the other still has highlights', function() + exec_lua(function() + vim.diagnostic.config({ virtual_text = true }) + end) -- 1 Error (1) -- 1 Warning (2) -- 1 Warning (2) + 1 Warning (1) @@ -442,6 +463,10 @@ describe('vim.diagnostic', function() end) it('does not display diagnostics when disabled', function() + exec_lua(function() + vim.diagnostic.config({ virtual_text = true }) + end) + eq( { 0, 2 }, exec_lua(function() @@ -574,7 +599,7 @@ describe('vim.diagnostic', function() vim.diagnostic.set( _G.diagnostic_ns, _G.diagnostic_bufnr, - { { lnum = 0, end_lnum = 0, col = 0, end_col = 0 } } + { { message = '', lnum = 0, end_lnum = 0, col = 0, end_col = 0 } } ) vim.cmd('bwipeout! ' .. _G.diagnostic_bufnr) @@ -915,6 +940,10 @@ describe('vim.diagnostic', function() describe('reset()', function() it('diagnostic count is 0 and displayed diagnostics are 0 after call', function() + exec_lua(function() + vim.diagnostic.config({ virtual_text = true }) + end) + -- 1 Error (1) -- 1 Warning (2) -- 1 Warning (2) + 1 Warning (1) @@ -1005,7 +1034,7 @@ describe('vim.diagnostic', function() vim.diagnostic.set( _G.diagnostic_ns, _G.diagnostic_bufnr, - { { lnum = 0, end_lnum = 0, col = 0, end_col = 0 } } + { { message = '', lnum = 0, end_lnum = 0, col = 0, end_col = 0 } } ) vim.cmd('bwipeout! ' .. _G.diagnostic_bufnr) @@ -2105,6 +2134,139 @@ describe('vim.diagnostic', function() end) ) end) + + it('can filter diagnostics by returning nil when formatting', function() + local result = exec_lua(function() + vim.diagnostic.config { + virtual_text = { + format = function(diagnostic) + if diagnostic.code == 'foo_err' then + return nil + end + return diagnostic.message + end, + }, + } + + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_error('An error here!', 0, 0, 0, 0, 'foo_server', 'foo_err'), + _G.make_error('An error there!', 1, 1, 1, 1, 'bar_server', 'bar_err'), + }) + + local extmarks = _G.get_virt_text_extmarks(_G.diagnostic_ns) + return extmarks + end) + + eq(1, #result) + eq(' An error there!', result[1][4].virt_text[3][1]) + end) + + it('can only show virtual_text for the current line', function() + local result = exec_lua(function() + vim.api.nvim_win_set_cursor(0, { 1, 0 }) + + vim.diagnostic.config({ virtual_text = { current_line = true } }) + + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_error('Error here!', 0, 0, 0, 0, 'foo_server'), + _G.make_error('Another error there!', 1, 0, 1, 0, 'foo_server'), + }) + + local extmarks = _G.get_virt_text_extmarks(_G.diagnostic_ns) + return extmarks + end) + + eq(1, #result) + eq(' Error here!', result[1][4].virt_text[3][1]) + end) + end) + + describe('handlers.virtual_lines', function() + it('includes diagnostic code and message', function() + local result = exec_lua(function() + vim.diagnostic.config({ virtual_lines = true }) + + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_error('Missed symbol `,`', 0, 0, 0, 0, 'lua_ls', 'miss-symbol'), + }) + + local extmarks = _G.get_virt_lines_extmarks(_G.diagnostic_ns) + return extmarks[1][4].virt_lines + end) + + eq('miss-symbol: Missed symbol `,`', result[1][3][1]) + end) + + it('adds space to the left of the diagnostic', function() + local error_offset = 5 + local result = exec_lua(function() + vim.diagnostic.config({ virtual_lines = true }) + + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_error('Error here!', 0, error_offset, 0, error_offset, 'foo_server'), + }) + + local extmarks = _G.get_virt_lines_extmarks(_G.diagnostic_ns) + return extmarks[1][4].virt_lines + end) + + eq(error_offset, result[1][1][1]:len()) + end) + + it('highlights diagnostics in multiple lines by default', function() + local result = exec_lua(function() + vim.diagnostic.config({ virtual_lines = true }) + + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_error('Error here!', 0, 0, 0, 0, 'foo_server'), + _G.make_error('Another error there!', 1, 0, 1, 0, 'foo_server'), + }) + + local extmarks = _G.get_virt_lines_extmarks(_G.diagnostic_ns) + return extmarks + end) + + eq(2, #result) + eq('Error here!', result[1][4].virt_lines[1][3][1]) + eq('Another error there!', result[2][4].virt_lines[1][3][1]) + end) + + it('can highlight diagnostics only in the current line', function() + local result = exec_lua(function() + vim.api.nvim_win_set_cursor(0, { 1, 0 }) + + vim.diagnostic.config({ virtual_lines = { current_line = true } }) + + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_error('Error here!', 0, 0, 0, 0, 'foo_server'), + _G.make_error('Another error there!', 1, 0, 1, 0, 'foo_server'), + }) + + local extmarks = _G.get_virt_lines_extmarks(_G.diagnostic_ns) + return extmarks + end) + + eq(1, #result) + eq('Error here!', result[1][4].virt_lines[1][3][1]) + end) + + it('supports a format function for diagnostic messages', function() + local result = exec_lua(function() + vim.diagnostic.config({ + virtual_lines = { + format = function() + return 'Error here!' + end, + }, + }) + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_error('Invalid syntax', 0, 0, 0, 0), + }) + local extmarks = _G.get_virt_lines_extmarks(_G.diagnostic_ns) + return extmarks[1][4].virt_lines + end) + eq('Error here!', result[1][3][1]) + end) end) describe('set()', function() @@ -2116,7 +2278,11 @@ describe('vim.diagnostic', function() end) it('can perform updates after insert_leave', function() - exec_lua [[vim.api.nvim_set_current_buf( _G.diagnostic_bufnr)]] + exec_lua(function() + vim.diagnostic.config({ virtual_text = true }) + vim.api.nvim_set_current_buf(_G.diagnostic_bufnr) + end) + api.nvim_input('o') eq({ mode = 'i', blocking = false }, api.nvim_get_mode()) @@ -2257,7 +2423,10 @@ describe('vim.diagnostic', function() end) it('can perform updates while in insert mode, if desired', function() - exec_lua [[vim.api.nvim_set_current_buf( _G.diagnostic_bufnr)]] + exec_lua(function() + vim.diagnostic.config({ virtual_text = true }) + vim.api.nvim_set_current_buf(_G.diagnostic_bufnr) + end) api.nvim_input('o') eq({ mode = 'i', blocking = false }, api.nvim_get_mode()) @@ -2291,6 +2460,10 @@ describe('vim.diagnostic', function() end) it('can set diagnostics without displaying them', function() + exec_lua(function() + vim.diagnostic.config({ virtual_text = true }) + end) + eq( 0, exec_lua(function() @@ -3212,6 +3385,74 @@ describe('vim.diagnostic', function() end) end) + describe('setqflist()', function() + it('updates existing diagnostics quickfix if one already exists', function() + local result = exec_lua(function() + vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr) + + vim.fn.setqflist({}, ' ', { title = 'Diagnostics' }) + local diagnostics_qf_id = vim.fn.getqflist({ id = 0 }).id + + vim.diagnostic.setqflist({ title = 'Diagnostics' }) + local qf_id = vim.fn.getqflist({ id = 0, nr = '$' }).id + + return { diagnostics_qf_id, qf_id } + end) + + eq(result[1], result[2]) + end) + + it('navigates to existing diagnostics quickfix if one already exists and open=true', function() + local result = exec_lua(function() + vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr) + + vim.fn.setqflist({}, ' ', { title = 'Diagnostics' }) + local diagnostics_qf_id = vim.fn.getqflist({ id = 0 }).id + + vim.fn.setqflist({}, ' ', { title = 'Other' }) + + vim.diagnostic.setqflist({ title = 'Diagnostics', open = true }) + local qf_id = vim.fn.getqflist({ id = 0 }).id + + return { diagnostics_qf_id, qf_id } + end) + + eq(result[1], result[2]) + end) + + it('sets new diagnostics quickfix as active when open=true', function() + local result = exec_lua(function() + vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr) + + vim.fn.setqflist({}, ' ', { title = 'Other' }) + local other_qf_id = vim.fn.getqflist({ id = 0 }).id + + vim.diagnostic.setqflist({ title = 'Diagnostics', open = true }) + local qf_id = vim.fn.getqflist({ id = 0 }).id + + return { other_qf_id, qf_id } + end) + + neq(result[1], result[2]) + end) + + it('opens quickfix window when open=true', function() + local qf_winid = exec_lua(function() + vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr) + + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_error('Error', 1, 1, 1, 1), + }) + + vim.diagnostic.setqflist({ open = true }) + + return vim.fn.getqflist({ winid = 0 }).winid + end) + + neq(0, qf_winid) + end) + end) + describe('match()', function() it('matches a string', function() local msg = 'ERROR: george.txt:19:84:Two plus two equals five' diff --git a/test/functional/lua/filetype_spec.lua b/test/functional/lua/filetype_spec.lua index b6011d5268..b75ff75b05 100644 --- a/test/functional/lua/filetype_spec.lua +++ b/test/functional/lua/filetype_spec.lua @@ -199,7 +199,19 @@ describe('filetype.lua', function() finally(function() uv.fs_unlink('Xfiletype/.config/git') end) - clear({ args = { '--clean', 'Xfiletype/.config/git/config' } }) + local args = { '--clean', 'Xfiletype/.config/git/config' } + clear({ args = args }) eq('gitconfig', api.nvim_get_option_value('filetype', {})) + table.insert(args, 2, '--cmd') + table.insert(args, 3, "autocmd BufRead * call expand('<afile>')") + clear({ args = args }) + eq('gitconfig', api.nvim_get_option_value('filetype', {})) + end) + + it('works with :doautocmd BufRead #31306', function() + clear({ args = { '--clean' } }) + eq('', api.nvim_get_option_value('filetype', {})) + command('doautocmd BufRead README.md') + eq('markdown', api.nvim_get_option_value('filetype', {})) end) end) diff --git a/test/functional/lua/fs_spec.lua b/test/functional/lua/fs_spec.lua index f0d49205e7..33af25f629 100644 --- a/test/functional/lua/fs_spec.lua +++ b/test/functional/lua/fs_spec.lua @@ -17,6 +17,8 @@ local mkdir = t.mkdir local nvim_prog_basename = is_os('win') and 'nvim.exe' or 'nvim' +local link_limit = is_os('win') and 64 or (is_os('mac') or is_os('bsd')) and 33 or 41 + local test_basename_dirname_eq = { '~/foo/', '~/foo', @@ -152,7 +154,7 @@ describe('vim.fs', function() ) end) - it('works with opts.depth and opts.skip', function() + it('works with opts.depth, opts.skip and opts.follow', function() io.open('testd/a1', 'w'):close() io.open('testd/b1', 'w'):close() io.open('testd/c1', 'w'):close() @@ -166,10 +168,10 @@ describe('vim.fs', function() io.open('testd/a/b/c/b4', 'w'):close() io.open('testd/a/b/c/c4', 'w'):close() - local function run(dir, depth, skip) - return exec_lua(function() - local r = {} - local skip_f + local function run(dir, depth, skip, follow) + return exec_lua(function(follow_) + local r = {} --- @type table<string, string> + local skip_f --- @type function if skip then skip_f = function(n0) if vim.tbl_contains(skip or {}, n0) then @@ -177,11 +179,11 @@ describe('vim.fs', function() end end end - for name, type_ in vim.fs.dir(dir, { depth = depth, skip = skip_f }) do + for name, type_ in vim.fs.dir(dir, { depth = depth, skip = skip_f, follow = follow_ }) do r[name] = type_ end return r - end) + end, follow) end local exp = {} @@ -197,6 +199,7 @@ describe('vim.fs', function() exp['a/b2'] = 'file' exp['a/c2'] = 'file' exp['a/b'] = 'directory' + local lexp = vim.deepcopy(exp) eq(exp, run('testd', 2)) @@ -213,6 +216,29 @@ describe('vim.fs', function() exp['a/b/c/c4'] = 'file' eq(exp, run('testd', 999)) + + vim.uv.fs_symlink(vim.uv.fs_realpath('testd/a'), 'testd/l', { junction = true, dir = true }) + lexp['l'] = 'link' + eq(lexp, run('testd', 2, nil, false)) + + lexp['l/a2'] = 'file' + lexp['l/b2'] = 'file' + lexp['l/c2'] = 'file' + lexp['l/b'] = 'directory' + eq(lexp, run('testd', 2, nil, true)) + end) + + it('follow=true handles symlink loop', function() + local cwd = 'testd/a/b/c' + local symlink = cwd .. '/link_loop' ---@type string + vim.uv.fs_symlink(vim.uv.fs_realpath(cwd), symlink, { junction = true, dir = true }) + + eq( + link_limit, + exec_lua(function() + return #vim.iter(vim.fs.dir(cwd, { depth = math.huge, follow = true })):totable() + end) + ) end) end) @@ -228,6 +254,53 @@ describe('vim.fs', function() eq({ nvim_dir }, vim.fs.find(name, { path = parent, upward = true, type = 'directory' })) end) + it('follows symlinks', function() + local build_dir = test_source_path .. '/build' ---@type string + local symlink = test_source_path .. '/build_link' ---@type string + vim.uv.fs_symlink(build_dir, symlink, { junction = true, dir = true }) + + finally(function() + vim.uv.fs_unlink(symlink) + end) + + eq( + { nvim_prog, symlink .. '/bin/' .. nvim_prog_basename }, + vim.fs.find(nvim_prog_basename, { + path = test_source_path, + type = 'file', + limit = 2, + follow = true, + }) + ) + + eq( + { nvim_prog }, + vim.fs.find(nvim_prog_basename, { + path = test_source_path, + type = 'file', + limit = 2, + follow = false, + }) + ) + end) + + it('follow=true handles symlink loop', function() + local cwd = test_source_path ---@type string + local symlink = test_source_path .. '/loop_link' ---@type string + vim.uv.fs_symlink(cwd, symlink, { junction = true, dir = true }) + + finally(function() + vim.uv.fs_unlink(symlink) + end) + + eq(link_limit, #vim.fs.find(nvim_prog_basename, { + path = test_source_path, + type = 'file', + limit = math.huge, + follow = true, + })) + end) + it('accepts predicate as names', function() local opts = { path = nvim_dir, upward = true, type = 'directory' } eq( @@ -273,14 +346,14 @@ describe('vim.fs', function() end) it('works with a single marker', function() - eq(test_source_path, exec_lua([[return vim.fs.root(0, '.git')]])) + eq(test_source_path, exec_lua([[return vim.fs.root(0, 'CMakePresets.json')]])) end) it('works with multiple markers', function() local bufnr = api.nvim_get_current_buf() eq( vim.fs.joinpath(test_source_path, 'test/functional/fixtures'), - exec_lua([[return vim.fs.root(..., {'CMakeLists.txt', '.git'})]], bufnr) + exec_lua([[return vim.fs.root(..., {'CMakeLists.txt', 'CMakePresets.json'})]], bufnr) ) end) @@ -295,26 +368,26 @@ describe('vim.fs', function() end) it('works with a filename argument', function() - eq(test_source_path, exec_lua([[return vim.fs.root(..., '.git')]], nvim_prog)) + eq(test_source_path, exec_lua([[return vim.fs.root(..., 'CMakePresets.json')]], nvim_prog)) end) it('works with a relative path', function() eq( test_source_path, - exec_lua([[return vim.fs.root(..., '.git')]], vim.fs.basename(nvim_prog)) + exec_lua([[return vim.fs.root(..., 'CMakePresets.json')]], vim.fs.basename(nvim_prog)) ) end) it('uses cwd for unnamed buffers', function() command('new') - eq(test_source_path, exec_lua([[return vim.fs.root(0, '.git')]])) + eq(test_source_path, exec_lua([[return vim.fs.root(0, 'CMakePresets.json')]])) end) it("uses cwd for buffers with non-empty 'buftype'", function() command('new') command('set buftype=nofile') command('file lua://') - eq(test_source_path, exec_lua([[return vim.fs.root(0, '.git')]])) + eq(test_source_path, exec_lua([[return vim.fs.root(0, 'CMakePresets.json')]])) end) end) @@ -323,6 +396,20 @@ describe('vim.fs', function() eq('foo/bar/baz', vim.fs.joinpath('foo', 'bar', 'baz')) eq('foo/bar/baz', vim.fs.joinpath('foo', '/bar/', '/baz')) end) + it('rewrites backslashes on Windows', function() + if is_os('win') then + eq('foo/bar/baz/zub/', vim.fs.joinpath([[foo]], [[\\bar\\\\baz]], [[zub\]])) + else + eq([[foo/\\bar\\\\baz/zub\]], vim.fs.joinpath([[foo]], [[\\bar\\\\baz]], [[zub\]])) + end + end) + it('strips redundant slashes', function() + if is_os('win') then + eq('foo/bar/baz/zub/', vim.fs.joinpath([[foo//]], [[\\bar\\\\baz]], [[zub\]])) + else + eq('foo/bar/baz/zub/', vim.fs.joinpath([[foo]], [[//bar////baz]], [[zub/]])) + end + end) end) describe('normalize()', function() @@ -347,8 +434,8 @@ describe('vim.fs', function() end) -- Opts required for testing posix paths and win paths - local posix_opts = is_os('win') and { win = false } or {} - local win_opts = is_os('win') and {} or { win = true } + local posix_opts = { win = false } + local win_opts = { win = true } it('preserves leading double slashes in POSIX paths', function() eq('//foo', vim.fs.normalize('//foo', posix_opts)) @@ -359,6 +446,29 @@ describe('vim.fs', function() eq('/foo/bar', vim.fs.normalize('/foo//bar////', posix_opts)) end) + it('normalizes drive letter', function() + eq('C:/', vim.fs.normalize('C:/', win_opts)) + eq('C:/', vim.fs.normalize('c:/', win_opts)) + eq('D:/', vim.fs.normalize('d:/', win_opts)) + eq('C:', vim.fs.normalize('C:', win_opts)) + eq('C:', vim.fs.normalize('c:', win_opts)) + eq('D:', vim.fs.normalize('d:', win_opts)) + eq('C:/foo/test', vim.fs.normalize('C:/foo/test/', win_opts)) + eq('C:/foo/test', vim.fs.normalize('c:/foo/test/', win_opts)) + eq('D:foo/test', vim.fs.normalize('D:foo/test/', win_opts)) + eq('D:foo/test', vim.fs.normalize('d:foo/test/', win_opts)) + end) + + it('always treats paths as case-sensitive #31833', function() + eq('TEST', vim.fs.normalize('TEST', win_opts)) + eq('test', vim.fs.normalize('test', win_opts)) + eq('C:/FOO/test', vim.fs.normalize('C:/FOO/test', win_opts)) + eq('C:/foo/test', vim.fs.normalize('C:/foo/test', win_opts)) + eq('//SERVER/SHARE/FOO/BAR', vim.fs.normalize('//SERVER/SHARE/FOO/BAR', win_opts)) + eq('//server/share/foo/bar', vim.fs.normalize('//server/share/foo/bar', win_opts)) + eq('C:/FOO/test', vim.fs.normalize('c:/FOO/test', win_opts)) + end) + it('allows backslashes on unix-based os', function() eq('/home/user/hello\\world', vim.fs.normalize('/home/user/hello\\world', posix_opts)) end) @@ -454,4 +564,92 @@ describe('vim.fs', function() end) end) end) + + describe('abspath()', function() + local cwd = assert(t.fix_slashes(assert(vim.uv.cwd()))) + local home = t.fix_slashes(assert(vim.uv.os_homedir())) + + it('works', function() + eq(cwd .. '/foo', vim.fs.abspath('foo')) + eq(cwd .. '/././foo', vim.fs.abspath('././foo')) + eq(cwd .. '/.././../foo', vim.fs.abspath('.././../foo')) + end) + + it('works with absolute paths', function() + if is_os('win') then + eq([[C:/foo]], vim.fs.abspath([[C:\foo]])) + eq([[C:/foo/../.]], vim.fs.abspath([[C:\foo\..\.]])) + eq('//foo/bar', vim.fs.abspath('\\\\foo\\bar')) + else + eq('/foo/../.', vim.fs.abspath('/foo/../.')) + eq('/foo/bar', vim.fs.abspath('/foo/bar')) + end + end) + + it('expands ~', function() + eq(home .. '/foo', vim.fs.abspath('~/foo')) + eq(home .. '/./.././foo', vim.fs.abspath('~/./.././foo')) + end) + + if is_os('win') then + it('works with drive-specific cwd on Windows', function() + local cwd_drive = cwd:match('^%w:') + + eq(cwd .. '/foo', vim.fs.abspath(cwd_drive .. 'foo')) + end) + end + end) + + describe('relpath()', function() + it('works', function() + local cwd = assert(t.fix_slashes(assert(vim.uv.cwd()))) + local my_dir = vim.fs.joinpath(cwd, 'foo') + + eq(nil, vim.fs.relpath('/var/lib', '/var')) + eq(nil, vim.fs.relpath('/var/lib', '/bin')) + eq(nil, vim.fs.relpath(my_dir, 'bin')) + eq(nil, vim.fs.relpath(my_dir, './bin')) + eq(nil, vim.fs.relpath(my_dir, '././')) + eq(nil, vim.fs.relpath(my_dir, '../')) + eq(nil, vim.fs.relpath('/var/lib', '/')) + eq(nil, vim.fs.relpath('/var/lib', '//')) + eq(nil, vim.fs.relpath(' ', '/var')) + eq(nil, vim.fs.relpath(' ', '/var')) + eq('.', vim.fs.relpath('/var/lib', '/var/lib')) + eq('lib', vim.fs.relpath('/var/', '/var/lib')) + eq('var/lib', vim.fs.relpath('/', '/var/lib')) + eq('bar/package.json', vim.fs.relpath('/foo/test', '/foo/test/bar/package.json')) + eq('foo/bar', vim.fs.relpath(cwd, 'foo/bar')) + eq('foo/bar', vim.fs.relpath('.', vim.fs.joinpath(cwd, 'foo/bar'))) + eq('bar', vim.fs.relpath('foo', 'foo/bar')) + eq(nil, vim.fs.relpath('/var/lib', '/var/library/foo')) + + if is_os('win') then + eq(nil, vim.fs.relpath('/', ' ')) + eq(nil, vim.fs.relpath('/', 'var')) + else + local cwd_rel_root = cwd:sub(2) + eq(cwd_rel_root .. '/ ', vim.fs.relpath('/', ' ')) + eq(cwd_rel_root .. '/var', vim.fs.relpath('/', 'var')) + end + + if is_os('win') then + eq(nil, vim.fs.relpath('c:/aaaa/', '/aaaa/cccc')) + eq(nil, vim.fs.relpath('c:/aaaa/', './aaaa/cccc')) + eq(nil, vim.fs.relpath('c:/aaaa/', 'aaaa/cccc')) + eq(nil, vim.fs.relpath('c:/blah\\blah', 'd:/games')) + eq(nil, vim.fs.relpath('c:/games', 'd:/games')) + eq(nil, vim.fs.relpath('c:/games', 'd:/games/foo')) + eq(nil, vim.fs.relpath('c:/aaaa/bbbb', 'c:/aaaa')) + eq('cccc', vim.fs.relpath('c:/aaaa/', 'c:/aaaa/cccc')) + eq('aaaa/bbbb', vim.fs.relpath('C:/', 'c:\\aaaa\\bbbb')) + eq('bar/package.json', vim.fs.relpath('C:\\foo\\test', 'C:\\foo\\test\\bar\\package.json')) + eq('baz', vim.fs.relpath('\\\\foo\\bar', '\\\\foo\\bar\\baz')) + eq(nil, vim.fs.relpath('a/b/c', 'a\\b')) + eq('d', vim.fs.relpath('a/b/c', 'a\\b\\c\\d')) + eq('.', vim.fs.relpath('\\\\foo\\bar\\baz', '\\\\foo\\bar\\baz')) + eq(nil, vim.fs.relpath('C:\\foo\\test', 'C:\\foo\\Test\\bar\\package.json')) + end + end) + end) end) diff --git a/test/functional/lua/func_memoize_spec.lua b/test/functional/lua/func_memoize_spec.lua new file mode 100644 index 0000000000..ca518ab88d --- /dev/null +++ b/test/functional/lua/func_memoize_spec.lua @@ -0,0 +1,142 @@ +local t = require('test.testutil') +local n = require('test.functional.testnvim')() +local clear = n.clear +local exec_lua = n.exec_lua +local eq = t.eq + +describe('vim.func._memoize', function() + before_each(clear) + + it('caches function results based on their parameters', function() + exec_lua([[ + _G.count = 0 + + local adder = vim.func._memoize('concat', function(arg1, arg2) + _G.count = _G.count + 1 + return arg1 + arg2 + end) + + collectgarbage('stop') + adder(3, -4) + adder(3, -4) + adder(3, -4) + adder(3, -4) + adder(3, -4) + collectgarbage('restart') + ]]) + + eq(1, exec_lua([[return _G.count]])) + end) + + it('caches function results using a weak table by default', function() + exec_lua([[ + _G.count = 0 + + local adder = vim.func._memoize('concat-2', function(arg1, arg2) + _G.count = _G.count + 1 + return arg1 + arg2 + end) + + adder(3, -4) + collectgarbage() + adder(3, -4) + collectgarbage() + adder(3, -4) + ]]) + + eq(3, exec_lua([[return _G.count]])) + end) + + it('can cache using a strong table', function() + exec_lua([[ + _G.count = 0 + + local adder = vim.func._memoize('concat-2', function(arg1, arg2) + _G.count = _G.count + 1 + return arg1 + arg2 + end, false) + + adder(3, -4) + collectgarbage() + adder(3, -4) + collectgarbage() + adder(3, -4) + ]]) + + eq(1, exec_lua([[return _G.count]])) + end) + + it('can clear a single cache entry', function() + exec_lua([[ + _G.count = 0 + + local adder = vim.func._memoize(function(arg1, arg2) + return tostring(arg1) .. '%%' .. tostring(arg2) + end, function(arg1, arg2) + _G.count = _G.count + 1 + return arg1 + arg2 + end) + + collectgarbage('stop') + adder(3, -4) + adder(3, -4) + adder(3, -4) + adder(3, -4) + adder(3, -4) + adder:clear(3, -4) + adder(3, -4) + collectgarbage('restart') + ]]) + + eq(2, exec_lua([[return _G.count]])) + end) + + it('can clear the entire cache', function() + exec_lua([[ + _G.count = 0 + + local adder = vim.func._memoize(function(arg1, arg2) + return tostring(arg1) .. '%%' .. tostring(arg2) + end, function(arg1, arg2) + _G.count = _G.count + 1 + return arg1 + arg2 + end) + + collectgarbage('stop') + adder(1, 2) + adder(3, -4) + adder(1, 2) + adder(3, -4) + adder(1, 2) + adder(3, -4) + adder:clear() + adder(1, 2) + adder(3, -4) + collectgarbage('restart') + ]]) + + eq(4, exec_lua([[return _G.count]])) + end) + + it('can cache functions that return nil', function() + exec_lua([[ + _G.count = 0 + + local adder = vim.func._memoize('concat', function(arg1, arg2) + _G.count = _G.count + 1 + return nil + end) + + collectgarbage('stop') + adder(1, 2) + adder(1, 2) + adder(1, 2) + adder(1, 2) + adder:clear() + adder(1, 2) + collectgarbage('restart') + ]]) + + eq(2, exec_lua([[return _G.count]])) + end) +end) diff --git a/test/functional/lua/hl_spec.lua b/test/functional/lua/hl_spec.lua index 89881973bf..512f6be48f 100644 --- a/test/functional/lua/hl_spec.lua +++ b/test/functional/lua/hl_spec.lua @@ -104,6 +104,33 @@ describe('vim.hl.range', function() | ]]) end) + + it('removes highlight after given `timeout`', function() + local timeout = 100 + exec_lua(function() + local ns = vim.api.nvim_create_namespace('') + vim.hl.range(0, ns, 'Search', { 0, 0 }, { 4, 0 }, { timeout = timeout }) + end) + screen:expect({ + grid = [[ + {10:^asdfghjkl}{100:$} | + {10:«口=口»}{100:$} | + {10:qwertyuiop}{100:$} | + {10:口口=口口}{1:$} | + zxcvbnm{1:$} | + | + ]], + timeout = timeout, + }) + screen:expect([[ + ^asdfghjkl{1:$} | + «口=口»{1:$} | + qwertyuiop{1:$} | + 口口=口口{1:$} | + zxcvbnm{1:$} | + | + ]]) + end) end) describe('vim.hl.on_yank', function() @@ -144,7 +171,7 @@ describe('vim.hl.on_yank', function() vim.api.nvim_buf_set_mark(0, ']', 1, 1, {}) vim.hl.on_yank({ timeout = math.huge, on_macro = true, event = { operator = 'y' } }) end) - local ns = api.nvim_create_namespace('hlyank') + local ns = api.nvim_create_namespace('nvim.hlyank') local win = api.nvim_get_current_win() eq({ win }, api.nvim__ns_get(ns).wins) command('wincmd w') @@ -158,7 +185,7 @@ describe('vim.hl.on_yank', function() vim.api.nvim_buf_set_mark(0, ']', 1, 1, {}) vim.hl.on_yank({ timeout = math.huge, on_macro = true, event = { operator = 'y' } }) end) - local ns = api.nvim_create_namespace('hlyank') + local ns = api.nvim_create_namespace('nvim.hlyank') eq(api.nvim_get_current_win(), api.nvim__ns_get(ns).wins[1]) command('wincmd w') exec_lua(function() diff --git a/test/functional/lua/json_spec.lua b/test/functional/lua/json_spec.lua index a6e814d739..e4a1df1d4c 100644 --- a/test/functional/lua/json_spec.lua +++ b/test/functional/lua/json_spec.lua @@ -152,6 +152,45 @@ describe('vim.json.encode()', function() clear() end) + it('escape_slash', function() + -- With slash + eq('"Test\\/"', exec_lua([[return vim.json.encode('Test/', { escape_slash = true })]])) + eq( + 'Test/', + exec_lua([[return vim.json.decode(vim.json.encode('Test/', { escape_slash = true }))]]) + ) + + -- Without slash + eq('"Test/"', exec_lua([[return vim.json.encode('Test/')]])) + eq('"Test/"', exec_lua([[return vim.json.encode('Test/', {})]])) + eq('"Test/"', exec_lua([[return vim.json.encode('Test/', { _invalid = true })]])) + eq('"Test/"', exec_lua([[return vim.json.encode('Test/', { escape_slash = false })]])) + eq( + '"Test/"', + exec_lua([[return vim.json.encode('Test/', { _invalid = true, escape_slash = false })]]) + ) + eq( + 'Test/', + exec_lua([[return vim.json.decode(vim.json.encode('Test/', { escape_slash = false }))]]) + ) + + -- Checks for for global side-effects + eq( + '"Test/"', + exec_lua([[ + vim.json.encode('Test/', { escape_slash = true }) + return vim.json.encode('Test/') + ]]) + ) + eq( + '"Test\\/"', + exec_lua([[ + vim.json.encode('Test/', { escape_slash = false }) + return vim.json.encode('Test/', { escape_slash = true }) + ]]) + ) + end) + it('dumps strings', function() eq('"Test"', exec_lua([[return vim.json.encode('Test')]])) eq('""', exec_lua([[return vim.json.encode('')]])) diff --git a/test/functional/lua/loader_spec.lua b/test/functional/lua/loader_spec.lua index 8508f2aa14..20d3a940b2 100644 --- a/test/functional/lua/loader_spec.lua +++ b/test/functional/lua/loader_spec.lua @@ -10,7 +10,17 @@ local eq = t.eq describe('vim.loader', function() before_each(clear) - it('can work in compatibility with --luamod-dev #27413', function() + it('can be disabled', function() + exec_lua(function() + local orig_loader = _G.loadfile + vim.loader.enable() + assert(orig_loader ~= _G.loadfile) + vim.loader.enable(false) + assert(orig_loader == _G.loadfile) + end) + end) + + it('works with --luamod-dev #27413', function() clear({ args = { '--luamod-dev' } }) exec_lua(function() vim.loader.enable() @@ -31,7 +41,7 @@ describe('vim.loader', function() end) end) - it('handles changing files (#23027)', function() + it('handles changing files #23027', function() exec_lua(function() vim.loader.enable() end) @@ -63,7 +73,7 @@ describe('vim.loader', function() ) end) - it('handles % signs in modpath (#24491)', function() + it('handles % signs in modpath #24491', function() exec_lua [[ vim.loader.enable() ]] @@ -82,7 +92,7 @@ describe('vim.loader', function() eq(2, exec_lua('return loadfile(...)()', tmp2)) end) - it('correct indent on error message (#29809)', function() + it('indents error message #29809', function() local errmsg = exec_lua [[ vim.loader.enable() local _, errmsg = pcall(require, 'non_existent_module') diff --git a/test/functional/lua/system_spec.lua b/test/functional/lua/system_spec.lua index afbada007d..6c320376c9 100644 --- a/test/functional/lua/system_spec.lua +++ b/test/functional/lua/system_spec.lua @@ -18,8 +18,7 @@ local function system_sync(cmd, opts) local res = obj:wait() -- Check the process is no longer running - local proc = vim.api.nvim_get_proc(obj.pid) - assert(not proc, 'process still exists') + assert(not vim.api.nvim_get_proc(obj.pid), 'process still exists') return res end) @@ -27,23 +26,23 @@ end local function system_async(cmd, opts) return exec_lua(function() - _G.done = false + local done = false + local res --- @type vim.SystemCompleted? local obj = vim.system(cmd, opts, function(obj) - _G.done = true - _G.ret = obj + done = true + res = obj end) local ok = vim.wait(10000, function() - return _G.done + return done end) assert(ok, 'process did not exit') -- Check the process is no longer running - local proc = vim.api.nvim_get_proc(obj.pid) - assert(not proc, 'process still exists') + assert(not vim.api.nvim_get_proc(obj.pid), 'process still exists') - return _G.ret + return res end) end @@ -114,10 +113,39 @@ describe('vim.system', function() end) if t.is_os('win') then - it('can resolve windows command extentions.', function() + it('can resolve windows command extensions', function() t.write_file('test.bat', 'echo hello world') system_sync({ 'chmod', '+x', 'test.bat' }) system_sync({ './test' }) end) end + + it('always captures all content of stdout/stderr #30846', function() + t.skip(n.fn.executable('git') == 0, 'missing "git" command') + t.skip(n.fn.isdirectory('.git') == 0, 'missing ".git" directory') + eq( + 0, + exec_lua(function() + local done = 0 + local fail = 0 + for _ = 1, 200 do + vim.system( + { 'git', 'show', ':0:test/functional/plugin/lsp_spec.lua' }, + { text = true }, + function(o) + if o.code ~= 0 or #o.stdout == 0 then + fail = fail + 1 + end + done = done + 1 + end + ) + end + + local ok = vim.wait(10000, function() + return done == 200 + end, 200) + return fail + (ok and 0 or 1) + end) + ) + end) end) diff --git a/test/functional/lua/text_spec.lua b/test/functional/lua/text_spec.lua index be471bfd62..dd08a6ec04 100644 --- a/test/functional/lua/text_spec.lua +++ b/test/functional/lua/text_spec.lua @@ -26,5 +26,21 @@ describe('vim.text', function() eq(output, vim.text.hexencode(input)) eq(input, vim.text.hexdecode(output)) end) + + it('errors on invalid input', function() + -- Odd number of hex characters + do + local res, err = vim.text.hexdecode('ABC') + eq(nil, res) + eq('string must have an even number of hex characters', err) + end + + -- Non-hexadecimal input + do + local res, err = vim.text.hexdecode('nothex') + eq(nil, res) + eq('string must contain only hex characters', err) + end + end) end) end) diff --git a/test/functional/lua/thread_spec.lua b/test/functional/lua/thread_spec.lua index 310705fd97..8ca4bdc4f5 100644 --- a/test/functional/lua/thread_spec.lua +++ b/test/functional/lua/thread_spec.lua @@ -19,6 +19,26 @@ describe('thread', function() screen = Screen.new(50, 10) end) + it('handle non-string error', function() + exec_lua [[ + local thread = vim.uv.new_thread(function() + error() + end) + vim.uv.thread_join(thread) + ]] + + screen:expect([[ + | + {1:~ }|*5 + {3: }| + {9:Error in luv thread:} | + {9:[NULL]} | + {6:Press ENTER or type command to continue}^ | + ]]) + feed('<cr>') + assert_alive() + end) + it('entry func is executed in protected mode', function() exec_lua [[ local thread = vim.uv.new_thread(function() diff --git a/test/functional/lua/ui_event_spec.lua b/test/functional/lua/ui_event_spec.lua index c8616e3e11..ddb10127e4 100644 --- a/test/functional/lua/ui_event_spec.lua +++ b/test/functional/lua/ui_event_spec.lua @@ -106,20 +106,15 @@ describe('vim.ui_attach', function() end) it('does not crash on exit', function() - fn.system({ - n.nvim_prog, - '-u', - 'NONE', - '-i', - 'NONE', + local p = n.spawn_wait( '--cmd', [[ lua ns = vim.api.nvim_create_namespace 'testspace' ]], '--cmd', [[ lua vim.ui_attach(ns, {ext_popupmenu=true}, function() end) ]], '--cmd', - 'quitall!', - }) - eq(0, n.eval('v:shell_error')) + 'quitall!' + ) + eq(0, p.status) end) it('can receive accurate message kinds even if they are history', function() @@ -173,18 +168,67 @@ describe('vim.ui_attach', function() vim.ui_attach(ns, { ext_messages = true }, function(ev) if ev == 'msg_show' then vim.schedule(function() vim.cmd.redraw() end) - else - vim.cmd.redraw() + elseif ev:find('cmdline') then + _G.cmdline = _G.cmdline + (ev == 'cmdline_show' and 1 or 0) + vim.api.nvim_buf_set_lines(0, 0, -1, false, { tostring(_G.cmdline) }) + vim.cmd('redraw') end - _G.cmdline = _G.cmdline + (ev == 'cmdline_show' and 1 or 0) end )]]) + screen:expect([[ + ^ | + {1:~ }|*4 + ]]) feed(':') - n.assert_alive() - eq(2, exec_lua('return _G.cmdline')) - n.assert_alive() + screen:expect({ + grid = [[ + ^1 | + {1:~ }|*4 + ]], + cmdline = { { + content = { { '' } }, + firstc = ':', + pos = 0, + } }, + }) feed('version<CR><CR>v<Esc>') - n.assert_alive() + screen:expect({ + grid = [[ + ^2 | + {1:~ }|*4 + ]], + cmdline = { { abort = false } }, + }) + feed([[:call confirm("Save changes?", "&Yes\n&No\n&Cancel")<CR>]]) + screen:expect({ + grid = [[ + ^4 | + {1:~ }|*4 + ]], + cmdline = { + { + content = { { '' } }, + hl_id = 10, + pos = 0, + prompt = '[Y]es, (N)o, (C)ancel: ', + }, + }, + messages = { + { + content = { { '\nSave changes?\n', 6, 10 } }, + history = false, + kind = 'confirm', + }, + }, + }) + feed('n') + screen:expect({ + grid = [[ + ^4 | + {1:~ }|*4 + ]], + cmdline = { { abort = false } }, + }) end) it("preserved 'incsearch/command' screen state after :redraw from ext_cmdline", function() @@ -261,30 +305,15 @@ describe('vim.ui_attach', function() lled in a fast event context | {1:~ }| ]], + cmdline = { { abort = false } }, messages = { { - content = { { 'E122: Function Foo already exists, add ! to replace it', 9, 7 } }, + content = { { 'E122: Function Foo already exists, add ! to replace it', 9, 6 } }, + history = true, kind = 'emsg', }, }, }) - -- No fast context for prompt message kinds - feed(':%s/Function/Replacement/c<cr>') - screen:expect({ - grid = [[ - ^E122: {10:Function} Foo already exists, add !| - to replace it | - replace with Replacement (y/n/a/q/l/^E/^| - Y)? | - {1:~ }| - ]], - messages = { - { - content = { { 'replace with Replacement (y/n/a/q/l/^E/^Y)?', 6, 19 } }, - kind = 'confirm_sub', - }, - }, - }) end) end) @@ -316,30 +345,36 @@ describe('vim.ui_attach', function() vim.api.nvim_buf_set_lines(0, -2, -1, false, { err[1] }) end) ]]) + local s1 = [[ + ^ | + {1:~ }|*4 + ]] + screen:expect(s1) + feed('QQQQQQ<CR>') screen:expect({ grid = [[ - ^ | - {1:~ }|*4 - ]], - }) - feed('ifoo') - screen:expect({ - grid = [[ - foo^ | - {1:~ }|*4 - ]], - showmode = { { '-- INSERT --', 5, 12 } }, - }) - feed('<esc>:1mes clear<cr>:mes<cr>') - screen:expect({ - grid = [[ - foo | - {3: }| - {9:Excessive errors in vim.ui_attach() call}| - {9:back from ns: 1.} | + {9:obal 'err' (a nil value)} | + {9:stack traceback:} | + {9: [string "<nvim>"]:2: in function}| + {9: <[string "<nvim>"]:1>} | {100:Press ENTER or type command to continue}^ | ]], + messages = { + { + content = { { 'Press ENTER or type command to continue', 100, 18 } }, + history = true, + kind = 'return_prompt', + }, + }, }) + feed(':1mes clear<CR>:mes<CR>') + screen:expect([[ + | + {3: }| + {9:Excessive errors in vim.ui_attach() call}| + {9:back from ns: 1.} | + {100:Press ENTER or type command to continue}^ | + ]]) feed('<cr>') -- Also when scheduled exec_lua([[ @@ -348,16 +383,17 @@ describe('vim.ui_attach', function() end) ]]) screen:expect({ - any = 'fo^o', + grid = s1, messages = { { content = { { 'Error executing vim.schedule lua callback: [string "<nvim>"]:2: attempt to index global \'err\' (a nil value)\nstack traceback:\n\t[string "<nvim>"]:2: in function <[string "<nvim>"]:2>', 9, - 7, + 6, }, }, + history = true, kind = 'lua_error', }, { @@ -365,26 +401,35 @@ describe('vim.ui_attach', function() { 'Error executing vim.schedule lua callback: [string "<nvim>"]:2: attempt to index global \'err\' (a nil value)\nstack traceback:\n\t[string "<nvim>"]:2: in function <[string "<nvim>"]:2>', 9, - 7, + 6, }, }, + history = true, kind = 'lua_error', }, { - content = { { 'Press ENTER or type command to continue', 100, 19 } }, + content = { { 'Press ENTER or type command to continue', 100, 18 } }, + history = false, kind = 'return_prompt', }, }, }) feed('<esc>:1mes clear<cr>:mes<cr>') - screen:expect({ - grid = [[ - foo | - {3: }| - {9:Excessive errors in vim.ui_attach() call}| - {9:back from ns: 2.} | - {100:Press ENTER or type command to continue}^ | - ]], - }) + screen:expect([[ + | + {3: }| + {9:Excessive errors in vim.ui_attach() call}| + {9:back from ns: 2.} | + {100:Press ENTER or type command to continue}^ | + ]]) + end) + + it('sourcing invalid file does not crash #32166', function() + exec_lua([[ + local ns = vim.api.nvim_create_namespace("") + vim.ui_attach(ns, { ext_messages = true }, function() end) + ]]) + feed((':luafile %s<CR>'):format(testlog)) + n.assert_alive() end) end) diff --git a/test/functional/lua/uri_spec.lua b/test/functional/lua/uri_spec.lua index 258b96bc43..d706f8b78b 100644 --- a/test/functional/lua/uri_spec.lua +++ b/test/functional/lua/uri_spec.lua @@ -252,4 +252,12 @@ describe('URI methods', function() end ) end) + + describe('encode to uri', function() + it('rfc2732 including brackets', function() + exec_lua("str = '[:]'") + exec_lua("rfc = 'rfc2732'") + eq('[%3a]', exec_lua('return vim.uri_encode(str, rfc)')) + end) + end) end) diff --git a/test/functional/lua/vim_spec.lua b/test/functional/lua/vim_spec.lua index 3cfbfe167a..55e5158596 100644 --- a/test/functional/lua/vim_spec.lua +++ b/test/functional/lua/vim_spec.lua @@ -3435,7 +3435,6 @@ stack traceback: end) it('can discard input', function() - clear() -- discard every other normal 'x' command exec_lua [[ n_key = 0 @@ -3461,7 +3460,6 @@ stack traceback: end) it('callback invalid return', function() - clear() -- second key produces an error which removes the callback exec_lua [[ n_call = 0 @@ -3955,6 +3953,17 @@ stack traceback: eq(win2, val) end) + it('failure modes', function() + matches( + 'nvim_exec2%(%): Vim:E492: Not an editor command: fooooo', + pcall_err(exec_lua, [[vim.api.nvim_win_call(0, function() vim.cmd 'fooooo' end)]]) + ) + eq( + 'Error executing lua: [string "<nvim>"]:0: fooooo', + pcall_err(exec_lua, [[vim.api.nvim_win_call(0, function() error('fooooo') end)]]) + ) + end) + it('does not cause ml_get errors with invalid visual selection', function() -- Add lines to the current buffer and make another window looking into an empty buffer. exec_lua [[ diff --git a/test/functional/lua/watch_spec.lua b/test/functional/lua/watch_spec.lua index ad16df8a7c..3b109b70d5 100644 --- a/test/functional/lua/watch_spec.lua +++ b/test/functional/lua/watch_spec.lua @@ -91,8 +91,7 @@ describe('vim._watch', function() skip(is_os('mac'), 'flaky test on mac') skip(is_os('bsd'), 'Stopped working on bsd after 3ca967387c49c754561c3b11a574797504d40f38') elseif watchfunc == 'watchdirs' and is_os('mac') then - -- Bump this (or fix the bug) if CI continues to fail in future versions of macos CI. - skip(is_ci() and vim.uv.os_uname().release == '24.0.0', 'weird failure for macOS arm 15 CI') + skip(true, 'weird failure since macOS 14 CI, see bbf208784ca279178ba0075b60d3e9c80f11da7a') else skip( is_os('bsd'), diff --git a/test/functional/lua/with_spec.lua b/test/functional/lua/with_spec.lua index 6127e83619..92e798e7f3 100644 --- a/test/functional/lua/with_spec.lua +++ b/test/functional/lua/with_spec.lua @@ -1621,4 +1621,21 @@ describe('vim._with', function() matches('Invalid buffer', get_error('{ buf = -1 }, function() end')) matches('Invalid window', get_error('{ win = -1 }, function() end')) end) + + it('no double-free when called from :filter browse oldfiles #31501', function() + exec_lua([=[ + vim.api.nvim_create_autocmd('BufEnter', { + callback = function() + vim._with({ lockmarks = true }, function() end) + end, + }) + vim.cmd([[ + let v:oldfiles = ['Xoldfile'] + call nvim_input('1<CR>') + noswapfile filter /Xoldfile/ browse oldfiles + ]]) + ]=]) + n.assert_alive() + eq('Xoldfile', fn.bufname('%')) + end) end) diff --git a/test/functional/options/defaults_spec.lua b/test/functional/options/defaults_spec.lua index e7f47ef4e9..a82279e775 100644 --- a/test/functional/options/defaults_spec.lua +++ b/test/functional/options/defaults_spec.lua @@ -14,7 +14,6 @@ local api = n.api local command = n.command local clear = n.clear local exc_exec = n.exc_exec -local exec_lua = n.exec_lua local eval = n.eval local eq = t.eq local ok = t.ok @@ -929,17 +928,12 @@ describe('stdpath()', function() assert_alive() -- Check for crash. #8393 -- Check that Nvim rejects invalid APPNAMEs - -- Call jobstart() and jobwait() in the same RPC request to reduce flakiness. local function test_appname(testAppname, expected_exitcode) - local lua_code = string.format( - [[ - local child = vim.fn.jobstart({ vim.v.progpath, '--clean', '--headless', '--listen', 'x', '+qall!' }, { env = { NVIM_APPNAME = %q } }) - return vim.fn.jobwait({ child }, %d)[1] - ]], - testAppname, - 3000 - ) - eq(expected_exitcode, exec_lua(lua_code)) + local p = n.spawn_wait({ + args = { '--listen', 'x', '+qall!' }, + env = { NVIM_APPNAME = testAppname }, + }) + eq(expected_exitcode, p.status) end -- Invalid appnames: test_appname('a/../b', 1) diff --git a/test/functional/options/winfixbuf_spec.lua b/test/functional/options/winfixbuf_spec.lua index 124f194b5a..a01650ea71 100644 --- a/test/functional/options/winfixbuf_spec.lua +++ b/test/functional/options/winfixbuf_spec.lua @@ -1,55 +1,51 @@ local n = require('test.functional.testnvim')() +local t = require('test.testutil') local clear = n.clear local exec_lua = n.exec_lua -describe("Nvim API calls with 'winfixbuf'", function() +describe("'winfixbuf'", function() before_each(function() clear() end) - it("Calling vim.api.nvim_win_set_buf with 'winfixbuf'", function() - local results = exec_lua([[ - local function _setup_two_buffers() - local buffer = vim.api.nvim_create_buf(true, true) - - vim.api.nvim_create_buf(true, true) -- Make another buffer - - local current_window = 0 - vim.api.nvim_set_option_value("winfixbuf", true, {win=current_window}) - - return buffer - end - - local other_buffer = _setup_two_buffers() - local current_window = 0 - local results, _ = pcall(vim.api.nvim_win_set_buf, current_window, other_buffer) - - return results + ---@return integer + local function setup_winfixbuf() + return exec_lua([[ + local buffer = vim.api.nvim_create_buf(true, true) + vim.api.nvim_create_buf(true, true) -- Make another buffer + vim.wo.winfixbuf = true + return buffer ]]) - - assert(results == false) + end + + it('nvim_win_set_buf on non-current buffer', function() + local other_buf = setup_winfixbuf() + t.eq( + "Vim:E1513: Cannot switch buffer. 'winfixbuf' is enabled", + t.pcall_err(n.api.nvim_win_set_buf, 0, other_buf) + ) end) - it("Calling vim.api.nvim_set_current_buf with 'winfixbuf'", function() - local results = exec_lua([[ - local function _setup_two_buffers() - local buffer = vim.api.nvim_create_buf(true, true) - - vim.api.nvim_create_buf(true, true) -- Make another buffer - - local current_window = 0 - vim.api.nvim_set_option_value("winfixbuf", true, {win=current_window}) - - return buffer - end - - local other_buffer = _setup_two_buffers() - local results, _ = pcall(vim.api.nvim_set_current_buf, other_buffer) + it('nvim_set_current_buf on non-current buffer', function() + local other_buf = setup_winfixbuf() + t.eq( + "Vim:E1513: Cannot switch buffer. 'winfixbuf' is enabled", + t.pcall_err(n.api.nvim_set_current_buf, other_buf) + ) + end) - return results - ]]) + it('nvim_win_set_buf on current buffer', function() + setup_winfixbuf() + local curbuf = n.api.nvim_get_current_buf() + n.api.nvim_win_set_buf(0, curbuf) + t.eq(curbuf, n.api.nvim_get_current_buf()) + end) - assert(results == false) + it('nvim_set_current_buf on current buffer', function() + setup_winfixbuf() + local curbuf = n.api.nvim_get_current_buf() + n.api.nvim_set_current_buf(curbuf) + t.eq(curbuf, n.api.nvim_get_current_buf()) end) end) diff --git a/test/functional/plugin/health_spec.lua b/test/functional/plugin/health_spec.lua index 753da64522..406b5c3c16 100644 --- a/test/functional/plugin/health_spec.lua +++ b/test/functional/plugin/health_spec.lua @@ -66,6 +66,18 @@ describe(':checkhealth', function() eq({}, getcompletion('', 'checkhealth')) assert_alive() end) + + it('vim.g.health', function() + clear() + command("let g:health = {'style':'float'}") + command('checkhealth lsp') + eq( + 'editor', + exec_lua([[ + return vim.api.nvim_win_get_config(0).relative + ]]) + ) + end) end) describe('vim.health', function() diff --git a/test/functional/plugin/lsp/completion_spec.lua b/test/functional/plugin/lsp/completion_spec.lua index 39b6ddc105..4e90c2fd1b 100644 --- a/test/functional/plugin/lsp/completion_spec.lua +++ b/test/functional/plugin/lsp/completion_spec.lua @@ -216,6 +216,43 @@ describe('vim.lsp.completion: item conversion', function() }) end) + it('uses filterText as word if label/newText would not match', function() + local items = { + { + filterText = '<module', + insertTextFormat = 2, + kind = 10, + label = 'module', + sortText = 'module', + textEdit = { + newText = '<module>$1</module>$0', + range = { + start = { + character = 0, + line = 0, + }, + ['end'] = { + character = 0, + line = 0, + }, + }, + }, + }, + } + assert_completion_matches('<mo', items, { + { + abbr = 'module', + word = '<module', + }, + }) + assert_completion_matches('', items, { + { + abbr = 'module', + word = 'module', + }, + }) + end) + it('fuzzy matches on label when filterText is missing', function() assert_completion_matches('fo', { { label = 'foo' }, @@ -731,9 +768,10 @@ describe('vim.lsp.completion: item conversion', function() ) end) +--- @param name string --- @param completion_result lsp.CompletionList --- @return integer -local function create_server(completion_result) +local function create_server(name, completion_result) return exec_lua(function() local server = _G._create_server({ capabilities = { @@ -751,7 +789,7 @@ local function create_server(completion_result) local bufnr = vim.api.nvim_get_current_buf() vim.api.nvim_win_set_buf(0, bufnr) return vim.lsp.start({ - name = 'dummy', + name = name, cmd = server.cmd, on_attach = function(client, bufnr0) vim.lsp.completion.enable(true, client.id, bufnr0, { @@ -800,7 +838,7 @@ describe('vim.lsp.completion: protocol', function() end it('fetches completions and shows them using complete on trigger', function() - create_server({ + create_server('dummy', { isIncomplete = false, items = { { @@ -892,7 +930,7 @@ describe('vim.lsp.completion: protocol', function() end) it('merges results from multiple clients', function() - create_server({ + create_server('dummy1', { isIncomplete = false, items = { { @@ -900,7 +938,7 @@ describe('vim.lsp.completion: protocol', function() }, }, }) - create_server({ + create_server('dummy2', { isIncomplete = false, items = { { @@ -933,7 +971,7 @@ describe('vim.lsp.completion: protocol', function() }, }, } - local client_id = create_server(completion_list) + local client_id = create_server('dummy', completion_list) exec_lua(function() _G.called = false @@ -970,7 +1008,7 @@ describe('vim.lsp.completion: protocol', function() end) it('enable(…,{convert=fn}) custom word/abbr format', function() - create_server({ + create_server('dummy', { isIncomplete = false, items = { { @@ -1012,7 +1050,7 @@ describe('vim.lsp.completion: integration', function() exec_lua(function() vim.o.completeopt = 'menuone,noselect' end) - create_server(completion_list) + create_server('dummy', completion_list) feed('i world<esc>0ih<c-x><c-o>') retry(nil, nil, function() eq( diff --git a/test/functional/plugin/lsp/diagnostic_spec.lua b/test/functional/plugin/lsp/diagnostic_spec.lua index 5afbe22793..4ecb056d01 100644 --- a/test/functional/plugin/lsp/diagnostic_spec.lua +++ b/test/functional/plugin/lsp/diagnostic_spec.lua @@ -89,7 +89,7 @@ describe('vim.lsp.diagnostic', function() return extmarks end - client_id = assert(vim.lsp.start_client { + client_id = assert(vim.lsp.start({ cmd_env = { NVIM_LUA_NOTRACK = '1', }, @@ -101,7 +101,7 @@ describe('vim.lsp.diagnostic', function() '--headless', }, offset_encoding = 'utf-16', - }) + }, { attach = false })) end) fake_uri = 'file:///fake/uri' @@ -209,10 +209,16 @@ describe('vim.lsp.diagnostic', function() before_each(function() exec_lua(create_server_definition) exec_lua(function() + _G.requests = 0 _G.server = _G._create_server({ capabilities = { diagnosticProvider = {}, }, + handlers = { + [vim.lsp.protocol.Methods.textDocument_diagnostic] = function() + _G.requests = _G.requests + 1 + end, + }, }) function _G.get_extmarks(bufnr, client_id0) @@ -373,5 +379,56 @@ describe('vim.lsp.diagnostic', function() end) ) end) + + it('handles server cancellation', function() + eq( + 1, + exec_lua(function() + vim.lsp.diagnostic.on_diagnostic({ + code = vim.lsp.protocol.ErrorCodes.ServerCancelled, + -- Empty data defaults to retriggering request + data = {}, + message = '', + }, {}, { + method = vim.lsp.protocol.Methods.textDocument_diagnostic, + client_id = client_id, + }) + + return _G.requests + end) + ) + + eq( + 2, + exec_lua(function() + vim.lsp.diagnostic.on_diagnostic({ + code = vim.lsp.protocol.ErrorCodes.ServerCancelled, + data = { retriggerRequest = true }, + message = '', + }, {}, { + method = vim.lsp.protocol.Methods.textDocument_diagnostic, + client_id = client_id, + }) + + return _G.requests + end) + ) + + eq( + 2, + exec_lua(function() + vim.lsp.diagnostic.on_diagnostic({ + code = vim.lsp.protocol.ErrorCodes.ServerCancelled, + data = { retriggerRequest = false }, + message = '', + }, {}, { + method = vim.lsp.protocol.Methods.textDocument_diagnostic, + client_id = client_id, + }) + + return _G.requests + end) + ) + end) end) end) diff --git a/test/functional/plugin/lsp/folding_range_spec.lua b/test/functional/plugin/lsp/folding_range_spec.lua new file mode 100644 index 0000000000..7e68a598d2 --- /dev/null +++ b/test/functional/plugin/lsp/folding_range_spec.lua @@ -0,0 +1,647 @@ +local t = require('test.testutil') +local n = require('test.functional.testnvim')() +local Screen = require('test.functional.ui.screen') +local t_lsp = require('test.functional.plugin.lsp.testutil') + +local eq = t.eq +local tempname = t.tmpname + +local clear_notrace = t_lsp.clear_notrace +local create_server_definition = t_lsp.create_server_definition + +local api = n.api +local exec_lua = n.exec_lua +local insert = n.insert +local command = n.command +local feed = n.feed + +describe('vim.lsp.folding_range', function() + local text = [[// foldLevel() {{{2 +/// @return fold level at line number "lnum" in the current window. +static int foldLevel(linenr_T lnum) +{ + // While updating the folds lines between invalid_top and invalid_bot have + // an undefined fold level. Otherwise update the folds first. + if (invalid_top == 0) { + checkupdate(curwin); + } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) { + return prev_lnum_lvl; + } else if (lnum >= invalid_top && lnum <= invalid_bot) { + return -1; + } + + // Return quickly when there is no folding at all in this window. + if (!hasAnyFolding(curwin)) { + return 0; + } + + return foldLevelWin(curwin, lnum); +}]] + + local result = { + { + endLine = 19, + kind = 'region', + startCharacter = 1, + startLine = 3, + }, + { + endCharacter = 2, + endLine = 7, + kind = 'region', + startCharacter = 25, + startLine = 6, + }, + { + endCharacter = 2, + endLine = 9, + kind = 'region', + startCharacter = 55, + startLine = 8, + }, + { + endCharacter = 2, + endLine = 11, + kind = 'region', + startCharacter = 58, + startLine = 10, + }, + { + endCharacter = 2, + endLine = 16, + kind = 'region', + startCharacter = 31, + startLine = 15, + }, + { + endCharacter = 68, + endLine = 1, + kind = 'comment', + startCharacter = 2, + startLine = 0, + }, + { + endCharacter = 64, + endLine = 5, + kind = 'comment', + startCharacter = 4, + startLine = 4, + }, + } + + local bufnr ---@type integer + local client_id ---@type integer + + clear_notrace() + before_each(function() + clear_notrace() + + exec_lua(create_server_definition) + bufnr = n.api.nvim_get_current_buf() + client_id = exec_lua(function() + _G.server = _G._create_server({ + capabilities = { + foldingRangeProvider = true, + }, + handlers = { + ['textDocument/foldingRange'] = function(_, _, callback) + callback(nil, result) + end, + }, + }) + + vim.api.nvim_win_set_buf(0, bufnr) + + return vim.lsp.start({ name = 'dummy', cmd = _G.server.cmd }) + end) + command('set foldmethod=expr foldcolumn=1 foldlevel=999') + insert(text) + end) + after_each(function() + api.nvim_exec_autocmds('VimLeavePre', { modeline = false }) + end) + + describe('setup()', function() + ---@type integer + local bufnr_set_expr + ---@type integer + local bufnr_never_set_expr + + local function buf_autocmd_num(bufnr_to_check) + return exec_lua(function() + return #vim.api.nvim_get_autocmds({ buffer = bufnr_to_check, event = 'LspNotify' }) + end) + end + + before_each(function() + command([[setlocal foldexpr=v:lua.vim.lsp.foldexpr()]]) + exec_lua(function() + bufnr_set_expr = vim.api.nvim_create_buf(true, false) + vim.api.nvim_set_current_buf(bufnr_set_expr) + end) + insert(text) + command('write ' .. tempname(false)) + command([[setlocal foldexpr=v:lua.vim.lsp.foldexpr()]]) + exec_lua(function() + bufnr_never_set_expr = vim.api.nvim_create_buf(true, false) + vim.api.nvim_set_current_buf(bufnr_never_set_expr) + end) + insert(text) + api.nvim_win_set_buf(0, bufnr_set_expr) + end) + + it('only create event hooks where foldexpr has been set', function() + eq(1, buf_autocmd_num(bufnr)) + eq(1, buf_autocmd_num(bufnr_set_expr)) + eq(0, buf_autocmd_num(bufnr_never_set_expr)) + end) + + it('does not create duplicate event hooks after reloaded', function() + command('edit') + eq(1, buf_autocmd_num(bufnr_set_expr)) + end) + + it('cleans up event hooks when buffer is unloaded', function() + command('bdelete') + eq(0, buf_autocmd_num(bufnr_set_expr)) + end) + end) + + describe('expr()', function() + --- @type test.functional.ui.screen + local screen + before_each(function() + screen = Screen.new(80, 45) + screen:set_default_attr_ids({ + [1] = { background = Screen.colors.Grey, foreground = Screen.colors.DarkBlue }, + [2] = { bold = true, foreground = Screen.colors.Blue1 }, + [3] = { bold = true, reverse = true }, + [4] = { reverse = true }, + }) + command([[set foldexpr=v:lua.vim.lsp.foldexpr()]]) + command([[split]]) + end) + + it('can compute fold levels', function() + ---@type table<integer, string> + local foldlevels = {} + for i = 1, 21 do + foldlevels[i] = exec_lua('return vim.lsp.foldexpr(' .. i .. ')') + end + eq({ + [1] = '>1', + [2] = '<1', + [3] = '0', + [4] = '>1', + [5] = '>2', + [6] = '<2', + [7] = '>2', + [8] = '<2', + [9] = '>2', + [10] = '<2', + [11] = '>2', + [12] = '<2', + [13] = '1', + [14] = '1', + [15] = '1', + [16] = '>2', + [17] = '<2', + [18] = '1', + [19] = '1', + [20] = '<1', + [21] = '0', + }, foldlevels) + end) + + it('updates folds in all windows', function() + screen:expect({ + grid = [[ +{1:-}// foldLevel() {{{2 | +{1:│}/// @return fold level at line number "lnum" in the current window. | +{1: }static int foldLevel(linenr_T lnum) | +{1:-}{ | +{1:-} // While updating the folds lines between invalid_top and invalid_bot have | +{1:2} // an undefined fold level. Otherwise update the folds first. | +{1:-} if (invalid_top == 0) { | +{1:2} checkupdate(curwin); | +{1:-} } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) { | +{1:2} return prev_lnum_lvl; | +{1:-} } else if (lnum >= invalid_top && lnum <= invalid_bot) { | +{1:2} return -1; | +{1:│} } | +{1:│} | +{1:│} // Return quickly when there is no folding at all in this window. | +{1:-} if (!hasAnyFolding(curwin)) { | +{1:2} return 0; | +{1:│} } | +{1:│} | +{1:│} return foldLevelWin(curwin, lnum); | +{1: }^} | +{3:[No Name] [+] }| +{1:-}// foldLevel() {{{2 | +{1:│}/// @return fold level at line number "lnum" in the current window. | +{1: }static int foldLevel(linenr_T lnum) | +{1:-}{ | +{1:-} // While updating the folds lines between invalid_top and invalid_bot have | +{1:2} // an undefined fold level. Otherwise update the folds first. | +{1:-} if (invalid_top == 0) { | +{1:2} checkupdate(curwin); | +{1:-} } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) { | +{1:2} return prev_lnum_lvl; | +{1:-} } else if (lnum >= invalid_top && lnum <= invalid_bot) { | +{1:2} return -1; | +{1:│} } | +{1:│} | +{1:│} // Return quickly when there is no folding at all in this window. | +{1:-} if (!hasAnyFolding(curwin)) { | +{1:2} return 0; | +{1:│} } | +{1:│} | +{1:│} return foldLevelWin(curwin, lnum); | +{1: }} | +{4:[No Name] [+] }| + | + ]], + }) + end) + + it('persists wherever foldexpr is set', function() + command([[setlocal foldexpr=]]) + feed('<C-w><C-w>zx') + screen:expect({ + grid = [[ +{1: }// foldLevel() {{{2 | +{1: }/// @return fold level at line number "lnum" in the current window. | +{1: }static int foldLevel(linenr_T lnum) | +{1: }{ | +{1: } // While updating the folds lines between invalid_top and invalid_bot have | +{1: } // an undefined fold level. Otherwise update the folds first. | +{1: } if (invalid_top == 0) { | +{1: } checkupdate(curwin); | +{1: } } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) { | +{1: } return prev_lnum_lvl; | +{1: } } else if (lnum >= invalid_top && lnum <= invalid_bot) { | +{1: } return -1; | +{1: } } | +{1: } | +{1: } // Return quickly when there is no folding at all in this window. | +{1: } if (!hasAnyFolding(curwin)) { | +{1: } return 0; | +{1: } } | +{1: } | +{1: } return foldLevelWin(curwin, lnum); | +{1: }} | +{4:[No Name] [+] }| +{1:-}// foldLevel() {{{2 | +{1:│}/// @return fold level at line number "lnum" in the current window. | +{1: }static int foldLevel(linenr_T lnum) | +{1:-}{ | +{1:-} // While updating the folds lines between invalid_top and invalid_bot have | +{1:2} // an undefined fold level. Otherwise update the folds first. | +{1:-} if (invalid_top == 0) { | +{1:2} checkupdate(curwin); | +{1:-} } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) { | +{1:2} return prev_lnum_lvl; | +{1:-} } else if (lnum >= invalid_top && lnum <= invalid_bot) { | +{1:2} return -1; | +{1:│} } | +{1:│} | +{1:│} // Return quickly when there is no folding at all in this window. | +{1:-} if (!hasAnyFolding(curwin)) { | +{1:2} return 0; | +{1:│} } | +{1:│} | +{1:│} return foldLevelWin(curwin, lnum); | +{1: }^} | +{3:[No Name] [+] }| + | + ]], + }) + end) + + it('synchronizes changed rows with their previous foldlevels', function() + command('1,2d') + screen:expect({ + grid = [[ +{1: }^static int foldLevel(linenr_T lnum) | +{1:-}{ | +{1:-} // While updating the folds lines between invalid_top and invalid_bot have | +{1:2} // an undefined fold level. Otherwise update the folds first. | +{1:-} if (invalid_top == 0) { | +{1:2} checkupdate(curwin); | +{1:-} } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) { | +{1:2} return prev_lnum_lvl; | +{1:-} } else if (lnum >= invalid_top && lnum <= invalid_bot) { | +{1:2} return -1; | +{1:│} } | +{1:│} | +{1:│} // Return quickly when there is no folding at all in this window. | +{1:-} if (!hasAnyFolding(curwin)) { | +{1:2} return 0; | +{1:│} } | +{1:│} | +{1:│} return foldLevelWin(curwin, lnum); | +{1: }} | +{2:~ }|*2 +{3:[No Name] [+] }| +{1: }static int foldLevel(linenr_T lnum) | +{1:-}{ | +{1:-} // While updating the folds lines between invalid_top and invalid_bot have | +{1:2} // an undefined fold level. Otherwise update the folds first. | +{1:-} if (invalid_top == 0) { | +{1:2} checkupdate(curwin); | +{1:-} } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) { | +{1:2} return prev_lnum_lvl; | +{1:-} } else if (lnum >= invalid_top && lnum <= invalid_bot) { | +{1:2} return -1; | +{1:│} } | +{1:│} | +{1:│} // Return quickly when there is no folding at all in this window. | +{1:-} if (!hasAnyFolding(curwin)) { | +{1:2} return 0; | +{1:│} } | +{1:│} | +{1:│} return foldLevelWin(curwin, lnum); | +{1: }} | +{2:~ }|*2 +{4:[No Name] [+] }| + | +]], + }) + end) + + it('clears folds when sole client detaches', function() + exec_lua(function() + vim.lsp.buf_detach_client(bufnr, client_id) + end) + screen:expect({ + grid = [[ +{1: }// foldLevel() {{{2 | +{1: }/// @return fold level at line number "lnum" in the current window. | +{1: }static int foldLevel(linenr_T lnum) | +{1: }{ | +{1: } // While updating the folds lines between invalid_top and invalid_bot have | +{1: } // an undefined fold level. Otherwise update the folds first. | +{1: } if (invalid_top == 0) { | +{1: } checkupdate(curwin); | +{1: } } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) { | +{1: } return prev_lnum_lvl; | +{1: } } else if (lnum >= invalid_top && lnum <= invalid_bot) { | +{1: } return -1; | +{1: } } | +{1: } | +{1: } // Return quickly when there is no folding at all in this window. | +{1: } if (!hasAnyFolding(curwin)) { | +{1: } return 0; | +{1: } } | +{1: } | +{1: } return foldLevelWin(curwin, lnum); | +{1: }^} | +{3:[No Name] [+] }| +{1: }// foldLevel() {{{2 | +{1: }/// @return fold level at line number "lnum" in the current window. | +{1: }static int foldLevel(linenr_T lnum) | +{1: }{ | +{1: } // While updating the folds lines between invalid_top and invalid_bot have | +{1: } // an undefined fold level. Otherwise update the folds first. | +{1: } if (invalid_top == 0) { | +{1: } checkupdate(curwin); | +{1: } } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) { | +{1: } return prev_lnum_lvl; | +{1: } } else if (lnum >= invalid_top && lnum <= invalid_bot) { | +{1: } return -1; | +{1: } } | +{1: } | +{1: } // Return quickly when there is no folding at all in this window. | +{1: } if (!hasAnyFolding(curwin)) { | +{1: } return 0; | +{1: } } | +{1: } | +{1: } return foldLevelWin(curwin, lnum); | +{1: }} | +{4:[No Name] [+] }| + | + ]], + }) + end) + + it('remains valid after the client re-attaches.', function() + exec_lua(function() + vim.lsp.buf_detach_client(bufnr, client_id) + vim.lsp.buf_attach_client(bufnr, client_id) + end) + screen:expect({ + grid = [[ +{1:-}// foldLevel() {{{2 | +{1:│}/// @return fold level at line number "lnum" in the current window. | +{1: }static int foldLevel(linenr_T lnum) | +{1:-}{ | +{1:-} // While updating the folds lines between invalid_top and invalid_bot have | +{1:2} // an undefined fold level. Otherwise update the folds first. | +{1:-} if (invalid_top == 0) { | +{1:2} checkupdate(curwin); | +{1:-} } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) { | +{1:2} return prev_lnum_lvl; | +{1:-} } else if (lnum >= invalid_top && lnum <= invalid_bot) { | +{1:2} return -1; | +{1:│} } | +{1:│} | +{1:│} // Return quickly when there is no folding at all in this window. | +{1:-} if (!hasAnyFolding(curwin)) { | +{1:2} return 0; | +{1:│} } | +{1:│} | +{1:│} return foldLevelWin(curwin, lnum); | +{1: }^} | +{3:[No Name] [+] }| +{1:-}// foldLevel() {{{2 | +{1:│}/// @return fold level at line number "lnum" in the current window. | +{1: }static int foldLevel(linenr_T lnum) | +{1:-}{ | +{1:-} // While updating the folds lines between invalid_top and invalid_bot have | +{1:2} // an undefined fold level. Otherwise update the folds first. | +{1:-} if (invalid_top == 0) { | +{1:2} checkupdate(curwin); | +{1:-} } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) { | +{1:2} return prev_lnum_lvl; | +{1:-} } else if (lnum >= invalid_top && lnum <= invalid_bot) { | +{1:2} return -1; | +{1:│} } | +{1:│} | +{1:│} // Return quickly when there is no folding at all in this window. | +{1:-} if (!hasAnyFolding(curwin)) { | +{1:2} return 0; | +{1:│} } | +{1:│} | +{1:│} return foldLevelWin(curwin, lnum); | +{1: }} | +{4:[No Name] [+] }| + | + ]], + }) + end) + end) + + describe('foldtext()', function() + --- @type test.functional.ui.screen + local screen + before_each(function() + screen = Screen.new(80, 23) + screen:set_default_attr_ids({ + [1] = { background = Screen.colors.Grey, foreground = Screen.colors.DarkBlue }, + [2] = { foreground = Screen.colors.DarkBlue, background = Screen.colors.LightGrey }, + [3] = { bold = true, foreground = Screen.colors.Blue1 }, + [4] = { bold = true, reverse = true }, + [5] = { reverse = true }, + }) + command( + [[set foldexpr=v:lua.vim.lsp.foldexpr() foldtext=v:lua.vim.lsp.foldtext() foldlevel=1]] + ) + end) + + it('shows the first folded line if `collapsedText` does not exist', function() + screen:expect({ + grid = [[ +{1:-}// foldLevel() {{{2 | +{1:│}/// @return fold level at line number "lnum" in the current window. | +{1: }static int foldLevel(linenr_T lnum) | +{1:-}{ | +{1:+}{2: // While updating the folds lines between invalid_top and invalid_bot have···}| +{1:+}{2: if (invalid_top == 0) {······················································}| +{1:+}{2: } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) {························}| +{1:+}{2: } else if (lnum >= invalid_top && lnum <= invalid_bot) {·····················}| +{1:│} } | +{1:│} | +{1:│} // Return quickly when there is no folding at all in this window. | +{1:+}{2: if (!hasAnyFolding(curwin)) {················································}| +{1:│} } | +{1:│} | +{1:│} return foldLevelWin(curwin, lnum); | +{1: }^} | +{3:~ }|*6 + | + ]], + }) + end) + end) + + describe('foldclose()', function() + --- @type test.functional.ui.screen + local screen + before_each(function() + screen = Screen.new(80, 23) + screen:set_default_attr_ids({ + [1] = { background = Screen.colors.Grey, foreground = Screen.colors.DarkBlue }, + [2] = { foreground = Screen.colors.DarkBlue, background = Screen.colors.LightGrey }, + [3] = { bold = true, foreground = Screen.colors.Blue1 }, + [4] = { bold = true, reverse = true }, + [5] = { reverse = true }, + }) + command([[set foldexpr=v:lua.vim.lsp.foldexpr()]]) + end) + + it('closes all folds of one kind immediately', function() + exec_lua(function() + vim.lsp.foldclose('comment') + end) + screen:expect({ + grid = [[ +{1:+}{2:+-- 2 lines: foldLevel()······················································}| +{1: }static int foldLevel(linenr_T lnum) | +{1:-}{ | +{1:+}{2:+--- 2 lines: While updating the folds lines between invalid_top and invalid_b}| +{1:-} if (invalid_top == 0) { | +{1:2} checkupdate(curwin); | +{1:-} } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) { | +{1:2} return prev_lnum_lvl; | +{1:-} } else if (lnum >= invalid_top && lnum <= invalid_bot) { | +{1:2} return -1; | +{1:│} } | +{1:│} | +{1:│} // Return quickly when there is no folding at all in this window. | +{1:-} if (!hasAnyFolding(curwin)) { | +{1:2} return 0; | +{1:│} } | +{1:│} | +{1:│} return foldLevelWin(curwin, lnum); | +{1: }^} | +{3:~ }|*3 + | + ]], + }) + end) + + it('closes the smallest fold first', function() + exec_lua(function() + vim.lsp.foldclose('region') + end) + screen:expect({ + grid = [[ +{1:-}// foldLevel() {{{2 | +{1:│}/// @return fold level at line number "lnum" in the current window. | +{1: }static int foldLevel(linenr_T lnum) | +{1:+}{2:+-- 17 lines: {································································}| +{1: }^} | +{3:~ }|*17 + | + ]], + }) + command('4foldopen') + screen:expect({ + grid = [[ +{1:-}// foldLevel() {{{2 | +{1:│}/// @return fold level at line number "lnum" in the current window. | +{1: }static int foldLevel(linenr_T lnum) | +{1:-}{ | +{1:-} // While updating the folds lines between invalid_top and invalid_bot have | +{1:2} // an undefined fold level. Otherwise update the folds first. | +{1:+}{2:+--- 2 lines: if (invalid_top == 0) {·········································}| +{1:+}{2:+--- 2 lines: } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) {···········}| +{1:+}{2:+--- 2 lines: } else if (lnum >= invalid_top && lnum <= invalid_bot) {········}| +{1:│} } | +{1:│} | +{1:│} // Return quickly when there is no folding at all in this window. | +{1:+}{2:+--- 2 lines: if (!hasAnyFolding(curwin)) {···································}| +{1:│} } | +{1:│} | +{1:│} return foldLevelWin(curwin, lnum); | +{1: }^} | +{3:~ }|*5 + | + ]], + }) + end) + + it('is defered when the buffer is not up-to-date', function() + exec_lua(function() + vim.lsp.foldclose('comment') + vim.lsp.util.buf_versions[bufnr] = 0 + end) + screen:expect({ + grid = [[ +{1:+}{2:+-- 2 lines: foldLevel()······················································}| +{1: }static int foldLevel(linenr_T lnum) | +{1:-}{ | +{1:+}{2:+--- 2 lines: While updating the folds lines between invalid_top and invalid_b}| +{1:-} if (invalid_top == 0) { | +{1:2} checkupdate(curwin); | +{1:-} } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) { | +{1:2} return prev_lnum_lvl; | +{1:-} } else if (lnum >= invalid_top && lnum <= invalid_bot) { | +{1:2} return -1; | +{1:│} } | +{1:│} | +{1:│} // Return quickly when there is no folding at all in this window. | +{1:-} if (!hasAnyFolding(curwin)) { | +{1:2} return 0; | +{1:│} } | +{1:│} | +{1:│} return foldLevelWin(curwin, lnum); | +{1: }^} | +{3:~ }|*3 + | + ]], + }) + end) + end) +end) diff --git a/test/functional/plugin/lsp/handler_spec.lua b/test/functional/plugin/lsp/handler_spec.lua deleted file mode 100644 index 4b05b676a8..0000000000 --- a/test/functional/plugin/lsp/handler_spec.lua +++ /dev/null @@ -1,42 +0,0 @@ -local t = require('test.testutil') -local n = require('test.functional.testnvim')() - -local eq = t.eq -local exec_lua = n.exec_lua -local pcall_err = t.pcall_err -local matches = t.matches - -describe('lsp-handlers', function() - describe('vim.lsp._with_extend', function() - it('should return a table with the default keys', function() - eq( - { hello = 'world' }, - exec_lua(function() - return vim.lsp._with_extend('test', { hello = 'world' }) - end) - ) - end) - - it('should override with config keys', function() - eq( - { hello = 'universe', other = true }, - exec_lua(function() - return vim.lsp._with_extend( - 'test', - { other = true, hello = 'world' }, - { hello = 'universe' } - ) - end) - ) - end) - - it('should not allow invalid keys', function() - matches( - '.*Invalid option for `test`.*', - pcall_err(exec_lua, function() - return vim.lsp._with_extend('test', { hello = 'world' }, { invalid = true }) - end) - ) - end) - end) -end) diff --git a/test/functional/plugin/lsp/incremental_sync_spec.lua b/test/functional/plugin/lsp/incremental_sync_spec.lua index f60e159d64..0bca1fa4ca 100644 --- a/test/functional/plugin/lsp/incremental_sync_spec.lua +++ b/test/functional/plugin/lsp/incremental_sync_spec.lua @@ -23,7 +23,7 @@ before_each(function() -- local line_ending = format_line_ending[vim.api.nvim_get_option_value('fileformat', {})] --- @diagnostic disable-next-line:duplicate-set-field - function _G.test_register(bufnr, id, offset_encoding, line_ending) + function _G.test_register(bufnr, id, position_encoding, line_ending) local prev_lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, true) local function callback(_, bufnr0, _changedtick, firstline, lastline, new_lastline) @@ -38,7 +38,7 @@ before_each(function() firstline, lastline, new_lastline, - offset_encoding, + position_encoding, line_ending ) @@ -63,15 +63,15 @@ local function test_edit( prev_buffer, edit_operations, expected_text_changes, - offset_encoding, + position_encoding, line_ending ) - offset_encoding = offset_encoding or 'utf-16' + position_encoding = position_encoding or 'utf-16' line_ending = line_ending or '\n' api.nvim_buf_set_lines(0, 0, -1, true, prev_buffer) exec_lua(function() - return _G.test_register(0, 'test1', offset_encoding, line_ending) + return _G.test_register(0, 'test1', position_encoding, line_ending) end) for _, edit in ipairs(edit_operations) do diff --git a/test/functional/plugin/lsp/semantic_tokens_spec.lua b/test/functional/plugin/lsp/semantic_tokens_spec.lua index 280bd27207..9912bf2063 100644 --- a/test/functional/plugin/lsp/semantic_tokens_spec.lua +++ b/test/functional/plugin/lsp/semantic_tokens_spec.lua @@ -456,7 +456,7 @@ describe('semantic token highlighting', function() vim.notify = function(...) table.insert(_G.notifications, 1, { ... }) end - return vim.lsp.start_client({ name = 'dummy', cmd = _G.server.cmd }) + return vim.lsp.start({ name = 'dummy', cmd = _G.server.cmd }, { attach = false }) end) eq(false, exec_lua('return vim.lsp.buf_is_attached(0, ...)', client_id)) diff --git a/test/functional/plugin/lsp/testutil.lua b/test/functional/plugin/lsp/testutil.lua index a36cbac568..95fc22b96b 100644 --- a/test/functional/plugin/lsp/testutil.lua +++ b/test/functional/plugin/lsp/testutil.lua @@ -182,16 +182,17 @@ function M.test_rpc_server(config) ) end local client = setmetatable({}, { - __index = function(_, name) + __index = function(t, name) -- Workaround for not being able to yield() inside __index for Lua 5.1 :( -- Otherwise I would just return the value here. - return function(...) + return function(arg1, ...) + local ismethod = arg1 == t return exec_lua(function(...) - if type(_G.TEST_RPC_CLIENT[name]) == 'function' then - return _G.TEST_RPC_CLIENT[name](...) - else - return _G.TEST_RPC_CLIENT[name] + local client = _G.TEST_RPC_CLIENT + if type(client[name]) == 'function' then + return client[name](ismethod and client or arg1, ...) end + return client[name] end, ...) end end, diff --git a/test/functional/plugin/lsp/utils_spec.lua b/test/functional/plugin/lsp/utils_spec.lua index 813b8de812..1e3e759e0b 100644 --- a/test/functional/plugin/lsp/utils_spec.lua +++ b/test/functional/plugin/lsp/utils_spec.lua @@ -5,6 +5,8 @@ local Screen = require('test.functional.ui.screen') local feed = n.feed local eq = t.eq local exec_lua = n.exec_lua +local command, api = n.command, n.api +local pcall_err = t.pcall_err describe('vim.lsp.util', function() before_each(n.clear) @@ -265,6 +267,66 @@ describe('vim.lsp.util', function() eq(56, opts.height) end) + + describe('vim.lsp.util.open_floating_preview', function() + local var_name = 'lsp_floating_preview' + local curbuf = api.nvim_get_current_buf() + + it('clean bufvar after fclose', function() + exec_lua(function() + vim.lsp.util.open_floating_preview({ 'test' }, '', { height = 5, width = 2 }) + end) + eq(true, api.nvim_win_is_valid(api.nvim_buf_get_var(curbuf, var_name))) + command('fclose') + eq( + 'Key not found: lsp_floating_preview', + pcall_err(api.nvim_buf_get_var, curbuf, var_name) + ) + end) + + it('clean bufvar after CursorMoved', function() + local result = exec_lua(function() + vim.lsp.util.open_floating_preview({ 'test' }, '', { height = 5, width = 2 }) + local winnr = vim.b[vim.api.nvim_get_current_buf()].lsp_floating_preview + local result = vim.api.nvim_win_is_valid(winnr) + vim.api.nvim_feedkeys(vim.keycode('G'), 'txn', false) + return result + end) + eq(true, result) + eq( + 'Key not found: lsp_floating_preview', + pcall_err(api.nvim_buf_get_var, curbuf, var_name) + ) + end) + end) + end) + end) + + it('open_floating_preview zindex greater than current window', function() + local screen = Screen.new() + exec_lua(function() + vim.api.nvim_open_win(0, true, { + relative = 'editor', + border = 'single', + height = 11, + width = 51, + row = 2, + col = 2, + }) + vim.keymap.set('n', 'K', function() + vim.lsp.util.open_floating_preview({ 'foo' }, '', { border = 'single' }) + end, {}) end) + feed('K') + screen:expect([[ + ┌───────────────────────────────────────────────────┐| + │{4:^ }│| + │┌───┐{11: }│| + ││{4:foo}│{11: }│| + │└───┘{11: }│| + │{11:~ }│|*7 + └───────────────────────────────────────────────────┘| + | + ]]) end) end) diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 5222216faf..db3ab8ed98 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -95,7 +95,7 @@ describe('LSP', function() exec_lua(function() _G.lsp = require('vim.lsp') function _G.test__start_client() - return vim.lsp.start_client { + return vim.lsp.start({ cmd_env = { NVIM_LOG_FILE = fake_lsp_logfile, NVIM_APPNAME = 'nvim_lsp_test', @@ -112,7 +112,7 @@ describe('LSP', function() name = 'test_folder', }, }, - } + }, { attach = false }) end _G.TEST_CLIENT1 = _G.test__start_client() end) @@ -232,7 +232,7 @@ describe('LSP', function() -- client is a dummy object which will queue up commands to be run -- once the server initializes. It can't accept lua callbacks or -- other types that may be unserializable for now. - client.stop() + client:stop() end, -- If the program timed out, then code will be nil. on_exit = function(code, signal) @@ -254,8 +254,8 @@ describe('LSP', function() test_rpc_server { test_name = 'basic_init', on_init = function(client) - client.notify('test') - client.stop() + client:notify('test') + client:stop() end, on_exit = function(code, signal) eq(101, code, 'exit code') -- See fake-lsp-server.lua @@ -275,7 +275,7 @@ describe('LSP', function() test_rpc_server({ test_name = 'basic_init_did_change_configuration', on_init = function(client, _) - client.stop() + client:stop() end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -333,9 +333,9 @@ describe('LSP', function() test_name = 'basic_init', on_init = function(client) eq(0, client.server_capabilities().textDocumentSync.change) - client.request('shutdown') - client.notify('exit') - client.stop() + client:request('shutdown') + client:notify('exit') + client:stop() end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -377,7 +377,7 @@ describe('LSP', function() end, on_init = function(_client) client = _client - client.notify('finish') + client:notify('finish') end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -395,7 +395,7 @@ describe('LSP', function() return vim.lsp.buf_is_attached(_G.BUFFER, _G.TEST_RPC_CLIENT_ID) end) ) - client.stop() + client:stop() end end, } @@ -430,7 +430,7 @@ describe('LSP', function() return vim.lsp.buf_attach_client(_G.BUFFER, _G.TEST_RPC_CLIENT_ID) end) ) - client.notify('finish') + client:notify('finish') end, on_handler = function(_, _, ctx) if ctx.method == 'finish' then @@ -439,7 +439,7 @@ describe('LSP', function() return vim.lsp.buf_detach_client(_G.BUFFER, _G.TEST_RPC_CLIENT_ID) end) eq('basic_init', api.nvim_get_var('lsp_detached')) - client.stop() + client:stop() end end, } @@ -466,13 +466,20 @@ describe('LSP', function() true, exec_lua(function() local keymap --- @type table<string,any> + local called = false + local origin = vim.lsp.buf.hover + vim.lsp.buf.hover = function() + called = true + end vim._with({ buf = _G.BUFFER }, function() keymap = vim.fn.maparg('K', 'n', false, true) end) - return keymap.callback == vim.lsp.buf.hover + keymap.callback() + vim.lsp.buf.hover = origin + return called end) ) - client.stop() + client:stop() end end, on_exit = function(_, _) @@ -480,13 +487,13 @@ describe('LSP', function() eq('', get_buf_option('omnifunc')) eq('', get_buf_option('formatexpr')) eq( - '', + true, exec_lua(function() local keymap --- @type string vim._with({ buf = _G.BUFFER }, function() keymap = vim.fn.maparg('K', 'n', false, false) end) - return keymap + return keymap:match('<Lua %d+: .+/runtime/lua/vim/lsp%.lua:%d+>') ~= nil end) ) end, @@ -524,7 +531,7 @@ describe('LSP', function() eq('v:lua.vim.lsp.tagfunc', get_buf_option('tagfunc', BUFFER_1)) eq('v:lua.vim.lsp.omnifunc', get_buf_option('omnifunc', BUFFER_2)) eq('v:lua.vim.lsp.formatexpr()', get_buf_option('formatexpr', BUFFER_2)) - client.stop() + client:stop() end end, on_exit = function(_, _) @@ -554,7 +561,7 @@ describe('LSP', function() eq('tfu', get_buf_option('tagfunc')) eq('ofu', get_buf_option('omnifunc')) eq('fex', get_buf_option('formatexpr')) - client.stop() + client:stop() end end, on_exit = function(_, _) @@ -711,10 +718,10 @@ describe('LSP', function() ctx.method, result ) - client.notify('workspace/configuration', server_result) + client:notify('workspace/configuration', server_result) end if ctx.method == 'shutdown' then - client.stop() + client:stop() end end, } @@ -756,7 +763,7 @@ describe('LSP', function() test_rpc_server { test_name = 'basic_check_capabilities', on_init = function(client) - client.stop() + client:stop() local full_kind = exec_lua(function() return require 'vim.lsp.protocol'.TextDocumentSyncKind.Full end) @@ -798,7 +805,7 @@ describe('LSP', function() vim.api.nvim_exec_autocmds('BufWritePost', { buffer = _G.BUFFER, modeline = false }) end) else - client.stop() + client:stop() end end, } @@ -898,7 +905,7 @@ describe('LSP', function() end) end) else - client.stop() + client:stop() end end, }) @@ -929,20 +936,20 @@ describe('LSP', function() vim.api.nvim_exec_autocmds('BufWritePost', { buffer = _G.BUFFER, modeline = false }) end) else - client.stop() + client:stop() end end, } end) - it('client.supports_methods() should validate capabilities', function() + it('client:supports_methods() should validate capabilities', function() local expected_handlers = { { NIL, {}, { method = 'shutdown', client_id = 1 } }, } test_rpc_server { test_name = 'capabilities_for_client_supports_method', on_init = function(client) - client.stop() + client:stop() local expected_sync_capabilities = { change = 1, openClose = true, @@ -958,11 +965,11 @@ describe('LSP', function() eq(true, client.server_capabilities().codeLensProvider.resolveProvider) -- known methods for resolved capabilities - eq(true, client.supports_method('textDocument/hover')) - eq(false, client.supports_method('textDocument/definition')) + eq(true, client:supports_method('textDocument/hover')) + eq(false, client:supports_method('textDocument/definition')) -- unknown methods are assumed to be supported. - eq(true, client.supports_method('unknown-method')) + eq(true, client:supports_method('unknown-method')) end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -989,7 +996,7 @@ describe('LSP', function() end) end, on_init = function(client) - client.stop() + client:stop() exec_lua(function() vim.lsp.buf.type_definition() end) @@ -1018,7 +1025,7 @@ describe('LSP', function() end) end, on_init = function(client) - client.stop() + client:stop() exec_lua(function() vim.lsp.buf.type_definition() end) @@ -1042,7 +1049,7 @@ describe('LSP', function() test_rpc_server { test_name = 'check_forward_request_cancelled', on_init = function(_client) - _client.request('error_code_test') + _client:request('error_code_test') client = _client end, on_exit = function(code, signal) @@ -1053,7 +1060,40 @@ describe('LSP', function() on_handler = function(err, _, ctx) eq(table.remove(expected_handlers), { err, {}, ctx }, 'expected handler') if ctx.method == 'finish' then - client.stop() + client:stop() + end + end, + } + end) + + it('should forward ServerCancelled to callback', function() + local expected_handlers = { + { NIL, {}, { method = 'finish', client_id = 1 } }, + { + { code = -32802 }, + NIL, + { method = 'error_code_test', bufnr = 1, client_id = 1, version = 0 }, + }, + } + local client --- @type vim.lsp.Client + test_rpc_server { + test_name = 'check_forward_server_cancelled', + on_init = function(_client) + _client:request('error_code_test') + client = _client + end, + on_exit = function(code, signal) + eq(0, code, 'exit code') + eq(0, signal, 'exit signal') + eq(0, #expected_handlers, 'did not call expected handler') + end, + on_handler = function(err, _, ctx) + eq(table.remove(expected_handlers), { err, _, ctx }, 'expected handler') + if ctx.method ~= 'finish' then + client:notify('finish') + end + if ctx.method == 'finish' then + client:stop() end end, } @@ -1072,7 +1112,7 @@ describe('LSP', function() test_rpc_server { test_name = 'check_forward_content_modified', on_init = function(_client) - _client.request('error_code_test') + _client:request('error_code_test') client = _client end, on_exit = function(code, signal) @@ -1082,12 +1122,11 @@ describe('LSP', function() end, on_handler = function(err, _, ctx) eq(table.remove(expected_handlers), { err, _, ctx }, 'expected handler') - -- if ctx.method == 'error_code_test' then client.notify("finish") end if ctx.method ~= 'finish' then - client.notify('finish') + client:notify('finish') end if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1103,13 +1142,13 @@ describe('LSP', function() test_name = 'check_pending_request_tracked', on_init = function(_client) client = _client - client.request('slow_request') + client:request('slow_request') local request = exec_lua(function() return _G.TEST_RPC_CLIENT.requests[2] end) eq('slow_request', request.method) eq('pending', request.type) - client.notify('release') + client:notify('release') end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -1123,10 +1162,10 @@ describe('LSP', function() return _G.TEST_RPC_CLIENT.requests[2] end) eq(nil, request) - client.notify('finish') + client:notify('finish') end if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1141,14 +1180,14 @@ describe('LSP', function() test_name = 'check_cancel_request_tracked', on_init = function(_client) client = _client - client.request('slow_request') - client.cancel_request(2) + client:request('slow_request') + client:cancel_request(2) local request = exec_lua(function() return _G.TEST_RPC_CLIENT.requests[2] end) eq('slow_request', request.method) eq('cancel', request.type) - client.notify('release') + client:notify('release') end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -1162,7 +1201,7 @@ describe('LSP', function() end) eq(nil, request) if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1178,19 +1217,19 @@ describe('LSP', function() test_name = 'check_tracked_requests_cleared', on_init = function(_client) client = _client - client.request('slow_request') + client:request('slow_request') local request = exec_lua(function() return _G.TEST_RPC_CLIENT.requests[2] end) eq('slow_request', request.method) eq('pending', request.type) - client.cancel_request(2) + client:cancel_request(2) request = exec_lua(function() return _G.TEST_RPC_CLIENT.requests[2] end) eq('slow_request', request.method) eq('cancel', request.type) - client.notify('release') + client:notify('release') end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -1204,10 +1243,10 @@ describe('LSP', function() return _G.TEST_RPC_CLIENT.requests[2] end) eq(nil, request) - client.notify('finish') + client:notify('finish') end if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1225,11 +1264,11 @@ describe('LSP', function() command('let g:requests = 0') command('autocmd LspRequest * let g:requests+=1') client = _client - client.request('slow_request') + client:request('slow_request') eq(1, eval('g:requests')) - client.cancel_request(2) + client:cancel_request(2) eq(2, eval('g:requests')) - client.notify('release') + client:notify('release') end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -1240,10 +1279,10 @@ describe('LSP', function() on_handler = function(err, _, ctx) eq(table.remove(expected_handlers), { err, {}, ctx }, 'expected handler') if ctx.method == 'slow_request' then - client.notify('finish') + client:notify('finish') end if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1277,7 +1316,7 @@ describe('LSP', function() end) eq(full_kind, client.server_capabilities().textDocumentSync.change) eq(true, client.server_capabilities().textDocumentSync.openClose) - client.notify('finish') + client:notify('finish') end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -1286,7 +1325,7 @@ describe('LSP', function() on_handler = function(err, result, ctx) eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1333,11 +1372,11 @@ describe('LSP', function() end, on_handler = function(err, result, ctx) if ctx.method == 'start' then - client.notify('finish') + client:notify('finish') end eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1378,11 +1417,11 @@ describe('LSP', function() end, on_handler = function(err, result, ctx) if ctx.method == 'start' then - client.notify('finish') + client:notify('finish') end eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1428,11 +1467,11 @@ describe('LSP', function() 'boop', }) end) - client.notify('finish') + client:notify('finish') end eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1479,11 +1518,11 @@ describe('LSP', function() 'boop', }) end) - client.notify('finish') + client:notify('finish') end eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1529,7 +1568,7 @@ describe('LSP', function() end, on_init = function(_client) client = _client - eq(true, client.supports_method('textDocument/inlayHint')) + eq(true, client:supports_method('textDocument/inlayHint')) exec_lua(function() assert(vim.lsp.buf_attach_client(_G.BUFFER, _G.TEST_RPC_CLIENT_ID)) end) @@ -1545,11 +1584,11 @@ describe('LSP', function() end) end if ctx.method == 'textDocument/inlayHint' then - client.notify('finish') + client:notify('finish') end eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1598,11 +1637,11 @@ describe('LSP', function() '123boop', }) end) - client.notify('finish') + client:notify('finish') end eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1652,11 +1691,11 @@ describe('LSP', function() '123boop', }) end) - client.notify('finish') + client:notify('finish') end eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1699,11 +1738,11 @@ describe('LSP', function() on_handler = function(err, result, ctx) if ctx.method == 'start' then n.command('normal! 1Go') - client.notify('finish') + client:notify('finish') end eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1752,11 +1791,11 @@ describe('LSP', function() 'boop', }) end) - client.notify('finish') + client:notify('finish') end eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1806,15 +1845,29 @@ describe('LSP', function() }) vim.api.nvim_command(_G.BUFFER .. 'bwipeout') end) - client.notify('finish') + client:notify('finish') end eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') if ctx.method == 'finish' then - client.stop() + client:stop() end end, } end) + + it('vim.lsp.start when existing client has no workspace_folders', function() + exec_lua(create_server_definition) + eq( + { 2, 'foo', 'foo' }, + exec_lua(function() + local server = _G._create_server() + vim.lsp.start { cmd = server.cmd, name = 'foo' } + vim.lsp.start { cmd = server.cmd, name = 'foo', root_dir = 'bar' } + local foos = vim.lsp.get_clients() + return { #foos, foos[1].name, foos[2].name } + end) + ) + end) end) describe('parsing tests', function() @@ -1830,7 +1883,7 @@ describe('LSP', function() on_setup = function() end, on_init = function(_client) client = _client - client.stop(true) + client:stop(true) end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -1882,7 +1935,7 @@ describe('LSP', function() on_handler = function(err, result, ctx) eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -2348,7 +2401,7 @@ describe('LSP', function() test_rpc_server { test_name = 'basic_init', on_init = function(client, _) - client.stop() + client:stop() end, -- If the program timed out, then code will be nil. on_exit = function(code, signal) @@ -3448,6 +3501,19 @@ describe('LSP', function() end) ) end) + it('handles empty line', function() + exec_lua(function() + _G.contents = { + '', + } + end) + eq( + { 20, 1 }, + exec_lua(function() + return { vim.lsp.util._make_floating_popup_size(_G.contents, { width = 20 }) } + end) + ) + end) end) describe('lsp.util.trim.trim_empty_lines', function() @@ -3499,7 +3565,7 @@ describe('LSP', function() } return vim.lsp.util.convert_signature_help_to_markdown_lines(signature_help, 'zig', { '(' }) end) - -- Note that although the higlight positions below are 0-indexed, the 2nd parameter + -- Note that although the highlight positions below are 0-indexed, the 2nd parameter -- corresponds to the 3rd line because the first line is the ``` from the -- Markdown block. local expected = { 3, 4, 3, 11 } @@ -4284,7 +4350,7 @@ describe('LSP', function() end) ) end - client.stop() + client:stop() end end, } @@ -4331,7 +4397,7 @@ describe('LSP', function() return type(vim.lsp.commands['dummy2']) end) ) - client.stop() + client:stop() end end, } @@ -4372,7 +4438,7 @@ describe('LSP', function() vim.lsp.buf.code_action() end) elseif ctx.method == 'shutdown' then - client.stop() + client:stop() end end, }) @@ -4447,7 +4513,7 @@ describe('LSP', function() return type(vim.lsp.commands['executed_type_annotate']) end) ) - client.stop() + client:stop() end end, } @@ -4564,7 +4630,7 @@ describe('LSP', function() end) eq({ command = 'Dummy', title = 'Lens1' }, cmd) elseif ctx.method == 'shutdown' then - client.stop() + client:stop() end end, } @@ -4653,7 +4719,7 @@ describe('LSP', function() end) eq({ command = 'Dummy', title = 'Lens2' }, response) elseif ctx.method == 'shutdown' then - client.stop() + client:stop() end end, } @@ -4762,7 +4828,7 @@ describe('LSP', function() return notify_msg end) eq('[LSP] Format request failed, no matching language servers.', notify_msg) - client.stop() + client:stop() end, } end) @@ -4795,7 +4861,7 @@ describe('LSP', function() end) eq(nil, notify_msg) elseif ctx.method == 'shutdown' then - client.stop() + client:stop() end end, } @@ -4836,7 +4902,7 @@ describe('LSP', function() end) eq(nil, notify_msg) elseif ctx.method == 'shutdown' then - client.stop() + client:stop() end end, } @@ -4883,7 +4949,7 @@ describe('LSP', function() end) eq(nil, notify_msg) elseif ctx.method == 'shutdown' then - client.stop() + client:stop() end end, } @@ -4930,7 +4996,7 @@ describe('LSP', function() end) eq({ handler_called = true }, result) elseif ctx.method == 'shutdown' then - client.stop() + client:stop() end end, } @@ -5145,8 +5211,8 @@ describe('LSP', function() local win = vim.api.nvim_get_current_win() vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, { 'local x = 10', '', 'print(x)' }) vim.api.nvim_win_set_cursor(win, { 3, 6 }) - local client_id1 = assert(vim.lsp.start({ name = 'dummy', cmd = server1.cmd })) - local client_id2 = assert(vim.lsp.start({ name = 'dummy', cmd = server2.cmd })) + local client_id1 = assert(vim.lsp.start({ name = 'dummy1', cmd = server1.cmd })) + local client_id2 = assert(vim.lsp.start({ name = 'dummy2', cmd = server2.cmd })) local response vim.lsp.buf.definition({ on_list = function(r) @@ -5477,7 +5543,7 @@ describe('LSP', function() result[#result + 1] = { method = method, fname = fname, - supported = client.supports_method(method, { bufnr = bufnr }), + supported = client:supports_method(method, { bufnr = bufnr }), } end @@ -6059,15 +6125,6 @@ describe('LSP', function() end eq(is_os('mac') or is_os('win'), check_registered(nil)) -- start{_client}() defaults to make_client_capabilities(). - eq(false, check_registered(vim.empty_dict())) - eq( - false, - check_registered({ - workspace = { - ignoreMe = true, - }, - }) - ) eq( false, check_registered({ @@ -6090,4 +6147,154 @@ describe('LSP', function() ) end) end) + + describe('vim.lsp.config() and vim.lsp.enable()', function() + it('can merge settings from "*"', function() + eq( + { + name = 'foo', + cmd = { 'foo' }, + root_markers = { '.git' }, + }, + exec_lua(function() + vim.lsp.config('*', { root_markers = { '.git' } }) + vim.lsp.config('foo', { cmd = { 'foo' } }) + + return vim.lsp.config['foo'] + end) + ) + end) + + it('sets up an autocmd', function() + eq( + 1, + exec_lua(function() + vim.lsp.config('foo', { + cmd = { 'foo' }, + root_markers = { '.foorc' }, + }) + vim.lsp.enable('foo') + return #vim.api.nvim_get_autocmds({ + group = 'nvim.lsp.enable', + event = 'FileType', + }) + end) + ) + end) + + it('attaches to buffers', function() + exec_lua(create_server_definition) + + local tmp1 = t.tmpname(true) + local tmp2 = t.tmpname(true) + + exec_lua(function() + local server = _G._create_server({ + handlers = { + initialize = function(_, _, callback) + callback(nil, { capabilities = {} }) + end, + }, + }) + + vim.lsp.config('foo', { + cmd = server.cmd, + filetypes = { 'foo' }, + root_markers = { '.foorc' }, + }) + + vim.lsp.config('bar', { + cmd = server.cmd, + filetypes = { 'bar' }, + root_markers = { '.foorc' }, + }) + + vim.lsp.enable('foo') + vim.lsp.enable('bar') + + vim.cmd.edit(tmp1) + vim.bo.filetype = 'foo' + _G.foo_buf = vim.api.nvim_get_current_buf() + + vim.cmd.edit(tmp2) + vim.bo.filetype = 'bar' + _G.bar_buf = vim.api.nvim_get_current_buf() + end) + + eq( + { 1, 'foo', 1, 'bar' }, + exec_lua(function() + local foos = vim.lsp.get_clients({ bufnr = assert(_G.foo_buf) }) + local bars = vim.lsp.get_clients({ bufnr = assert(_G.bar_buf) }) + return { #foos, foos[1].name, #bars, bars[1].name } + end) + ) + end) + + it('does not attach to buffers more than once if no root_dir', function() + exec_lua(create_server_definition) + + local tmp1 = t.tmpname(true) + + eq( + 1, + exec_lua(function() + local server = _G._create_server({ + handlers = { + initialize = function(_, _, callback) + callback(nil, { capabilities = {} }) + end, + }, + }) + + vim.lsp.config('foo', { cmd = server.cmd, filetypes = { 'foo' } }) + vim.lsp.enable('foo') + + vim.cmd.edit(assert(tmp1)) + vim.bo.filetype = 'foo' + vim.bo.filetype = 'foo' + + return #vim.lsp.get_clients({ bufnr = vim.api.nvim_get_current_buf() }) + end) + ) + end) + + it('supports async function for root_dir', function() + exec_lua(create_server_definition) + + local tmp1 = t.tmpname(true) + exec_lua(function() + local server = _G._create_server({ + handlers = { + initialize = function(_, _, callback) + callback(nil, { capabilities = {} }) + end, + }, + }) + + vim.lsp.config('foo', { + cmd = server.cmd, + filetypes = { 'foo' }, + root_dir = function(cb) + vim.system({ 'sleep', '0' }, {}, function() + cb('some_dir') + end) + end, + }) + vim.lsp.enable('foo') + + vim.cmd.edit(assert(tmp1)) + vim.bo.filetype = 'foo' + end) + + retry(nil, 1000, function() + eq( + 'some_dir', + exec_lua(function() + return vim.lsp.get_clients({ bufnr = vim.api.nvim_get_current_buf() })[1].root_dir + end) + ) + end) + end) + end) end) diff --git a/test/functional/plugin/man_spec.lua b/test/functional/plugin/man_spec.lua index 8906e60dce..c1dbc6dac3 100644 --- a/test/functional/plugin/man_spec.lua +++ b/test/functional/plugin/man_spec.lua @@ -21,13 +21,12 @@ local function get_search_history(name) local man = require('man') local res = {} --- @diagnostic disable-next-line:duplicate-set-field - man.find_path = function(sect, name0) + man._find_path = function(name0, sect) table.insert(res, { sect, name0 }) return nil end - local ok, rv = pcall(man.open_page, -1, { tab = 0 }, args) - assert(not ok) - assert(rv and rv:match('no manual entry')) + local err = man.open_page(-1, { tab = 0 }, args) + assert(err and err:match('no manual entry')) return res end) end @@ -225,7 +224,7 @@ describe(':Man', function() matches('^/.+', actual_file) local args = { nvim_prog, '--headless', '+:Man ' .. actual_file, '+q' } matches( - ('Error detected while processing command line:\r\n' .. 'man.lua: "no manual entry for %s"'):format( + ('Error detected while processing command line:\r\n' .. 'man.lua: no manual entry for %s'):format( pesc(actual_file) ), fn.system(args, { '' }) @@ -235,8 +234,8 @@ describe(':Man', function() it('tries variants with spaces, underscores #22503', function() eq({ - { '', 'NAME WITH SPACES' }, - { '', 'NAME_WITH_SPACES' }, + { vim.NIL, 'NAME WITH SPACES' }, + { vim.NIL, 'NAME_WITH_SPACES' }, }, get_search_history('NAME WITH SPACES')) eq({ { '3', 'some other man' }, @@ -255,12 +254,21 @@ describe(':Man', function() { 'n', 'some_other_man' }, }, get_search_history('n some other man')) eq({ - { '', '123some other man' }, - { '', '123some_other_man' }, + { vim.NIL, '123some other man' }, + { vim.NIL, '123some_other_man' }, }, get_search_history('123some other man')) eq({ { '1', 'other_man' }, { '1', 'other_man' }, }, get_search_history('other_man(1)')) end) + + it('can complete', function() + eq( + true, + exec_lua(function() + return #require('man').man_complete('f', 'Man f') > 0 + end) + ) + end) end) diff --git a/test/functional/provider/clipboard_spec.lua b/test/functional/provider/clipboard_spec.lua index 722442acbd..2b54ea93e0 100644 --- a/test/functional/provider/clipboard_spec.lua +++ b/test/functional/provider/clipboard_spec.lua @@ -544,7 +544,7 @@ describe('clipboard (with fake clipboard.vim)', function() ]]) feed('gg^<C-v>') -- Goto start of top line enter visual block mode feed('3ljy^k') -- yank 4x2 block & goto initial location - feed('P') -- Paste it infront + feed('P') -- Paste it before cursor expect([[ aabbaabbcc ddeeddeeff diff --git a/test/functional/script/luacats_grammar_spec.lua b/test/functional/script/luacats_grammar_spec.lua index 8bc55879d4..6e73f6894b 100644 --- a/test/functional/script/luacats_grammar_spec.lua +++ b/test/functional/script/luacats_grammar_spec.lua @@ -118,9 +118,9 @@ describe('luacats grammar', function() type = '"rfc2396" | "rfc2732" | "rfc3986" | nil', }) - test('@param offset_encoding "utf-8" | "utf-16" | "utf-32" | nil', { + test('@param position_encoding "utf-8" | "utf-16" | "utf-32" | nil', { kind = 'param', - name = 'offset_encoding', + name = 'position_encoding', type = '"utf-8" | "utf-16" | "utf-32" | nil', }) diff --git a/test/functional/shada/marks_spec.lua b/test/functional/shada/marks_spec.lua index d680f0b83b..837978dee1 100644 --- a/test/functional/shada/marks_spec.lua +++ b/test/functional/shada/marks_spec.lua @@ -218,7 +218,7 @@ describe('ShaDa support code', function() -- -c temporary sets lnum to zero to make `+/pat` work, so calling setpcmark() -- during -c used to add item with zero lnum to jump list. it('does not create incorrect file for non-existent buffers when writing from -c', function() - local argv = n.new_argv { + local p = n.spawn_wait { args_rm = { '-i', '--embed', -- no --embed @@ -232,12 +232,13 @@ describe('ShaDa support code', function() 'qall', }, } - eq('', fn.system(argv)) + eq('', p:output()) + eq(0, p.status) eq(0, exc_exec('rshada')) end) it('does not create incorrect file for non-existent buffers opened from -c', function() - local argv = n.new_argv { + local p = n.spawn_wait { args_rm = { '-i', '--embed', -- no --embed @@ -251,7 +252,8 @@ describe('ShaDa support code', function() 'autocmd VimEnter * qall', }, } - eq('', fn.system(argv)) + eq('', p:output()) + eq(0, p.status) eq(0, exc_exec('rshada')) end) diff --git a/test/functional/shada/shada_spec.lua b/test/functional/shada/shada_spec.lua index 5debdc6c77..78fe19b984 100644 --- a/test/functional/shada/shada_spec.lua +++ b/test/functional/shada/shada_spec.lua @@ -6,8 +6,7 @@ local uv = vim.uv local paths = t.paths local api, nvim_command, fn, eq = n.api, n.command, n.fn, t.eq -local write_file, spawn, set_session, nvim_prog, exc_exec = - t.write_file, n.spawn, n.set_session, n.nvim_prog, n.exc_exec +local write_file, set_session, exc_exec = t.write_file, n.set_session, n.exc_exec local is_os = t.is_os local skip = t.skip @@ -254,7 +253,7 @@ describe('ShaDa support code', function() it('does not crash when ShaDa file directory is not writable', function() skip(is_os('win')) - fn.mkdir(dirname, '', 0) + fn.mkdir(dirname, '', '0') eq(0, fn.filewritable(dirname)) reset { shadafile = dirshada, args = { '--cmd', 'set shada=' } } api.nvim_set_option_value('shada', "'10", {}) @@ -270,10 +269,10 @@ end) describe('ShaDa support code', function() it('does not write NONE file', function() - local session = spawn( - { nvim_prog, '-u', 'NONE', '-i', 'NONE', '--embed', '--headless', '--cmd', 'qall' }, - true - ) + local session = n.new_session(false, { + merge = false, + args = { '-u', 'NONE', '-i', 'NONE', '--embed', '--headless', '--cmd', 'qall' }, + }) session:close() eq(nil, uv.fs_stat('NONE')) eq(nil, uv.fs_stat('NONE.tmp.a')) @@ -281,7 +280,10 @@ describe('ShaDa support code', function() it('does not read NONE file', function() write_file('NONE', '\005\001\015\131\161na\162rX\194\162rc\145\196\001-') - local session = spawn({ nvim_prog, '-u', 'NONE', '-i', 'NONE', '--embed', '--headless' }, true) + local session = n.new_session( + false, + { merge = false, args = { '-u', 'NONE', '-i', 'NONE', '--embed', '--headless' } } + ) set_session(session) eq('', fn.getreg('a')) session:close() diff --git a/test/functional/terminal/altscreen_spec.lua b/test/functional/terminal/altscreen_spec.lua index 4a61e0203d..839e37f541 100644 --- a/test/functional/terminal/altscreen_spec.lua +++ b/test/functional/terminal/altscreen_spec.lua @@ -35,13 +35,13 @@ describe(':terminal altscreen', function() line6 | line7 | line8 | - {1: } | + ^ | {3:-- TERMINAL --} | ]]) enter_altscreen() screen:expect([[ |*5 - {1: } | + ^ | {3:-- TERMINAL --} | ]]) eq(10, api.nvim_buf_line_count(0)) @@ -68,7 +68,7 @@ describe(':terminal altscreen', function() line6 | line7 | line8 | - {1: } | + ^ | {3:-- TERMINAL --} | ]]) feed('<c-\\><c-n>gg') @@ -103,7 +103,7 @@ describe(':terminal altscreen', function() line14 | line15 | line16 | - {1: } | + ^ | {3:-- TERMINAL --} | ]]) end) @@ -132,7 +132,7 @@ describe(':terminal altscreen', function() screen:expect([[ |*2 rows: 4, cols: 50 | - {1: } | + ^ | {3:-- TERMINAL --} | ]]) end @@ -160,7 +160,7 @@ describe(':terminal altscreen', function() line5 | line6 | line7 | - line8 | + ^line8 | {3:-- TERMINAL --} | ]]) end) diff --git a/test/functional/terminal/api_spec.lua b/test/functional/terminal/api_spec.lua index b550df80c3..a8e5367176 100644 --- a/test/functional/terminal/api_spec.lua +++ b/test/functional/terminal/api_spec.lua @@ -33,7 +33,7 @@ describe('api', function() it('qa! RPC request during insert-mode', function() screen:expect { grid = [[ - {1: } | + ^ | {4:~ }|*4 | {3:-- TERMINAL --} | @@ -45,7 +45,7 @@ describe('api', function() -- Wait for socket creation. screen:expect([[ - {1: } | + ^ | {4:~ }|*4 ]] .. socket_name .. [[ | {3:-- TERMINAL --} | @@ -57,7 +57,7 @@ describe('api', function() tt.feed_data('i[tui] insert-mode') -- Wait for stdin to be processed. screen:expect([[ - [tui] insert-mode{1: } | + [tui] insert-mode^ | {4:~ }|*4 {3:-- INSERT --} | {3:-- TERMINAL --} | @@ -73,7 +73,7 @@ describe('api', function() [tui] insert-mode | [socket 1] this is more t | han 25 columns | - [socket 2] input{1: } | + [socket 2] input^ | {4:~ } | {3:-- INSERT --} | {3:-- TERMINAL --} | diff --git a/test/functional/terminal/buffer_spec.lua b/test/functional/terminal/buffer_spec.lua index 05258a9e50..4635259e33 100644 --- a/test/functional/terminal/buffer_spec.lua +++ b/test/functional/terminal/buffer_spec.lua @@ -89,7 +89,7 @@ describe(':terminal buffer', function() feed('<c-\\><c-n>') screen:expect([[ tty ready | - {2:^ } | + ^ | |*5 ]]) end) @@ -109,7 +109,7 @@ describe(':terminal buffer', function() feed('<c-\\><c-n>dd') screen:expect([[ tty ready | - {2:^ } | + ^ | |*4 {8:E21: Cannot make changes, 'modifiable' is off} | ]]) @@ -122,7 +122,7 @@ describe(':terminal buffer', function() screen:expect([[ ^tty ready | appended tty ready |*2 - {2: } | + | |*2 :let @a = "appended " . @a | ]]) @@ -142,7 +142,7 @@ describe(':terminal buffer', function() screen:expect([[ ^tty ready | appended tty ready | - {2: } | + | |*3 :put a | ]]) @@ -151,7 +151,7 @@ describe(':terminal buffer', function() screen:expect([[ tty ready | appended tty ready |*2 - {2: } | + | | ^ | :6put a | @@ -198,7 +198,7 @@ describe(':terminal buffer', function() {4:~ }| {5:========== }| rows: 2, cols: 50 | - {2: } | + | {18:========== }| | ]]) @@ -234,7 +234,7 @@ describe(':terminal buffer', function() command('set rightleft') screen:expect([[ ydaer ytt| - {1:a}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + ^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| |*4 {3:-- TERMINAL --} | ]]) @@ -277,7 +277,7 @@ describe(':terminal buffer', function() screen:expect { grid = [[ tty ready | - {2:^ } | + ^ | |*4 {3:-- (terminal) --} | ]], @@ -288,7 +288,7 @@ describe(':terminal buffer', function() screen:expect { grid = [[ tty ready | - {2: } | + | |*4 :let g:x = 17^ | ]], @@ -298,7 +298,7 @@ describe(':terminal buffer', function() screen:expect { grid = [[ tty ready | - {1: } | + ^ | |*4 {3:-- TERMINAL --} | ]], @@ -351,7 +351,7 @@ describe(':terminal buffer', function() end) it('TermRequest synchronization #27572', function() - command('autocmd! nvim_terminal TermRequest') + command('autocmd! nvim.terminal TermRequest') local term = exec_lua([[ _G.input = {} local term = vim.api.nvim_open_term(0, { @@ -378,7 +378,7 @@ describe(':terminal buffer', function() }, exec_lua('return _G.input')) end) - it('no heap-buffer-overflow when using termopen(echo) #3161', function() + it('no heap-buffer-overflow when using jobstart("echo",{term=true}) #3161', function() local testfilename = 'Xtestfile-functional-terminal-buffers_spec' write_file(testfilename, 'aaaaaaaaaaaaaaaaaaaaaaaaaaaa') finally(function() @@ -387,8 +387,8 @@ describe(':terminal buffer', function() feed_command('edit ' .. testfilename) -- Move cursor away from the beginning of the line feed('$') - -- Let termopen() modify the buffer - feed_command('call termopen("echo")') + -- Let jobstart(…,{term=true}) modify the buffer + feed_command([[call jobstart("echo", {'term':v:true})]]) assert_alive() feed_command('bdelete!') end) @@ -400,18 +400,31 @@ describe(':terminal buffer', function() assert_alive() end) - it('truncates number of composing characters to 5', function() + it('truncates the size of grapheme clusters', function() local chan = api.nvim_open_term(0, {}) local composing = ('a̳'):sub(2) - api.nvim_chan_send(chan, 'a' .. composing:rep(8)) + api.nvim_chan_send(chan, 'a' .. composing:rep(20)) retry(nil, nil, function() - eq('a' .. composing:rep(5), api.nvim_get_current_line()) + eq('a' .. composing:rep(14), api.nvim_get_current_line()) end) end) + it('handles extended grapheme clusters', function() + local screen = Screen.new(50, 7) + feed 'i' + local chan = api.nvim_open_term(0, {}) + api.nvim_chan_send(chan, '🏴☠️ yarrr') + screen:expect([[ + 🏴☠️ yarrr^ | + |*5 + {5:-- TERMINAL --} | + ]]) + eq('🏴☠️ yarrr', api.nvim_get_current_line()) + end) + it('handles split UTF-8 sequences #16245', function() local screen = Screen.new(50, 7) - fn.termopen({ testprg('shell-test'), 'UTF-8' }) + fn.jobstart({ testprg('shell-test'), 'UTF-8' }, { term = true }) screen:expect([[ ^å | ref: å̲ | @@ -422,6 +435,19 @@ describe(':terminal buffer', function() ]]) end) + it('handles unprintable chars', function() + local screen = Screen.new(50, 7) + feed 'i' + local chan = api.nvim_open_term(0, {}) + api.nvim_chan_send(chan, '\239\187\191') -- '\xef\xbb\xbf' + screen:expect([[ + {18:<feff>}^ | + |*5 + {5:-- TERMINAL --} | + ]]) + eq('\239\187\191', api.nvim_get_current_line()) + end) + it("handles bell respecting 'belloff' and 'visualbell'", function() local screen = Screen.new(50, 7) local chan = api.nvim_open_term(0, {}) @@ -531,16 +557,19 @@ describe('terminal input', function() '--cmd', 'set notermguicolors', '-c', - 'while 1 | redraw | echo keytrans(getcharstr()) | endwhile', + 'while 1 | redraw | echo keytrans(getcharstr(-1, #{simplify: 0})) | endwhile', }) screen:expect([[ - {1: } | + ^ | {4:~ }|*3 {5:[No Name] 0,0-1 All}| | {3:-- TERMINAL --} | ]]) - for _, key in ipairs({ + local keys = { + '<Tab>', + '<CR>', + '<Esc>', '<M-Tab>', '<M-CR>', '<M-Esc>', @@ -568,18 +597,36 @@ describe('terminal input', function() '<S-End>', '<C-End>', '<End>', - '<C-LeftMouse>', - '<C-LeftRelease>', - '<2-LeftMouse>', - '<2-LeftRelease>', - '<S-RightMouse>', - '<S-RightRelease>', - '<2-RightMouse>', - '<2-RightRelease>', - '<M-MiddleMouse>', - '<M-MiddleRelease>', - '<2-MiddleMouse>', - '<2-MiddleRelease>', + '<C-LeftMouse><0,0>', + '<C-LeftDrag><0,1>', + '<C-LeftRelease><0,1>', + '<2-LeftMouse><0,1>', + '<2-LeftDrag><0,0>', + '<2-LeftRelease><0,0>', + '<M-MiddleMouse><0,0>', + '<M-MiddleDrag><0,1>', + '<M-MiddleRelease><0,1>', + '<2-MiddleMouse><0,1>', + '<2-MiddleDrag><0,0>', + '<2-MiddleRelease><0,0>', + '<S-RightMouse><0,0>', + '<S-RightDrag><0,1>', + '<S-RightRelease><0,1>', + '<2-RightMouse><0,1>', + '<2-RightDrag><0,0>', + '<2-RightRelease><0,0>', + '<S-X1Mouse><0,0>', + '<S-X1Drag><0,1>', + '<S-X1Release><0,1>', + '<2-X1Mouse><0,1>', + '<2-X1Drag><0,0>', + '<2-X1Release><0,0>', + '<S-X2Mouse><0,0>', + '<S-X2Drag><0,1>', + '<S-X2Release><0,1>', + '<2-X2Mouse><0,1>', + '<2-X2Drag><0,0>', + '<2-X2Release><0,0>', '<S-ScrollWheelUp>', '<S-ScrollWheelDown>', '<ScrollWheelUp>', @@ -588,15 +635,22 @@ describe('terminal input', function() '<S-ScrollWheelRight>', '<ScrollWheelLeft>', '<ScrollWheelRight>', - }) do + } + -- FIXME: The escape sequence to enable kitty keyboard mode doesn't work on Windows + if not is_os('win') then + table.insert(keys, '<C-I>') + table.insert(keys, '<C-M>') + table.insert(keys, '<C-[>') + end + for _, key in ipairs(keys) do feed(key) screen:expect(([[ | {4:~ }|*3 {5:[No Name] 0,0-1 All}| - %s{1: }{MATCH: *}| + %s^ {MATCH: *}| {3:-- TERMINAL --} | - ]]):format(key)) + ]]):format(key:gsub('<%d+,%d+>$', ''))) end end) end) @@ -624,7 +678,7 @@ if is_os('win') then > :: appended :: tty ready | > :: tty ready | > :: appended :: tty ready | - ^> {2: } | + ^> | :let @a = @a . "\n:: appended " . @a . "\n\n" | ]]) -- operator count is also taken into consideration @@ -635,7 +689,7 @@ if is_os('win') then > :: appended :: tty ready | > :: tty ready | > :: appended :: tty ready | - ^> {2: } | + ^> | :let @a = @a . "\n:: appended " . @a . "\n\n" | ]]) end) @@ -649,7 +703,7 @@ if is_os('win') then | > :: tty ready | > :: appended :: tty ready | - > {2: } | + > | | ^ | :put a | @@ -662,14 +716,14 @@ if is_os('win') then > :: appended :: tty ready | > :: tty ready | > :: appended :: tty ready | - ^> {2: } | + ^> | :6put a | ]]) end) end) end -describe('termopen()', function() +describe('termopen() (deprecated alias to `jobstart(…,{term=true})`)', function() before_each(clear) it('disallowed when textlocked and in cmdwin buffer', function() diff --git a/test/functional/terminal/channel_spec.lua b/test/functional/terminal/channel_spec.lua index 9912c1ff7b..bb97411f43 100644 --- a/test/functional/terminal/channel_spec.lua +++ b/test/functional/terminal/channel_spec.lua @@ -75,8 +75,8 @@ describe('terminal channel is closed and later released if', function() eq(chans - 1, eval('len(nvim_list_chans())')) end) - it('opened by termopen(), exited, and deleted by pressing a key', function() - command([[let id = termopen('echo')]]) + it('opened by jobstart(…,{term=true}), exited, and deleted by pressing a key', function() + command([[let id = jobstart('echo',{'term':v:true})]]) local chans = eval('len(nvim_list_chans())') -- wait for process to exit screen:expect({ any = '%[Process exited 0%]' }) @@ -96,8 +96,8 @@ describe('terminal channel is closed and later released if', function() end) -- This indirectly covers #16264 - it('opened by termopen(), exited, and deleted by :bdelete', function() - command([[let id = termopen('echo')]]) + it('opened by jobstart(…,{term=true}), exited, and deleted by :bdelete', function() + command([[let id = jobstart('echo', {'term':v:true})]]) local chans = eval('len(nvim_list_chans())') -- wait for process to exit screen:expect({ any = '%[Process exited 0%]' }) @@ -124,7 +124,7 @@ it('chansend sends lines to terminal channel in proper order', function() screen._default_attr_ids = nil local shells = is_os('win') and { 'cmd.exe', 'pwsh.exe -nop', 'powershell.exe -nop' } or { 'sh' } for _, sh in ipairs(shells) do - command([[let id = termopen(']] .. sh .. [[')]]) + command([[let id = jobstart(']] .. sh .. [[', {'term':v:true})]]) command([[call chansend(id, ['echo "hello"', 'echo "world"', ''])]]) screen:expect { any = [[echo "hello".*echo "world"]], @@ -149,7 +149,7 @@ describe('no crash when TermOpen autocommand', function() }) end) - it('processes job exit event when using termopen()', function() + it('processes job exit event when using jobstart(…,{term=true})', function() command([[autocmd TermOpen * call input('')]]) async_meths.nvim_command('terminal foobar') screen:expect { @@ -179,7 +179,7 @@ describe('no crash when TermOpen autocommand', function() assert_alive() end) - it('wipes buffer and processes events when using termopen()', function() + it('wipes buffer and processes events when using jobstart(…,{term=true})', function() command([[autocmd TermOpen * bwipe! | call input('')]]) async_meths.nvim_command('terminal foobar') screen:expect { diff --git a/test/functional/terminal/clipboard_spec.lua b/test/functional/terminal/clipboard_spec.lua index 4a1a0e29fd..f0ce407eaa 100644 --- a/test/functional/terminal/clipboard_spec.lua +++ b/test/functional/terminal/clipboard_spec.lua @@ -56,7 +56,7 @@ describe(':terminal', function() return string.format('\027]52;;%s\027\\', arg) end - fn.termopen({ testprg('shell-test'), '-t', osc52(encoded) }) + fn.jobstart({ testprg('shell-test'), '-t', osc52(encoded) }, { term = true }) retry(nil, 1000, function() eq(text, exec_lua([[ return vim.g.clipboard_data ]])) diff --git a/test/functional/terminal/cursor_spec.lua b/test/functional/terminal/cursor_spec.lua index f223cdd417..83408e41b3 100644 --- a/test/functional/terminal/cursor_spec.lua +++ b/test/functional/terminal/cursor_spec.lua @@ -1,13 +1,12 @@ local t = require('test.testutil') local n = require('test.functional.testnvim')() -local Screen = require('test.functional.ui.screen') local tt = require('test.functional.testterm') local feed, clear = n.feed, n.clear local testprg, command = n.testprg, n.command local eq, eval = t.eq, n.eval local matches = t.matches -local poke_eventloop = n.poke_eventloop +local call = n.call local hide_cursor = tt.hide_cursor local show_cursor = tt.show_cursor local is_os = t.is_os @@ -16,16 +15,27 @@ local skip = t.skip describe(':terminal cursor', function() local screen + local terminal_mode_idx ---@type number + before_each(function() clear() screen = tt.setup_screen() + + if terminal_mode_idx == nil then + for i, v in ipairs(screen._mode_info) do + if v.name == 'terminal' then + terminal_mode_idx = i + end + end + assert(terminal_mode_idx) + end end) it('moves the screen cursor when focused', function() tt.feed_data('testing cursor') screen:expect([[ tty ready | - testing cursor{1: } | + testing cursor^ | |*4 {3:-- TERMINAL --} | ]]) @@ -35,7 +45,7 @@ describe(':terminal cursor', function() feed('<c-\\><c-n>') screen:expect([[ tty ready | - {2:^ } | + ^ | |*5 ]]) end) @@ -49,7 +59,7 @@ describe(':terminal cursor', function() screen:expect([[ {7: 1 }tty ready | {7: 2 }^rows: 6, cols: 46 | - {7: 3 }{2: } | + {7: 3 } | {7: 4 } | {7: 5 } | {7: 6 } | @@ -61,7 +71,7 @@ describe(':terminal cursor', function() screen:expect([[ {7: 1 }tty ready | {7: 2 }^rows: 6, cols: 46 | - {7: 3 }{2: } | + {7: 3 } | {7: 4 } | {7: 5 } | {7: 6 } | @@ -72,7 +82,7 @@ describe(':terminal cursor', function() screen:expect([[ {7: 1 }tty ready | {7: 2 }rows: 6, cols: 46 | - {7: 3 }{1: } | + {7: 3 }^ | {7: 4 } | {7: 5 } | {7: 6 } | @@ -82,8 +92,8 @@ describe(':terminal cursor', function() end) describe('when invisible', function() - it('is not highlighted and is detached from screen cursor', function() - skip(is_os('win')) + it('is not highlighted', function() + skip(is_os('win'), '#31587') hide_cursor() screen:expect([[ tty ready | @@ -93,59 +103,259 @@ describe(':terminal cursor', function() show_cursor() screen:expect([[ tty ready | - {1: } | + ^ | |*4 {3:-- TERMINAL --} | ]]) -- same for when the terminal is unfocused feed('<c-\\><c-n>') hide_cursor() + screen:expect({ + grid = [[ + tty ready | + ^ | + |*5 + ]], + unchanged = true, + }) + show_cursor() + screen:expect({ + grid = [[ + tty ready | + ^ | + |*5 + ]], + unchanged = true, + }) + end) + + it('becomes visible when exiting Terminal mode', function() + skip(is_os('win'), '#31587') + hide_cursor() + screen:expect([[ + tty ready | + |*5 + {3:-- TERMINAL --} | + ]]) + feed('<c-\\><c-n>') screen:expect([[ tty ready | ^ | |*5 ]]) - show_cursor() + feed('i') screen:expect([[ tty ready | - {2:^ } | |*5 + {3:-- TERMINAL --} | ]]) end) end) -end) -describe('cursor with customized highlighting', function() - local screen + it('can be modified by application #3681 #31685', function() + skip(is_os('win'), '#31587') - before_each(function() - clear() - command('highlight TermCursor ctermfg=45 ctermbg=46 cterm=NONE') - command('highlight TermCursorNC ctermfg=55 ctermbg=56 cterm=NONE') - screen = Screen.new(50, 7, { rgb = false }) - screen:set_default_attr_ids({ - [1] = { foreground = 45, background = 46 }, - [2] = { foreground = 55, background = 56 }, - [3] = { bold = true }, + local states = { + [1] = { blink = true, shape = 'block' }, + [2] = { blink = false, shape = 'block' }, + [3] = { blink = true, shape = 'horizontal' }, + [4] = { blink = false, shape = 'horizontal' }, + [5] = { blink = true, shape = 'vertical' }, + [6] = { blink = false, shape = 'vertical' }, + } + + for k, v in pairs(states) do + tt.feed_csi(('%d q'):format(k)) + screen:expect({ + grid = [[ + tty ready | + ^ | + |*4 + {3:-- TERMINAL --} | + ]], + condition = function() + if v.blink then + eq(500, screen._mode_info[terminal_mode_idx].blinkon) + eq(500, screen._mode_info[terminal_mode_idx].blinkoff) + else + eq(0, screen._mode_info[terminal_mode_idx].blinkon) + eq(0, screen._mode_info[terminal_mode_idx].blinkoff) + end + + eq(v.shape, screen._mode_info[terminal_mode_idx].cursor_shape) + + -- Cell percentages are hard coded for each shape in terminal.c + if v.shape == 'horizontal' then + eq(20, screen._mode_info[terminal_mode_idx].cell_percentage) + elseif v.shape == 'vertical' then + eq(25, screen._mode_info[terminal_mode_idx].cell_percentage) + end + end, + }) + end + + feed([[<C-\><C-N>]]) + + screen:expect([[ + tty ready | + ^ | + |*5 + ]]) + + -- Cursor returns to default on TermLeave + eq(500, screen._mode_info[terminal_mode_idx].blinkon) + eq(500, screen._mode_info[terminal_mode_idx].blinkoff) + eq('block', screen._mode_info[terminal_mode_idx].cursor_shape) + end) + + it('can be modified per terminal', function() + skip(is_os('win'), '#31587') + + -- Set cursor to vertical bar with blink + tt.feed_csi('5 q') + screen:expect({ + grid = [[ + tty ready | + ^ | + |*4 + {3:-- TERMINAL --} | + ]], + condition = function() + eq(500, screen._mode_info[terminal_mode_idx].blinkon) + eq(500, screen._mode_info[terminal_mode_idx].blinkoff) + eq('vertical', screen._mode_info[terminal_mode_idx].cursor_shape) + end, + }) + + tt.hide_cursor() + screen:expect({ + grid = [[ + tty ready | + | + |*4 + {3:-- TERMINAL --} | + ]], + condition = function() + eq(500, screen._mode_info[terminal_mode_idx].blinkon) + eq(500, screen._mode_info[terminal_mode_idx].blinkoff) + eq('vertical', screen._mode_info[terminal_mode_idx].cursor_shape) + end, }) - command('call termopen(["' .. testprg('tty-test') .. '"])') + + -- Exit terminal mode to reset terminal cursor settings to default and + -- create a new terminal window + feed([[<C-\><C-N>]]) + command('set statusline=~~~') + command('new') + call('jobstart', { testprg('tty-test') }, { term = true }) feed('i') - poke_eventloop() + screen:expect({ + grid = [[ + tty ready | + ^ | + {17:~~~ }| + rows: 2, cols: 50 | + | + {18:~~~ }| + {3:-- TERMINAL --} | + ]], + condition = function() + -- New terminal, cursor resets to defaults + eq(500, screen._mode_info[terminal_mode_idx].blinkon) + eq(500, screen._mode_info[terminal_mode_idx].blinkoff) + eq('block', screen._mode_info[terminal_mode_idx].cursor_shape) + end, + }) + + -- Set cursor to underline, no blink + tt.feed_csi('4 q') + screen:expect({ + grid = [[ + tty ready | + ^ | + {17:~~~ }| + rows: 2, cols: 50 | + | + {18:~~~ }| + {3:-- TERMINAL --} | + ]], + condition = function() + eq(0, screen._mode_info[terminal_mode_idx].blinkon) + eq(0, screen._mode_info[terminal_mode_idx].blinkoff) + eq('horizontal', screen._mode_info[terminal_mode_idx].cursor_shape) + end, + }) + + -- Switch back to first terminal, cursor should still be hidden + command('wincmd p') + screen:expect({ + grid = [[ + tty ready | + | + {18:~~~ }| + rows: 2, cols: 50 | + | + {17:~~~ }| + {3:-- TERMINAL --} | + ]], + condition = function() + eq(500, screen._mode_info[terminal_mode_idx].blinkon) + eq(500, screen._mode_info[terminal_mode_idx].blinkoff) + eq('vertical', screen._mode_info[terminal_mode_idx].cursor_shape) + end, + }) end) - it('overrides the default highlighting', function() + it('can be positioned arbitrarily', function() + clear() + screen = tt.setup_child_nvim({ + '-u', + 'NONE', + '-i', + 'NONE', + '--cmd', + n.nvim_set .. ' noshowmode', + }) + screen:expect([[ + ^ | + ~ |*4 + | + {3:-- TERMINAL --} | + ]]) + + feed('i<Tab>') screen:expect([[ - tty ready | - {1: } | - |*4 + ^ | + ~ |*4 + | {3:-- TERMINAL --} | ]]) - feed('<c-\\><c-n>') + end) + + it('preserves guicursor value on TermLeave #31612', function() + eq(3, screen._mode_info[terminal_mode_idx].hl_id) + + -- Change 'guicursor' while terminal mode is active + command('set guicursor+=t:Error') + + local error_hl_id = call('hlID', 'Error') + + screen:expect({ + condition = function() + eq(error_hl_id, screen._mode_info[terminal_mode_idx].hl_id) + end, + }) + + -- Exit terminal mode + feed([[<C-\><C-N>]]) + screen:expect([[ tty ready | - {2:^ } | + ^ | |*5 ]]) + + eq(error_hl_id, screen._mode_info[terminal_mode_idx].hl_id) end) end) @@ -171,19 +381,10 @@ describe('buffer cursor position is correct in terminal without number column', }, { cols = 70, }) - screen:set_default_attr_ids({ - [1] = { foreground = 253, background = 11 }, - [2] = { reverse = true }, - [3] = { bold = true }, - [4] = { background = 11 }, - }) - -- Also check for real cursor position, as it is used for stuff like input methods - screen._handle_busy_start = function() end - screen._handle_busy_stop = function() end screen:expect([[ |*4 Entering Ex mode. Type "visual" to go to Normal mode. | - :{2:^ } | + :^ | {3:-- TERMINAL --} | ]]) end @@ -200,7 +401,7 @@ describe('buffer cursor position is correct in terminal without number column', screen:expect([[ |*4 Entering Ex mode. Type "visual" to go to Normal mode. | - :aaaaaaaa{2:^ } | + :aaaaaaaa^ | {3:-- TERMINAL --} | ]]) eq({ 6, 9 }, eval('nvim_win_get_cursor(0)')) @@ -208,7 +409,7 @@ describe('buffer cursor position is correct in terminal without number column', screen:expect([[ |*4 Entering Ex mode. Type "visual" to go to Normal mode. | - :aaaaaaa^a{4: } | + :aaaaaaa^a | | ]]) eq({ 6, 8 }, eval('nvim_win_get_cursor(0)')) @@ -219,7 +420,7 @@ describe('buffer cursor position is correct in terminal without number column', screen:expect([[ |*4 Entering Ex mode. Type "visual" to go to Normal mode. | - :aaaaaa{2:^a}a | + :aaaaaa^aa | {3:-- TERMINAL --} | ]]) eq({ 6, 7 }, eval('nvim_win_get_cursor(0)')) @@ -227,7 +428,7 @@ describe('buffer cursor position is correct in terminal without number column', screen:expect([[ |*4 Entering Ex mode. Type "visual" to go to Normal mode. | - :aaaaa^a{4:a}a | + :aaaaa^aaa | | ]]) eq({ 6, 6 }, eval('nvim_win_get_cursor(0)')) @@ -238,7 +439,7 @@ describe('buffer cursor position is correct in terminal without number column', screen:expect([[ |*4 Entering Ex mode. Type "visual" to go to Normal mode. | - :a{2:^a}aaaaaa | + :a^aaaaaaa | {3:-- TERMINAL --} | ]]) eq({ 6, 2 }, eval('nvim_win_get_cursor(0)')) @@ -246,7 +447,7 @@ describe('buffer cursor position is correct in terminal without number column', screen:expect([[ |*4 Entering Ex mode. Type "visual" to go to Normal mode. | - :^a{4:a}aaaaaa | + :^aaaaaaaa | | ]]) eq({ 6, 1 }, eval('nvim_win_get_cursor(0)')) @@ -263,7 +464,7 @@ describe('buffer cursor position is correct in terminal without number column', screen:expect([[ |*4 Entering Ex mode. Type "visual" to go to Normal mode. | - :µµµµµµµµ{2:^ } | + :µµµµµµµµ^ | {3:-- TERMINAL --} | ]]) eq({ 6, 17 }, eval('nvim_win_get_cursor(0)')) @@ -271,7 +472,7 @@ describe('buffer cursor position is correct in terminal without number column', screen:expect([[ |*4 Entering Ex mode. Type "visual" to go to Normal mode. | - :µµµµµµµ^µ{4: } | + :µµµµµµµ^µ | | ]]) eq({ 6, 15 }, eval('nvim_win_get_cursor(0)')) @@ -282,7 +483,7 @@ describe('buffer cursor position is correct in terminal without number column', screen:expect([[ |*4 Entering Ex mode. Type "visual" to go to Normal mode. | - :µµµµµµ{2:^µ}µ | + :µµµµµµ^µµ | {3:-- TERMINAL --} | ]]) eq({ 6, 13 }, eval('nvim_win_get_cursor(0)')) @@ -290,7 +491,7 @@ describe('buffer cursor position is correct in terminal without number column', screen:expect([[ |*4 Entering Ex mode. Type "visual" to go to Normal mode. | - :µµµµµ^µ{4:µ}µ | + :µµµµµ^µµµ | | ]]) eq({ 6, 11 }, eval('nvim_win_get_cursor(0)')) @@ -301,7 +502,7 @@ describe('buffer cursor position is correct in terminal without number column', screen:expect([[ |*4 Entering Ex mode. Type "visual" to go to Normal mode. | - :µ{2:^µ}µµµµµµ | + :µ^µµµµµµµ | {3:-- TERMINAL --} | ]]) eq({ 6, 3 }, eval('nvim_win_get_cursor(0)')) @@ -309,7 +510,7 @@ describe('buffer cursor position is correct in terminal without number column', screen:expect([[ |*4 Entering Ex mode. Type "visual" to go to Normal mode. | - :^µ{4:µ}µµµµµµ | + :^µµµµµµµµ | | ]]) eq({ 6, 1 }, eval('nvim_win_get_cursor(0)')) @@ -326,7 +527,7 @@ describe('buffer cursor position is correct in terminal without number column', screen:expect([[ |*4 Entering Ex mode. Type "visual" to go to Normal mode. | - :µ̳µ̳µ̳µ̳µ̳µ̳µ̳µ̳{2:^ } | + :µ̳µ̳µ̳µ̳µ̳µ̳µ̳µ̳^ | {3:-- TERMINAL --} | ]]) eq({ 6, 33 }, eval('nvim_win_get_cursor(0)')) @@ -334,7 +535,7 @@ describe('buffer cursor position is correct in terminal without number column', screen:expect([[ |*4 Entering Ex mode. Type "visual" to go to Normal mode. | - :µ̳µ̳µ̳µ̳µ̳µ̳µ̳^µ̳{4: } | + :µ̳µ̳µ̳µ̳µ̳µ̳µ̳^µ̳ | | ]]) eq({ 6, 29 }, eval('nvim_win_get_cursor(0)')) @@ -346,7 +547,7 @@ describe('buffer cursor position is correct in terminal without number column', screen:expect([[ |*4 Entering Ex mode. Type "visual" to go to Normal mode. | - :µ̳µ̳µ̳µ̳µ̳µ̳{2:^µ̳}µ̳ | + :µ̳µ̳µ̳µ̳µ̳µ̳^µ̳µ̳ | {3:-- TERMINAL --} | ]]) eq({ 6, 25 }, eval('nvim_win_get_cursor(0)')) @@ -354,7 +555,7 @@ describe('buffer cursor position is correct in terminal without number column', screen:expect([[ |*4 Entering Ex mode. Type "visual" to go to Normal mode. | - :µ̳µ̳µ̳µ̳µ̳^µ̳{4:µ̳}µ̳ | + :µ̳µ̳µ̳µ̳µ̳^µ̳µ̳µ̳ | | ]]) eq({ 6, 21 }, eval('nvim_win_get_cursor(0)')) @@ -366,7 +567,7 @@ describe('buffer cursor position is correct in terminal without number column', screen:expect([[ |*4 Entering Ex mode. Type "visual" to go to Normal mode. | - :µ̳{2:^µ̳}µ̳µ̳µ̳µ̳µ̳µ̳ | + :µ̳^µ̳µ̳µ̳µ̳µ̳µ̳µ̳ | {3:-- TERMINAL --} | ]]) eq({ 6, 5 }, eval('nvim_win_get_cursor(0)')) @@ -374,7 +575,7 @@ describe('buffer cursor position is correct in terminal without number column', screen:expect([[ |*4 Entering Ex mode. Type "visual" to go to Normal mode. | - :^µ̳{4:µ̳}µ̳µ̳µ̳µ̳µ̳µ̳ | + :^µ̳µ̳µ̳µ̳µ̳µ̳µ̳µ̳ | | ]]) eq({ 6, 1 }, eval('nvim_win_get_cursor(0)')) @@ -391,7 +592,7 @@ describe('buffer cursor position is correct in terminal without number column', screen:expect([[ |*4 Entering Ex mode. Type "visual" to go to Normal mode. | - :哦哦哦哦哦哦哦哦{2:^ } | + :哦哦哦哦哦哦哦哦^ | {3:-- TERMINAL --} | ]]) eq({ 6, 25 }, eval('nvim_win_get_cursor(0)')) @@ -399,7 +600,7 @@ describe('buffer cursor position is correct in terminal without number column', screen:expect([[ |*4 Entering Ex mode. Type "visual" to go to Normal mode. | - :哦哦哦哦哦哦哦^哦{4: } | + :哦哦哦哦哦哦哦^哦 | | ]]) eq({ 6, 22 }, eval('nvim_win_get_cursor(0)')) @@ -410,7 +611,7 @@ describe('buffer cursor position is correct in terminal without number column', screen:expect([[ |*4 Entering Ex mode. Type "visual" to go to Normal mode. | - :哦哦哦哦哦哦{2:^哦}哦 | + :哦哦哦哦哦哦^哦哦 | {3:-- TERMINAL --} | ]]) eq({ 6, 19 }, eval('nvim_win_get_cursor(0)')) @@ -418,7 +619,7 @@ describe('buffer cursor position is correct in terminal without number column', screen:expect([[ |*4 Entering Ex mode. Type "visual" to go to Normal mode. | - :哦哦哦哦哦^哦{4:哦}哦 | + :哦哦哦哦哦^哦哦哦 | | ]]) eq({ 6, 16 }, eval('nvim_win_get_cursor(0)')) @@ -429,7 +630,7 @@ describe('buffer cursor position is correct in terminal without number column', screen:expect([[ |*4 Entering Ex mode. Type "visual" to go to Normal mode. | - :哦{2:^哦}哦哦哦哦哦哦 | + :哦^哦哦哦哦哦哦哦 | {3:-- TERMINAL --} | ]]) eq({ 6, 4 }, eval('nvim_win_get_cursor(0)')) @@ -437,7 +638,7 @@ describe('buffer cursor position is correct in terminal without number column', screen:expect([[ |*4 Entering Ex mode. Type "visual" to go to Normal mode. | - :^哦{4:哦}哦哦哦哦哦哦 | + :^哦哦哦哦哦哦哦哦 | | ]]) eq({ 6, 1 }, eval('nvim_win_get_cursor(0)')) @@ -450,7 +651,7 @@ describe('buffer cursor position is correct in terminal without number column', screen:expect([[ |*4 Entering Ex mode. Type "visual" to go to Normal mode. | - :aaaaaaaa {2:^ } | + :aaaaaaaa ^ | {3:-- TERMINAL --} | ]]) matches('^:aaaaaaaa [ ]*$', eval('nvim_get_current_line()')) @@ -459,7 +660,7 @@ describe('buffer cursor position is correct in terminal without number column', screen:expect([[ |*4 Entering Ex mode. Type "visual" to go to Normal mode. | - :aaaaaaaa ^ {4: } | + :aaaaaaaa ^ | | ]]) eq({ 6, 12 }, eval('nvim_win_get_cursor(0)')) @@ -488,30 +689,20 @@ describe('buffer cursor position is correct in terminal with number column', fun }, { cols = 70, }) - screen:set_default_attr_ids({ - [1] = { foreground = 253, background = 11 }, - [2] = { reverse = true }, - [3] = { bold = true }, - [4] = { background = 11 }, - [7] = { foreground = 130 }, - }) - -- Also check for real cursor position, as it is used for stuff like input methods - screen._handle_busy_start = function() end - screen._handle_busy_stop = function() end screen:expect([[ {7: 1 } | {7: 2 } | {7: 3 } | {7: 4 } | {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | - {7: 6 }:{2:^ } | + {7: 6 }:^ | {3:-- TERMINAL --} | ]]) end before_each(function() clear() - command('set number') + command('au TermOpen * set number') end) describe('in a line with no multibyte chars or trailing spaces,', function() @@ -527,7 +718,7 @@ describe('buffer cursor position is correct in terminal with number column', fun {7: 3 } | {7: 4 } | {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | - {7: 6 }:aaaaaaaa{2:^ } | + {7: 6 }:aaaaaaaa^ | {3:-- TERMINAL --} | ]]) eq({ 6, 9 }, eval('nvim_win_get_cursor(0)')) @@ -538,7 +729,7 @@ describe('buffer cursor position is correct in terminal with number column', fun {7: 3 } | {7: 4 } | {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | - {7: 6 }:aaaaaaa^a{4: } | + {7: 6 }:aaaaaaa^a | | ]]) eq({ 6, 8 }, eval('nvim_win_get_cursor(0)')) @@ -552,7 +743,7 @@ describe('buffer cursor position is correct in terminal with number column', fun {7: 3 } | {7: 4 } | {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | - {7: 6 }:aaaaaa{2:^a}a | + {7: 6 }:aaaaaa^aa | {3:-- TERMINAL --} | ]]) eq({ 6, 7 }, eval('nvim_win_get_cursor(0)')) @@ -563,7 +754,7 @@ describe('buffer cursor position is correct in terminal with number column', fun {7: 3 } | {7: 4 } | {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | - {7: 6 }:aaaaa^a{4:a}a | + {7: 6 }:aaaaa^aaa | | ]]) eq({ 6, 6 }, eval('nvim_win_get_cursor(0)')) @@ -577,7 +768,7 @@ describe('buffer cursor position is correct in terminal with number column', fun {7: 3 } | {7: 4 } | {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | - {7: 6 }:a{2:^a}aaaaaa | + {7: 6 }:a^aaaaaaa | {3:-- TERMINAL --} | ]]) eq({ 6, 2 }, eval('nvim_win_get_cursor(0)')) @@ -588,7 +779,7 @@ describe('buffer cursor position is correct in terminal with number column', fun {7: 3 } | {7: 4 } | {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | - {7: 6 }:^a{4:a}aaaaaa | + {7: 6 }:^aaaaaaaa | | ]]) eq({ 6, 1 }, eval('nvim_win_get_cursor(0)')) @@ -608,7 +799,7 @@ describe('buffer cursor position is correct in terminal with number column', fun {7: 3 } | {7: 4 } | {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | - {7: 6 }:µµµµµµµµ{2:^ } | + {7: 6 }:µµµµµµµµ^ | {3:-- TERMINAL --} | ]]) eq({ 6, 17 }, eval('nvim_win_get_cursor(0)')) @@ -619,7 +810,7 @@ describe('buffer cursor position is correct in terminal with number column', fun {7: 3 } | {7: 4 } | {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | - {7: 6 }:µµµµµµµ^µ{4: } | + {7: 6 }:µµµµµµµ^µ | | ]]) eq({ 6, 15 }, eval('nvim_win_get_cursor(0)')) @@ -633,7 +824,7 @@ describe('buffer cursor position is correct in terminal with number column', fun {7: 3 } | {7: 4 } | {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | - {7: 6 }:µµµµµµ{2:^µ}µ | + {7: 6 }:µµµµµµ^µµ | {3:-- TERMINAL --} | ]]) eq({ 6, 13 }, eval('nvim_win_get_cursor(0)')) @@ -644,7 +835,7 @@ describe('buffer cursor position is correct in terminal with number column', fun {7: 3 } | {7: 4 } | {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | - {7: 6 }:µµµµµ^µ{4:µ}µ | + {7: 6 }:µµµµµ^µµµ | | ]]) eq({ 6, 11 }, eval('nvim_win_get_cursor(0)')) @@ -658,7 +849,7 @@ describe('buffer cursor position is correct in terminal with number column', fun {7: 3 } | {7: 4 } | {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | - {7: 6 }:µ{2:^µ}µµµµµµ | + {7: 6 }:µ^µµµµµµµ | {3:-- TERMINAL --} | ]]) eq({ 6, 3 }, eval('nvim_win_get_cursor(0)')) @@ -669,7 +860,7 @@ describe('buffer cursor position is correct in terminal with number column', fun {7: 3 } | {7: 4 } | {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | - {7: 6 }:^µ{4:µ}µµµµµµ | + {7: 6 }:^µµµµµµµµ | | ]]) eq({ 6, 1 }, eval('nvim_win_get_cursor(0)')) @@ -689,7 +880,7 @@ describe('buffer cursor position is correct in terminal with number column', fun {7: 3 } | {7: 4 } | {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | - {7: 6 }:µ̳µ̳µ̳µ̳µ̳µ̳µ̳µ̳{2:^ } | + {7: 6 }:µ̳µ̳µ̳µ̳µ̳µ̳µ̳µ̳^ | {3:-- TERMINAL --} | ]]) eq({ 6, 33 }, eval('nvim_win_get_cursor(0)')) @@ -700,7 +891,7 @@ describe('buffer cursor position is correct in terminal with number column', fun {7: 3 } | {7: 4 } | {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | - {7: 6 }:µ̳µ̳µ̳µ̳µ̳µ̳µ̳^µ̳{4: } | + {7: 6 }:µ̳µ̳µ̳µ̳µ̳µ̳µ̳^µ̳ | | ]]) eq({ 6, 29 }, eval('nvim_win_get_cursor(0)')) @@ -715,7 +906,7 @@ describe('buffer cursor position is correct in terminal with number column', fun {7: 3 } | {7: 4 } | {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | - {7: 6 }:µ̳µ̳µ̳µ̳µ̳µ̳{2:^µ̳}µ̳ | + {7: 6 }:µ̳µ̳µ̳µ̳µ̳µ̳^µ̳µ̳ | {3:-- TERMINAL --} | ]]) eq({ 6, 25 }, eval('nvim_win_get_cursor(0)')) @@ -726,7 +917,7 @@ describe('buffer cursor position is correct in terminal with number column', fun {7: 3 } | {7: 4 } | {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | - {7: 6 }:µ̳µ̳µ̳µ̳µ̳^µ̳{4:µ̳}µ̳ | + {7: 6 }:µ̳µ̳µ̳µ̳µ̳^µ̳µ̳µ̳ | | ]]) eq({ 6, 21 }, eval('nvim_win_get_cursor(0)')) @@ -741,7 +932,7 @@ describe('buffer cursor position is correct in terminal with number column', fun {7: 3 } | {7: 4 } | {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | - {7: 6 }:µ̳{2:^µ̳}µ̳µ̳µ̳µ̳µ̳µ̳ | + {7: 6 }:µ̳^µ̳µ̳µ̳µ̳µ̳µ̳µ̳ | {3:-- TERMINAL --} | ]]) eq({ 6, 5 }, eval('nvim_win_get_cursor(0)')) @@ -752,7 +943,7 @@ describe('buffer cursor position is correct in terminal with number column', fun {7: 3 } | {7: 4 } | {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | - {7: 6 }:^µ̳{4:µ̳}µ̳µ̳µ̳µ̳µ̳µ̳ | + {7: 6 }:^µ̳µ̳µ̳µ̳µ̳µ̳µ̳µ̳ | | ]]) eq({ 6, 1 }, eval('nvim_win_get_cursor(0)')) @@ -772,7 +963,7 @@ describe('buffer cursor position is correct in terminal with number column', fun {7: 3 } | {7: 4 } | {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | - {7: 6 }:哦哦哦哦哦哦哦哦{2:^ } | + {7: 6 }:哦哦哦哦哦哦哦哦^ | {3:-- TERMINAL --} | ]]) eq({ 6, 25 }, eval('nvim_win_get_cursor(0)')) @@ -783,7 +974,7 @@ describe('buffer cursor position is correct in terminal with number column', fun {7: 3 } | {7: 4 } | {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | - {7: 6 }:哦哦哦哦哦哦哦^哦{4: } | + {7: 6 }:哦哦哦哦哦哦哦^哦 | | ]]) eq({ 6, 22 }, eval('nvim_win_get_cursor(0)')) @@ -797,7 +988,7 @@ describe('buffer cursor position is correct in terminal with number column', fun {7: 3 } | {7: 4 } | {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | - {7: 6 }:哦哦哦哦哦哦{2:^哦}哦 | + {7: 6 }:哦哦哦哦哦哦^哦哦 | {3:-- TERMINAL --} | ]]) eq({ 6, 19 }, eval('nvim_win_get_cursor(0)')) @@ -808,7 +999,7 @@ describe('buffer cursor position is correct in terminal with number column', fun {7: 3 } | {7: 4 } | {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | - {7: 6 }:哦哦哦哦哦^哦{4:哦}哦 | + {7: 6 }:哦哦哦哦哦^哦哦哦 | | ]]) eq({ 6, 16 }, eval('nvim_win_get_cursor(0)')) @@ -822,7 +1013,7 @@ describe('buffer cursor position is correct in terminal with number column', fun {7: 3 } | {7: 4 } | {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | - {7: 6 }:哦{2:^哦}哦哦哦哦哦哦 | + {7: 6 }:哦^哦哦哦哦哦哦哦 | {3:-- TERMINAL --} | ]]) eq({ 6, 4 }, eval('nvim_win_get_cursor(0)')) @@ -833,7 +1024,7 @@ describe('buffer cursor position is correct in terminal with number column', fun {7: 3 } | {7: 4 } | {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | - {7: 6 }:^哦{4:哦}哦哦哦哦哦哦 | + {7: 6 }:^哦哦哦哦哦哦哦哦 | | ]]) eq({ 6, 1 }, eval('nvim_win_get_cursor(0)')) @@ -849,7 +1040,7 @@ describe('buffer cursor position is correct in terminal with number column', fun {7: 3 } | {7: 4 } | {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | - {7: 6 }:aaaaaaaa {2:^ } | + {7: 6 }:aaaaaaaa ^ | {3:-- TERMINAL --} | ]]) matches('^:aaaaaaaa [ ]*$', eval('nvim_get_current_line()')) @@ -861,7 +1052,7 @@ describe('buffer cursor position is correct in terminal with number column', fun {7: 3 } | {7: 4 } | {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | - {7: 6 }:aaaaaaaa ^ {4: } | + {7: 6 }:aaaaaaaa ^ | | ]]) eq({ 6, 12 }, eval('nvim_win_get_cursor(0)')) diff --git a/test/functional/terminal/ex_terminal_spec.lua b/test/functional/terminal/ex_terminal_spec.lua index 5ebe7bd4fc..c29a1e9cd4 100644 --- a/test/functional/terminal/ex_terminal_spec.lua +++ b/test/functional/terminal/ex_terminal_spec.lua @@ -175,8 +175,8 @@ local function test_terminal_with_fake_shell(backslash) api.nvim_set_option_value('shellxquote', '', {}) -- win: avoid extra quotes end) - it('with no argument, acts like termopen()', function() - command('autocmd! nvim_terminal TermClose') + it('with no argument, acts like jobstart(…,{term=true})', function() + command('autocmd! nvim.terminal TermClose') feed_command('terminal') screen:expect([[ ^ready $ | @@ -196,7 +196,7 @@ local function test_terminal_with_fake_shell(backslash) ]]) end) - it("with no argument, but 'shell' has arguments, acts like termopen()", function() + it("with no argument, but 'shell' has arguments, acts like jobstart(…,{term=true})", function() api.nvim_set_option_value('shell', shell_path .. ' INTERACT', {}) feed_command('terminal') screen:expect([[ @@ -246,7 +246,7 @@ local function test_terminal_with_fake_shell(backslash) end) it('ignores writes if the backing stream closes', function() - command('autocmd! nvim_terminal TermClose') + command('autocmd! nvim.terminal TermClose') feed_command('terminal') feed('iiXXXXXXX') poke_eventloop() @@ -258,14 +258,14 @@ local function test_terminal_with_fake_shell(backslash) end) it('works with findfile()', function() - command('autocmd! nvim_terminal TermClose') + command('autocmd! nvim.terminal TermClose') feed_command('terminal') eq('term://', string.match(eval('bufname("%")'), '^term://')) eq('scripts/shadacat.py', eval('findfile("scripts/shadacat.py", ".")')) end) it('works with :find', function() - command('autocmd! nvim_terminal TermClose') + command('autocmd! nvim.terminal TermClose') feed_command('terminal') screen:expect([[ ^ready $ | diff --git a/test/functional/terminal/highlight_spec.lua b/test/functional/terminal/highlight_spec.lua index 7822a27b93..0afbd010f7 100644 --- a/test/functional/terminal/highlight_spec.lua +++ b/test/functional/terminal/highlight_spec.lua @@ -6,7 +6,6 @@ local tt = require('test.functional.testterm') local feed, clear = n.feed, n.clear local api = n.api local testprg, command = n.testprg, n.command -local nvim_prog_abs = n.nvim_prog_abs local fn = n.fn local nvim_set = n.nvim_set local is_os = t.is_os @@ -33,11 +32,11 @@ describe(':terminal highlight', function() [12] = { bold = true, underdouble = true }, [13] = { italic = true, undercurl = true }, }) - command(("enew | call termopen(['%s'])"):format(testprg('tty-test'))) + command(("enew | call jobstart(['%s'], {'term':v:true})"):format(testprg('tty-test'))) feed('i') screen:expect([[ tty ready | - {10: } | + ^ | |*4 {5:-- TERMINAL --} | ]]) @@ -61,7 +60,7 @@ describe(':terminal highlight', function() skip(is_os('win')) screen:expect(sub([[ tty ready | - {NUM:text}text{10: } | + {NUM:text}text^ | |*4 {5:-- TERMINAL --} | ]])) @@ -84,7 +83,7 @@ describe(':terminal highlight', function() line6 | line7 | line8 | - {10: } | + ^ | {5:-- TERMINAL --} | ]]) feed('<c-\\><c-n>gg') @@ -150,8 +149,8 @@ it(':terminal highlight has lower precedence than editor #9964', function() }, }) -- Child nvim process in :terminal (with cterm colors). - fn.termopen({ - nvim_prog_abs(), + fn.jobstart({ + n.nvim_prog, '-n', '-u', 'NORC', @@ -163,6 +162,7 @@ it(':terminal highlight has lower precedence than editor #9964', function() '+norm! ichild nvim', '+norm! oline 2', }, { + term = true, env = { VIMRUNTIME = os.getenv('VIMRUNTIME'), }, @@ -195,12 +195,12 @@ it('CursorLine and CursorColumn work in :terminal buffer in Normal mode', functi local screen = Screen.new(50, 7) screen:set_default_attr_ids({ [1] = { background = Screen.colors.Grey90 }, -- CursorLine, CursorColumn - [2] = { reverse = true }, -- TermCursor + [2] = { reverse = true }, [3] = { bold = true }, -- ModeMsg [4] = { background = Screen.colors.Grey90, reverse = true }, [5] = { background = Screen.colors.Red }, }) - command(("enew | call termopen(['%s'])"):format(testprg('tty-test'))) + command(("enew | call jobstart(['%s'], {'term':v:true})"):format(testprg('tty-test'))) screen:expect([[ ^tty ready | |*6 @@ -234,7 +234,7 @@ it('CursorLine and CursorColumn work in :terminal buffer in Normal mode', functi foobar foobar foobar foobar foobar foobar foobar f| oobar foobar foobar foobar foobar foobar foobar fo| obar foobar foobar foobar foobar foobar foobar foo| - bar foobar{2: } | + bar foobar^ | {3:-- TERMINAL --} | ]]) -- Leaving terminal mode restores old values. @@ -248,46 +248,60 @@ it('CursorLine and CursorColumn work in :terminal buffer in Normal mode', functi {1:bar fooba^r }| | ]]) - -- CursorLine and CursorColumn are combined with TermCursorNC. - command('highlight TermCursorNC gui=reverse') + + -- Skip the rest of these tests on Windows #31587 + if is_os('win') then + return + end + + -- CursorLine and CursorColumn are combined with terminal colors. + tt.set_reverse() + tt.feed_data(' foobar') + tt.clear_attrs() screen:expect([[ tty ready{1: } | foobar f{1:o}obar foobar foobar foobar foobar foobar | foobar fo{1:o}bar foobar foobar foobar foobar foobar f| oobar foo{1:b}ar foobar foobar foobar foobar foobar fo| obar foob{1:a}r foobar foobar foobar foobar foobar foo| - {1:bar fooba^r}{4: }{1: }| + {1:bar fooba^r}{4: foobar}{1: }| | ]]) - feed('2gg11|') + feed('2gg15|') screen:expect([[ - tty ready {1: } | - {1: foobar fo^obar foobar foobar foobar foobar foobar }| - foobar foo{1:b}ar foobar foobar foobar foobar foobar f| - oobar foob{1:a}r foobar foobar foobar foobar foobar fo| - obar fooba{1:r} foobar foobar foobar foobar foobar foo| - bar foobar{4: } | + tty ready {1: } | + {1: foobar foobar^ foobar foobar foobar foobar foobar }| + foobar foobar {1:f}oobar foobar foobar foobar foobar f| + oobar foobar f{1:o}obar foobar foobar foobar foobar fo| + obar foobar fo{1:o}bar foobar foobar foobar foobar foo| + bar foobar{2: foo}{4:b}{2:ar} | | ]]) - -- TermCursorNC has higher precedence. - command('highlight TermCursorNC gui=NONE guibg=Red') + + -- Set bg color to red + tt.feed_csi('48;2;255:0:0m') + tt.feed_data(' foobar') + tt.clear_attrs() + feed('2gg20|') + + -- Terminal color has higher precedence screen:expect([[ - tty ready {1: } | - {1: foobar fo^obar foobar foobar foobar foobar foobar }| - foobar foo{1:b}ar foobar foobar foobar foobar foobar f| - oobar foob{1:a}r foobar foobar foobar foobar foobar fo| - obar fooba{1:r} foobar foobar foobar foobar foobar foo| - bar foobar{5: } | + tty ready {1: } | + {1: foobar foobar foob^ar foobar foobar foobar foobar }| + foobar foobar fooba{1:r} foobar foobar foobar foobar f| + oobar foobar foobar{1: }foobar foobar foobar foobar fo| + obar foobar foobar {1:f}oobar foobar foobar foobar foo| + bar foobar{2: foobar}{5: foobar} | | ]]) feed('G$') screen:expect([[ - tty ready{1: } | - foobar f{1:o}obar foobar foobar foobar foobar foobar | - foobar fo{1:o}bar foobar foobar foobar foobar foobar f| - oobar foo{1:b}ar foobar foobar foobar foobar foobar fo| - obar foob{1:a}r foobar foobar foobar foobar foobar foo| - {1:bar fooba^r}{5: }{1: }| + tty ready {1: } | + foobar foobar foobar f{1:o}obar foobar foobar foobar | + foobar foobar foobar fo{1:o}bar foobar foobar foobar f| + oobar foobar foobar foo{1:b}ar foobar foobar foobar fo| + obar foobar foobar foob{1:a}r foobar foobar foobar foo| + {1:bar foobar}{4: foobar}{5: fooba^r}{1: }| | ]]) end) @@ -300,18 +314,17 @@ describe(':terminal highlight forwarding', function() screen = Screen.new(50, 7) screen:set_rgb_cterm(true) screen:set_default_attr_ids({ - [1] = { { reverse = true }, { reverse = true } }, - [2] = { { bold = true }, { bold = true } }, - [3] = { { fg_indexed = true, foreground = tonumber('0xe0e000') }, { foreground = 3 } }, - [4] = { { foreground = tonumber('0xff8000') }, {} }, + [1] = { { bold = true }, { bold = true } }, + [2] = { { fg_indexed = true, foreground = tonumber('0xe0e000') }, { foreground = 3 } }, + [3] = { { foreground = tonumber('0xff8000') }, {} }, }) - command(("enew | call termopen(['%s'])"):format(testprg('tty-test'))) + command(("enew | call jobstart(['%s'], {'term':v:true})"):format(testprg('tty-test'))) feed('i') screen:expect([[ tty ready | - {1: } | + ^ | |*4 - {2:-- TERMINAL --} | + {1:-- TERMINAL --} | ]]) end) @@ -326,9 +339,9 @@ describe(':terminal highlight forwarding', function() screen:expect { grid = [[ tty ready | - {3:text}{4:color}text{1: } | + {2:text}{3:color}text^ | |*4 - {2:-- TERMINAL --} | + {1:-- TERMINAL --} | ]], } end) @@ -351,11 +364,11 @@ describe(':terminal highlight with custom palette', function() [9] = { bold = true }, }) api.nvim_set_var('terminal_color_3', '#123456') - command(("enew | call termopen(['%s'])"):format(testprg('tty-test'))) + command(("enew | call jobstart(['%s'], {'term':v:true})"):format(testprg('tty-test'))) feed('i') screen:expect([[ tty ready | - {7: } | + ^ | |*4 {9:-- TERMINAL --} | ]]) @@ -369,7 +382,7 @@ describe(':terminal highlight with custom palette', function() tt.feed_data('text') screen:expect([[ tty ready | - {1:text}text{7: } | + {1:text}text^ | |*4 {9:-- TERMINAL --} | ]]) diff --git a/test/functional/terminal/mouse_spec.lua b/test/functional/terminal/mouse_spec.lua index 38d6b83417..5898484449 100644 --- a/test/functional/terminal/mouse_spec.lua +++ b/test/functional/terminal/mouse_spec.lua @@ -32,7 +32,7 @@ describe(':terminal mouse', function() line28 | line29 | line30 | - {1: } | + ^ | {3:-- TERMINAL --} | ]]) end) @@ -107,7 +107,7 @@ describe(':terminal mouse', function() line29 | line30 | mouse enabled | - {1: } | + ^ | {3:-- TERMINAL --} | ]]) end) @@ -121,7 +121,7 @@ describe(':terminal mouse', function() line29 | line30 | mouse enabled | - "#{1: } | + "#^ | {3:-- TERMINAL --} | ]]) feed('<LeftDrag><2,2>') @@ -131,7 +131,7 @@ describe(':terminal mouse', function() line29 | line30 | mouse enabled | - @##{1: } | + @##^ | {3:-- TERMINAL --} | ]]) feed('<LeftDrag><3,2>') @@ -141,7 +141,7 @@ describe(':terminal mouse', function() line29 | line30 | mouse enabled | - @$#{1: } | + @$#^ | {3:-- TERMINAL --} | ]]) feed('<LeftRelease><3,2>') @@ -151,7 +151,7 @@ describe(':terminal mouse', function() line29 | line30 | mouse enabled | - #$#{1: } | + #$#^ | {3:-- TERMINAL --} | ]]) end) @@ -165,7 +165,7 @@ describe(':terminal mouse', function() line29 | line30 | mouse enabled | - `!!{1: } | + `!!^ | {3:-- TERMINAL --} | ]]) end) @@ -179,7 +179,7 @@ describe(':terminal mouse', function() line29 | line30 | mouse enabled | - "#{1: } | + "#^ | {3:-- TERMINAL --} | ]]) feed('<ScrollWheelUp><1,2>') @@ -189,7 +189,7 @@ describe(':terminal mouse', function() line29 | line30 | mouse enabled | - `"#{1: } | + `"#^ | {3:-- TERMINAL --} | ]]) feed('<LeftDrag><2,2>') @@ -199,7 +199,7 @@ describe(':terminal mouse', function() line29 | line30 | mouse enabled | - @##{1: } | + @##^ | {3:-- TERMINAL --} | ]]) feed('<ScrollWheelUp><2,2>') @@ -209,7 +209,7 @@ describe(':terminal mouse', function() line29 | line30 | mouse enabled | - `##{1: } | + `##^ | {3:-- TERMINAL --} | ]]) feed('<LeftRelease><2,2>') @@ -219,7 +219,7 @@ describe(':terminal mouse', function() line29 | line30 | mouse enabled | - ###{1: } | + ###^ | {3:-- TERMINAL --} | ]]) end) @@ -237,7 +237,7 @@ describe(':terminal mouse', function() {7: 13 }line30 | {7: 14 }mouse enabled | {7: 15 }rows: 6, cols: 46 | - {7: 16 }{2: } | + {7: 16 } | | ]]) -- If click on the coordinate (0,1) of the region of the terminal @@ -249,7 +249,7 @@ describe(':terminal mouse', function() {7: 13 }line30 | {7: 14 }mouse enabled | {7: 15 }rows: 6, cols: 46 | - {7: 16 } !"{1: } | + {7: 16 } !"^ | {3:-- TERMINAL --} | ]]) end) @@ -261,7 +261,7 @@ describe(':terminal mouse', function() line30 | mouse enabled | rows: 5, cols: 50 | - {1: } | + ^ | ========== | {3:-- TERMINAL --} | ]]) @@ -271,7 +271,7 @@ describe(':terminal mouse', function() line30 | mouse enabled | rows: 5, cols: 50 | - {2:^ } | + ^ | ========== | | ]]) @@ -280,7 +280,7 @@ describe(':terminal mouse', function() mouse enabled | rows: 5, cols: 50 | rows: 4, cols: 50 | - {2:^ } | + ^ | ========== | |*2 ]]) @@ -293,7 +293,7 @@ describe(':terminal mouse', function() line30 │{4:~ }| mouse enabled │{4:~ }| rows: 5, cols: 24 │{4:~ }| - {1: } │{4:~ }| + ^ │{4:~ }| ========== ========== | {3:-- TERMINAL --} | ]]) @@ -303,7 +303,7 @@ describe(':terminal mouse', function() line30 │{4:~ }| mouse enabled │{4:~ }| rows: 5, cols: 24 │{4:~ }| - {2:^ } │{4:~ }| + ^ │{4:~ }| ========== ========== | | ]]) @@ -313,7 +313,7 @@ describe(':terminal mouse', function() mouse enabled │{4:~ }| rows: 5, cols: 24 │{4:~ }| rows: 5, cols: 23 │{4:~ }| - {2:^ } │{4:~ }| + ^ │{4:~ }| ========== ========== | | ]]) @@ -327,7 +327,7 @@ describe(':terminal mouse', function() line30 | mouse enabled | rows: 5, cols: 50 | - {1: } | + ^ | {3:-- TERMINAL --} | ]]) feed('<LeftMouse><0,0>') @@ -337,7 +337,7 @@ describe(':terminal mouse', function() line30 | mouse enabled | rows: 5, cols: 50 | - {2:^ } | + ^ | | ]]) command('set showtabline=2 tabline=TABLINE | startinsert') @@ -347,7 +347,7 @@ describe(':terminal mouse', function() mouse enabled | rows: 5, cols: 50 | rows: 4, cols: 50 | - {1: } | + ^ | {3:-- TERMINAL --} | ]]) feed('<LeftMouse><0,0>') @@ -357,7 +357,7 @@ describe(':terminal mouse', function() mouse enabled | rows: 5, cols: 50 | rows: 4, cols: 50 | - {2:^ } | + ^ | | ]]) command('setlocal winbar= | startinsert') @@ -367,7 +367,7 @@ describe(':terminal mouse', function() rows: 5, cols: 50 | rows: 4, cols: 50 | rows: 5, cols: 50 | - {1: } | + ^ | {3:-- TERMINAL --} | ]]) feed('<LeftMouse><0,0>') @@ -377,7 +377,7 @@ describe(':terminal mouse', function() rows: 5, cols: 50 | rows: 4, cols: 50 | rows: 5, cols: 50 | - {2:^ } | + ^ | | ]]) end) @@ -391,7 +391,7 @@ describe(':terminal mouse', function() line29 │line29 | line30 │line30 | rows: 5, cols: 25 │rows: 5, cols: 25 | - {2:^ } │{2: } | + ^ │ | ========== ========== | :vsp | ]]) @@ -401,7 +401,7 @@ describe(':terminal mouse', function() {4:~ }│line30 | {4:~ }│rows: 5, cols: 25 | {4:~ }│rows: 5, cols: 24 | - {4:~ }│{2: } | + {4:~ }│ | ========== ========== | :enew | set number | ]]) @@ -411,7 +411,7 @@ describe(':terminal mouse', function() {7: 28 }line │line30 | {7: 29 }line │rows: 5, cols: 25 | {7: 30 }line │rows: 5, cols: 24 | - {7: 31 }^ │{2: } | + {7: 31 }^ │ | ========== ========== | | ]]) @@ -421,7 +421,7 @@ describe(':terminal mouse', function() {7: 28 }line │line30 | {7: 29 }line │rows: 5, cols: 25 | {7: 30 }line │rows: 5, cols: 24 | - {7: 31 } │{1: } | + {7: 31 } │^ | ========== ========== | {3:-- TERMINAL --} | ]]) @@ -434,7 +434,7 @@ describe(':terminal mouse', function() {7: 28 }line │rows: 5, cols: 25 | {7: 29 }line │rows: 5, cols: 24 | {7: 30 }line │mouse enabled | - {7: 31 } │{1: } | + {7: 31 } │^ | ========== ========== | {3:-- TERMINAL --} | ]]) @@ -447,7 +447,7 @@ describe(':terminal mouse', function() {7: 22 }line │rows: 5, cols: 25 | {7: 23 }line │rows: 5, cols: 24 | {7: 24 }line │mouse enabled | - {7: 25 }line │{1: } | + {7: 25 }line │^ | ========== ========== | {3:-- TERMINAL --} | ]]) @@ -457,7 +457,7 @@ describe(':terminal mouse', function() {7: 27 }line │rows: 5, cols: 25 | {7: 28 }line │rows: 5, cols: 24 | {7: 29 }line │mouse enabled | - {7: 30 }line │{1: } | + {7: 30 }line │^ | ========== ========== | {3:-- TERMINAL --} | ]]) @@ -468,7 +468,7 @@ describe(':terminal mouse', function() {7: 17 }line │rows: 5, cols: 25 | {7: 18 }line │rows: 5, cols: 24 | {7: 19 }line │mouse enabled | - {7: 20 }line │{1: } | + {7: 20 }line │^ | ========== ========== | {3:-- TERMINAL --} | ]]) @@ -483,7 +483,7 @@ describe(':terminal mouse', function() {7: 2 }linelinelinelineline │rows: 5, cols: 25 | {7: 3 }linelinelinelineline │rows: 5, cols: 24 | {7: 4 }linelinelinelineline │mouse enabled | - {7: 5 }linelinelinelineline │{1: } | + {7: 5 }linelinelinelineline │^ | ========== ========== | {3:-- TERMINAL --} | ]]) @@ -493,7 +493,7 @@ describe(':terminal mouse', function() {7: 2 }nelinelineline │rows: 5, cols: 25 | {7: 3 }nelinelineline │rows: 5, cols: 24 | {7: 4 }nelinelineline │mouse enabled | - {7: 5 }nelinelineline │{1: } | + {7: 5 }nelinelineline │^ | ========== ========== | {3:-- TERMINAL --} | ]]) @@ -504,7 +504,7 @@ describe(':terminal mouse', function() {7: 2 }nelinelinelineline │rows: 5, cols: 25 | {7: 3 }nelinelinelineline │rows: 5, cols: 24 | {7: 4 }nelinelinelineline │mouse enabled | - {7: 5 }nelinelinelineline │{1: } | + {7: 5 }nelinelinelineline │^ | ========== ========== | {3:-- TERMINAL --} | ]]) @@ -517,7 +517,7 @@ describe(':terminal mouse', function() {7: 28 }l^ine │rows: 5, cols: 25 | {7: 29 }line │rows: 5, cols: 24 | {7: 30 }line │mouse enabled | - {7: 31 } │{2: } | + {7: 31 } │ | ========== ========== | | ]]) @@ -531,7 +531,7 @@ describe(':terminal mouse', function() {7: 28 }line │rows: 5, cols: 25 | {7: 29 }line │rows: 5, cols: 24 | {7: 30 }line │mouse enabled | - {7: 31 }^ │{2: } | + {7: 31 }^ │ | ========== ========== | | ]]) @@ -541,7 +541,7 @@ describe(':terminal mouse', function() rows: 5, cols: 24 │rows: 5, cols: 24 | mouse enabled │mouse enabled | rows: 5, cols: 25 │rows: 5, cols: 25 | - {2:^ } │{2: } | + ^ │ | ========== ========== | :bn | ]]) @@ -551,7 +551,7 @@ describe(':terminal mouse', function() {7: 28 }line │mouse enabled | {7: 29 }line │rows: 5, cols: 25 | {7: 30 }line │rows: 5, cols: 24 | - {7: 31 }^ │{2: } | + {7: 31 }^ │ | ========== ========== | :bn | ]]) diff --git a/test/functional/terminal/scrollback_spec.lua b/test/functional/terminal/scrollback_spec.lua index 1751db1aa9..804c5367eb 100644 --- a/test/functional/terminal/scrollback_spec.lua +++ b/test/functional/terminal/scrollback_spec.lua @@ -39,7 +39,7 @@ describe(':terminal scrollback', function() line28 | line29 | line30 | - {1: } | + ^ | {3:-- TERMINAL --} | ]]) end) @@ -67,7 +67,7 @@ describe(':terminal scrollback', function() line2 | line3 | line4 | - {1: } | + ^ | {3:-- TERMINAL --} | ]]) end) @@ -84,7 +84,7 @@ describe(':terminal scrollback', function() line3 | line4 | line5 | - {1: } | + ^ | {3:-- TERMINAL --} | ]]) eq(7, api.nvim_buf_line_count(0)) @@ -102,7 +102,7 @@ describe(':terminal scrollback', function() line5 | line6 | line7 | - line8{1: } | + line8^ | {3:-- TERMINAL --} | ]]) @@ -135,7 +135,7 @@ describe(':terminal scrollback', function() line5 | line6 | line7 | - ^line8{2: } | + ^line8 | | ]]) end) @@ -151,7 +151,7 @@ describe(':terminal scrollback', function() line3 | line4 | rows: 5, cols: 28 | - {2:^ } | + ^ | | ]]) end @@ -168,7 +168,7 @@ describe(':terminal scrollback', function() screen:expect([[ rows: 5, cols: 28 | rows: 3, cols: 26 | - {2:^ } | + ^ | | ]]) eq(8, api.nvim_buf_line_count(0)) @@ -201,7 +201,7 @@ describe(':terminal scrollback', function() screen:expect([[ tty ready | rows: 4, cols: 30 | - {1: } | + ^ | | {3:-- TERMINAL --} | ]]) @@ -220,7 +220,7 @@ describe(':terminal scrollback', function() screen:expect([[ rows: 4, cols: 30 | rows: 3, cols: 30 | - {1: } | + ^ | {3:-- TERMINAL --} | ]]) eq(4, api.nvim_buf_line_count(0)) @@ -235,7 +235,7 @@ describe(':terminal scrollback', function() screen:expect([[ rows: 4, cols: 30 | rows: 3, cols: 30 | - {1: } | + ^ | {3:-- TERMINAL --} | ]]) end) @@ -252,14 +252,14 @@ describe(':terminal scrollback', function() line2 | line3 | line4 | - {1: } | + ^ | {3:-- TERMINAL --} | ]]) screen:try_resize(screen._width, screen._height - 3) screen:expect([[ line4 | rows: 3, cols: 30 | - {1: } | + ^ | {3:-- TERMINAL --} | ]]) eq(7, api.nvim_buf_line_count(0)) @@ -278,7 +278,7 @@ describe(':terminal scrollback', function() line4 | rows: 3, cols: 30 | rows: 4, cols: 30 | - {1: } | + ^ | {3:-- TERMINAL --} | ]]) end @@ -300,7 +300,7 @@ describe(':terminal scrollback', function() rows: 3, cols: 30 | rows: 4, cols: 30 | rows: 7, cols: 30 | - {1: } | + ^ | {3:-- TERMINAL --} | ]]) eq(9, api.nvim_buf_line_count(0)) @@ -337,7 +337,7 @@ describe(':terminal scrollback', function() rows: 4, cols: 30 | rows: 7, cols: 30 | rows: 11, cols: 30 | - {1: } | + ^ | | {3:-- TERMINAL --} | ]]) @@ -355,14 +355,16 @@ describe(':terminal prints more lines than the screen height and exits', functio it('will push extra lines to scrollback', function() clear() local screen = Screen.new(30, 7, { rgb = false }) - command(("call termopen(['%s', '10']) | startinsert"):format(testprg('tty-test'))) + command( + ("call jobstart(['%s', '10'], {'term':v:true}) | startinsert"):format(testprg('tty-test')) + ) screen:expect([[ line6 | line7 | line8 | line9 | | - [Process exited 0]{2: } | + [Process exited 0]^ | {5:-- TERMINAL --} | ]]) feed('<cr>') @@ -454,7 +456,7 @@ describe("'scrollback' option", function() 39: line | 40: line | | - ${1: } | + $^ | {3:-- TERMINAL --} | ]], } @@ -493,7 +495,7 @@ describe("'scrollback' option", function() line28 | line29 | line30 | - {1: } | + ^ | {3:-- TERMINAL --} | ]]) local term_height = 6 -- Actual terminal screen height, not the scrollback @@ -623,7 +625,7 @@ describe('pending scrollback line handling', function() local bufnr = vim.api.nvim_create_buf(false, true) local args = ... vim.api.nvim_buf_call(bufnr, function() - vim.fn.termopen(args) + vim.fn.jobstart(args, { term = true }) end) vim.api.nvim_win_set_buf(0, bufnr) vim.cmd('startinsert') @@ -634,7 +636,7 @@ describe('pending scrollback line handling', function() screen:expect [[ hi |*4 | - [Process exited 0]{2: } | + [Process exited 0]^ | {3:-- TERMINAL --} | ]] assert_alive() diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua index ded0cd99d3..a2dc3c500f 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -26,7 +26,6 @@ local api = n.api local is_ci = t.is_ci local is_os = t.is_os local new_pipename = n.new_pipename -local spawn_argv = n.spawn_argv local set_session = n.set_session local write_file = t.write_file local eval = n.eval @@ -59,7 +58,7 @@ describe('TUI', function() 'colorscheme vim', }) screen:expect([[ - {1: } | + ^ | {4:~ }|*3 {5:[No Name] }| | @@ -105,7 +104,7 @@ describe('TUI', function() -- Need buffer rows to provoke the behavior. feed_data(':edit test/functional/fixtures/bigfile.txt\n') screen:expect([[ - {1:0}000;<control>;Cc;0;BN;;;;;N;NULL;;;; | + ^0000;<control>;Cc;0;BN;;;;;N;NULL;;;; | 0001;<control>;Cc;0;BN;;;;;N;START OF HEADING;;;; | 0002;<control>;Cc;0;BN;;;;;N;START OF TEXT;;;; | 0003;<control>;Cc;0;BN;;;;;N;END OF TEXT;;;; | @@ -155,7 +154,7 @@ describe('TUI', function() {8:FAIL 0} | {8:FAIL 1} | {8:FAIL 2} | - {10:-- More --}{1: } | + {10:-- More --}^ | {3:-- TERMINAL --} | ]], } @@ -170,7 +169,7 @@ describe('TUI', function() {8:FAIL 1} | {8:FAIL 2} | |*2 - {10:-- More --}{1: } | + {10:-- More --}^ | {3:-- TERMINAL --} | ]], } @@ -186,7 +185,7 @@ describe('TUI', function() {8:FAIL 3} | {8:FAIL 4} | {8:FAIL 5} | - {10:-- More --}{1: } | + {10:-- More --}^ | {3:-- TERMINAL --} | ]], } @@ -199,7 +198,7 @@ describe('TUI', function() {8:FAIL 3} | {8:FAIL 4} | {8:FAIL 5} | - {10:-- More --}{1: } | + {10:-- More --}^ | {3:-- TERMINAL --} | ]], } @@ -210,7 +209,7 @@ describe('TUI', function() {8:FAIL 3} | {8:FAIL 4} | {8:FAIL 5} | - {10:-- More --}{1: } | + {10:-- More --}^ | {3:-- TERMINAL --} | ]], } @@ -221,7 +220,7 @@ describe('TUI', function() :call ManyErr() | {8:Error detected while processing function ManyErr:} | {11:line 2:} | - {10:-- More --}{1: } | + {10:-- More --}^ | {3:-- TERMINAL --} | ]], } @@ -237,7 +236,7 @@ describe('TUI', function() {8:FAIL 2} | {8:FAIL 3} | {8:FAIL 4} | - {10:-- More --}{1: } | + {10:-- More --}^ | {3:-- TERMINAL --} | ]], } @@ -245,7 +244,7 @@ describe('TUI', function() feed_data('\003') screen:expect { grid = [[ - {1: } | + ^ | {4:~ }|*6 {5:[No Name] }| | @@ -259,7 +258,7 @@ describe('TUI', function() screen:expect([[ abc | test1 | - test2{1: } | + test2^ | {4:~ }| {5:[No Name] [+] }| {3:-- INSERT --} | @@ -269,7 +268,7 @@ describe('TUI', function() screen:expect([[ abc | test1 | - test{1:2} | + test^2 | {4:~ }| {5:[No Name] [+] }| | @@ -287,14 +286,14 @@ describe('TUI', function() alt-j | alt-k | alt-l | - {1: } | + ^ | {5:[No Name] [+] }| | {3:-- TERMINAL --} | ]]) feed_data('gg') screen:expect([[ - {1:a}lt-d | + ^alt-d | alt-f | alt-g | alt-h | @@ -309,7 +308,7 @@ describe('TUI', function() -- ALT+j inserts "ê". Nvim does not (#3982). feed_data('i\022\027j') screen:expect([[ - <M-j>{1: } | + <M-j>^ | {4:~ }|*3 {5:[No Name] [+] }| {3:-- INSERT --} | @@ -329,7 +328,7 @@ describe('TUI', function() ) feed_data('\027[27u;') screen:expect([[ - ESCsemicolo{1:n} | + ESCsemicolo^n | {4:~ }|*3 {5:[No Name] [+] }| | @@ -343,7 +342,7 @@ describe('TUI', function() it('interprets <Esc><Nul> as <M-C-Space> #17198', function() feed_data('i\022\027\000') screen:expect([[ - <M-C-Space>{1: } | + <M-C-Space>^ | {4:~ }|*3 {5:[No Name] [+] }| {3:-- INSERT --} | @@ -357,7 +356,7 @@ describe('TUI', function() feed_data('\022\022') -- ctrl+v feed_data('\022\013') -- ctrl+m screen:expect([[ - {6:^G^V^M}{1: } | + {6:^G^V^M}^ | {4:~ }|*3 {5:[No Name] [+] }| {3:-- INSERT --} | @@ -376,7 +375,7 @@ describe('TUI', function() {} ) screen:expect([[ - {11: 1 }{1:0}----1----2----3----4│{11: 1 }0----1----2----3----| + {11: 1 }^0----1----2----3----4│{11: 1 }0----1----2----3----| {11: 2 }0----1----2----3----4│{11: 2 }0----1----2----3----| {11: 3 }0----1----2----3----4│{11: 3 }0----1----2----3----| {11: 4 }0----1----2----3----4│{11: 4 }0----1----2----3----| @@ -391,7 +390,7 @@ describe('TUI', function() api.nvim_input_mouse('wheel', 'down', '', 0, 0, 7) end screen:expect([[ - {11: 2 }{1:0}----1----2----3----4│{11: 1 }0----1----2----3----| + {11: 2 }^0----1----2----3----4│{11: 1 }0----1----2----3----| {11: 3 }0----1----2----3----4│{11: 2 }0----1----2----3----| {11: 4 }0----1----2----3----4│{11: 3 }0----1----2----3----| {11: 5 }0----1----2----3----4│{11: 4 }0----1----2----3----| @@ -406,7 +405,7 @@ describe('TUI', function() api.nvim_input_mouse('wheel', 'down', '', 0, 0, 47) end screen:expect([[ - {11: 2 }{1:0}----1----2----3----4│{11: 2 }0----1----2----3----| + {11: 2 }^0----1----2----3----4│{11: 2 }0----1----2----3----| {11: 3 }0----1----2----3----4│{11: 3 }0----1----2----3----| {11: 4 }0----1----2----3----4│{11: 4 }0----1----2----3----| {11: 5 }0----1----2----3----4│{11: 5 }0----1----2----3----| @@ -421,7 +420,7 @@ describe('TUI', function() api.nvim_input_mouse('wheel', 'right', '', 0, 0, 7) end screen:expect([[ - {11: 2 }{1:-}---1----2----3----4-│{11: 2 }0----1----2----3----| + {11: 2 }^----1----2----3----4-│{11: 2 }0----1----2----3----| {11: 3 }----1----2----3----4-│{11: 3 }0----1----2----3----| {11: 4 }----1----2----3----4-│{11: 4 }0----1----2----3----| {11: 5 }----1----2----3----4-│{11: 5 }0----1----2----3----| @@ -436,7 +435,7 @@ describe('TUI', function() api.nvim_input_mouse('wheel', 'right', '', 0, 0, 47) end screen:expect([[ - {11: 2 }{1:-}---1----2----3----4-│{11: 2 }----1----2----3----4| + {11: 2 }^----1----2----3----4-│{11: 2 }----1----2----3----4| {11: 3 }----1----2----3----4-│{11: 3 }----1----2----3----4| {11: 4 }----1----2----3----4-│{11: 4 }----1----2----3----4| {11: 5 }----1----2----3----4-│{11: 5 }----1----2----3----4| @@ -451,7 +450,7 @@ describe('TUI', function() api.nvim_input_mouse('wheel', 'down', 'S', 0, 0, 7) end screen:expect([[ - {11: 5 }{1:-}---1----2----3----4-│{11: 2 }----1----2----3----4| + {11: 5 }^----1----2----3----4-│{11: 2 }----1----2----3----4| {11: 6 }----1----2----3----4-│{11: 3 }----1----2----3----4| {11: 7 }----1----2----3----4-│{11: 4 }----1----2----3----4| {11: 8 }----1----2----3----4-│{11: 5 }----1----2----3----4| @@ -466,7 +465,7 @@ describe('TUI', function() api.nvim_input_mouse('wheel', 'down', 'S', 0, 0, 47) end screen:expect([[ - {11: 5 }{1:-}---1----2----3----4-│{11: 5 }----1----2----3----4| + {11: 5 }^----1----2----3----4-│{11: 5 }----1----2----3----4| {11: 6 }----1----2----3----4-│{11: 6 }----1----2----3----4| {11: 7 }----1----2----3----4-│{11: 7 }----1----2----3----4| {11: 8 }----1----2----3----4-│{11: 8 }----1----2----3----4| @@ -481,7 +480,7 @@ describe('TUI', function() api.nvim_input_mouse('wheel', 'right', 'S', 0, 0, 7) end screen:expect([[ - {11: 5 }{1:-}---6----7----8----9 │{11: 5 }----1----2----3----4| + {11: 5 }^----6----7----8----9 │{11: 5 }----1----2----3----4| {11: 6 }----6----7----8----9 │{11: 6 }----1----2----3----4| {11: 7 }----6----7----8----9 │{11: 7 }----1----2----3----4| {11: 8 }----6----7----8----9 │{11: 8 }----1----2----3----4| @@ -496,7 +495,7 @@ describe('TUI', function() api.nvim_input_mouse('wheel', 'right', 'S', 0, 0, 47) end screen:expect([[ - {11: 5 }{1:-}---6----7----8----9 │{11: 5 }5----6----7----8----| + {11: 5 }^----6----7----8----9 │{11: 5 }5----6----7----8----| {11: 6 }----6----7----8----9 │{11: 6 }5----6----7----8----| {11: 7 }----6----7----8----9 │{11: 7 }5----6----7----8----| {11: 8 }----6----7----8----9 │{11: 8 }5----6----7----8----| @@ -512,7 +511,7 @@ describe('TUI', function() end screen:expect([[ {11: 4 }----6----7----8----9 │{11: 5 }5----6----7----8----| - {11: 5 }{1:-}---6----7----8----9 │{11: 6 }5----6----7----8----| + {11: 5 }^----6----7----8----9 │{11: 6 }5----6----7----8----| {11: 6 }----6----7----8----9 │{11: 7 }5----6----7----8----| {11: 7 }----6----7----8----9 │{11: 8 }5----6----7----8----| {5:[No Name] [+] }{1:[No Name] [+] }| @@ -527,7 +526,7 @@ describe('TUI', function() end screen:expect([[ {11: 4 }----6----7----8----9 │{11: 4 }5----6----7----8----| - {11: 5 }{1:-}---6----7----8----9 │{11: 5 }5----6----7----8----| + {11: 5 }^----6----7----8----9 │{11: 5 }5----6----7----8----| {11: 6 }----6----7----8----9 │{11: 6 }5----6----7----8----| {11: 7 }----6----7----8----9 │{11: 7 }5----6----7----8----| {5:[No Name] [+] }{1:[No Name] [+] }| @@ -542,7 +541,7 @@ describe('TUI', function() end screen:expect([[ {11: 4 }5----6----7----8----9│{11: 4 }5----6----7----8----| - {11: 5 }5{1:-}---6----7----8----9│{11: 5 }5----6----7----8----| + {11: 5 }5^----6----7----8----9│{11: 5 }5----6----7----8----| {11: 6 }5----6----7----8----9│{11: 6 }5----6----7----8----| {11: 7 }5----6----7----8----9│{11: 7 }5----6----7----8----| {5:[No Name] [+] }{1:[No Name] [+] }| @@ -557,7 +556,7 @@ describe('TUI', function() end screen:expect([[ {11: 4 }5----6----7----8----9│{11: 4 }-5----6----7----8---| - {11: 5 }5{1:-}---6----7----8----9│{11: 5 }-5----6----7----8---| + {11: 5 }5^----6----7----8----9│{11: 5 }-5----6----7----8---| {11: 6 }5----6----7----8----9│{11: 6 }-5----6----7----8---| {11: 7 }5----6----7----8----9│{11: 7 }-5----6----7----8---| {5:[No Name] [+] }{1:[No Name] [+] }| @@ -574,7 +573,7 @@ describe('TUI', function() {11: 1 }5----6----7----8----9│{11: 4 }-5----6----7----8---| {11: 2 }5----6----7----8----9│{11: 5 }-5----6----7----8---| {11: 3 }5----6----7----8----9│{11: 6 }-5----6----7----8---| - {11: 4 }5{1:-}---6----7----8----9│{11: 7 }-5----6----7----8---| + {11: 4 }5^----6----7----8----9│{11: 7 }-5----6----7----8---| {5:[No Name] [+] }{1:[No Name] [+] }| | {3:-- TERMINAL --} | @@ -589,7 +588,7 @@ describe('TUI', function() {11: 1 }5----6----7----8----9│{11: 1 }-5----6----7----8---| {11: 2 }5----6----7----8----9│{11: 2 }-5----6----7----8---| {11: 3 }5----6----7----8----9│{11: 3 }-5----6----7----8---| - {11: 4 }5{1:-}---6----7----8----9│{11: 4 }-5----6----7----8---| + {11: 4 }5^----6----7----8----9│{11: 4 }-5----6----7----8---| {5:[No Name] [+] }{1:[No Name] [+] }| | {3:-- TERMINAL --} | @@ -604,7 +603,7 @@ describe('TUI', function() {11: 1 }0----1----2----3----4│{11: 1 }-5----6----7----8---| {11: 2 }0----1----2----3----4│{11: 2 }-5----6----7----8---| {11: 3 }0----1----2----3----4│{11: 3 }-5----6----7----8---| - {11: 4 }0----1----2----3----{1:4}│{11: 4 }-5----6----7----8---| + {11: 4 }0----1----2----3----^4│{11: 4 }-5----6----7----8---| {5:[No Name] [+] }{1:[No Name] [+] }| | {3:-- TERMINAL --} | @@ -619,7 +618,7 @@ describe('TUI', function() {11: 1 }0----1----2----3----4│{11: 1 }0----1----2----3----| {11: 2 }0----1----2----3----4│{11: 2 }0----1----2----3----| {11: 3 }0----1----2----3----4│{11: 3 }0----1----2----3----| - {11: 4 }0----1----2----3----{1:4}│{11: 4 }0----1----2----3----| + {11: 4 }0----1----2----3----^4│{11: 4 }0----1----2----3----| {5:[No Name] [+] }{1:[No Name] [+] }| | {3:-- TERMINAL --} | @@ -645,7 +644,7 @@ describe('TUI', function() aunmenu PopUp " Delete the default MenuPopup event handler. - autocmd! nvim_popupmenu + autocmd! nvim.popupmenu menu PopUp.foo :let g:menustr = 'foo'<CR> menu PopUp.bar :let g:menustr = 'bar'<CR> menu PopUp.baz :let g:menustr = 'baz'<CR> @@ -660,7 +659,7 @@ describe('TUI', function() api.nvim_input_mouse('right', 'press', '', 0, 0, 4) end screen:expect([[ - {1:p}opup menu test | + ^popup menu test | {4:~ }{13: foo }{4: }| {4:~ }{13: bar }{4: }| {4:~ }{13: baz }{4: }| @@ -680,7 +679,7 @@ describe('TUI', function() api.nvim_input_mouse('wheel', 'up', '', 0, 0, 4) end screen:expect([[ - {1:p}opup menu test | + ^popup menu test | {4:~ }{14: foo }{4: }| {4:~ }{13: bar }{4: }| {4:~ }{13: baz }{4: }| @@ -694,7 +693,7 @@ describe('TUI', function() api.nvim_input_mouse('move', '', '', 0, 3, 6) end screen:expect([[ - {1:p}opup menu test | + ^popup menu test | {4:~ }{13: foo }{4: }| {4:~ }{13: bar }{4: }| {4:~ }{14: baz }{4: }| @@ -708,7 +707,7 @@ describe('TUI', function() api.nvim_input_mouse('wheel', 'down', '', 0, 3, 6) end screen:expect([[ - {1:p}opup menu test | + ^popup menu test | {4:~ }{13: foo }{4: }| {4:~ }{14: bar }{4: }| {4:~ }{13: baz }{4: }| @@ -722,7 +721,7 @@ describe('TUI', function() api.nvim_input_mouse('left', 'press', '', 0, 2, 6) end screen:expect([[ - {1:p}opup menu test | + ^popup menu test | {4:~ }|*3 {5:[No Name] [+] }| :let g:menustr = 'bar' | @@ -740,7 +739,7 @@ describe('TUI', function() api.nvim_input_mouse('right', 'press', '', 0, 2, 44) end screen:expect([[ - {1:p}opup menu test | + ^popup menu test | {4:~ }|*2 {4:~ }{13: foo }{4: }| {5:[No Name] [+] }{13: bar }{5: }| @@ -753,7 +752,7 @@ describe('TUI', function() api.nvim_input_mouse('right', 'drag', '', 0, 5, 47) end screen:expect([[ - {1:p}opup menu test | + ^popup menu test | {4:~ }|*2 {4:~ }{13: foo }{4: }| {5:[No Name] [+] }{13: bar }{5: }| @@ -766,7 +765,7 @@ describe('TUI', function() api.nvim_input_mouse('right', 'release', '', 0, 5, 47) end screen:expect([[ - {1:p}opup menu test | + ^popup menu test | {4:~ }|*3 {5:[No Name] [+] }| :let g:menustr = 'baz' | @@ -805,7 +804,7 @@ describe('TUI', function() feed_data(fn.nr2char(57415)) -- KP_EQUAL screen:expect([[ 0123456789./*-+ | - ={1: } | + =^ | {4:~ }|*2 {5:[No Name] [+] }| {3:-- INSERT --} | @@ -814,7 +813,7 @@ describe('TUI', function() feed_data(fn.nr2char(57417)) -- KP_LEFT screen:expect([[ 0123456789./*-+ | - {1:=} | + ^= | {4:~ }|*2 {5:[No Name] [+] }| {3:-- INSERT --} | @@ -823,7 +822,7 @@ describe('TUI', function() feed_data(fn.nr2char(57418)) -- KP_RIGHT screen:expect([[ 0123456789./*-+ | - ={1: } | + =^ | {4:~ }|*2 {5:[No Name] [+] }| {3:-- INSERT --} | @@ -831,7 +830,7 @@ describe('TUI', function() ]]) feed_data(fn.nr2char(57419)) -- KP_UP screen:expect([[ - 0{1:1}23456789./*-+ | + 0^123456789./*-+ | = | {4:~ }|*2 {5:[No Name] [+] }| @@ -841,7 +840,7 @@ describe('TUI', function() feed_data(fn.nr2char(57420)) -- KP_DOWN screen:expect([[ 0123456789./*-+ | - ={1: } | + =^ | {4:~ }|*2 {5:[No Name] [+] }| {3:-- INSERT --} | @@ -850,7 +849,7 @@ describe('TUI', function() feed_data(fn.nr2char(57425)) -- KP_INSERT screen:expect([[ 0123456789./*-+ | - ={1: } | + =^ | {4:~ }|*2 {5:[No Name] [+] }| {3:-- REPLACE --} | @@ -859,7 +858,7 @@ describe('TUI', function() feed_data('\027[27u') -- ESC screen:expect([[ 0123456789./*-+ | - {1:=} | + ^= | {4:~ }|*2 {5:[No Name] [+] }| | @@ -867,7 +866,7 @@ describe('TUI', function() ]]) feed_data('\027[57417;5u') -- CTRL + KP_LEFT screen:expect([[ - {1:0}123456789./*-+ | + ^0123456789./*-+ | = | {4:~ }|*2 {5:[No Name] [+] }| @@ -876,7 +875,7 @@ describe('TUI', function() ]]) feed_data('\027[57418;2u') -- SHIFT + KP_RIGHT screen:expect([[ - 0123456789{1:.}/*-+ | + 0123456789^./*-+ | = | {4:~ }|*2 {5:[No Name] [+] }| @@ -885,7 +884,7 @@ describe('TUI', function() ]]) feed_data(fn.nr2char(57426)) -- KP_DELETE screen:expect([[ - 0123456789{1:/}*-+ | + 0123456789^/*-+ | = | {4:~ }|*2 {5:[No Name] [+] }| @@ -894,7 +893,7 @@ describe('TUI', function() ]]) feed_data(fn.nr2char(57423)) -- KP_HOME screen:expect([[ - {1:0}123456789/*-+ | + ^0123456789/*-+ | = | {4:~ }|*2 {5:[No Name] [+] }| @@ -903,7 +902,7 @@ describe('TUI', function() ]]) feed_data(fn.nr2char(57424)) -- KP_END screen:expect([[ - 0123456789/*-{1:+} | + 0123456789/*-^+ | = | {4:~ }|*2 {5:[No Name] [+] }| @@ -921,7 +920,7 @@ describe('TUI', function() ) screen:expect([[ {12: + [No Name] + [No Name] }{3: [No Name] }{1: }{12:X}| - {1: } | + ^ | {4:~ }|*2 {5:[No Name] }| | @@ -930,7 +929,7 @@ describe('TUI', function() feed_data('\027[57421;5u') -- CTRL + KP_PAGE_UP screen:expect([[ {12: + [No Name] }{3: + [No Name] }{12: [No Name] }{1: }{12:X}| - 0123456789/*-{1:+} | + 0123456789/*-^+ | = | {4:~ }| {5:[No Name] [+] }| @@ -940,7 +939,7 @@ describe('TUI', function() feed_data('\027[57422;5u') -- CTRL + KP_PAGE_DOWN screen:expect([[ {12: + [No Name] + [No Name] }{3: [No Name] }{1: }{12:X}| - {1: } | + ^ | {4:~ }|*2 {5:[No Name] }| | @@ -961,7 +960,7 @@ describe('TUI', function() feed_data('\022\027[57379;48u') -- Shift + Alt + Ctrl + Super + Meta + F16 screen:expect([[ <D-j><T-k><T-D-CR><M-T-C-S-D-BS> | - <D-F13><T-F14><T-D-F15><M-T-C-S-D-F16>{1: } | + <D-F13><T-F14><T-D-F15><M-T-C-S-D-F16>^ | {4:~ }|*2 {5:[No Name] [+] }| {3:-- INSERT --} | @@ -973,7 +972,7 @@ describe('TUI', function() -- "bracketed paste" feed_data('i""\027i\027[200~') screen:expect([[ - "{1:"} | + "^" | {4:~ }|*3 {5:[No Name] [+] }| {3:-- INSERT --} | @@ -982,7 +981,7 @@ describe('TUI', function() feed_data('pasted from terminal') expect_child_buf_lines({ '"pasted from terminal"' }) screen:expect([[ - "pasted from terminal{1:"} | + "pasted from terminal^" | {4:~ }|*3 {5:[No Name] [+] }| {3:-- INSERT --} | @@ -994,7 +993,7 @@ describe('TUI', function() feed_data('\027[27u') -- ESC: go to Normal mode. wait_for_mode('n') screen:expect([[ - "pasted from termina{1:l}" | + "pasted from termina^l" | {4:~ }|*3 {5:[No Name] [+] }| | @@ -1005,7 +1004,7 @@ describe('TUI', function() expect_child_buf_lines({ '"pasted from terminapasted from terminalpasted from terminall"' }) screen:expect([[ "pasted from terminapasted from terminalpasted fro| - m termina{1:l}l" | + m termina^ll" | {4:~ }|*2 {5:[No Name] [+] }| | @@ -1027,7 +1026,7 @@ describe('TUI', function() this is line 1 | this is line 2 | line 3 is here | - {1: } | + ^ | {5:[No Name] [+] }| | {3:-- TERMINAL --} | @@ -1037,7 +1036,7 @@ describe('TUI', function() screen:expect([[ this{16: is line 1} | {16:this is line 2} | - {16:line}{1: }3 is here | + {16:line}^ 3 is here | | {5:[No Name] [+] }| {3:-- SELECT --} | @@ -1047,7 +1046,7 @@ describe('TUI', function() feed_data('just paste it™') feed_data('\027[201~') screen:expect([[ - thisjust paste it{1:™}3 is here | + thisjust paste it^™3 is here | | {4:~ }|*2 {5:[No Name] [+] }| @@ -1084,7 +1083,7 @@ describe('TUI', function() feed_data('i') screen:expect([[ tty ready | - {1: } | + ^ | |*2 {19:^^^^^^^ }| {3:-- TERMINAL --} |*2 @@ -1094,7 +1093,7 @@ describe('TUI', function() feed_data('\027[201~') screen:expect([[ tty ready | - hallo{1: } | + hallo^ | |*2 {19:^^^^^^^ }| {3:-- TERMINAL --} |*2 @@ -1111,7 +1110,7 @@ describe('TUI', function() local expected_grid1 = [[ line 1 | ESC:{6:^[} / CR: | - {1:x} | + ^x | {4:~ }| {5:[No Name] [+] 3,1 All}| | @@ -1126,7 +1125,7 @@ describe('TUI', function() ESC:{6:^[} / CR: | xline 1 | ESC:{6:^[} / CR: | - {1:x} | + ^x | {5:[No Name] [+] 5,1 Bot}| | {3:-- TERMINAL --} | @@ -1165,7 +1164,7 @@ describe('TUI', function() | {4:~ }|*2 {5:[No Name] [+] }| - :"{1:"} | + :"^" | {3:-- TERMINAL --} | ]]) -- "bracketed paste" @@ -1179,7 +1178,7 @@ describe('TUI', function() | {4:~ }|*2 {5:[No Name] [+] }| - :"line 1{1:"} | + :"line 1^" | {3:-- TERMINAL --} | ]]) -- Dot-repeat/redo. @@ -1188,7 +1187,7 @@ describe('TUI', function() feed_data('.') screen:expect([[ foo |*2 - {1: } | + ^ | {4:~ }| {5:[No Name] [+] }| | @@ -1235,7 +1234,7 @@ describe('TUI', function() wait_for_mode('n') screen:expect([[ foo | - {1: } | + ^ | {4:~ }|*2 {5:[No Name] [+] }| | @@ -1249,7 +1248,7 @@ describe('TUI', function() {5: }| {8:paste: Error executing lua: [string "<nvim>"]:4: f}| {8:ake fail} | - {10:Press ENTER or type command to continue}{1: } | + {10:Press ENTER or type command to continue}^ | {3:-- TERMINAL --} | ]]) -- Remaining chunks are discarded after vim.paste() failure. @@ -1265,7 +1264,7 @@ describe('TUI', function() feed_data('.') screen:expect([[ foo |*2 - {1: } | + ^ | {4:~ }| {5:[No Name] [+] }| | @@ -1275,7 +1274,7 @@ describe('TUI', function() feed_data('ityped input...\027[27u') screen:expect([[ foo |*2 - typed input..{1:.} | + typed input..^. | {4:~ }| {5:[No Name] [+] }| | @@ -1288,7 +1287,7 @@ describe('TUI', function() foo | typed input...line A | line B | - {1: } | + ^ | {5:[No Name] [+] }| | {3:-- TERMINAL --} | @@ -1352,7 +1351,7 @@ describe('TUI', function() {5: }| {8:paste: Error executing lua: Vim:E21: Cannot make c}| {8:hanges, 'modifiable' is off} | - {10:Press ENTER or type command to continue}{1: } | + {10:Press ENTER or type command to continue}^ | {3:-- TERMINAL --} | ]]) feed_data('\n') -- <Enter> to dismiss hit-enter prompt @@ -1361,7 +1360,7 @@ describe('TUI', function() screen:expect([[ success 1 | success 2 | - {1: } | + ^ | {4:~ }| {5:[No Name] [+] }| | @@ -1380,7 +1379,7 @@ describe('TUI', function() expected = expected .. ' end' screen:expect([[ zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz| - zzzzzzzzzzzzzz end{1: } | + zzzzzzzzzzzzzz end^ | {4:~ }|*2 {5:[No Name] [+] }| {3:-- INSERT --} | @@ -1399,7 +1398,7 @@ describe('TUI', function() | {4:~ }|*3 {5:[No Name] }| - :<{1: } | + :<^ | {3:-- TERMINAL --} | ]]) end) @@ -1420,7 +1419,7 @@ describe('TUI', function() item 2997 | item 2998 | item 2999 | - item 3000 end{1: } | + item 3000 end^ | {5:[No Name] [+] 3000,14 Bot}| {3:-- INSERT --} | {3:-- TERMINAL --} | @@ -1433,7 +1432,7 @@ describe('TUI', function() item 2997 | item 2998 | item 2999 | - item 3000 en{1:d}d | + item 3000 en^dd | {5:[No Name] [+] 5999,13 Bot}| | {3:-- TERMINAL --} | @@ -1457,7 +1456,7 @@ describe('TUI', function() | pasted from terminal (1) | {6:^[}[200~ | - {1: } | + ^ | {5:[No Name] [+] }| {3:-- INSERT --} | {3:-- TERMINAL --} | @@ -1472,7 +1471,7 @@ describe('TUI', function() -- Send "stop paste" sequence. feed_data('\027[201~') screen:expect([[ - {1: } | + ^ | {4:~ }|*3 {5:[No Name] }| {3:-- INSERT --} | @@ -1487,7 +1486,7 @@ describe('TUI', function() feed_data('\027[2') feed_data('00~pasted from terminal\027[201~') screen:expect([[ - pasted from terminal{1: } | + pasted from terminal^ | {4:~ }|*3 {5:[No Name] [+] }| {3:-- INSERT --} | @@ -1502,7 +1501,7 @@ describe('TUI', function() feed_data('\027[200~pasted from terminal\027[20') feed_data('1~') screen:expect([[ - pasted from terminal{1: } | + pasted from terminal^ | {4:~ }|*3 {5:[No Name] [+] }| {3:-- INSERT --} | @@ -1524,7 +1523,7 @@ describe('TUI', function() wait_for_mode('i') feed_data('\027[200~pasted') -- phase 1 screen:expect([[ - pasted{1: } | + pasted^ | {4:~ }|*3 {5:[No Name] [+] }| {3:-- INSERT --} | @@ -1532,7 +1531,7 @@ describe('TUI', function() ]]) feed_data(' from terminal') -- phase 2 screen:expect([[ - pasted from terminal{1: } | + pasted from terminal^ | {4:~ }|*3 {5:[No Name] [+] }| {3:-- INSERT --} | @@ -1568,7 +1567,7 @@ describe('TUI', function() feed_data('\028\014') -- crtl+\ ctrl+N feed_data(':set termguicolors?\n') screen:expect([[ - {5:^}{6:G} | + {6:^^G} | {2:~ }|*3 {3:[No Name] [+] }| notermguicolors | @@ -1577,7 +1576,7 @@ describe('TUI', function() feed_data(':set termguicolors\n') screen:expect([[ - {7:^}{8:G} | + {8:^^G} | {9:~}{10: }|*3 {3:[No Name] [+] }| :set termguicolors | @@ -1586,7 +1585,7 @@ describe('TUI', function() feed_data(':set notermguicolors\n') screen:expect([[ - {5:^}{6:G} | + {6:^^G} | {2:~ }|*3 {3:[No Name] [+] }| :set notermguicolors | @@ -1634,7 +1633,7 @@ describe('TUI', function() child_exec_lua('vim.cmd.terminal(...)', testprg('tty-test')) screen:expect { grid = [[ - {1:t}ty ready | + ^tty ready | |*3 {2:^^^^^^^ }| | @@ -1646,7 +1645,7 @@ describe('TUI', function() ) screen:expect { grid = [[ - {1:t}ty ready | + ^tty ready | {4:text}{5:color}text | |*2 {2:^^^^^^^ }| @@ -1658,7 +1657,7 @@ describe('TUI', function() feed_data(':set notermguicolors\n') screen:expect { grid = [[ - {1:t}ty ready | + ^tty ready | {4:text}colortext | |*2 {6:^^^^^^^}{7: }| @@ -1681,7 +1680,7 @@ describe('TUI', function() child_session:request('nvim_set_hl', 0, 'Visual', { undercurl = true }) feed_data('ifoobar\027V') screen:expect([[ - {5:fooba}{1:r} | + {5:fooba}^r | {4:~ }|*3 {2:[No Name] [+] }| {3:-- VISUAL LINE --} | @@ -1689,7 +1688,7 @@ describe('TUI', function() ]]) child_session:request('nvim_set_hl', 0, 'Visual', { underdouble = true }) screen:expect([[ - {6:fooba}{1:r} | + {6:fooba}^r | {4:~ }|*3 {2:[No Name] [+] }| {3:-- VISUAL LINE --} | @@ -1777,7 +1776,7 @@ describe('TUI', function() child_session:request('nvim_set_option_value', 'listchars', 'eol:$', { win = 0 }) feed_data('gg') local singlewidth_screen = [[ - {13:℃}{12:℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃}| + {12:^℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃}| {12:℃℃℃℃℃℃℃℃℃℃}{15:$}{12: }| ℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃| ℃℃℃℃℃℃℃℃℃℃{4:$} | @@ -1788,7 +1787,7 @@ describe('TUI', function() -- When grid assumes "℃" to be double-width but host terminal assumes it to be single-width, -- the second cell of "℃" is a space and the attributes of the "℃" are applied to it. local doublewidth_screen = [[ - {13:℃}{12: ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ }| + {12:^℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ }| {12:℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ }| {12:℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ }{15:$}{12: }| ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ {4:@@@@}| @@ -1821,7 +1820,7 @@ describe('TUI', function() child_session:request('nvim_set_option_value', 'listchars', 'eol:$', { win = 0 }) feed_data('gg') local singlewidth_screen = [[ - {13:✓}{12:✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓}| + {12:^✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓}| {12:✓✓✓✓✓✓✓✓✓✓}{15:$}{12: }| ✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓| ✓✓✓✓✓✓✓✓✓✓{4:$} | @@ -1832,7 +1831,7 @@ describe('TUI', function() -- When grid assumes "✓" to be double-width but host terminal assumes it to be single-width, -- the second cell of "✓" is a space and the attributes of the "✓" are applied to it. local doublewidth_screen = [[ - {13:✓}{12: ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ }| + {12:^✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ }| {12:✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ }| {12:✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ }{15:$}{12: }| ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ {4:@@@@}| @@ -1870,7 +1869,7 @@ describe('TUI', function() -- Close the :intro message and redraw the lines. feed_data('\n') screen:expect([[ - {13:Ꝩ}{12:ꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨ}| + {12:^ꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨ}| {12:ꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨ}|*310 {12:ꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨ℃ }| b | @@ -1912,7 +1911,7 @@ describe('TUI', function() -- Close the :intro message and redraw the lines. feed_data('\n') screen:expect([[ - {1:Ꝩ}ꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨ| + ^ꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨ| ꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨ|*325 {3:-- TERMINAL --} | ]]) @@ -1925,7 +1924,7 @@ describe('TUI', function() feed_data ':set visualbell\n' screen:expect { grid = [[ - {1: } | + ^ | {4:~ }|*3 {5:[No Name] }| :set visualbell | @@ -1939,7 +1938,7 @@ describe('TUI', function() feed_data 'i' screen:expect { grid = [[ - {1: } | + ^ | {4:~ }|*3 {5:[No Name] }| {3:-- INSERT --} | @@ -1954,7 +1953,7 @@ describe('TUI', function() grid = [[ Vim: Caught deadly signal 'SIGTERM' | |*2 - [Process exited 1]{1: } | + [Process exited 1]^ | |*2 {3:-- TERMINAL --} | ]], @@ -1981,7 +1980,7 @@ describe('TUI', function() [5] = { bold = true }, }) screen:expect([[ - {1: } | + ^ | {2:~}{3: }|*3 {4:[No Name] }| | @@ -1989,7 +1988,7 @@ describe('TUI', function() ]]) feed_data('i') screen:expect([[ - {1: } | + ^ | {2:~}{3: }|*3 {4:[No Name] }| {5:-- INSERT --} | @@ -2000,7 +1999,7 @@ describe('TUI', function() it('redraws on SIGWINCH even if terminal size is unchanged #23411', function() child_session:request('nvim_echo', { { 'foo' } }, false, {}) screen:expect([[ - {1: } | + ^ | {4:~ }|*3 {5:[No Name] }| foo | @@ -2008,7 +2007,7 @@ describe('TUI', function() ]]) exec_lua([[vim.uv.kill(vim.fn.jobpid(vim.bo.channel), 'sigwinch')]]) screen:expect([[ - {1: } | + ^ | {4:~ }|*3 {5:[No Name] }| | @@ -2031,7 +2030,7 @@ describe('TUI', function() ]]) feed_data('\003') screen:expect([[ - {1: } | + ^ | {4:~ }|*3 {5:[No Name] }| Type :qa and press <Enter> to exit Nvim | @@ -2046,7 +2045,7 @@ describe('TUI', function() {1:foo} | {4:~ }|*3 {5:[No Name] [+] }| - /foo{1: } | + /foo^ | {3:-- TERMINAL --} | ]]) screen:sleep(10) @@ -2055,7 +2054,7 @@ describe('TUI', function() foo | {4:~ }|*3 {5:[No Name] [+] }| - /foob{1: } | + /foob^ | {3:-- TERMINAL --} | ]]) screen:sleep(10) @@ -2064,7 +2063,7 @@ describe('TUI', function() foo | {4:~ }|*3 {5:[No Name] [+] }| - /fooba{1: } | + /fooba^ | {3:-- TERMINAL --} | ]]) end) @@ -2114,7 +2113,7 @@ describe('TUI', function() [5] = { bold = true, reverse = true }, [6] = { foreground = Screen.colors.White, background = Screen.colors.DarkGreen }, }) - fn.termopen({ + fn.jobstart({ nvim_prog, '--clean', '--cmd', @@ -2124,6 +2123,7 @@ describe('TUI', function() '--cmd', 'let start = reltime() | while v:true | if reltimefloat(reltime(start)) > 2 | break | endif | endwhile', }, { + term = true, env = { VIMRUNTIME = os.getenv('VIMRUNTIME'), }, @@ -2146,7 +2146,7 @@ describe('TUI', function() for _, guicolors in ipairs({ 'notermguicolors', 'termguicolors' }) do it('has no black flicker when clearing regions during startup with ' .. guicolors, function() local screen = Screen.new(50, 10) - fn.termopen({ + fn.jobstart({ nvim_prog, '--clean', '--cmd', @@ -2154,6 +2154,7 @@ describe('TUI', function() '--cmd', 'sleep 10', }, { + term = true, env = { VIMRUNTIME = os.getenv('VIMRUNTIME'), }, @@ -2194,7 +2195,7 @@ describe('TUI', function() local screen = tt.setup_child_nvim({ '--clean', '-l', script_file }) screen:expect { grid = [[ - {1: } | + ^ | ~ |*3 [No Name] 0,0-1 All| | @@ -2207,7 +2208,7 @@ describe('TUI', function() Xargv0nvim | --embed | --clean | - {1:X}argv0nvim | + ^Xargv0nvim | [No Name] [+] 5,1 Bot| 4 more lines | {3:-- TERMINAL --} | @@ -2233,7 +2234,7 @@ describe('TUI', function() :q | abc | | - [Process exited 0]{1: } | + [Process exited 0]^ | | {3:-- TERMINAL --} | ]]) @@ -2254,7 +2255,7 @@ describe('TUI', function() }) screen:expect { grid = [[ - {1: } | + ^ | {4:~ }|*3 {5:[No Name] }| | @@ -2264,7 +2265,7 @@ describe('TUI', function() command([[call chansend(b:terminal_job_id, "\<C-h>")]]) screen:expect([[ - {1: } | + ^ | {4:~ }|*3 {5:[No Name] }| <C-h> | @@ -2287,7 +2288,7 @@ describe('TUI', function() }, { cols = 80 }) screen:expect { grid = [[ - {1:1}st line | + ^1st line | |*2 2nd line | {5:[No Name] [+] 1,1 All}| @@ -2300,7 +2301,7 @@ describe('TUI', function() grid = [[ 1st line | | - {1: } | + ^ | 2nd line | {5:[No Name] [+] 1,161 All}| | @@ -2320,7 +2321,7 @@ describe('TUI', function() }, { extra_rows = 10, cols = 66 }) screen:expect { grid = [[ - | + ^ | aabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabb|*12 aabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabba@@@| [No Name] [+] 1,0-1 Top| @@ -2339,7 +2340,7 @@ describe('TUI', function() -- 500-cell limit, so the buffer is flushed after these spaces. screen:expect { grid = [[ - | + ^ | aabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabb|*12 aabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabba@@@| [No Name] [+] 1,0-1 Top| @@ -2366,7 +2367,7 @@ describe('TUI', function() screen:expect { grid = [[ - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + ^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| ~ |*3 [No Name] [+] 1,1 All| | @@ -2378,7 +2379,8 @@ describe('TUI', function() feed_data(':set columns=12\n') screen:expect { grid = [[ - aaaaaaaaaaaa |*4 + ^aaaaaaaaaaaa | + aaaaaaaaaaaa |*3 < [+] 1,1 | | -- TERMINAL -- | @@ -2416,7 +2418,7 @@ describe('TUI UIEnter/UILeave', function() }) screen:expect { grid = [[ - {1: } | + ^ | {4:~ }|*3 {5:[No Name] }| | @@ -2426,7 +2428,7 @@ describe('TUI UIEnter/UILeave', function() feed_data(':echo g:evs\n') screen:expect { grid = [[ - {1: } | + ^ | {4:~ }|*3 {5:[No Name] }| ['VimEnter', 'UIEnter'] | @@ -2457,7 +2459,7 @@ describe('TUI FocusGained/FocusLost', function() }) screen:expect([[ - {1: } | + ^ | {4:~ }|*3 {5:[No Name] }| | @@ -2479,7 +2481,7 @@ describe('TUI FocusGained/FocusLost', function() retry(2, 3 * screen.timeout, function() feed_data('\027[I') screen:expect([[ - {1: } | + ^ | {4:~ }|*3 {5:[No Name] }| gained | @@ -2488,7 +2490,7 @@ describe('TUI FocusGained/FocusLost', function() feed_data('\027[O') screen:expect([[ - {1: } | + ^ | {4:~ }|*3 {5:[No Name] }| lost | @@ -2502,7 +2504,7 @@ describe('TUI FocusGained/FocusLost', function() feed_data('i') screen:expect { grid = [[ - {1: } | + ^ | {4:~ }|*3 {5:[No Name] }| :set noshowmode | @@ -2512,7 +2514,7 @@ describe('TUI FocusGained/FocusLost', function() retry(2, 3 * screen.timeout, function() feed_data('\027[I') screen:expect([[ - {1: } | + ^ | {4:~ }|*3 {5:[No Name] }| gained | @@ -2520,7 +2522,7 @@ describe('TUI FocusGained/FocusLost', function() ]]) feed_data('\027[O') screen:expect([[ - {1: } | + ^ | {4:~ }|*3 {5:[No Name] }| lost | @@ -2538,7 +2540,7 @@ describe('TUI FocusGained/FocusLost', function() | {4:~ }|*3 {5:[No Name] }| - :{1: } | + :^ | {3:-- TERMINAL --} | ]]) feed_data('\027[O') @@ -2547,7 +2549,7 @@ describe('TUI FocusGained/FocusLost', function() | {4:~ }|*3 {5:[No Name] }| - :{1: } | + :^ | {3:-- TERMINAL --} | ]], unchanged = true, @@ -2590,7 +2592,7 @@ describe('TUI FocusGained/FocusLost', function() -- Wait for terminal to be ready. screen:expect { grid = [[ - {1:r}eady $ zia | + ^ready $ zia | | [Process exited 0] | |*2 @@ -2602,7 +2604,7 @@ describe('TUI FocusGained/FocusLost', function() feed_data('\027[I') screen:expect { grid = [[ - {1:r}eady $ zia | + ^ready $ zia | | [Process exited 0] | |*2 @@ -2614,7 +2616,7 @@ describe('TUI FocusGained/FocusLost', function() feed_data('\027[O') screen:expect([[ - {1:r}eady $ zia | + ^ready $ zia | | [Process exited 0] | |*2 @@ -2634,7 +2636,7 @@ describe('TUI FocusGained/FocusLost', function() msg3 | msg4 | msg5 | - {10:Press ENTER or type command to continue}{1: } | + {10:Press ENTER or type command to continue}^ | {3:-- TERMINAL --} | ]], } @@ -2647,7 +2649,7 @@ describe('TUI FocusGained/FocusLost', function() msg3 | msg4 | msg5 | - {10:Press ENTER or type command to continue}{1: } | + {10:Press ENTER or type command to continue}^ | {3:-- TERMINAL --} | ]], unchanged = true, @@ -2690,7 +2692,7 @@ describe("TUI 't_Co' (terminal colors)", function() screen:expect(string.format( [[ - {1: } | + ^ | %s|*4 | {3:-- TERMINAL --} | @@ -2701,7 +2703,7 @@ describe("TUI 't_Co' (terminal colors)", function() feed_data(':echo &t_Co\n') screen:expect(string.format( [[ - {1: } | + ^ | %s|*4 %-3s | {3:-- TERMINAL --} | @@ -3028,7 +3030,7 @@ describe('TUI', function() -- Wait for TUI to start. feed_data('Gitext') screen:expect([[ - text{1: } | + text^ | {4:~ }|*4 {3:-- INSERT --} | {3:-- TERMINAL --} | @@ -3045,7 +3047,7 @@ describe('TUI', function() nvim_tui() screen:expect([[ - {1: } | + ^ | {4:~ }|*4 | {3:-- TERMINAL --} | @@ -3055,7 +3057,7 @@ describe('TUI', function() screen:expect { grid = [[ - {1: } | + ^ | {4:~ }|*4 | {3:-- TERMINAL --} | @@ -3181,7 +3183,6 @@ describe('TUI', function() local req = args.data local payload = req:match('^\027P%+q([%x;]+)$') if payload and vim.text.hexdecode(payload) == 'Ms' then - vim.g.xtgettcap = 'Ms' local resp = string.format('\027P1+r%s=%s\027\\', payload, vim.text.hexencode('\027]52;;\027\\')) vim.api.nvim_chan_send(vim.bo[args.buf].channel, resp) return true @@ -3199,9 +3200,6 @@ describe('TUI', function() }, { env = { VIMRUNTIME = os.getenv('VIMRUNTIME'), - - -- Only queries when SSH_TTY is set - SSH_TTY = '/dev/pts/1', }, }) @@ -3209,8 +3207,7 @@ describe('TUI', function() local child_session = n.connect(child_server) retry(nil, 1000, function() - eq('Ms', eval("get(g:, 'xtgettcap', '')")) - eq({ true, 'OSC 52' }, { child_session:request('nvim_eval', 'g:clipboard.name') }) + eq({ true, { osc52 = true } }, { child_session:request('nvim_eval', 'g:termfeatures') }) end) end) end) @@ -3305,13 +3302,39 @@ describe('TUI bg color', function() 'autocmd OptionSet background echo "did OptionSet, yay!"', }) screen:expect([[ - {1: } | + ^ | {3:~} |*3 {5:[No Name] 0,0-1 All}| did OptionSet, yay! | {3:-- TERMINAL --} | ]]) end) + + it('sends theme update notifications when background changes #31652', function() + command('set background=dark') -- set outer Nvim background + local child_server = new_pipename() + local screen = tt.setup_child_nvim({ + '--listen', + child_server, + '-u', + 'NONE', + '-i', + 'NONE', + '--cmd', + 'colorscheme vim', + '--cmd', + 'set noswapfile', + }) + screen:expect({ any = '%[No Name%]' }) + local child_session = n.connect(child_server) + retry(nil, nil, function() + eq({ true, 'dark' }, { child_session:request('nvim_eval', '&background') }) + end) + command('set background=light') -- set outer Nvim background + retry(nil, nil, function() + eq({ true, 'light' }, { child_session:request('nvim_eval', '&background') }) + end) + end) end) -- These tests require `tt` because --headless/--embed @@ -3322,8 +3345,8 @@ describe('TUI as a client', function() end) it('connects to remote instance (with its own TUI)', function() - local server_super = spawn_argv(false) -- equivalent to clear() - local client_super = spawn_argv(true) + local server_super = n.new_session(false) + local client_super = n.new_session(true) set_session(server_super) local server_pipe = new_pipename() @@ -3343,7 +3366,7 @@ describe('TUI as a client', function() feed_data('iHello, World') screen_server:expect { grid = [[ - Hello, World{1: } | + Hello, World^ | {4:~ }|*3 {5:[No Name] [+] }| {3:-- INSERT --} | @@ -3353,7 +3376,7 @@ describe('TUI as a client', function() feed_data('\027') screen_server:expect { grid = [[ - Hello, Worl{1:d} | + Hello, Worl^d | {4:~ }|*3 {5:[No Name] [+] }| | @@ -3370,7 +3393,7 @@ describe('TUI as a client', function() screen_client:expect { grid = [[ - Hello, Worl{1:d} | + Hello, Worl^d | {4:~ }|*3 {5:[No Name] [+] }| | @@ -3383,7 +3406,7 @@ describe('TUI as a client', function() feed_data('0:set lines=3\n') screen_server:expect { grid = [[ - {1:a}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + ^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {5:[No Name] [+] }| |*4 {3:-- TERMINAL --} | @@ -3397,8 +3420,8 @@ describe('TUI as a client', function() end) it('connects to remote instance (--headless)', function() - local server = spawn_argv(false) -- equivalent to clear() - local client_super = spawn_argv(true, { env = { NVIM_LOG_FILE = testlog } }) + local server = n.new_session(false) + local client_super = n.new_session(true, { env = { NVIM_LOG_FILE = testlog } }) set_session(server) local server_pipe = api.nvim_get_vvar('servername') @@ -3414,7 +3437,7 @@ describe('TUI as a client', function() screen_client:expect { grid = [[ - Halloj{1:!} | + Halloj^! | {4:~ }|*4 | {3:-- TERMINAL --} | @@ -3428,7 +3451,7 @@ describe('TUI as a client', function() grid = [[ Vim: Caught deadly signal 'SIGTERM' | |*2 - [Process exited 1]{1: } | + [Process exited 1]^ | |*2 {3:-- TERMINAL --} | ]], @@ -3457,15 +3480,15 @@ describe('TUI as a client', function() screen:expect([[ Remote ui failed to start: {MATCH:.*}| | - [Process exited 1]{1: } | + [Process exited 1]^ | |*3 {3:-- TERMINAL --} | ]]) end) local function test_remote_tui_quit(status) - local server_super = spawn_argv(false) -- equivalent to clear() - local client_super = spawn_argv(true) + local server_super = n.new_session(false) + local client_super = n.new_session(true) set_session(server_super) local server_pipe = new_pipename() @@ -3483,7 +3506,7 @@ describe('TUI as a client', function() }) screen_server:expect { grid = [[ - {1: } | + ^ | {4:~ }|*3 {5:[No Name] }| | @@ -3494,7 +3517,7 @@ describe('TUI as a client', function() feed_data('iHello, World') screen_server:expect { grid = [[ - Hello, World{1: } | + Hello, World^ | {4:~ }|*3 {5:[No Name] [+] }| {3:-- INSERT --} | @@ -3504,7 +3527,7 @@ describe('TUI as a client', function() feed_data('\027') screen_server:expect { grid = [[ - Hello, Worl{1:d} | + Hello, Worl^d | {4:~ }|*3 {5:[No Name] [+] }| | @@ -3521,7 +3544,7 @@ describe('TUI as a client', function() screen_client:expect { grid = [[ - Hello, Worl{1:d} | + Hello, Worl^d | {4:~ }|*3 {5:[No Name] [+] }| | @@ -3536,7 +3559,7 @@ describe('TUI as a client', function() screen_server:expect { grid = [[ | - [Process exited ]] .. status .. [[]{1: }{MATCH:%s+}| + [Process exited ]] .. status .. [[]^ {MATCH:%s+}| |*4 {3:-- TERMINAL --} | ]], @@ -3545,7 +3568,7 @@ describe('TUI as a client', function() screen_client:expect { grid = [[ | - [Process exited ]] .. status .. [[]{1: }{MATCH:%s+}| + [Process exited ]] .. status .. [[]^ {MATCH:%s+}| |*4 {3:-- TERMINAL --} | ]], diff --git a/test/functional/terminal/window_spec.lua b/test/functional/terminal/window_spec.lua index fdb606e959..a65d18de70 100644 --- a/test/functional/terminal/window_spec.lua +++ b/test/functional/terminal/window_spec.lua @@ -62,7 +62,7 @@ describe(':terminal window', function() screen:expect([[ {7:1 }tty ready | {7:2 }rows: 6, cols: 48 | - {7:3 }{1: } | + {7:3 }^ | {7:4 } | {7:5 } | {7:6 } | @@ -73,7 +73,7 @@ describe(':terminal window', function() {7:1 }tty ready | {7:2 }rows: 6, cols: 48 | {7:3 }abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUV| - {7:4 }WXYZ{1: } | + {7:4 }WXYZ^ | {7:5 } | {7:6 } | {3:-- TERMINAL --} | @@ -87,7 +87,7 @@ describe(':terminal window', function() {7: 2 }rows: 6, cols: 48 | {7: 3 }abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNO| {7: 4 }PQRSTUVWXYZrows: 6, cols: 41 | - {7: 5 }{1: } | + {7: 5 }^ | {7: 6 } | {3:-- TERMINAL --} | ]]) @@ -98,7 +98,7 @@ describe(':terminal window', function() {7: 3 }abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNO| {7: 4 }PQRSTUVWXYZrows: 6, cols: 41 | {7: 5 } abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMN| - {7: 6 }OPQRSTUVWXYZ{1: } | + {7: 6 }OPQRSTUVWXYZ^ | {3:-- TERMINAL --} | ]]) end) @@ -110,7 +110,7 @@ describe(':terminal window', function() screen:expect([[ {7:++1 }tty ready | {7:++2 }rows: 6, cols: 45 | - {7:++3 }{1: } | + {7:++3 }^ | {7:++4 } | {7:++5 } | {7:++6 } | @@ -123,7 +123,7 @@ describe(':terminal window', function() {7:++6 } | {7:++7 } | {7:++8 }abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRS| - {7:++9 }TUVWXYZ{1: } | + {7:++9 }TUVWXYZ^ | {3:-- TERMINAL --} | ]]) feed_data('\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ') @@ -133,7 +133,7 @@ describe(':terminal window', function() {7:++ 9 }STUVWXYZ | {7:++10 }abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQR| {7:++11 }STUVWXYZrows: 6, cols: 44 | - {7:++12 }{1: } | + {7:++12 }^ | {3:-- TERMINAL --} | ]]) end) @@ -144,7 +144,7 @@ describe(':terminal window', function() feed([[<C-\><C-N>]]) screen:expect([[ tty ready | - {2:^ } | + ^ | |*5 ]]) feed(':set colorcolumn=20<CR>i') @@ -153,7 +153,7 @@ describe(':terminal window', function() it('wont show the color column', function() screen:expect([[ tty ready | - {1: } | + ^ | |*4 {3:-- TERMINAL --} | ]]) @@ -170,7 +170,7 @@ describe(':terminal window', function() line2 | line3 | line4 | - {1: } | + ^ | {3:-- TERMINAL --} | ]]) end) @@ -184,7 +184,7 @@ describe(':terminal window', function() line2 | line3 | line4 | - {2: } | + | | ]]) end) @@ -206,7 +206,7 @@ describe(':terminal with multigrid', function() [3:--------------------------------------------------]| ## grid 2 tty ready | - {1: } | + ^ | |*4 ## grid 3 {3:-- TERMINAL --} | @@ -223,7 +223,7 @@ describe(':terminal with multigrid', function() ## grid 2 tty ready | rows: 10, cols: 20 | - {1: } | + ^ | |*7 ## grid 3 {3:-- TERMINAL --} | @@ -241,7 +241,7 @@ describe(':terminal with multigrid', function() ## grid 2 rows: 10, cols: 20 | rows: 3, cols: 70 | - {1: } | + ^ | ## grid 3 {3:-- TERMINAL --} | ]]) @@ -260,7 +260,7 @@ describe(':terminal with multigrid', function() rows: 10, cols: 20 | rows: 3, cols: 70 | rows: 6, cols: 50 | - {1: } | + ^ | | ## grid 3 {3:-- TERMINAL --} | diff --git a/test/functional/terminal/window_split_tab_spec.lua b/test/functional/terminal/window_split_tab_spec.lua index 272fc513af..dc22c87ca0 100644 --- a/test/functional/terminal/window_split_tab_spec.lua +++ b/test/functional/terminal/window_split_tab_spec.lua @@ -49,7 +49,7 @@ describe(':terminal', function() ========== | tty ready | rows: 5, cols: 50 | - {2: } | + | |*2 ========== | :2split | @@ -61,7 +61,7 @@ describe(':terminal', function() ========== | ^tty ready | rows: 5, cols: 50 | - {2: } | + | |*2 ========== | :wincmd p | @@ -77,7 +77,7 @@ describe(':terminal', function() command('bprevious') screen:expect([[ tty ready | - ^foo{2: } | + ^foo | |*8 ]]) end) @@ -102,7 +102,7 @@ describe(':terminal', function() screen:expect([[ tty ready | rows: 7, cols: 47 | - {2: } | + | |*3 ^ | | @@ -112,7 +112,7 @@ describe(':terminal', function() tty ready | rows: 7, cols: 47 | rows: 4, cols: 41 | - {2:^ } | + ^ | | ]]) end) diff --git a/test/functional/testnvim.lua b/test/functional/testnvim.lua index 60b2f872fc..59cb593cf7 100644 --- a/test/functional/testnvim.lua +++ b/test/functional/testnvim.lua @@ -4,7 +4,7 @@ local t = require('test.testutil') local Session = require('test.client.session') local uv_stream = require('test.client.uv_stream') local SocketStream = uv_stream.SocketStream -local ChildProcessStream = uv_stream.ChildProcessStream +local ProcStream = uv_stream.ProcStream local check_cores = t.check_cores local check_logs = t.check_logs @@ -48,6 +48,16 @@ M.nvim_argv = { 'unlet g:colors_name', '--embed', } +if os.getenv('OSV_PORT') then + table.insert(M.nvim_argv, '--cmd') + table.insert( + M.nvim_argv, + string.format( + "lua require('osv').launch({ port = %s, blocking = true })", + os.getenv('OSV_PORT') + ) + ) +end -- Directory containing nvim. M.nvim_dir = M.nvim_prog:gsub('[/\\][^/\\]+$', '') @@ -308,24 +318,14 @@ function M.stop() assert(session):stop() end -function M.nvim_prog_abs() - -- system(['build/bin/nvim']) does not work for whatever reason. It must - -- be executable searched in $PATH or something starting with / or ./. - if M.nvim_prog:match('[/\\]') then - return M.request('nvim_call_function', 'fnamemodify', { M.nvim_prog, ':p' }) - else - return M.nvim_prog - end -end - -- Use for commands which expect nvim to quit. -- The first argument can also be a timeout. function M.expect_exit(fn_or_timeout, ...) local eof_err_msg = 'EOF was received from Nvim. Likely the Nvim process crashed.' if type(fn_or_timeout) == 'function' then - eq(eof_err_msg, t.pcall_err(fn_or_timeout, ...)) + t.matches(eof_err_msg, t.pcall_err(fn_or_timeout, ...)) else - eq( + t.matches( eof_err_msg, t.pcall_err(function(timeout, fn, ...) fn(...) @@ -455,22 +455,6 @@ function M.check_close() session = nil end ---- @param argv string[] ---- @param merge boolean? ---- @param env string[]? ---- @param keep boolean? ---- @param io_extra uv.uv_pipe_t? used for stdin_fd, see :help ui-option ---- @return test.Session -function M.spawn(argv, merge, env, keep, io_extra) - if not keep then - M.check_close() - end - - local child_stream = - ChildProcessStream.spawn(merge and M.merge_args(prepend_argv, argv) or argv, env, io_extra) - return Session.new(child_stream) -end - -- Creates a new Session connected by domain socket (named pipe) or TCP. function M.connect(file_or_address) local addr, port = string.match(file_or_address, '(.*):(%d+)') @@ -479,61 +463,112 @@ function M.connect(file_or_address) return Session.new(stream) end --- Starts (and returns) a new global Nvim session. --- --- Parameters are interpreted as startup args, OR a map with these keys: --- args: List: Args appended to the default `nvim_argv` set. --- args_rm: List: Args removed from the default set. All cases are --- removed, e.g. args_rm={'--cmd'} removes all cases of "--cmd" --- (and its value) from the default set. --- env: Map: Defines the environment of the new session. --- --- Example: --- clear('-e') --- clear{args={'-e'}, args_rm={'-i'}, env={TERM=term}} +--- Starts a new, global Nvim session and clears the current one. +--- +--- Note: Use `new_session()` to start a session without replacing the current one. +--- +--- Parameters are interpreted as startup args, OR a map with these keys: +--- - args: List: Args appended to the default `nvim_argv` set. +--- - args_rm: List: Args removed from the default set. All cases are +--- removed, e.g. args_rm={'--cmd'} removes all cases of "--cmd" +--- (and its value) from the default set. +--- - env: Map: Defines the environment of the new session. +--- +--- Example: +--- ``` +--- clear('-e') +--- clear{args={'-e'}, args_rm={'-i'}, env={TERM=term}} +--- ``` +--- +--- @param ... string Nvim CLI args +--- @return test.Session +--- @overload fun(opts: test.session.Opts): test.Session function M.clear(...) - M.set_session(M.spawn_argv(false, ...)) + M.set_session(M.new_session(false, ...)) return M.get_session() end ---- same params as clear, but does returns the session instead ---- of replacing the default session +--- Starts a new Nvim process with the given args and returns a msgpack-RPC session. +--- +--- Does not replace the current global session, unlike `clear()`. +--- +--- @param keep boolean (default: false) Don't close the current global session. +--- @param ... string Nvim CLI args (or see overload) --- @return test.Session -function M.spawn_argv(keep, ...) - local argv, env, io_extra = M.new_argv(...) - return M.spawn(argv, nil, env, keep, io_extra) +--- @overload fun(keep: boolean, opts: test.session.Opts): test.Session +function M.new_session(keep, ...) + if not keep then + M.check_close() + end + + local argv, env, io_extra = M._new_argv(...) + + local proc = ProcStream.spawn(argv, env, io_extra) + return Session.new(proc) end ---- @class test.new_argv.Opts +--- Starts a (non-RPC, `--headless --listen "Tx"`) Nvim process, waits for exit, and returns result. +--- +--- @param ... string Nvim CLI args, or `test.session.Opts` table. +--- @return test.ProcStream +--- @overload fun(opts: test.session.Opts): test.ProcStream +function M.spawn_wait(...) + local opts = type(...) == 'string' and { args = { ... } } or ... + opts.args_rm = opts.args_rm and opts.args_rm or {} + table.insert(opts.args_rm, '--embed') + local argv, env, io_extra = M._new_argv(opts) + local proc = ProcStream.spawn(argv, env, io_extra) + proc.collect_text = true + proc:read_start() + proc:wait() + proc:close() + return proc +end + +--- @class test.session.Opts +--- Nvim CLI args --- @field args? string[] +--- Remove these args from the default `nvim_argv` args set. Ignored if `merge=false`. --- @field args_rm? string[] +--- (default: true) Merge `args` with the default set. Else use only the provided `args`. +--- @field merge? boolean +--- Environment variables --- @field env? table<string,string> +--- Used for stdin_fd, see `:help ui-option` --- @field io_extra? uv.uv_pipe_t ---- Builds an argument list for use in clear(). +--- @private --- ---- @see clear() for parameters. ---- @param ... string +--- Builds an argument list for use in `new_session()`, `clear()`, and `spawn_wait()`. +--- +--- @param ... string Nvim CLI args, or `test.session.Opts` table. --- @return string[] --- @return string[]? --- @return uv.uv_pipe_t? -function M.new_argv(...) - local args = { unpack(M.nvim_argv) } - table.insert(args, '--headless') - if _G._nvim_test_id then - -- Set the server name to the test-id for logging. #8519 - table.insert(args, '--listen') - table.insert(args, _G._nvim_test_id) +--- @overload fun(opts: test.session.Opts): string[], string[]?, uv.uv_pipe_t? +function M._new_argv(...) + --- @type test.session.Opts|string + local opts = select(1, ...) + local merge = type(opts) ~= 'table' and true or opts.merge ~= false + + local args = merge and { unpack(M.nvim_argv) } or { M.nvim_prog } + if merge then + table.insert(args, '--headless') + if _G._nvim_test_id then + -- Set the server name to the test-id for logging. #8519 + table.insert(args, '--listen') + table.insert(args, _G._nvim_test_id) + end end + local new_args --- @type string[] local io_extra --- @type uv.uv_pipe_t? - local env --- @type string[]? - --- @type test.new_argv.Opts|string - local opts = select(1, ...) + local env --- @type string[]? List of "key=value" env vars. + if type(opts) ~= 'table' then new_args = { ... } else - args = remove_args(args, opts.args_rm) + args = merge and remove_args(args, opts.args_rm) or args if opts.env then local env_opt = {} --- @type table<string,string> for k, v in pairs(opts.env) do @@ -800,81 +835,6 @@ function M.exec_capture(code) return M.api.nvim_exec2(code, { output = true }).output end ---- @param f function ---- @return table<string,any> -local function get_upvalues(f) - local i = 1 - local upvalues = {} --- @type table<string,any> - while true do - local n, v = debug.getupvalue(f, i) - if not n then - break - end - upvalues[n] = v - i = i + 1 - end - return upvalues -end - ---- @param f function ---- @param upvalues table<string,any> -local function set_upvalues(f, upvalues) - local i = 1 - while true do - local n = debug.getupvalue(f, i) - if not n then - break - end - if upvalues[n] then - debug.setupvalue(f, i, upvalues[n]) - end - i = i + 1 - end -end - ---- @type fun(f: function): table<string,any> -_G.__get_upvalues = nil - ---- @type fun(f: function, upvalues: table<string,any>) -_G.__set_upvalues = nil - ---- @param self table<string,function> ---- @param bytecode string ---- @param upvalues table<string,any> ---- @param ... any[] ---- @return any[] result ---- @return table<string,any> upvalues -local function exec_lua_handler(self, bytecode, upvalues, ...) - local f = assert(loadstring(bytecode)) - self.set_upvalues(f, upvalues) - local ret = { f(...) } --- @type any[] - --- @type table<string,any> - local new_upvalues = self.get_upvalues(f) - - do -- Check return value types for better error messages - local invalid_types = { - ['thread'] = true, - ['function'] = true, - ['userdata'] = true, - } - - for k, v in pairs(ret) do - if invalid_types[type(v)] then - error( - string.format( - "Return index %d with value '%s' of type '%s' cannot be serialized over RPC", - k, - tostring(v), - type(v) - ) - ) - end - end - end - - return ret, new_upvalues -end - --- Execute Lua code in the wrapped Nvim session. --- --- When `code` is passed as a function, it is converted into Lua byte code. @@ -921,52 +881,7 @@ function M.exec_lua(code, ...) end assert(session, 'no Nvim session') - - if not session.exec_lua_setup then - assert( - session:request( - 'nvim_exec_lua', - [[ - _G.__test_exec_lua = { - get_upvalues = loadstring((select(1,...))), - set_upvalues = loadstring((select(2,...))), - handler = loadstring((select(3,...))) - } - setmetatable(_G.__test_exec_lua, { __index = _G.__test_exec_lua }) - ]], - { string.dump(get_upvalues), string.dump(set_upvalues), string.dump(exec_lua_handler) } - ) - ) - session.exec_lua_setup = true - end - - local stat, rv = session:request( - 'nvim_exec_lua', - 'return { _G.__test_exec_lua:handler(...) }', - { string.dump(code), get_upvalues(code), ... } - ) - - if not stat then - error(rv[2]) - end - - --- @type any[], table<string,any> - local ret, upvalues = unpack(rv) - - -- Update upvalues - if next(upvalues) then - local caller = debug.getinfo(2) - local f = caller.func - -- On PUC-Lua, if the function is a tail call, then func will be nil. - -- In this case we need to use the current function. - if not f then - assert(caller.source == '=(tail call)') - f = debug.getinfo(1).func - end - set_upvalues(f, upvalues) - end - - return unpack(ret, 1, table.maxn(ret)) + return require('test.functional.testnvim.exec_lua')(session, 2, code, ...) end function M.get_pathsep() diff --git a/test/functional/testnvim/exec_lua.lua b/test/functional/testnvim/exec_lua.lua new file mode 100644 index 0000000000..ddd9905ce7 --- /dev/null +++ b/test/functional/testnvim/exec_lua.lua @@ -0,0 +1,148 @@ +--- @param f function +--- @return table<string,any> +local function get_upvalues(f) + local i = 1 + local upvalues = {} --- @type table<string,any> + while true do + local n, v = debug.getupvalue(f, i) + if not n then + break + end + upvalues[n] = v + i = i + 1 + end + return upvalues +end + +--- @param f function +--- @param upvalues table<string,any> +local function set_upvalues(f, upvalues) + local i = 1 + while true do + local n = debug.getupvalue(f, i) + if not n then + break + end + if upvalues[n] then + debug.setupvalue(f, i, upvalues[n]) + end + i = i + 1 + end +end + +--- @param messages string[] +--- @param ... ... +local function add_print(messages, ...) + local msg = {} --- @type string[] + for i = 1, select('#', ...) do + msg[#msg + 1] = tostring(select(i, ...)) + end + table.insert(messages, table.concat(msg, '\t')) +end + +local invalid_types = { + ['thread'] = true, + ['function'] = true, + ['userdata'] = true, +} + +--- @param r any[] +local function check_returns(r) + for k, v in pairs(r) do + if invalid_types[type(v)] then + error( + string.format( + "Return index %d with value '%s' of type '%s' cannot be serialized over RPC", + k, + tostring(v), + type(v) + ), + 2 + ) + end + end +end + +local M = {} + +--- This is run in the context of the remote Nvim instance. +--- @param bytecode string +--- @param upvalues table<string,any> +--- @param ... any[] +--- @return any[] result +--- @return table<string,any> upvalues +--- @return string[] messages +function M.handler(bytecode, upvalues, ...) + local messages = {} --- @type string[] + local orig_print = _G.print + + function _G.print(...) + add_print(messages, ...) + return orig_print(...) + end + + local f = assert(loadstring(bytecode)) + + set_upvalues(f, upvalues) + + -- Run in pcall so we can return any print messages + local ret = { pcall(f, ...) } --- @type any[] + + _G.print = orig_print + + local new_upvalues = get_upvalues(f) + + -- Check return value types for better error messages + check_returns(ret) + + return ret, new_upvalues, messages +end + +--- @param session test.Session +--- @param lvl integer +--- @param code function +--- @param ... ... +local function run(session, lvl, code, ...) + local stat, rv = session:request( + 'nvim_exec_lua', + [[return { require('test.functional.testnvim.exec_lua').handler(...) }]], + { string.dump(code), get_upvalues(code), ... } + ) + + if not stat then + error(rv[2], 2) + end + + --- @type any[], table<string,any>, string[] + local ret, upvalues, messages = unpack(rv) + + for _, m in ipairs(messages) do + print(m) + end + + if not ret[1] then + error(ret[2], 2) + end + + -- Update upvalues + if next(upvalues) then + local caller = debug.getinfo(lvl) + local i = 0 + + -- On PUC-Lua, if the function is a tail call, then func will be nil. + -- In this case we need to use the caller. + while not caller.func do + i = i + 1 + caller = debug.getinfo(lvl + i) + end + set_upvalues(caller.func, upvalues) + end + + return unpack(ret, 2, table.maxn(ret)) +end + +return setmetatable(M, { + __call = function(_, ...) + return run(...) + end, +}) diff --git a/test/functional/testterm.lua b/test/functional/testterm.lua index 3aadcc59a7..17209d947e 100644 --- a/test/functional/testterm.lua +++ b/test/functional/testterm.lua @@ -29,6 +29,10 @@ function M.feed_termcode(data) M.feed_data('\027' .. data) end +function M.feed_csi(data) + M.feed_termcode('[' .. data) +end + function M.make_lua_executor(session) return function(code, ...) local status, rv = session:request('nvim_exec_lua', code, { ... }) @@ -78,6 +82,9 @@ end function M.set_undercurl() M.feed_termcode('[4:3m') end +function M.set_reverse() + M.feed_termcode('[7m') +end function M.set_strikethrough() M.feed_termcode('[9m') end @@ -108,7 +115,6 @@ function M.setup_screen(extra_rows, cmd, cols, env, screen_opts) cols = cols and cols or 50 api.nvim_command('highlight TermCursor cterm=reverse') - api.nvim_command('highlight TermCursorNC ctermbg=11') api.nvim_command('highlight StatusLineTerm ctermbg=2 ctermfg=0') api.nvim_command('highlight StatusLineTermNC ctermbg=2 ctermfg=8') @@ -135,7 +141,7 @@ function M.setup_screen(extra_rows, cmd, cols, env, screen_opts) }) api.nvim_command('enew') - api.nvim_call_function('termopen', { cmd, env and { env = env } or nil }) + api.nvim_call_function('jobstart', { cmd, { term = true, env = (env and env or nil) } }) api.nvim_input('<CR>') local vim_errmsg = api.nvim_eval('v:errmsg') if vim_errmsg and '' ~= vim_errmsg then @@ -154,7 +160,7 @@ function M.setup_screen(extra_rows, cmd, cols, env, screen_opts) local empty_line = (' '):rep(cols) local expected = { 'tty ready' .. (' '):rep(cols - 9), - '{1: }' .. (' '):rep(cols - 1), + '^' .. (' '):rep(cols), empty_line, empty_line, empty_line, diff --git a/test/functional/treesitter/fold_spec.lua b/test/functional/treesitter/fold_spec.lua index e38e58ff92..ac58df4bba 100644 --- a/test/functional/treesitter/fold_spec.lua +++ b/test/functional/treesitter/fold_spec.lua @@ -5,6 +5,7 @@ local Screen = require('test.functional.ui.screen') local clear = n.clear local eq = t.eq local insert = n.insert +local write_file = t.write_file local exec_lua = n.exec_lua local command = n.command local feed = n.feed @@ -767,4 +768,79 @@ t2]]) ]], } end) + + it("doesn't call get_parser too often when parser is not available", function() + -- spy on vim.treesitter.get_parser() to keep track of how many times it is called + exec_lua(function() + _G.count = 0 + vim.treesitter.get_parser = (function(wrapped) + return function(...) + _G.count = _G.count + 1 + return wrapped(...) + end + end)(vim.treesitter.get_parser) + end) + + insert(test_text) + command [[ + set filetype=some_filetype_without_treesitter_parser + set foldmethod=expr foldexpr=v:lua.vim.treesitter.foldexpr() foldcolumn=1 foldlevel=0 + ]] + + -- foldexpr will return '0' for all lines + local levels = get_fold_levels() ---@type integer[] + eq(19, #levels) + for lnum, level in ipairs(levels) do + eq('0', level, string.format("foldlevel[%d] == %s; expected '0'", lnum, level)) + end + + eq( + 1, + exec_lua [[ return _G.count ]], + 'count should not be as high as the # of lines; actually only once for the buffer.' + ) + end) + + it('can detect a new parser and refresh folds accordingly', function() + local name = t.tmpname() + write_file(name, test_text) + command('edit ' .. name) + command [[ + set filetype=some_filetype_without_treesitter_parser + set foldmethod=expr foldexpr=v:lua.vim.treesitter.foldexpr() foldcolumn=1 foldlevel=0 + ]] + + -- foldexpr will return '0' for all lines + local levels = get_fold_levels() ---@type integer[] + eq(19, #levels) + for lnum, level in ipairs(levels) do + eq('0', level, string.format("foldlevel[%d] == %s; expected '0'", lnum, level)) + end + + -- reload buffer as c filetype to simulate new parser being found + feed('GA// vim: ft=c<Esc>') + command([[write | edit]]) + + eq({ + [1] = '>1', + [2] = '1', + [3] = '1', + [4] = '1', + [5] = '>2', + [6] = '2', + [7] = '2', + [8] = '1', + [9] = '1', + [10] = '>2', + [11] = '2', + [12] = '2', + [13] = '2', + [14] = '2', + [15] = '>3', + [16] = '3', + [17] = '3', + [18] = '2', + [19] = '1', + }, get_fold_levels()) + end) end) diff --git a/test/functional/treesitter/highlight_spec.lua b/test/functional/treesitter/highlight_spec.lua index 5c6be869c6..7f0a3cb342 100644 --- a/test/functional/treesitter/highlight_spec.lua +++ b/test/functional/treesitter/highlight_spec.lua @@ -12,6 +12,7 @@ local fn = n.fn local eq = t.eq local hl_query_c = [[ + ; query (ERROR) @error "if" @keyword @@ -65,40 +66,40 @@ static int nlua_schedule(lua_State *const lstate) }]] local hl_grid_legacy_c = [[ - {2:^/// Schedule Lua callback on main loop's event queue} | - {3:static} {3:int} nlua_schedule(lua_State *{3:const} lstate) | + {18:^/// Schedule Lua callback on main loop's event queue} | + {6:static} {6:int} nlua_schedule(lua_State *{6:const} lstate) | { | - {4:if} (lua_type(lstate, {5:1}) != LUA_TFUNCTION | + {15:if} (lua_type(lstate, {26:1}) != LUA_TFUNCTION | || lstate != lstate) { | - lua_pushliteral(lstate, {5:"vim.schedule: expected function"}); | - {4:return} lua_error(lstate); | + lua_pushliteral(lstate, {26:"vim.schedule: expected function"}); | + {15:return} lua_error(lstate); | } | | - LuaRef cb = nlua_ref(lstate, {5:1}); | + LuaRef cb = nlua_ref(lstate, {26:1}); | | multiqueue_put(main_loop.events, nlua_schedule_event, | - {5:1}, ({3:void} *)({3:ptrdiff_t})cb); | - {4:return} {5:0}; | + {26:1}, ({6:void} *)({6:ptrdiff_t})cb); | + {15:return} {26:0}; | } | {1:~ }|*2 | ]] local hl_grid_ts_c = [[ - {2:^/// Schedule Lua callback on main loop's event queue} | - {3:static} {3:int} {11:nlua_schedule}({3:lua_State} *{3:const} lstate) | + {18:^/// Schedule Lua callback on main loop's event queue} | + {6:static} {6:int} {25:nlua_schedule}({6:lua_State} *{6:const} lstate) | { | - {4:if} ({11:lua_type}(lstate, {5:1}) != {5:LUA_TFUNCTION} | - || {6:lstate} != {6:lstate}) { | - {11:lua_pushliteral}(lstate, {5:"vim.schedule: expected function"}); | - {4:return} {11:lua_error}(lstate); | + {15:if} ({25:lua_type}(lstate, {26:1}) != {26:LUA_TFUNCTION} | + || {19:lstate} != {19:lstate}) { | + {25:lua_pushliteral}(lstate, {26:"vim.schedule: expected function"}); | + {15:return} {25:lua_error}(lstate); | } | | - {7:LuaRef} cb = {11:nlua_ref}(lstate, {5:1}); | + {29:LuaRef} cb = {25:nlua_ref}(lstate, {26:1}); | | - multiqueue_put(main_loop.events, {11:nlua_schedule_event}, | - {5:1}, ({3:void} *)({3:ptrdiff_t})cb); | - {4:return} {5:0}; | + multiqueue_put(main_loop.events, {25:nlua_schedule_event}, | + {26:1}, ({6:void} *)({6:ptrdiff_t})cb); | + {15:return} {26:0}; | } | {1:~ }|*2 | @@ -145,10 +146,10 @@ local injection_grid_c = [[ ]] local injection_grid_expected_c = [[ - {3:int} x = {5:INT_MAX}; | - #define {5:READ_STRING}(x, y) ({3:char} *)read_string((x), ({3:size_t})(y)) | - #define foo {3:void} main() { \ | - {4:return} {5:42}; \ | + {6:int} x = {26:INT_MAX}; | + #define {26:READ_STRING}(x, y) ({6:char} *)read_string((x), ({6:size_t})(y)) | + #define foo {6:void} main() { \ | + {15:return} {26:42}; \ | } | ^ | {1:~ }|*11 @@ -161,20 +162,6 @@ describe('treesitter highlighting (C)', function() before_each(function() clear() screen = Screen.new(65, 18) - screen:set_default_attr_ids { - [1] = { bold = true, foreground = Screen.colors.Blue1 }, - [2] = { foreground = Screen.colors.Blue1 }, - [3] = { bold = true, foreground = Screen.colors.SeaGreen4 }, - [4] = { bold = true, foreground = Screen.colors.Brown }, - [5] = { foreground = Screen.colors.Magenta }, - [6] = { foreground = Screen.colors.Red }, - [7] = { bold = true, foreground = Screen.colors.SlateBlue }, - [8] = { foreground = Screen.colors.Grey100, background = Screen.colors.Red }, - [9] = { foreground = Screen.colors.Magenta, background = Screen.colors.Red }, - [10] = { foreground = Screen.colors.Red, background = Screen.colors.Red }, - [11] = { foreground = Screen.colors.Cyan4 }, - } - command [[ hi link @error ErrorMsg ]] command [[ hi link @warning WarningMsg ]] end) @@ -246,124 +233,124 @@ describe('treesitter highlighting (C)', function() feed('5Goc<esc>dd') - screen:expect { + screen:expect({ grid = [[ - {2:/// Schedule Lua callback on main loop's event queue} | - {3:static} {3:int} {11:nlua_schedule}({3:lua_State} *{3:const} lstate) | - { | - {4:if} ({11:lua_type}(lstate, {5:1}) != {5:LUA_TFUNCTION} | - || {6:lstate} != {6:lstate}) { | - {11:^lua_pushliteral}(lstate, {5:"vim.schedule: expected function"}); | - {4:return} {11:lua_error}(lstate); | - } | - | - {7:LuaRef} cb = {11:nlua_ref}(lstate, {5:1}); | - | - multiqueue_put(main_loop.events, {11:nlua_schedule_event}, | - {5:1}, ({3:void} *)({3:ptrdiff_t})cb); | - {4:return} {5:0}; | - } | - {1:~ }|*2 - | - ]], - } + {18:/// Schedule Lua callback on main loop's event queue} | + {6:static} {6:int} {25:nlua_schedule}({6:lua_State} *{6:const} lstate) | + { | + {15:if} ({25:lua_type}(lstate, {26:1}) != {26:LUA_TFUNCTION} | + || {19:lstate} != {19:lstate}) { | + {25:^lua_pushliteral}(lstate, {26:"vim.schedule: expected function"}); | + {15:return} {25:lua_error}(lstate); | + } | + | + {29:LuaRef} cb = {25:nlua_ref}(lstate, {26:1}); | + | + multiqueue_put(main_loop.events, {25:nlua_schedule_event}, | + {26:1}, ({6:void} *)({6:ptrdiff_t})cb); | + {15:return} {26:0}; | + } | + {1:~ }|*2 + | + ]], + }) feed('7Go*/<esc>') - screen:expect { + screen:expect({ grid = [[ - {2:/// Schedule Lua callback on main loop's event queue} | - {3:static} {3:int} {11:nlua_schedule}({3:lua_State} *{3:const} lstate) | - { | - {4:if} ({11:lua_type}(lstate, {5:1}) != {5:LUA_TFUNCTION} | - || {6:lstate} != {6:lstate}) { | - {11:lua_pushliteral}(lstate, {5:"vim.schedule: expected function"}); | - {4:return} {11:lua_error}(lstate); | - {8:*^/} | - } | - | - {7:LuaRef} cb = {11:nlua_ref}(lstate, {5:1}); | - | - multiqueue_put(main_loop.events, {11:nlua_schedule_event}, | - {5:1}, ({3:void} *)({3:ptrdiff_t})cb); | - {4:return} {5:0}; | - } | - {1:~ }| - | - ]], - } + {18:/// Schedule Lua callback on main loop's event queue} | + {6:static} {6:int} {25:nlua_schedule}({6:lua_State} *{6:const} lstate) | + { | + {15:if} ({25:lua_type}(lstate, {26:1}) != {26:LUA_TFUNCTION} | + || {19:lstate} != {19:lstate}) { | + {25:lua_pushliteral}(lstate, {26:"vim.schedule: expected function"}); | + {15:return} {25:lua_error}(lstate); | + {9:*^/} | + } | + | + {29:LuaRef} cb = {25:nlua_ref}(lstate, {26:1}); | + | + multiqueue_put(main_loop.events, {25:nlua_schedule_event}, | + {26:1}, ({6:void} *)({6:ptrdiff_t})cb); | + {15:return} {26:0}; | + } | + {1:~ }| + | + ]], + }) feed('3Go/*<esc>') - screen:expect { + screen:expect({ grid = [[ - {2:/// Schedule Lua callback on main loop's event queue} | - {3:static} {3:int} {11:nlua_schedule}({3:lua_State} *{3:const} lstate) | - { | - {2:/^*} | - {2: if (lua_type(lstate, 1) != LUA_TFUNCTION} | - {2: || lstate != lstate) {} | - {2: lua_pushliteral(lstate, "vim.schedule: expected function");} | - {2: return lua_error(lstate);} | - {2:*/} | - } | - | - {7:LuaRef} cb = {11:nlua_ref}(lstate, {5:1}); | - | - multiqueue_put(main_loop.events, {11:nlua_schedule_event}, | - {5:1}, ({3:void} *)({3:ptrdiff_t})cb); | - {4:return} {5:0}; | - {8:}} | - | - ]], - } + {18:/// Schedule Lua callback on main loop's event queue} | + {6:static} {6:int} {25:nlua_schedule}({6:lua_State} *{6:const} lstate) | + { | + {18:/^*} | + {18: if (lua_type(lstate, 1) != LUA_TFUNCTION} | + {18: || lstate != lstate) {} | + {18: lua_pushliteral(lstate, "vim.schedule: expected function");} | + {18: return lua_error(lstate);} | + {18:*/} | + } | + | + {29:LuaRef} cb = {25:nlua_ref}(lstate, {26:1}); | + | + multiqueue_put(main_loop.events, {25:nlua_schedule_event}, | + {26:1}, ({6:void} *)({6:ptrdiff_t})cb); | + {15:return} {26:0}; | + {9:}} | + | + ]], + }) feed('gg$') feed('~') - screen:expect { + screen:expect({ grid = [[ - {2:/// Schedule Lua callback on main loop's event queu^E} | - {3:static} {3:int} {11:nlua_schedule}({3:lua_State} *{3:const} lstate) | - { | - {2:/*} | - {2: if (lua_type(lstate, 1) != LUA_TFUNCTION} | - {2: || lstate != lstate) {} | - {2: lua_pushliteral(lstate, "vim.schedule: expected function");} | - {2: return lua_error(lstate);} | - {2:*/} | - } | - | - {7:LuaRef} cb = {11:nlua_ref}(lstate, {5:1}); | - | - multiqueue_put(main_loop.events, {11:nlua_schedule_event}, | - {5:1}, ({3:void} *)({3:ptrdiff_t})cb); | - {4:return} {5:0}; | - {8:}} | - | - ]], - } + {18:/// Schedule Lua callback on main loop's event queu^E} | + {6:static} {6:int} {25:nlua_schedule}({6:lua_State} *{6:const} lstate) | + { | + {18:/*} | + {18: if (lua_type(lstate, 1) != LUA_TFUNCTION} | + {18: || lstate != lstate) {} | + {18: lua_pushliteral(lstate, "vim.schedule: expected function");} | + {18: return lua_error(lstate);} | + {18:*/} | + } | + | + {29:LuaRef} cb = {25:nlua_ref}(lstate, {26:1}); | + | + multiqueue_put(main_loop.events, {25:nlua_schedule_event}, | + {26:1}, ({6:void} *)({6:ptrdiff_t})cb); | + {15:return} {26:0}; | + {9:}} | + | + ]], + }) feed('re') - screen:expect { + screen:expect({ grid = [[ - {2:/// Schedule Lua callback on main loop's event queu^e} | - {3:static} {3:int} {11:nlua_schedule}({3:lua_State} *{3:const} lstate) | - { | - {2:/*} | - {2: if (lua_type(lstate, 1) != LUA_TFUNCTION} | - {2: || lstate != lstate) {} | - {2: lua_pushliteral(lstate, "vim.schedule: expected function");} | - {2: return lua_error(lstate);} | - {2:*/} | - } | - | - {7:LuaRef} cb = {11:nlua_ref}(lstate, {5:1}); | - | - multiqueue_put(main_loop.events, {11:nlua_schedule_event}, | - {5:1}, ({3:void} *)({3:ptrdiff_t})cb); | - {4:return} {5:0}; | - {8:}} | - | - ]], - } + {18:/// Schedule Lua callback on main loop's event queu^e} | + {6:static} {6:int} {25:nlua_schedule}({6:lua_State} *{6:const} lstate) | + { | + {18:/*} | + {18: if (lua_type(lstate, 1) != LUA_TFUNCTION} | + {18: || lstate != lstate) {} | + {18: lua_pushliteral(lstate, "vim.schedule: expected function");} | + {18: return lua_error(lstate);} | + {18:*/} | + } | + | + {29:LuaRef} cb = {25:nlua_ref}(lstate, {26:1}); | + | + multiqueue_put(main_loop.events, {25:nlua_schedule_event}, | + {26:1}, ({6:void} *)({6:ptrdiff_t})cb); | + {15:return} {26:0}; | + {9:}} | + | + ]], + }) end) it('is updated with :sort', function() @@ -372,83 +359,79 @@ describe('treesitter highlighting (C)', function() local parser = vim.treesitter.get_parser(0, 'c') vim.treesitter.highlighter.new(parser, { queries = { c = hl_query_c } }) end) - screen:expect { + screen:expect({ grid = [[ - {3:int} width = {5:INT_MAX}, height = {5:INT_MAX}; | - {3:bool} ext_widgets[kUIExtCount]; | - {4:for} ({3:UIExtension} i = {5:0}; ({3:int})i < kUIExtCount; i++) { | - ext_widgets[i] = true; | - } | - | - {3:bool} inclusive = ui_override(); | - {4:for} ({3:size_t} i = {5:0}; i < ui_count; i++) { | - {3:UI} *ui = uis[i]; | - width = {5:MIN}(ui->width, width); | - height = {5:MIN}(ui->height, height); | - foo = {5:BAR}(ui->bazaar, bazaar); | - {4:for} ({3:UIExtension} j = {5:0}; ({3:int})j < kUIExtCount; j++) { | - ext_widgets[j] &= (ui->ui_ext[j] || inclusive); | - } | - } | - ^} | - | - ]], - } + {6:int} width = {26:INT_MAX}, height = {26:INT_MAX}; | + {6:bool} ext_widgets[kUIExtCount]; | + {15:for} ({6:UIExtension} i = {26:0}; ({6:int})i < kUIExtCount; i++) { | + ext_widgets[i] = true; | + } | + | + {6:bool} inclusive = ui_override(); | + {15:for} ({6:size_t} i = {26:0}; i < ui_count; i++) { | + {6:UI} *ui = uis[i]; | + width = {26:MIN}(ui->width, width); | + height = {26:MIN}(ui->height, height); | + foo = {26:BAR}(ui->bazaar, bazaar); | + {15:for} ({6:UIExtension} j = {26:0}; ({6:int})j < kUIExtCount; j++) { | + ext_widgets[j] &= (ui->ui_ext[j] || inclusive); | + } | + } | + ^} | + | + ]], + }) feed ':sort<cr>' - screen:expect { + screen:expect({ grid = [[ - ^ | - ext_widgets[j] &= (ui->ui_ext[j] || inclusive); | - {3:UI} *ui = uis[i]; | - ext_widgets[i] = true; | - foo = {5:BAR}(ui->bazaar, bazaar); | - {4:for} ({3:UIExtension} j = {5:0}; ({3:int})j < kUIExtCount; j++) { | - height = {5:MIN}(ui->height, height); | - width = {5:MIN}(ui->width, width); | - } | - {3:bool} ext_widgets[kUIExtCount]; | - {3:bool} inclusive = ui_override(); | - {4:for} ({3:UIExtension} i = {5:0}; ({3:int})i < kUIExtCount; i++) { | - {4:for} ({3:size_t} i = {5:0}; i < ui_count; i++) { | - {3:int} width = {5:INT_MAX}, height = {5:INT_MAX}; | - } |*2 - {3:void} ui_refresh({3:void}) | - :sort | - ]], - } + ^ | + ext_widgets[j] &= (ui->ui_ext[j] || inclusive); | + {6:UI} *ui = uis[i]; | + ext_widgets[i] = true; | + foo = {26:BAR}(ui->bazaar, bazaar); | + {15:for} ({6:UIExtension} j = {26:0}; ({6:int})j < kUIExtCount; j++) { | + height = {26:MIN}(ui->height, height); | + width = {26:MIN}(ui->width, width); | + } | + {6:bool} ext_widgets[kUIExtCount]; | + {6:bool} inclusive = ui_override(); | + {15:for} ({6:UIExtension} i = {26:0}; ({6:int})i < kUIExtCount; i++) { | + {15:for} ({6:size_t} i = {26:0}; i < ui_count; i++) { | + {6:int} width = {26:INT_MAX}, height = {26:INT_MAX}; | + } |*2 + {6:void} ui_refresh({6:void}) | + :sort | + ]], + }) - feed 'u' + feed 'u:<esc>' - screen:expect { + screen:expect({ grid = [[ - {3:int} width = {5:INT_MAX}, height = {5:INT_MAX}; | - {3:bool} ext_widgets[kUIExtCount]; | - {4:for} ({3:UIExtension} i = {5:0}; ({3:int})i < kUIExtCount; i++) { | - ext_widgets[i] = true; | - } | - | - {3:bool} inclusive = ui_override(); | - {4:for} ({3:size_t} i = {5:0}; i < ui_count; i++) { | - {3:UI} *ui = uis[i]; | - width = {5:MIN}(ui->width, width); | - height = {5:MIN}(ui->height, height); | - foo = {5:BAR}(ui->bazaar, bazaar); | - {4:for} ({3:UIExtension} j = {5:0}; ({3:int})j < kUIExtCount; j++) { | - ext_widgets[j] &= (ui->ui_ext[j] || inclusive); | - } | - } | - ^} | - 19 changes; before #2 {MATCH:.*}| - ]], - } + {6:int} width = {26:INT_MAX}, height = {26:INT_MAX}; | + {6:bool} ext_widgets[kUIExtCount]; | + {15:for} ({6:UIExtension} i = {26:0}; ({6:int})i < kUIExtCount; i++) { | + ext_widgets[i] = true; | + } | + | + {6:bool} inclusive = ui_override(); | + {15:for} ({6:size_t} i = {26:0}; i < ui_count; i++) { | + {6:UI} *ui = uis[i]; | + width = {26:MIN}(ui->width, width); | + height = {26:MIN}(ui->height, height); | + foo = {26:BAR}(ui->bazaar, bazaar); | + {15:for} ({6:UIExtension} j = {26:0}; ({6:int})j < kUIExtCount; j++) { | + ext_widgets[j] &= (ui->ui_ext[j] || inclusive); | + } | + } | + ^} | + | + ]], + }) end) it('supports with custom parser', function() - screen:set_default_attr_ids { - [1] = { bold = true, foreground = Screen.colors.SeaGreen4 }, - } - insert(test_text_c) screen:expect { @@ -488,28 +471,28 @@ describe('treesitter highlighting (C)', function() vim.treesitter.highlighter.new(parser, { queries = { c = '(identifier) @type' } }) end) - screen:expect { + screen:expect({ grid = [[ - int {1:width} = {1:INT_MAX}, {1:height} = {1:INT_MAX}; | - bool {1:ext_widgets}[{1:kUIExtCount}]; | - for (UIExtension {1:i} = 0; (int)i < kUIExtCount; i++) { | - ext_widgets[i] = true; | - } | - | - bool {1:inclusive} = {1:ui_override}(); | - for (size_t {1:i} = 0; i < ui_count; i++) { | - UI *{1:ui} = {1:uis}[{1:i}]; | - width = MIN(ui->width, width); | - height = MIN(ui->height, height); | - foo = BAR(ui->bazaar, bazaar); | - for (UIExtension {1:j} = 0; (int)j < kUIExtCount; j++) { | - ext_widgets[j] &= (ui->ui_ext[j] || inclusive); | - } | - } | - ^} | - | - ]], - } + int {6:width} = {6:INT_MAX}, {6:height} = {6:INT_MAX}; | + bool {6:ext_widgets}[{6:kUIExtCount}]; | + for (UIExtension {6:i} = 0; (int)i < kUIExtCount; i++) { | + ext_widgets[i] = true; | + } | + | + bool {6:inclusive} = {6:ui_override}(); | + for (size_t {6:i} = 0; i < ui_count; i++) { | + UI *{6:ui} = {6:uis}[{6:i}]; | + width = MIN(ui->width, width); | + height = MIN(ui->height, height); | + foo = BAR(ui->bazaar, bazaar); | + for (UIExtension {6:j} = 0; (int)j < kUIExtCount; j++) { | + ext_widgets[j] &= (ui->ui_ext[j] || inclusive); | + } | + } | + ^} | + | + ]], + }) end) it('supports injected languages', function() @@ -567,18 +550,18 @@ describe('treesitter highlighting (C)', function() vim.treesitter.highlighter.new(vim.treesitter.get_parser(0, 'c')) end) - screen:expect { + screen:expect({ grid = [[ - {3:int} x = {5:INT_MAX}; | - #define {5:READ_STRING}(x, y) ({3:char} *)read_string((x), ({3:size_t})(y)) | - #define foo {3:void} main() { \ | - {4:return} {5:42}; \ | - } | - ^ | - {1:~ }|*11 - | - ]], - } + {6:int} x = {26:INT_MAX}; | + #define {26:READ_STRING}(x, y) ({6:char} *)read_string((x), ({6:size_t})(y)) | + #define foo {6:void} main() { \ | + {15:return} {26:42}; \ | + } | + ^ | + {1:~ }|*11 + | + ]], + }) end) it('supports highlighting with custom highlight groups', function() @@ -595,27 +578,27 @@ describe('treesitter highlighting (C)', function() -- This will change ONLY the literal strings to look like comments -- The only literal string is the "vim.schedule: expected function" in this test. exec_lua [[vim.cmd("highlight link @string.nonexistent_specializer comment")]] - screen:expect { + screen:expect({ grid = [[ - {2:^/// Schedule Lua callback on main loop's event queue} | - {3:static} {3:int} {11:nlua_schedule}({3:lua_State} *{3:const} lstate) | - { | - {4:if} ({11:lua_type}(lstate, {5:1}) != {5:LUA_TFUNCTION} | - || {6:lstate} != {6:lstate}) { | - {11:lua_pushliteral}(lstate, {2:"vim.schedule: expected function"}); | - {4:return} {11:lua_error}(lstate); | - } | - | - {7:LuaRef} cb = {11:nlua_ref}(lstate, {5:1}); | - | - multiqueue_put(main_loop.events, {11:nlua_schedule_event}, | - {5:1}, ({3:void} *)({3:ptrdiff_t})cb); | - {4:return} {5:0}; | - } | - {1:~ }|*2 - | - ]], - } + {18:^/// Schedule Lua callback on main loop's event queue} | + {6:static} {6:int} {25:nlua_schedule}({6:lua_State} *{6:const} lstate) | + { | + {15:if} ({25:lua_type}(lstate, {26:1}) != {26:LUA_TFUNCTION} | + || {19:lstate} != {19:lstate}) { | + {25:lua_pushliteral}(lstate, {18:"vim.schedule: expected function"}); | + {15:return} {25:lua_error}(lstate); | + } | + | + {29:LuaRef} cb = {25:nlua_ref}(lstate, {26:1}); | + | + multiqueue_put(main_loop.events, {25:nlua_schedule_event}, | + {26:1}, ({6:void} *)({6:ptrdiff_t})cb); | + {15:return} {26:0}; | + } | + {1:~ }|*2 + | + ]], + }) screen:expect { unchanged = true } end) @@ -657,8 +640,8 @@ describe('treesitter highlighting (C)', function() } eq({ - { capture = 'constant', metadata = { priority = '101' }, lang = 'c' }, - { capture = 'type', metadata = {}, lang = 'c' }, + { capture = 'constant', metadata = { priority = '101' }, lang = 'c', id = 14 }, + { capture = 'type', metadata = {}, lang = 'c', id = 3 }, }, exec_lua [[ return vim.treesitter.get_captures_at_pos(0, 0, 2) ]]) end) @@ -691,25 +674,25 @@ describe('treesitter highlighting (C)', function() ) end) - screen:expect { + screen:expect({ grid = [[ - {3:char}* x = {5:"Will somebody ever read this?"}; | - ^ | - {1:~ }|*15 - | - ]], - } + {6:char}* x = {26:"Will somebody ever read this?"}; | + ^ | + {1:~ }|*15 + | + ]], + }) -- clearing specialization reactivates fallback command [[ hi clear @foo.bar ]] - screen:expect { + screen:expect({ grid = [[ - {5:char}* x = {5:"Will somebody ever read this?"}; | - ^ | - {1:~ }|*15 - | - ]], - } + {26:char}* x = {26:"Will somebody ever read this?"}; | + ^ | + {1:~ }|*15 + | + ]], + }) end ) @@ -740,27 +723,27 @@ describe('treesitter highlighting (C)', function() }) end) - screen:expect { + screen:expect({ grid = [[ - /// Schedule Lua callback on main loop's event queue | - {4:R} int nlua_schedule(lua_State *const ) | - { | - if (lua_type(, 1) != LUA_TFUNCTION | - || != ) { | - lua_pushliteral(, "vim.schedule: expected function"); | - return lua_error(); | - } | - | - LuaRef cb = nlua_ref(, 1); | - | - {11:V}(main_loop.events, nlua_schedule_event, | - 1, (void *)(ptrdiff_t)cb); | - return 0; | - ^} | - {1:~ }|*2 - | - ]], - } + /// Schedule Lua callback on main loop's event queue | + {15:R} int nlua_schedule(lua_State *const ) | + { | + if (lua_type(, 1) != LUA_TFUNCTION | + || != ) { | + lua_pushliteral(, "vim.schedule: expected function"); | + return lua_error(); | + } | + | + LuaRef cb = nlua_ref(, 1); | + | + {25:V}(main_loop.events, nlua_schedule_event, | + 1, (void *)(ptrdiff_t)cb); | + return 0; | + ^} | + {1:~ }|*2 + | + ]], + }) end) it('@foo.bar groups has the correct fallback behavior', function() @@ -801,16 +784,16 @@ describe('treesitter highlighting (C)', function() vim.treesitter.highlighter.new(vim.treesitter.get_parser(0, 'c')) end) - screen:expect { + screen:expect({ grid = [[ - {5:int x = 4;} | - {5:int y = 5;} | - {5:int z = 6;} | - ^ | - {1:~ }|*13 - | - ]], - } + {26:int x = 4;} | + {26:int y = 5;} | + {26:int z = 6;} | + ^ | + {1:~ }|*13 + | + ]], + }) end) it('gives higher priority to more specific captures #27895', function() @@ -830,14 +813,52 @@ describe('treesitter highlighting (C)', function() vim.treesitter.highlighter.new(vim.treesitter.get_parser(0, 'c')) end) - screen:expect { + screen:expect({ grid = [[ - void foo(int {4:*}{11:bar}); | - ^ | - {1:~ }|*15 - | - ]], - } + void foo(int {15:*}{25:bar}); | + ^ | + {1:~ }|*15 + | + ]], + }) + end) + + it('highlights applied to first line of closed fold', function() + insert(hl_text_c) + exec_lua(function() + vim.treesitter.query.set('c', 'highlights', hl_query_c) + vim.treesitter.highlighter.new(vim.treesitter.get_parser(0, 'c')) + end) + feed('ggjzfj') + command('set foldtext=') + screen:add_extra_attr_ids({ + [100] = { + bold = true, + background = Screen.colors.LightGray, + foreground = Screen.colors.SeaGreen4, + }, + [101] = { background = Screen.colors.LightGray, foreground = Screen.colors.DarkCyan }, + }) + screen:expect({ + grid = [[ + {18:/// Schedule Lua callback on main loop's event queue} | + {100:^static}{13: }{100:int}{13: }{101:nlua_schedule}{13:(}{100:lua_State}{13: *}{100:const}{13: lstate)················}| + {15:if} ({25:lua_type}(lstate, {26:1}) != {26:LUA_TFUNCTION} | + || {19:lstate} != {19:lstate}) { | + {25:lua_pushliteral}(lstate, {26:"vim.schedule: expected function"}); | + {15:return} {25:lua_error}(lstate); | + } | + | + {29:LuaRef} cb = {25:nlua_ref}(lstate, {26:1}); | + | + multiqueue_put(main_loop.events, {25:nlua_schedule_event}, | + {26:1}, ({6:void} *)({6:ptrdiff_t})cb); | + {15:return} {26:0}; | + } | + {1:~ }|*3 + | + ]], + }) end) end) @@ -847,13 +868,6 @@ describe('treesitter highlighting (lua)', function() before_each(function() clear() screen = Screen.new(65, 18) - screen:set_default_attr_ids { - [1] = { bold = true, foreground = Screen.colors.Blue }, - [2] = { foreground = Screen.colors.DarkCyan }, - [3] = { foreground = Screen.colors.Magenta }, - [4] = { foreground = Screen.colors.SlateBlue }, - [5] = { bold = true, foreground = Screen.colors.Brown }, - } end) it('supports language injections', function() @@ -867,15 +881,15 @@ describe('treesitter highlighting (lua)', function() vim.treesitter.start() end) - screen:expect { + screen:expect({ grid = [[ - {5:local} {2:ffi} {5:=} {4:require(}{3:'ffi'}{4:)} | - {2:ffi}{4:.}{2:cdef}{4:(}{3:"}{4:int}{3: }{4:(}{5:*}{3:fun}{4:)(int,}{3: }{4:char}{3: }{5:*}{4:);}{3:"}{4:)} | - ^ | - {1:~ }|*14 - | - ]], - } + {15:local} {25:ffi} {15:=} {16:require(}{26:'ffi'}{16:)} | + {25:ffi}{16:.}{25:cdef}{16:(}{26:"}{16:int}{26: }{16:(}{15:*}{26:fun}{16:)(int,}{26: }{16:char}{26: }{15:*}{16:);}{26:"}{16:)} | + ^ | + {1:~ }|*14 + | + ]], + }) end) end) @@ -885,16 +899,6 @@ describe('treesitter highlighting (help)', function() before_each(function() clear() screen = Screen.new(40, 6) - screen:set_default_attr_ids { - [1] = { foreground = Screen.colors.Blue1 }, - [2] = { bold = true, foreground = Screen.colors.Blue1 }, - [3] = { bold = true, foreground = Screen.colors.Brown }, - [4] = { foreground = Screen.colors.Cyan4 }, - [5] = { foreground = Screen.colors.Magenta1 }, - title = { bold = true, foreground = Screen.colors.Magenta1 }, - h1_delim = { nocombine = true, underdouble = true }, - h2_delim = { nocombine = true, underline = true }, - } end) it('defaults in vimdoc/highlights.scm', function() @@ -918,13 +922,18 @@ describe('treesitter highlighting (help)', function() vim.treesitter.start() end) + screen:add_extra_attr_ids({ + [100] = { nocombine = true, underdouble = true }, + [101] = { foreground = Screen.colors.Fuchsia, bold = true }, + [102] = { underline = true, nocombine = true }, + }) screen:expect({ grid = [[ - {h1_delim:^========================================}| - {title:NVIM DOCUMENTATION} | + {100:^========================================}| + {101:NVIM DOCUMENTATION} | | - {h2_delim:----------------------------------------}| - {title:ABOUT NVIM} | + {102:----------------------------------------}| + {101:ABOUT NVIM} | | ]], }) @@ -943,42 +952,42 @@ describe('treesitter highlighting (help)', function() vim.treesitter.start() end) - screen:expect { + screen:expect({ grid = [[ - {1:>}{3:ruby} | - {1: -- comment} | - {1: local this_is = 'actually_lua'} | - {1:<} | - ^ | - | - ]], - } + {18:>}{15:ruby} | + {18: -- comment} | + {18: local this_is = 'actually_lua'} | + {18:<} | + ^ | + | + ]], + }) n.api.nvim_buf_set_text(0, 0, 1, 0, 5, { 'lua' }) - screen:expect { + screen:expect({ grid = [[ - {1:>}{3:lua} | - {1: -- comment} | - {1: }{3:local}{1: }{4:this_is}{1: }{3:=}{1: }{5:'actually_lua'} | - {1:<} | - ^ | - | - ]], - } + {18:>}{15:lua} | + {18: -- comment} | + {18: }{15:local}{18: }{25:this_is}{18: }{15:=}{18: }{26:'actually_lua'} | + {18:<} | + ^ | + | + ]], + }) n.api.nvim_buf_set_text(0, 0, 1, 0, 4, { 'ruby' }) - screen:expect { + screen:expect({ grid = [[ - {1:>}{3:ruby} | - {1: -- comment} | - {1: local this_is = 'actually_lua'} | - {1:<} | - ^ | - | - ]], - } + {18:>}{15:ruby} | + {18: -- comment} | + {18: local this_is = 'actually_lua'} | + {18:<} | + ^ | + | + ]], + }) end) it('correctly redraws injections subpriorities', function() @@ -1003,16 +1012,16 @@ describe('treesitter highlighting (help)', function() vim.treesitter.highlighter.new(parser) end) - screen:expect { + screen:expect({ grid = [=[ - {3:local} {4:s} {3:=} {5:[[} | - {5: }{3:local}{5: }{4:also}{5: }{3:=}{5: }{4:lua} | - {5:]]} | - ^ | - {2:~ }| - | - ]=], - } + {15:local} {25:s} {15:=} {26:[[} | + {26: }{15:local}{26: }{25:also}{26: }{15:=}{26: }{25:lua} | + {26:]]} | + ^ | + {1:~ }| + | + ]=], + }) end) end) @@ -1022,12 +1031,6 @@ describe('treesitter highlighting (nested injections)', function() before_each(function() clear() screen = Screen.new(80, 7) - screen:set_default_attr_ids { - [1] = { foreground = Screen.colors.SlateBlue }, - [2] = { bold = true, foreground = Screen.colors.Brown }, - [3] = { foreground = Screen.colors.Cyan4 }, - [4] = { foreground = Screen.colors.Fuchsia }, - } end) it('correctly redraws nested injections (GitHub #25252)', function() @@ -1054,32 +1057,32 @@ vim.cmd([[ -- invalidate the language tree feed('ggi--[[<ESC>04x') - screen:expect { + screen:expect({ grid = [[ - {2:^function} {3:foo}{1:()} {1:print(}{4:"Lua!"}{1:)} {2:end} | - | - {2:local} {3:lorem} {2:=} {1:{} | - {3:ipsum} {2:=} {1:{},} | - {3:bar} {2:=} {1:{},} | - {1:}} | - | - ]], - } + {15:^function} {25:foo}{16:()} {16:print(}{26:"Lua!"}{16:)} {15:end} | + | + {15:local} {25:lorem} {15:=} {16:{} | + {25:ipsum} {15:=} {16:{},} | + {25:bar} {15:=} {16:{},} | + {16:}} | + | + ]], + }) -- spam newline insert/delete to invalidate Lua > Vim > Lua region feed('3jo<ESC>ddko<ESC>ddko<ESC>ddko<ESC>ddk0') - screen:expect { + screen:expect({ grid = [[ - {2:function} {3:foo}{1:()} {1:print(}{4:"Lua!"}{1:)} {2:end} | - | - {2:local} {3:lorem} {2:=} {1:{} | - ^ {3:ipsum} {2:=} {1:{},} | - {3:bar} {2:=} {1:{},} | - {1:}} | - | - ]], - } + {15:function} {25:foo}{16:()} {16:print(}{26:"Lua!"}{16:)} {15:end} | + | + {15:local} {25:lorem} {15:=} {16:{} | + ^ {25:ipsum} {15:=} {16:{},} | + {25:bar} {15:=} {16:{},} | + {16:}} | + | + ]], + }) end) end) @@ -1108,7 +1111,7 @@ describe('treesitter highlighting (markdown)', function() }) screen:expect({ grid = [[ - {25:[}{100:This link text}{25:](}{101:https://example.com}{25:)} is| + {100:[This link text](}{101:https://example.com}{100:)} is| a hyperlink^. | {1:~ }|*3 | @@ -1156,20 +1159,6 @@ it('starting and stopping treesitter highlight in init.lua works #29541', functi eq('', api.nvim_get_vvar('errmsg')) local screen = Screen.new(65, 18) - screen:set_default_attr_ids { - [1] = { bold = true, foreground = Screen.colors.Blue1 }, - [2] = { foreground = Screen.colors.Blue1 }, - [3] = { bold = true, foreground = Screen.colors.SeaGreen4 }, - [4] = { bold = true, foreground = Screen.colors.Brown }, - [5] = { foreground = Screen.colors.Magenta }, - [6] = { foreground = Screen.colors.Red }, - [7] = { bold = true, foreground = Screen.colors.SlateBlue }, - [8] = { foreground = Screen.colors.Grey100, background = Screen.colors.Red }, - [9] = { foreground = Screen.colors.Magenta, background = Screen.colors.Red }, - [10] = { foreground = Screen.colors.Red, background = Screen.colors.Red }, - [11] = { foreground = Screen.colors.Cyan4 }, - } - fn.setreg('r', hl_text_c) feed('i<C-R><C-O>r<Esc>gg') -- legacy syntax highlighting is used diff --git a/test/functional/treesitter/inspect_tree_spec.lua b/test/functional/treesitter/inspect_tree_spec.lua index 1f7d15cc96..68622140e4 100644 --- a/test/functional/treesitter/inspect_tree_spec.lua +++ b/test/functional/treesitter/inspect_tree_spec.lua @@ -120,14 +120,17 @@ describe('vim.treesitter.inspect_tree', function() end) it('updates source and tree buffer windows and closes them correctly', function() + local name = t.tmpname() + n.command('edit ' .. name) insert([[ print() ]]) + n.command('set filetype=lua | write') -- setup two windows for the source buffer exec_lua(function() _G.source_win = vim.api.nvim_get_current_win() - vim.api.nvim_open_win(0, false, { + _G.source_win2 = vim.api.nvim_open_win(0, false, { win = 0, split = 'left', }) @@ -135,40 +138,103 @@ describe('vim.treesitter.inspect_tree', function() -- setup three windows for the tree buffer exec_lua(function() - vim.treesitter.start(0, 'lua') vim.treesitter.inspect_tree() _G.tree_win = vim.api.nvim_get_current_win() - _G.tree_win_copy_1 = vim.api.nvim_open_win(0, false, { + _G.tree_win2 = vim.api.nvim_open_win(0, false, { win = 0, split = 'left', }) - _G.tree_win_copy_2 = vim.api.nvim_open_win(0, false, { + _G.tree_win3 = vim.api.nvim_open_win(0, false, { win = 0, split = 'left', }) end) - -- close original source window - exec_lua('vim.api.nvim_win_close(source_win, false)') + -- close original source window without closing tree views + exec_lua('vim.api.nvim_set_current_win(source_win)') + feed(':quit<CR>') + eq('', n.api.nvim_get_vvar('errmsg')) + eq(true, exec_lua('return vim.api.nvim_win_is_valid(tree_win)')) + eq(true, exec_lua('return vim.api.nvim_win_is_valid(tree_win2)')) + eq(true, exec_lua('return vim.api.nvim_win_is_valid(tree_win3)')) -- navigates correctly to the remaining source buffer window + exec_lua('vim.api.nvim_set_current_win(tree_win)') feed('<CR>') eq('', n.api.nvim_get_vvar('errmsg')) + eq(true, exec_lua('return vim.api.nvim_get_current_win() == source_win2')) -- close original tree window exec_lua(function() - vim.api.nvim_set_current_win(_G.tree_win_copy_1) + vim.api.nvim_set_current_win(_G.tree_win2) vim.api.nvim_win_close(_G.tree_win, false) end) -- navigates correctly to the remaining source buffer window feed('<CR>') eq('', n.api.nvim_get_vvar('errmsg')) + eq(true, exec_lua('return vim.api.nvim_get_current_win() == source_win2')) -- close source buffer window and all remaining tree windows - t.pcall_err(exec_lua, 'vim.api.nvim_win_close(0, false)') + n.expect_exit(n.command, 'quit') + end) - eq(false, exec_lua('return vim.api.nvim_win_is_valid(tree_win_copy_1)')) - eq(false, exec_lua('return vim.api.nvim_win_is_valid(tree_win_copy_2)')) + it('shows which nodes are missing', function() + insert([[ + int main() { + if (a.) { + // ^ MISSING field_identifier here + if (1) d() + // ^ MISSING ";" here + } + } + ]]) + + exec_lua(function() + vim.treesitter.start(0, 'c') + vim.treesitter.inspect_tree() + end) + feed('a') + + expect_tree [[ + (translation_unit ; [0, 0] - [8, 0] + (function_definition ; [0, 0] - [6, 1] + type: (primitive_type) ; [0, 0] - [0, 3] + declarator: (function_declarator ; [0, 4] - [0, 10] + declarator: (identifier) ; [0, 4] - [0, 8] + parameters: (parameter_list ; [0, 8] - [0, 10] + "(" ; [0, 8] - [0, 9] + ")")) ; [0, 9] - [0, 10] + body: (compound_statement ; [0, 11] - [6, 1] + "{" ; [0, 11] - [0, 12] + (if_statement ; [1, 4] - [5, 5] + "if" ; [1, 4] - [1, 6] + condition: (parenthesized_expression ; [1, 7] - [1, 11] + "(" ; [1, 7] - [1, 8] + (field_expression ; [1, 8] - [1, 10] + argument: (identifier) ; [1, 8] - [1, 9] + operator: "." ; [1, 9] - [1, 10] + field: (MISSING field_identifier)) ; [1, 10] - [1, 10] + ")") ; [1, 10] - [1, 11] + consequence: (compound_statement ; [1, 12] - [5, 5] + "{" ; [1, 12] - [1, 13] + (comment) ; [2, 4] - [2, 41] + (if_statement ; [3, 8] - [4, 36] + "if" ; [3, 8] - [3, 10] + condition: (parenthesized_expression ; [3, 11] - [3, 14] + "(" ; [3, 11] - [3, 12] + (number_literal) ; [3, 12] - [3, 13] + ")") ; [3, 13] - [3, 14] + consequence: (expression_statement ; [3, 15] - [4, 36] + (call_expression ; [3, 15] - [3, 18] + function: (identifier) ; [3, 15] - [3, 16] + arguments: (argument_list ; [3, 16] - [3, 18] + "(" ; [3, 16] - [3, 17] + ")")) ; [3, 17] - [3, 18] + (comment) ; [4, 8] - [4, 36] + (MISSING ";"))) ; [4, 36] - [4, 36] + "}")) ; [5, 4] - [5, 5] + "}"))) ; [6, 0] - [6, 1] + ]] end) end) diff --git a/test/functional/treesitter/language_spec.lua b/test/functional/treesitter/language_spec.lua index 120a15d7f9..a93b1063a1 100644 --- a/test/functional/treesitter/language_spec.lua +++ b/test/functional/treesitter/language_spec.lua @@ -117,6 +117,7 @@ describe('treesitter language API', function() '<node translation_unit>', exec_lua(function() local langtree = vim.treesitter.get_parser(0, 'c') + langtree:parse() local tree = langtree:tree_for_range({ 1, 3, 1, 3 }) return tostring(tree:root()) end) @@ -133,6 +134,7 @@ describe('treesitter language API', function() '<node translation_unit>', exec_lua(function() local langtree = vim.treesitter.get_parser(0, 'c') + langtree:parse() local tree = langtree:tree_for_range({ 10, 10, 10, 10 }) return tostring(tree:root()) end) @@ -149,6 +151,7 @@ describe('treesitter language API', function() '<node primitive_type>', exec_lua(function() local langtree = vim.treesitter.get_parser(0, 'c') + langtree:parse() local node = langtree:named_node_for_range({ 1, 3, 1, 3 }) return tostring(node) end) @@ -160,6 +163,7 @@ describe('treesitter language API', function() exec_lua(function() _G.langtree = vim.treesitter.get_parser(0, 'lua') + _G.langtree:parse() _G.node = _G.langtree:node_for_range({ 0, 3, 0, 3 }) end) diff --git a/test/functional/treesitter/node_spec.lua b/test/functional/treesitter/node_spec.lua index c87a56b160..235bf7861c 100644 --- a/test/functional/treesitter/node_spec.lua +++ b/test/functional/treesitter/node_spec.lua @@ -20,6 +20,7 @@ describe('treesitter node API', function() insert('F') exec_lua(function() vim.treesitter.start(0, 'lua') + vim.treesitter.get_parser(0):parse() vim.treesitter.get_node():tree() vim.treesitter.get_node():tree() collectgarbage() @@ -45,6 +46,7 @@ describe('treesitter node API', function() -- this buffer doesn't have filetype set! insert('local foo = function() end') exec_lua(function() + vim.treesitter.get_parser(0, 'lua'):parse() _G.node = vim.treesitter.get_node({ bufnr = 0, pos = { 0, 6 }, -- on "foo" @@ -161,32 +163,6 @@ describe('treesitter node API', function() eq(3, lua_eval('child:byte_length()')) end) - it('child_containing_descendant() works', function() - insert([[ - int main() { - int x = 3; - }]]) - - exec_lua(function() - local tree = vim.treesitter.get_parser(0, 'c'):parse()[1] - _G.root = tree:root() - _G.main = _G.root:child(0) - _G.body = _G.main:child(2) - _G.statement = _G.body:child(1) - _G.declarator = _G.statement:child(1) - _G.value = _G.declarator:child(1) - end) - - eq(lua_eval('main:type()'), lua_eval('root:child_containing_descendant(value):type()')) - eq(lua_eval('body:type()'), lua_eval('main:child_containing_descendant(value):type()')) - eq(lua_eval('statement:type()'), lua_eval('body:child_containing_descendant(value):type()')) - eq( - lua_eval('declarator:type()'), - lua_eval('statement:child_containing_descendant(value):type()') - ) - eq(vim.NIL, lua_eval('declarator:child_containing_descendant(value)')) - end) - it('child_with_descendant() works', function() insert([[ int main() { diff --git a/test/functional/treesitter/parser_spec.lua b/test/functional/treesitter/parser_spec.lua index 2f8d204d36..eb4651a81d 100644 --- a/test/functional/treesitter/parser_spec.lua +++ b/test/functional/treesitter/parser_spec.lua @@ -1,5 +1,6 @@ local t = require('test.testutil') local n = require('test.functional.testnvim')() +local ts_t = require('test.functional.treesitter.testutil') local clear = n.clear local dedent = t.dedent @@ -8,6 +9,8 @@ local insert = n.insert local exec_lua = n.exec_lua local pcall_err = t.pcall_err local feed = n.feed +local run_query = ts_t.run_query +local assert_alive = n.assert_alive describe('treesitter parser API', function() before_each(function() @@ -88,6 +91,197 @@ describe('treesitter parser API', function() eq(true, exec_lua('return parser:parse()[1] == tree2')) end) + it('parses buffer asynchronously', function() + insert([[ + int main() { + int x = 3; + }]]) + + exec_lua(function() + _G.parser = vim.treesitter.get_parser(0, 'c') + _G.lang = vim.treesitter.language.inspect('c') + _G.parser:parse(nil, function(_, trees) + _G.tree = trees[1] + _G.root = _G.tree:root() + end) + vim.wait(100, function() end) + end) + + eq('<tree>', exec_lua('return tostring(tree)')) + eq('<node translation_unit>', exec_lua('return tostring(root)')) + eq({ 0, 0, 3, 0 }, exec_lua('return {root:range()}')) + + eq(1, exec_lua('return root:child_count()')) + exec_lua('child = root:child(0)') + eq('<node function_definition>', exec_lua('return tostring(child)')) + eq({ 0, 0, 2, 1 }, exec_lua('return {child:range()}')) + + eq('function_definition', exec_lua('return child:type()')) + eq(true, exec_lua('return child:named()')) + eq('number', type(exec_lua('return child:symbol()'))) + eq(true, exec_lua('return lang.symbols[child:type()]')) + + exec_lua('anon = root:descendant_for_range(0,8,0,9)') + eq('(', exec_lua('return anon:type()')) + eq(false, exec_lua('return anon:named()')) + eq('number', type(exec_lua('return anon:symbol()'))) + eq(false, exec_lua([=[return lang.symbols[string.format('"%s"', anon:type())]]=])) + + exec_lua('descendant = root:descendant_for_range(1,2,1,12)') + eq('<node declaration>', exec_lua('return tostring(descendant)')) + eq({ 1, 2, 1, 12 }, exec_lua('return {descendant:range()}')) + eq( + '(declaration type: (primitive_type) declarator: (init_declarator declarator: (identifier) value: (number_literal)))', + exec_lua('return descendant:sexpr()') + ) + + feed('2G7|ay') + exec_lua(function() + _G.parser:parse(nil, function(_, trees) + _G.tree2 = trees[1] + _G.root2 = _G.tree2:root() + _G.descendant2 = _G.root2:descendant_for_range(1, 2, 1, 13) + end) + vim.wait(100, function() end) + end) + eq(false, exec_lua('return tree2 == tree1')) + eq(false, exec_lua('return root2 == root')) + eq('<node declaration>', exec_lua('return tostring(descendant2)')) + eq({ 1, 2, 1, 13 }, exec_lua('return {descendant2:range()}')) + + eq(true, exec_lua('return child == child')) + -- separate lua object, but represents same node + eq(true, exec_lua('return child == root:child(0)')) + eq(false, exec_lua('return child == descendant2')) + eq(false, exec_lua('return child == nil')) + eq(false, exec_lua('return child == tree')) + + eq('string', exec_lua('return type(child:id())')) + eq(true, exec_lua('return child:id() == child:id()')) + -- separate lua object, but represents same node + eq(true, exec_lua('return child:id() == root:child(0):id()')) + eq(false, exec_lua('return child:id() == descendant2:id()')) + eq(false, exec_lua('return child:id() == nil')) + eq(false, exec_lua('return child:id() == tree')) + + -- unchanged buffer: return the same tree + eq(true, exec_lua('return parser:parse()[1] == tree2')) + end) + + it('does not crash when editing large files', function() + insert([[printf("%s", "some text");]]) + feed('yy49999p') + + exec_lua(function() + _G.parser = vim.treesitter.get_parser(0, 'c') + _G.done = false + vim.treesitter.start(0, 'c') + _G.parser:parse(nil, function() + _G.done = true + end) + while not _G.done do + -- Busy wait until async parsing has completed + vim.wait(100, function() end) + end + end) + + eq(true, exec_lua([[return done]])) + exec_lua(function() + vim.api.nvim_input('Lxj') + end) + exec_lua(function() + vim.api.nvim_input('xj') + end) + exec_lua(function() + vim.api.nvim_input('xj') + end) + assert_alive() + end) + + it('resets parsing state on tree changes', function() + insert([[vim.api.nvim_set_hl(0, 'test2', { bg = 'green' })]]) + feed('yy1000p') + + exec_lua(function() + vim.cmd('set ft=lua') + + vim.treesitter.start(0) + local parser = assert(vim.treesitter.get_parser(0)) + + parser:parse(true, function() end) + vim.api.nvim_buf_set_lines(0, 1, -1, false, {}) + parser:parse(true) + end) + end) + + it('resets when buffer was editing during an async parse', function() + insert([[printf("%s", "some text");]]) + feed('yy49999p') + feed('gg4jO// Comment<Esc>') + + exec_lua(function() + _G.parser = vim.treesitter.get_parser(0, 'c') + _G.done = false + vim.treesitter.start(0, 'c') + _G.parser:parse(nil, function() + _G.done = true + end) + end) + + exec_lua(function() + vim.api.nvim_input('ggdj') + end) + + eq(false, exec_lua([[return done]])) + exec_lua(function() + while not _G.done do + -- Busy wait until async parsing finishes + vim.wait(100, function() end) + end + end) + eq(true, exec_lua([[return done]])) + eq('comment', exec_lua([[return parser:parse()[1]:root():named_child(2):type()]])) + eq({ 2, 0, 2, 10 }, exec_lua([[return {parser:parse()[1]:root():named_child(2):range()}]])) + end) + + it('handles multiple async parse calls', function() + insert([[printf("%s", "some text");]]) + feed('yy49999p') + + exec_lua(function() + -- Spy on vim.schedule + local schedule = vim.schedule + vim.schedule = function(fn) + _G.schedules = _G.schedules + 1 + schedule(fn) + end + _G.schedules = 0 + _G.parser = vim.treesitter.get_parser(0, 'c') + for i = 1, 5 do + _G['done' .. i] = false + _G.parser:parse(nil, function() + _G['done' .. i] = true + end) + end + schedule(function() + _G.schedules_snapshot = _G.schedules + end) + end) + + eq(2, exec_lua([[return schedules_snapshot]])) + eq( + { false, false, false, false, false }, + exec_lua([[return { done1, done2, done3, done4, done5 }]]) + ) + exec_lua(function() + while not _G.done1 do + -- Busy wait until async parsing finishes + vim.wait(100, function() end) + end + end) + eq({ true, true, true, true, true }, exec_lua([[return { done1, done2, done3, done4, done5 }]])) + end) + local test_text = [[ void ui_refresh(void) { @@ -310,6 +504,15 @@ end]] eq({ 0, 0, 0, 13 }, ret) end) + it('can run async parses with string parsers', function() + local ret = exec_lua(function() + local parser = vim.treesitter.get_string_parser('int foo = 42;', 'c') + return { parser:parse(nil, function() end)[1]:root():range() } + end) + + eq({ 0, 0, 0, 13 }, ret) + end) + it('allows to run queries with string parsers', function() local txt = [[ int foo = 42; @@ -430,7 +633,7 @@ int x = INT_MAX; }, get_ranges()) n.feed('7ggI//<esc>') - exec_lua([[parser:parse({6, 7})]]) + exec_lua([[parser:parse({5, 6})]]) eq('table', exec_lua('return type(parser:children().c)')) eq(2, exec_lua('return #parser:children().c:trees()')) eq({ @@ -644,6 +847,109 @@ print() end) end) + describe('trim! directive', function() + it('can trim all whitespace', function() + -- luacheck: push ignore 611 613 + insert([=[ + print([[ + + f + helllo + there + asdf + asdfassd + + + + ]]) + print([[ + + + + ]]) + + print([[]]) + + print([[ + ]]) + + print([[ hello 😃 ]]) + ]=]) + -- luacheck: pop + + local query_text = [[ + ; query + ((string_content) @str + (#trim! @str 1 1 1 1)) + ]] + + exec_lua(function() + vim.treesitter.start(0, 'lua') + end) + + eq({ + { 'str', { 2, 12, 6, 10 } }, + { 'str', { 11, 10, 11, 10 } }, + { 'str', { 17, 10, 17, 10 } }, + { 'str', { 19, 10, 19, 10 } }, + { 'str', { 22, 15, 22, 25 } }, + }, run_query('lua', query_text)) + end) + + it('trims only empty lines by default (backwards compatible)', function() + insert(dedent [[ + ## Heading + + With some text + + ## And another + + With some more here]]) + + local query_text = [[ + ; query + ((section) @fold + (#trim! @fold)) + ]] + + exec_lua(function() + vim.treesitter.start(0, 'markdown') + end) + + eq({ + { 'fold', { 0, 0, 2, 14 } }, + { 'fold', { 4, 0, 6, 19 } }, + }, run_query('markdown', query_text)) + end) + + it('can trim lines', function() + insert(dedent [[ + - Fold list + - Fold list + - Fold list + - Fold list + - Fold list + - Fold list + ]]) + + local query_text = [[ + ; query + ((list_item + (list)) @fold + (#trim! @fold 1 1 1 1)) + ]] + + exec_lua(function() + vim.treesitter.start(0, 'markdown') + end) + + eq({ + { 'fold', { 0, 0, 4, 13 } }, + { 'fold', { 1, 2, 3, 15 } }, + }, run_query('markdown', query_text)) + end) + end) + it('tracks the root range properly (#22911)', function() insert([[ int main() { @@ -659,32 +965,19 @@ print() vim.treesitter.start(0, 'c') end) - local function run_query() - return exec_lua(function() - local query = vim.treesitter.query.parse('c', query0) - local parser = vim.treesitter.get_parser() - local tree = parser:parse()[1] - local res = {} - for id, node in query:iter_captures(tree:root()) do - table.insert(res, { query.captures[id], node:range() }) - end - return res - end) - end - eq({ - { 'function', 0, 0, 2, 1 }, - { 'declaration', 1, 2, 1, 12 }, - }, run_query()) + { 'function', { 0, 0, 2, 1 } }, + { 'declaration', { 1, 2, 1, 12 } }, + }, run_query('c', query0)) n.command 'normal ggO' insert('int a;') eq({ - { 'declaration', 0, 0, 0, 6 }, - { 'function', 1, 0, 3, 1 }, - { 'declaration', 2, 2, 2, 12 }, - }, run_query()) + { 'declaration', { 0, 0, 0, 6 } }, + { 'function', { 1, 0, 3, 1 } }, + { 'declaration', { 2, 2, 2, 12 } }, + }, run_query('c', query0)) end) it('handles ranges when source is a multiline string (#20419)', function() @@ -858,11 +1151,13 @@ print() feed(':set ft=help<cr>') exec_lua(function() - vim.treesitter.get_parser(0, 'vimdoc', { - injections = { - vimdoc = '((codeblock (language) @injection.language (code) @injection.content) (#set! injection.include-children))', - }, - }) + vim.treesitter + .get_parser(0, 'vimdoc', { + injections = { + vimdoc = '((codeblock (language) @injection.language (code) @injection.content) (#set! injection.include-children))', + }, + }) + :parse() end) end) diff --git a/test/functional/treesitter/query_spec.lua b/test/functional/treesitter/query_spec.lua index 634f8af83d..6db0ffe5a0 100644 --- a/test/functional/treesitter/query_spec.lua +++ b/test/functional/treesitter/query_spec.lua @@ -86,7 +86,7 @@ void ui_refresh(void) local before = vim.api.nvim__stats().ts_query_parse_count collectgarbage('stop') for _ = 1, _n, 1 do - vim.treesitter.query.parse('c', long_query, _n) + vim.treesitter.query.parse('c', long_query) end collectgarbage('restart') collectgarbage('collect') @@ -96,8 +96,39 @@ void ui_refresh(void) end eq(1, q(1)) - -- cache is cleared by garbage collection even if valid "cquery" reference is kept around - eq(1, q(100)) + -- cache is retained even after garbage collection + eq(0, q(100)) + end) + + it('cache is cleared upon runtimepath changes, or setting query manually', function() + ---@return number + exec_lua(function() + _G.query_parse_count = _G.query_parse_count or 0 + local parse = vim.treesitter.query.parse + vim.treesitter.query.parse = function(...) + _G.query_parse_count = _G.query_parse_count + 1 + return parse(...) + end + end) + + local function q(_n) + return exec_lua(function() + for _ = 1, _n, 1 do + vim.treesitter.query.get('c', 'highlights') + end + return _G.query_parse_count + end) + end + + eq(1, q(10)) + exec_lua(function() + vim.opt.rtp:prepend('/another/dir') + end) + eq(2, q(100)) + exec_lua(function() + vim.treesitter.query.set('c', 'highlights', [[; test]]) + end) + eq(3, q(100)) end) it('supports query and iter by capture (iter_captures)', function() @@ -781,6 +812,34 @@ void ui_refresh(void) ) end) + it('supports "; extends" modeline in custom queries', function() + insert('int zeero = 0;') + local result = exec_lua(function() + vim.treesitter.query.set( + 'c', + 'highlights', + [[; extends + (identifier) @spell]] + ) + local query = vim.treesitter.query.get('c', 'highlights') + local parser = vim.treesitter.get_parser(0, 'c') + local root = parser:parse()[1]:root() + local res = {} + for id, node in query:iter_captures(root, 0) do + table.insert(res, { query.captures[id], vim.treesitter.get_node_text(node, 0) }) + end + return res + end) + eq({ + { 'type.builtin', 'int' }, + { 'variable', 'zeero' }, + { 'spell', 'zeero' }, + { 'operator', '=' }, + { 'number', '0' }, + { 'punctuation.delimiter', ';' }, + }, result) + end) + describe('Query:iter_captures', function() it('includes metadata for all captured nodes #23664', function() insert([[ @@ -835,9 +894,9 @@ void ui_refresh(void) local result = exec_lua(function() local query0 = vim.treesitter.query.parse('c', query) - local match_preds = query0.match_preds + local match_preds = query0._match_predicates local called = 0 - function query0:match_preds(...) + function query0:_match_predicates(...) called = called + 1 return match_preds(self, ...) end diff --git a/test/functional/treesitter/testutil.lua b/test/functional/treesitter/testutil.lua new file mode 100644 index 0000000000..f8934f06c3 --- /dev/null +++ b/test/functional/treesitter/testutil.lua @@ -0,0 +1,25 @@ +local n = require('test.functional.testnvim')() + +local exec_lua = n.exec_lua + +local M = {} + +---@param language string +---@param query_string string +function M.run_query(language, query_string) + return exec_lua(function(lang, query_str) + local query = vim.treesitter.query.parse(lang, query_str) + local parser = vim.treesitter.get_parser() + local tree = parser:parse()[1] + local res = {} + for id, node, metadata in query:iter_captures(tree:root(), 0) do + table.insert( + res, + { query.captures[id], metadata[id] and metadata[id].range or { node:range() } } + ) + end + return res + end, language, query_string) +end + +return M diff --git a/test/functional/ui/cmdline_spec.lua b/test/functional/ui/cmdline_spec.lua index 0221c1e0b0..ce7c9596bb 100644 --- a/test/functional/ui/cmdline_spec.lua +++ b/test/functional/ui/cmdline_spec.lua @@ -91,25 +91,27 @@ local function test_cmdline(linegrid) {1:~ }|*3 | ]], + cmdline = { { abort = true } }, } end) it('works with input()', function() feed(':call input("input", "default")<cr>') - screen:expect { + screen:expect({ grid = [[ - ^ | - {1:~ }|*3 - | - ]], + ^ | + {1:~ }|*3 + | + ]], cmdline = { { - prompt = 'input', content = { { 'default' } }, + hl_id = 0, pos = 7, + prompt = 'input', }, }, - } + }) feed('<cr>') screen:expect { @@ -118,6 +120,7 @@ local function test_cmdline(linegrid) {1:~ }|*3 | ]], + cmdline = { { abort = false } }, } end) @@ -210,6 +213,7 @@ local function test_cmdline(linegrid) content = { { 'xx3' } }, pos = 3, }, + { abort = false }, }, } @@ -220,6 +224,7 @@ local function test_cmdline(linegrid) {1:~ }|*3 | ]], + cmdline = { { abort = true } }, } end) @@ -294,6 +299,7 @@ local function test_cmdline(linegrid) {1:~ }|*3 | ]], + cmdline = { { abort = false } }, } -- Try once more, to check buffer is reinitialized. #8007 @@ -324,6 +330,7 @@ local function test_cmdline(linegrid) {1:~ }|*3 | ]], + cmdline = { { abort = false } }, } end) @@ -353,6 +360,7 @@ local function test_cmdline(linegrid) {3:[Command Line] }| | ]], + cmdline = { { abort = false } }, } -- nested cmdline @@ -404,6 +412,7 @@ local function test_cmdline(linegrid) {3:[Command Line] }| | ]], + cmdline = { [2] = { abort = true } }, } feed('<c-c>') @@ -452,6 +461,7 @@ local function test_cmdline(linegrid) cmdline = { { prompt = 'secret:', + hl_id = 0, content = { { '******' } }, pos = 6, }, @@ -495,6 +505,7 @@ local function test_cmdline(linegrid) cmdline = { { prompt = '>', + hl_id = 0, content = { { '(', 30 }, { 'a' }, @@ -797,11 +808,14 @@ local function test_cmdline(linegrid) -- This used to send an invalid event where pos where larger than the total -- length of content. Checked in _handle_cmdline_show. feed('<esc>') - screen:expect([[ - ^ | - {1:~ }|*3 - | - ]]) + screen:expect({ + grid = [[ + ^ | + {1:~ }|*3 + | + ]], + cmdline = { { abort = true } }, + }) end) it('does not move cursor to curwin #20309', function() @@ -827,6 +841,30 @@ local function test_cmdline(linegrid) } }, } end) + + it('show prompt hl_id', function() + screen:expect([[ + ^ | + {1:~ }|*3 + | + ]]) + feed(':echohl Error | call input("Prompt:")<CR>') + screen:expect({ + grid = [[ + ^ | + {1:~ }|*3 + | + ]], + cmdline = { + { + content = { { '' } }, + hl_id = 242, + pos = 0, + prompt = 'Prompt:', + }, + }, + }) + end) end -- the representation of cmdline and cmdline_block contents changed with ext_linegrid @@ -1000,6 +1038,36 @@ describe('cmdline redraw', function() ]], } end) + + it('silent prompt', function() + command([[nmap <silent> T :call confirm("Save changes?", "&Yes\n&No\n&Cancel")<CR>]]) + feed('T') + screen:expect([[ + | + {3: }| + | + {6:Save changes?} | + {6:[Y]es, (N)o, (C)ancel: }^ | + ]]) + end) + + it('substitute confirm prompt does not scroll', function() + screen:try_resize(75, screen._height) + command('call setline(1, "foo")') + command('set report=0') + feed(':%s/foo/bar/c<CR>') + screen:expect([[ + {2:foo} | + {1:~ }|*3 + {6:replace with bar? (y)es/(n)o/(a)ll/(q)uit/(l)ast/scroll up(^E)/down(^Y)}^ | + ]]) + feed('y') + screen:expect([[ + ^bar | + {1:~ }|*3 + 1 substitution on 1 line | + ]]) + end) end) describe('statusline is redrawn on entering cmdline', function() @@ -1447,31 +1515,29 @@ describe('cmdheight=0', function() it('when substitute text', function() command('set cmdheight=0 noruler laststatus=3') feed('ifoo<ESC>') - screen:expect { - grid = [[ + screen:try_resize(screen._width, 7) + screen:expect([[ fo^o | - {1:~ }|*3 + {1:~ }|*5 {3:[No Name] [+] }| - ]], - } + ]]) feed(':%s/foo/bar/gc<CR>') - screen:expect { - grid = [[ + screen:expect([[ {2:foo} | - {1:~ }|*3 - {6:replace wi...q/l/^E/^Y)?}^ | - ]], - } + {3: }| + |*2 + {6:replace with bar? (y)es/(}| + {6:n)o/(a)ll/(q)uit/(l)ast/s}| + {6:croll up(^E)/down(^Y)}^ | + ]]) feed('y') - screen:expect { - grid = [[ + screen:expect([[ ^bar | - {1:~ }|*3 + {1:~ }|*5 {3:[No Name] [+] }| - ]], - } + ]]) assert_alive() end) diff --git a/test/functional/ui/cursor_spec.lua b/test/functional/ui/cursor_spec.lua index d7c0657820..825a90fbc8 100644 --- a/test/functional/ui/cursor_spec.lua +++ b/test/functional/ui/cursor_spec.lua @@ -190,6 +190,19 @@ describe('ui/cursor', function() attr_lm = {}, short_name = 'sm', }, + [18] = { + blinkoff = 500, + blinkon = 500, + blinkwait = 0, + cell_percentage = 0, + cursor_shape = 'block', + name = 'terminal', + hl_id = 3, + id_lm = 3, + attr = { reverse = true }, + attr_lm = { reverse = true }, + short_name = 't', + }, } screen:expect(function() @@ -245,17 +258,20 @@ describe('ui/cursor', function() end end if m.hl_id then - m.hl_id = 66 + m.hl_id = 65 m.attr = { background = Screen.colors.DarkGray } end if m.id_lm then - m.id_lm = 73 + m.id_lm = 72 + m.attr_lm = {} end end -- Assert the new expectation. screen:expect(function() - eq(expected_mode_info, screen._mode_info) + for i, v in ipairs(expected_mode_info) do + eq(v, screen._mode_info[i]) + end eq(true, screen._cursor_style_enabled) eq('normal', screen.mode) end) @@ -361,4 +377,38 @@ describe('ui/cursor', function() end end) end) + + it(':sleep does not hide cursor when sleeping', function() + n.feed(':sleep 100m | echo 42\n') + screen:expect({ + grid = [[ + ^ | + {1:~ }|*3 + :sleep 100m | echo 42 | + ]], + timeout = 100, + }) + screen:expect([[ + ^ | + {1:~ }|*3 + 42 | + ]]) + end) + + it(':sleep! hides cursor when sleeping', function() + n.feed(':sleep! 100m | echo 42\n') + screen:expect({ + grid = [[ + | + {1:~ }|*3 + :sleep! 100m | echo 42 | + ]], + timeout = 100, + }) + screen:expect([[ + ^ | + {1:~ }|*3 + 42 | + ]]) + end) end) diff --git a/test/functional/ui/decorations_spec.lua b/test/functional/ui/decorations_spec.lua index fbf16f3afe..7969dd5d3b 100644 --- a/test/functional/ui/decorations_spec.lua +++ b/test/functional/ui/decorations_spec.lua @@ -509,6 +509,69 @@ describe('decorations providers', function() ]]} end) + it('can have virtual text of the style: eol_right_align', function() + insert(mulholland) + setup_provider [[ + local hl = api.nvim_get_hl_id_by_name "ErrorMsg" + local test_ns = api.nvim_create_namespace "mulholland" + function on_do(event, ...) + if event == "line" then + local win, buf, line = ... + api.nvim_buf_set_extmark(buf, test_ns, line, 0, { + virt_text = {{'+'}, {'1234567890', 'ErrorMsg'}}; + virt_text_pos='eol_right_align'; + ephemeral = true; + }) + end + end + ]] + + screen:expect{grid=[[ + // just to see if there was an accident | + // on Mulholland Drive +{2:1234567890}| + try_start(); +{2:1234567890}| + bufref_T save_buf; +{2:1234567890}| + switch_buffer(&save_buf, buf); +{2:12345678}| + posp = getmark(mark, false); +{2:1234567890}| + restore_buffer(&save_buf);^ +{2:1234567890}| + | + ]]} + end) + + it('multiple eol_right_align', function() + insert(mulholland) + setup_provider [[ + local hl = api.nvim_get_hl_id_by_name "ErrorMsg" + local test_ns = api.nvim_create_namespace "mulholland" + function on_do(event, ...) + if event == "line" then + local win, buf, line = ... + api.nvim_buf_set_extmark(buf, test_ns, line, 0, { + virt_text = {{'11111'}}; + virt_text_pos='eol_right_align'; + ephemeral = true; + }) + api.nvim_buf_set_extmark(0, test_ns, line, 0, { + virt_text = {{'22222'}}; + virt_text_pos='eol_right_align'; + ephemeral = true; + }) + end + end + ]] + + screen:expect{grid=[[ + // just to see if there was an accident | + // on Mulholland Drive 11111 22222| + try_start(); 11111 22222| + bufref_T save_buf; 11111 22222| + switch_buffer(&save_buf, buf); 11111 222| + posp = getmark(mark, false); 11111 22222| + restore_buffer(&save_buf);^ 11111 22222| + | + ]]} + end) + it('virtual text works with wrapped lines', function() insert(mulholland) feed('ggJj3JjJ') @@ -631,7 +694,7 @@ describe('decorations providers', function() {14: }hello97 | {14: }hello98 | {14: }hello99 | - X ^hello100 | + {14:X }^hello100 | {14: }hello101 | {14: }hello102 | {14: }hello103 | @@ -744,6 +807,30 @@ describe('decorations providers', function() ]]) eq(2, exec_lua([[return _G.cnt]])) end) + + it('can do large changes to the marktree', function() + insert("line1 with a lot of text\nline2 with a lot of text") + setup_provider([[ + function on_do(event, _, _, row) + if event == 'win' or (event == 'line' and row == 1) then + vim.api.nvim_buf_clear_namespace(0, ns1, 0, -1) + for i = 0,1 do + for j = 0,23 do + vim.api.nvim_buf_set_extmark(0, ns1, i, j, {hl_group='ErrorMsg', end_col = j+1}) + end + end + end + end + ]]) + + -- Doesn't crash when modifying the marktree between line1 and line2 + screen:expect([[ + {2:line1 with a lot of text} | + {2:line2 with a lot of tex^t} | + {1:~ }|*5 + | + ]]) + end) end) local example_text = [[ @@ -810,6 +897,9 @@ describe('extmark decorations', function() [42] = {undercurl = true, special = Screen.colors.Red}; [43] = {background = Screen.colors.Yellow, undercurl = true, special = Screen.colors.Red}; [44] = {background = Screen.colors.LightMagenta}; + [45] = { background = Screen.colors.Red, special = Screen.colors.Red, foreground = Screen.colors.Red }; + [46] = { background = Screen.colors.Blue, foreground = Screen.colors.Blue, special = Screen.colors.Red }; + [47] = { background = Screen.colors.Green, foreground = Screen.colors.Blue, special = Screen.colors.Red }; } ns = api.nvim_create_namespace 'test' @@ -1900,6 +1990,46 @@ describe('extmark decorations', function() ]]} end) + it('highlight can combine multiple groups', function() + screen:try_resize(50, 3) + command('hi Group1 guibg=Red guifg=Red guisp=Red') + command('hi Group2 guibg=Blue guifg=Blue') + command('hi Group3 guibg=Green') + insert([[example text]]) + api.nvim_buf_set_extmark(0, ns, 0, 0, { end_row=1, hl_group = {} }) + screen:expect([[ + example tex^t | + {1:~ }| + | + ]]) + + api.nvim_buf_clear_namespace(0, ns, 0, -1) + api.nvim_buf_set_extmark(0, ns, 0, 0, { end_row=1, hl_group = {'Group1'} }) + screen:expect([[ + {45:example tex^t} | + {1:~ }| + | + ]]) + api.nvim_buf_clear_namespace(0, ns, 0, -1) + api.nvim_buf_set_extmark(0, ns, 0, 0, { end_row = 1, hl_group = {'Group1', 'Group2'} }) + screen:expect([[ + {46:example tex^t} | + {1:~ }| + | + ]]) + api.nvim_buf_clear_namespace(0, ns, 0, -1) + api.nvim_buf_set_extmark(0, ns, 0, 0, { end_row = 1, hl_group = {'Group1', 'Group2', 'Group3'}, hl_eol=true }) + screen:expect([[ + {47:example tex^t }| + {1:~ }| + | + ]]) + + eq('Invalid hl_group: hl_group item', + pcall_err(api.nvim_buf_set_extmark, 0, ns, 0, 0, { end_row = 1, hl_group = {'Group1', 'Group2', {'fail'}}, hl_eol=true })) + end) + + it('highlight works after TAB with sidescroll #14201', function() screen:try_resize(50, 3) command('set nowrap') @@ -2301,13 +2431,16 @@ describe('extmark decorations', function() it('works with both hl_group and sign_hl_group', function() screen:try_resize(50, 3) + screen:add_extra_attr_ids({ + [100] = { background = Screen.colors.WebGray, foreground = Screen.colors.Blue, bold = true }, + }) insert('abcdefghijklmn') api.nvim_buf_set_extmark(0, ns, 0, 0, {sign_text='S', sign_hl_group='NonText', hl_group='Error', end_col=14}) - screen:expect{grid=[[ - {1:S }{4:abcdefghijklm^n} | + screen:expect([[ + {100:S }{9:abcdefghijklm^n} | {1:~ }| | - ]]} + ]]) end) it('virt_text_repeat_linebreak repeats virtual text on wrapped lines', function() @@ -5064,16 +5197,16 @@ l5 api.nvim_buf_set_extmark(0, ns, 1, -1, {sign_text='S'}) - screen:expect{grid=[[ + screen:expect([[ {7: }^l1 | - S l2 | + {7:S }l2 | {7: }l3 | {7: }l4 | {7: }l5 | {7: } | {1:~ }|*3 | - ]]} + ]]) end) it('can add a single sign (with end row)', function() @@ -5082,16 +5215,16 @@ l5 api.nvim_buf_set_extmark(0, ns, 1, -1, {sign_text='S', end_row=1}) - screen:expect{grid=[[ + screen:expect([[ {7: }^l1 | - S l2 | + {7:S }l2 | {7: }l3 | {7: }l4 | {7: }l5 | {7: } | {1:~ }|*3 | - ]]} + ]]) end) it('can add a single sign and text highlight', function() @@ -5099,16 +5232,16 @@ l5 feed 'gg' api.nvim_buf_set_extmark(0, ns, 1, 0, {sign_text='S', hl_group='Todo', end_col=1}) - screen:expect{grid=[[ + screen:expect([[ {7: }^l1 | - S {100:l}2 | + {7:S }{100:l}2 | {7: }l3 | {7: }l4 | {7: }l5 | {7: } | {1:~ }|*3 | - ]]} + ]]) api.nvim_buf_clear_namespace(0, ns, 0, -1) end) @@ -5119,16 +5252,16 @@ l5 api.nvim_buf_set_extmark(0, ns, 1, -1, {sign_text='S', end_row = 2}) - screen:expect{grid=[[ + screen:expect([[ {7: }^l1 | - S l2 | - S l3 | + {7:S }l2 | + {7:S }l3 | {7: }l4 | {7: }l5 | {7: } | {1:~ }|*3 | - ]]} + ]]) end) it('can add multiple signs (multiple extmarks)', function() @@ -5138,16 +5271,16 @@ l5 api.nvim_buf_set_extmark(0, ns, 1, -1, {sign_text='S1'}) api.nvim_buf_set_extmark(0, ns, 3, -1, {sign_text='S2', end_row = 4}) - screen:expect{grid=[[ + screen:expect([[ {7: }^l1 | - S1l2 | + {7:S1}l2 | {7: }l3 | - S2l4 | - S2l5 | + {7:S2}l4 | + {7:S2}l5 | {7: } | {1:~ }|*3 | - ]]} + ]]) end) it('can add multiple signs (multiple extmarks) 2', function() @@ -5156,16 +5289,16 @@ l5 api.nvim_buf_set_extmark(0, ns, 3, -1, {sign_text='S1'}) api.nvim_buf_set_extmark(0, ns, 1, -1, {sign_text='S2', end_row = 3}) - screen:expect{grid=[[ + screen:expect([[ {7: }^l1 | - S2{7: }l2 | - S2{7: }l3 | - S2S1l4 | + {7:S2 }l2 | + {7:S2 }l3 | + {7:S2S1}l4 | {7: }l5 | {7: } | {1:~ }|*3 | - ]]} + ]]) end) it('can add multiple signs (multiple extmarks) 3', function() @@ -5176,16 +5309,16 @@ l5 api.nvim_buf_set_extmark(0, ns, 1, -1, {sign_text='S1', end_row=2}) api.nvim_buf_set_extmark(0, ns, 2, -1, {sign_text='S2', end_row=3}) - screen:expect{grid=[[ + screen:expect([[ {7: }^l1 | - S1{7: }l2 | - S2S1l3 | - S2{7: }l4 | + {7:S1 }l2 | + {7:S2S1}l3 | + {7:S2 }l4 | {7: }l5 | {7: } | {1:~ }|*3 | - ]]} + ]]) end) it('can add multiple signs (multiple extmarks) 4', function() @@ -5195,16 +5328,16 @@ l5 api.nvim_buf_set_extmark(0, ns, 0, -1, {sign_text='S1', end_row=0}) api.nvim_buf_set_extmark(0, ns, 1, -1, {sign_text='S2', end_row=1}) - screen:expect{grid=[[ - S1^l1 | - S2l2 | + screen:expect([[ + {7:S1}^l1 | + {7:S2}l2 | {7: }l3 | {7: }l4 | {7: }l5 | {7: } | {1:~ }|*3 | - ]]} + ]]) end) it('works with old signs', function() @@ -5219,16 +5352,16 @@ l5 api.nvim_buf_set_extmark(0, ns, 0, -1, {sign_text='S4'}) api.nvim_buf_set_extmark(0, ns, 2, -1, {sign_text='S5'}) - screen:expect{grid=[[ - S4S1^l1 | - S2x l2 | - S5{7: }l3 | + screen:expect([[ + {7:S4S1}^l1 | + {7:S2x }l2 | + {7:S5 }l3 | {7: }l4 | {7: }l5 | {7: } | {1:~ }|*3 | - ]]} + ]]) end) it('works with old signs (with range)', function() @@ -5244,16 +5377,16 @@ l5 api.nvim_buf_set_extmark(0, ns, 0, -1, {sign_text='S4'}) api.nvim_buf_set_extmark(0, ns, 2, -1, {sign_text='S5'}) - screen:expect{grid=[[ - S4S3S1^l1 | - S3S2x l2 | - S5S3{7: }l3 | - S3{7: }l4 | - S3{7: }l5 | + screen:expect([[ + {7:S4S3S1}^l1 | + {7:S3S2x }l2 | + {7:S5S3 }l3 | + {7:S3 }l4 | + {7:S3 }l5 | {7: } | {1:~ }|*3 | - ]]} + ]]) end) it('can add a ranged sign (with start out of view)', function() @@ -5264,14 +5397,14 @@ l5 api.nvim_buf_set_extmark(0, ns, 1, -1, {sign_text='X', end_row=3}) - screen:expect{grid=[[ - X {7: }^l3 | - X {7: }l4 | + screen:expect([[ + {7:X }^l3 | + {7:X }l4 | {7: }l5 | {7: } | {1:~ }|*5 | - ]]} + ]]) end) it('can add lots of signs', function() @@ -5293,11 +5426,11 @@ l5 api.nvim_buf_set_extmark(0, ns, i, -1, { sign_text='Z' }) end - screen:expect{grid=[[ - Z Y X W {100:a} {100:b} {100:c} {100:d} {100:e} {100:f} {100:g} {100:h} |*8 - Z Y X W {100:a} {100:b} {100:c} {100:d} {100:e} {100:f} {100:g} {100:^h} | + screen:expect([[ + {7:Z Y X W }{100:a} {100:b} {100:c} {100:d} {100:e} {100:f} {100:g} {100:h} |*8 + {7:Z Y X W }{100:a} {100:b} {100:c} {100:d} {100:e} {100:f} {100:g} {100:^h} | | - ]]} + ]]) end) it('works with priority #19716', function() @@ -5313,20 +5446,20 @@ l5 api.nvim_buf_set_extmark(0, ns, 0, -1, {sign_text='S5', priority=200}) api.nvim_buf_set_extmark(0, ns, 0, -1, {sign_text='S1', priority=1}) - screen:expect{grid=[[ - S5S4O3S2S1^l1 | + screen:expect([[ + {7:S5S4O3S2S1}^l1 | {7: }l2 | | - ]]} + ]]) -- Check truncation works too api.nvim_set_option_value('signcolumn', 'auto', {}) - screen:expect{grid=[[ - S5^l1 | + screen:expect([[ + {7:S5}^l1 | {7: }l2 | | - ]]} + ]]) end) it('does not overflow with many old signs #23852', function() @@ -5343,21 +5476,21 @@ l5 command([[exe 'sign place 07 line=1 name=Oldsign priority=10 buffer=' . bufnr('')]]) command([[exe 'sign place 08 line=1 name=Oldsign priority=10 buffer=' . bufnr('')]]) command([[exe 'sign place 09 line=1 name=Oldsign priority=10 buffer=' . bufnr('')]]) - screen:expect{grid=[[ - O3O3O3O3O3O3O3O3O3^ | + screen:expect([[ + {7:O3O3O3O3O3O3O3O3O3}^ | {1:~ }| | - ]]} + ]]) api.nvim_buf_set_extmark(0, ns, 0, -1, {sign_text='S1', priority=1}) screen:expect_unchanged() api.nvim_buf_set_extmark(0, ns, 0, -1, {sign_text='S5', priority=200}) - screen:expect{grid=[[ - S5O3O3O3O3O3O3O3O3^ | + screen:expect([[ + {7:S5O3O3O3O3O3O3O3O3}^ | {1:~ }| | - ]]} + ]]) assert_alive() end) @@ -5383,12 +5516,12 @@ l5 api.nvim_buf_set_extmark(0, ns, 1, -1, {invalidate = true, sign_text='S3'}) feed('2Gdd') - screen:expect{grid=[[ - S1l1 | - S1^l3 | - S1l4 | + screen:expect([[ + {7:S1}l1 | + {7:S1}^l3 | + {7:S1}l4 | | - ]]} + ]]) end) it('correct width with multiple overlapping signs', function() @@ -5400,36 +5533,36 @@ l5 feed('gg') local s1 = [[ - S2S1^l1 | - S3S2l2 | - S3S2l3 | + {7:S2S1}^l1 | + {7:S3S2}l2 | + {7:S3S2}l3 | | ]] - screen:expect{grid=s1} + screen:expect(s1) -- Correct width when :move'ing a line with signs command('move2') - screen:expect{grid=[[ - S3{7: }l2 | - S3S2S1^l1 | + screen:expect([[ + {7:S3 }l2 | + {7:S3S2S1}^l1 | {7: }l3 | | - ]]} + ]]) command('silent undo') screen:expect{grid=s1} command('d') - screen:expect{grid=[[ - S3S2S1^l2 | - S3S2{7: }l3 | + screen:expect([[ + {7:S3S2S1}^l2 | + {7:S3S2 }l3 | {7: }l4 | | - ]]} + ]]) command('d') - screen:expect{grid=[[ - S3S2S1^l3 | + screen:expect([[ + {7:S3S2S1}^l3 | {7: }l4 | {7: }l5 | | - ]]} + ]]) end) it('correct width when adding and removing multiple signs', function() @@ -5452,12 +5585,12 @@ l5 redraw! call nvim_buf_del_extmark(0, ns, s1) ]]) - screen:expect{grid=[[ - S1^l1 | - S1l2 | - S1l3 | + screen:expect([[ + {7:S1}^l1 | + {7:S1}l2 | + {7:S1}l3 | | - ]]} + ]]) end) it('correct width when deleting lines', function() @@ -5472,12 +5605,12 @@ l5 call nvim_buf_del_extmark(0, ns, s3) norm 4Gdd ]]) - screen:expect{grid=[[ + screen:expect([[ {7: }l3 | - S2S1l5 | + {7:S2S1}l5 | {7: }^ | | - ]]} + ]]) end) it('correct width when splitting lines with signs on different columns', function() @@ -5487,12 +5620,12 @@ l5 api.nvim_buf_set_extmark(0, ns, 0, 0, {sign_text='S1'}) api.nvim_buf_set_extmark(0, ns, 0, 1, {sign_text='S2'}) feed('a<cr><esc>') - screen:expect{grid=[[ - S1l | - S2^1 | + screen:expect([[ + {7:S1}l | + {7:S2}^1 | {7: }l2 | | - ]]} + ]]) end) it('correct width after wiping a buffer', function() @@ -5501,12 +5634,12 @@ l5 feed('gg') local buf = api.nvim_get_current_buf() api.nvim_buf_set_extmark(buf, ns, 0, 0, { sign_text = 'h' }) - screen:expect{grid=[[ - h ^l1 | + screen:expect([[ + {7:h }^l1 | {7: }l2 | {7: }l3 | | - ]]} + ]]) api.nvim_win_set_buf(0, api.nvim_create_buf(false, true)) api.nvim_buf_delete(buf, {unload=true, force=true}) api.nvim_buf_set_lines(buf, 0, -1, false, {''}) @@ -5537,12 +5670,12 @@ l5 end) ]]) - screen:expect{grid=[[ - S1^l1 | - S2l2 | - S4l3 | + screen:expect([[ + {7:S1}^l1 | + {7:S2}l2 | + {7:S4}l3 | | - ]]} + ]]) end) it('no crash with sign after many marks #27137', function() @@ -5553,11 +5686,11 @@ l5 end api.nvim_buf_set_extmark(0, ns, 0, 0, {sign_text = 'S1'}) - screen:expect{grid=[[ - S1{9:^a} | + screen:expect([[ + {7:S1}{9:^a} | {1:~ }|*2 | - ]]} + ]]) end) it('correct sort order with multiple namespaces and same id', function() @@ -5565,11 +5698,11 @@ l5 api.nvim_buf_set_extmark(0, ns, 0, 0, {sign_text = 'S1', id = 1}) api.nvim_buf_set_extmark(0, ns2, 0, 0, {sign_text = 'S2', id = 1}) - screen:expect{grid=[[ - S2S1^ | + screen:expect([[ + {7:S2S1}^ | {1:~ }|*8 | - ]]} + ]]) end) it('correct number of signs after deleting text (#27046)', function() @@ -5586,12 +5719,12 @@ l5 api.nvim_buf_set_extmark(0, ns, 30, 0, {end_row = 30, end_col = 3, hl_group = 'Error'}) command('0d29') - screen:expect{grid=[[ - S4S3S2S1{9:^foo} | - S5{7: }{9:foo} | + screen:expect([[ + {7:S4S3S2S1}{9:^foo} | + {7:S5 }{9:foo} | {1:~ }|*7 29 fewer lines | - ]]} + ]]) api.nvim_buf_clear_namespace(0, ns, 0, -1) end) @@ -5599,21 +5732,17 @@ l5 it([[correct numberwidth with 'signcolumn' set to "number" #28984]], function() command('set number numberwidth=1 signcolumn=number') api.nvim_buf_set_extmark(0, ns, 0, 0, { sign_text = 'S1' }) - screen:expect({ - grid = [[ - S1 ^ | - {1:~ }|*8 - | - ]] - }) + screen:expect([[ + {7:S1 }^ | + {1:~ }|*8 + | + ]]) api.nvim_buf_del_extmark(0, ns, 1) - screen:expect({ - grid = [[ - {8:1 }^ | - {1:~ }|*8 - | - ]] - }) + screen:expect([[ + {8:1 }^ | + {1:~ }|*8 + | + ]]) end) it('supports emoji as signs', function() @@ -5626,10 +5755,10 @@ l5 api.nvim_buf_set_extmark(0, ns, 4, 0, {sign_text='❤x'}) screen:expect([[ {7: }^l1 | - 🧑🌾l2 | - ❤️l3 | - ❤ l4 | - ❤xl5 | + {7:🧑🌾}l2 | + {7:❤️}l3 | + {7:❤ }l4 | + {7:❤x}l5 | {7: } | {1:~ }|*3 | diff --git a/test/functional/ui/diff_spec.lua b/test/functional/ui/diff_spec.lua index 95159011f1..dae373297a 100644 --- a/test/functional/ui/diff_spec.lua +++ b/test/functional/ui/diff_spec.lua @@ -2044,6 +2044,26 @@ it('diff mode overlapped diff blocks will be merged', function() {2:Xdifile1 Xdifile2 }{3:Xdifile3 }| | ]]) + + WriteDiffFiles3('a\nb\nc', 'd\ne', 'b\nf') + screen:expect([[ + {7: }{27:a}{4: }│{7: }{27:d}{4: }│{7: }{27:^b}{4: }| + {7: }{27:b}{4: }│{7: }{27:e}{4: }│{7: }{27:f}{4: }| + {7: }{22:c }│{7: }{23:---------}│{7: }{23:---------}| + {1:~ }│{1:~ }│{1:~ }|*15 + {2:Xdifile1 Xdifile2 }{3:Xdifile3 }| + | + ]]) + + WriteDiffFiles3('a\nb\nc', 'd\ne', 'b') + screen:expect([[ + {7: }{27:a}{4: }│{7: }{27:d}{4: }│{7: }{27:^b}{4: }| + {7: }{27:b}{4: }│{7: }{27:e}{4: }│{7: }{23:---------}| + {7: }{22:c }│{7: }{23:---------}│{7: }{23:---------}| + {1:~ }│{1:~ }│{1:~ }|*15 + {2:Xdifile1 Xdifile2 }{3:Xdifile3 }| + | + ]]) end) -- oldtest: Test_diff_topline_noscroll() diff --git a/test/functional/ui/float_spec.lua b/test/functional/ui/float_spec.lua index 57ef9bcff6..15231e0f8c 100644 --- a/test/functional/ui/float_spec.lua +++ b/test/functional/ui/float_spec.lua @@ -1012,6 +1012,97 @@ describe('float window', function() end) end) + it('placed relative to tabline and laststatus', function() + local screen = Screen.new(20, 10) + screen:add_extra_attr_ids({ [100] = { bold = true, foreground = Screen.colors.Magenta } }) + command('set showtabline=1 laststatus=1') + api.nvim_open_win(0, false, { + relative = 'laststatus', + border = 'single', + anchor = 'SE', + width = 5, + height = 1, + row = 0, + col = 1000, + }) + local tabwin = api.nvim_open_win(0, false, { + relative = 'tabline', + border = 'single', + width = 5, + height = 1, + row = 0, + col = 1000, + }) + screen:expect([[ + ^ {2:┌─────┐}| + {1:~ }{2:│}{4: }{2:│}| + {1:~ }{2:└─────┘}| + {1:~ }|*3 + {1:~ }{2:┌─────┐}| + {1:~ }{2:│}{4: }{2:│}| + {1:~ }{2:└─────┘}| + | + ]]) + command('tabnew | tabnext') + screen:expect([[ + {5: }{100:3}{5: Name] }{24: No Name]X}| + ^ {2:┌─────┐}| + {1:~ }{2:│}{4: }{2:│}| + {1:~ }{2:└─────┘}| + {1:~ }|*2 + {1:~ }{2:┌─────┐}| + {1:~ }{2:│}{4: }{2:│}| + {1:~ }{2:└─────┘}| + | + ]]) + command('vsplit') + screen:expect([[ + {5: }{100:4}{5: Name] }{24: No Name]X}| + ^ {2:┌─────┐}| + {1:~ }{2:│}{4: }{2:│}| + {1:~ }{2:└─────┘}| + {1:~ }{2:│}{1:~}| + {1:~ }{2:┌─────┐}| + {1:~ }{2:│}{4: }{2:│}| + {1:~ }{2:└─────┘}| + {3:[No Name] }{2:<}| + | + ]]) + command('quit') + api.nvim_win_set_config(tabwin, { + relative = 'tabline', + border = 'single', + width = 5, + height = 1, + row = 1, + col = 0, + }) + screen:expect([[ + {5: }{100:3}{5: Name] }{24: No Name]X}| + ^ | + {2:┌─────┐}{1: }| + {2:│}{4: }{2:│}{1: }| + {2:└─────┘}{1: }| + {1:~ }| + {1:~ }{2:┌─────┐}| + {1:~ }{2:│}{4: }{2:│}| + {1:~ }{2:└─────┘}| + | + ]]) + command('tabonly') + screen:expect([[ + ^ | + {2:┌─────┐}{1: }| + {2:│}{4: }{2:│}{1: }| + {2:└─────┘}{1: }| + {1:~ }|*2 + {1:~ }{2:┌─────┐}| + {1:~ }{2:│}{4: }{2:│}| + {1:~ }{2:└─────┘}| + | + ]]) + end) + local function with_ext_multigrid(multigrid) local screen, attrs before_each(function() @@ -1046,6 +1137,8 @@ describe('float window', function() [26] = {blend = 80, background = Screen.colors.Gray0}; [27] = {foreground = Screen.colors.Black, background = Screen.colors.LightGrey}; [28] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.LightGrey}; + [29] = {background = Screen.colors.Yellow1, foreground = Screen.colors.Blue4}; + [30] = {background = Screen.colors.Grey, foreground = Screen.colors.Blue4, bold = true}; } screen:set_default_attr_ids(attrs) end) @@ -1451,14 +1544,14 @@ describe('float window', function() [2:----------------------------------------]|*6 [3:----------------------------------------]| ## grid 2 - {19: }{17:𐌢̀́̂̃̅̄𐌢̀́̂̃̅̄}{20: 1 }{22:^x}{21: }| + {19: }{29:𐌢̀́̂̃̅̄𐌢̀́̂̃̅̄}{20: 1 }{22:^x}{21: }| {19: }{14: 2 }{22:y} | {19: }{14: 3 }{22: } | {0:~ }|*3 ## grid 3 | ## grid 4 - {17:𐌢̀́̂̃̅̄𐌢̀́̂̃̅̄}{15:x }| + {29:𐌢̀́̂̃̅̄𐌢̀́̂̃̅̄}{15:x }| {19: }{15:y }| {19: }{15: }| {15: }| @@ -1466,9 +1559,9 @@ describe('float window', function() else screen:expect([[ - {19: }{17:𐌢̀́̂̃̅̄𐌢̀́̂̃̅̄}{20: 1 }{22:^x}{21: }| + {19: }{29:𐌢̀́̂̃̅̄𐌢̀́̂̃̅̄}{20: 1 }{22:^x}{21: }| {19: }{14: 2 }{22:y} | - {19: }{14: 3 }{22: } {17:𐌢̀́̂̃̅̄𐌢̀́̂̃̅̄}{15:x } | + {19: }{14: 3 }{22: } {29:𐌢̀́̂̃̅̄𐌢̀́̂̃̅̄}{15:x } | {0:~ }{19: }{15:y }{0: }| {0:~ }{19: }{15: }{0: }| {0:~ }{15: }{0: }| @@ -1551,14 +1644,14 @@ describe('float window', function() [2:----------------------------------------]|*6 [3:----------------------------------------]| ## grid 2 - {19: }{17:𐌢̀́̂̃̅̄𐌢̀́̂̃̅̄}{20: 1 }{22:^x}{21: }| + {19: }{29:𐌢̀́̂̃̅̄𐌢̀́̂̃̅̄}{20: 1 }{22:^x}{21: }| {19: }{14: 2 }{22:y} | {19: }{14: 3 }{22: } | {0:~ }|*3 ## grid 3 | ## grid 4 - {17:𐌢̀́̂̃̅̄𐌢̀́̂̃̅̄}{15:x }| + {29:𐌢̀́̂̃̅̄𐌢̀́̂̃̅̄}{15:x }| {19: }{15:y }| {19: }{15: }| {15: }| @@ -1566,9 +1659,9 @@ describe('float window', function() else screen:expect([[ - {19: }{17:𐌢̀́̂̃̅̄𐌢̀́̂̃̅̄}{20: 1 }{22:^x}{21: }| + {19: }{29:𐌢̀́̂̃̅̄𐌢̀́̂̃̅̄}{20: 1 }{22:^x}{21: }| {19: }{14: 2 }{22:y} | - {19: }{14: 3 }{22: } {17:𐌢̀́̂̃̅̄𐌢̀́̂̃̅̄}{15:x } | + {19: }{14: 3 }{22: } {29:𐌢̀́̂̃̅̄𐌢̀́̂̃̅̄}{15:x } | {0:~ }{19: }{15:y }{0: }| {0:~ }{19: }{15: }{0: }| {0:~ }{15: }{0: }| @@ -1616,31 +1709,34 @@ describe('float window', function() feed('ix<cr>y<cr><esc>gg') api.nvim_open_win(0, false, {relative='editor', width=20, height=4, row=4, col=10, style='minimal'}) if multigrid then - screen:expect{grid=[[ - ## grid 1 - [2:----------------------------------------]|*6 - [3:----------------------------------------]| - ## grid 2 - {20: 1}{19: }{22:^x}{21: }| - {14: 2}{19: }{22:y} | - {14: 3}{19: }{22: } | - {0:~ }|*3 - ## grid 3 - | - ## grid 4 - {15:x }| - {15:y }| - {15: }|*2 - ]], float_pos={[4] = {1001, "NW", 1, 4, 10, true}}} + screen:expect({ + grid = [[ + ## grid 1 + [2:----------------------------------------]|*6 + [3:----------------------------------------]| + ## grid 2 + {20: 1}{19: }{22:^x}{21: }| + {14: 2}{19: }{22:y} | + {14: 3}{19: }{22: } | + {0:~ }|*3 + ## grid 3 + | + ## grid 4 + {15:x }| + {15:y }| + {15: }|*2 + ]], + float_pos = { [4] = { 1001, "NW", 1, 4, 10, true, 50 } }, + }) else - screen:expect{grid=[[ + screen:expect([[ {20: 1}{19: }{22:^x}{21: }| {14: 2}{19: }{22:y} | {14: 3}{19: }{22: } {15:x } | {0:~ }{15:y }{0: }| {0:~ }{15: }{0: }|*2 | - ]]} + ]]) end end) @@ -2237,6 +2333,61 @@ describe('float window', function() | ]]} end + + -- reuse before title pos + api.nvim_win_set_config(win, {title= 'new'}) + if multigrid then + screen:expect({ + grid = [[ + ## grid 1 + [2:----------------------------------------]|*6 + [3:----------------------------------------]| + ## grid 2 + ^ | + {0:~ }|*5 + ## grid 3 + | + ## grid 4 + {5:╔══════}{11:new}{5:╗}| + {5:║}{1: halloj! }{5:║}| + {5:║}{1: BORDAA }{5:║}| + {5:╚═════════╝}| + ]], + float_pos = { + [4] = {1001, "NW", 1, 2, 5, true, 50}; + }, + win_viewport = { + [2] = {win = 1000, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0}; + [4] = {win = 1001, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 2, sum_scroll_delta = 0}; + }, + win_viewport_margins = { + [2] = { + bottom = 0, + left = 0, + right = 0, + top = 0, + win = 1000 + }, + [4] = { + bottom = 1, + left = 1, + right = 1, + top = 1, + win = 1001 + } + }, + }) + else + screen:expect([[ + ^ | + {0:~ }| + {0:~ }{5:╔══════}{11:new}{5:╗}{0: }| + {0:~ }{5:║}{1: halloj! }{5:║}{0: }| + {0:~ }{5:║}{1: BORDAA }{5:║}{0: }| + {0:~ }{5:╚═════════╝}{0: }| + | + ]]) + end end) it('border with footer', function() @@ -2382,6 +2533,61 @@ describe('float window', function() | ]]} end + + -- reuse before footer pos + api.nvim_win_set_config(win, { footer = 'new' }) + if multigrid then + screen:expect({ + grid = [[ + ## grid 1 + [2:----------------------------------------]|*6 + [3:----------------------------------------]| + ## grid 2 + ^ | + {0:~ }|*5 + ## grid 3 + | + ## grid 4 + {5:╔═════════╗}| + {5:║}{1: halloj! }{5:║}| + {5:║}{1: BORDAA }{5:║}| + {5:╚══════}{11:new}{5:╝}| + ]], + float_pos = { + [4] = {1001, "NW", 1, 2, 5, true, 50}; + }, + win_viewport = { + [2] = {win = 1000, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0}; + [4] = {win = 1001, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 2, sum_scroll_delta = 0}; + }, + win_viewport_margins = { + [2] = { + bottom = 0, + left = 0, + right = 0, + top = 0, + win = 1000 + }, + [4] = { + bottom = 1, + left = 1, + right = 1, + top = 1, + win = 1001 + } + }, + }) + else + screen:expect([[ + ^ | + {0:~ }| + {0:~ }{5:╔═════════╗}{0: }| + {0:~ }{5:║}{1: halloj! }{5:║}{0: }| + {0:~ }{5:║}{1: BORDAA }{5:║}{0: }| + {0:~ }{5:╚══════}{11:new}{5:╝}{0: }| + | + ]]) + end end) it('border with title and footer', function() @@ -8542,6 +8748,131 @@ describe('float window', function() | ]]} end + + -- + -- Check that floats are positioned correctly after changing the zindexes. + -- + command('fclose') + exec_lua([[ + local win1, win3 = ... + vim.api.nvim_win_set_config(win1, { zindex = 400, title = 'win_400', title_pos = 'center', border = 'double' }) + vim.api.nvim_win_set_config(win3, { zindex = 300, title = 'win_300', title_pos = 'center', border = 'single' }) + ]], win1, win3) + if multigrid then + screen:expect({ + grid = [[ + ## grid 1 + [2:----------------------------------------]|*6 + [3:----------------------------------------]| + ## grid 2 + ^ | + {0:~ }|*5 + ## grid 3 + | + ## grid 4 + {5:╔══════}{11:win_400}{5:═══════╗}| + {5:║}{7: }{5:║}| + {5:║}{7:~ }{5:║}|*2 + {5:╚════════════════════╝}| + ## grid 6 + {5:┌──────}{11:win_300}{5:───────┐}| + {5:│}{8: }{5:│}| + {5:│}{8:~ }{5:│}|*2 + {5:└────────────────────┘}| + ]], float_pos={ + [4] = {1001, "NW", 1, 1, 5, true, 400}; + [6] = {1003, "NW", 1, 3, 7, true, 300}; + }, win_viewport={ + [2] = {win = 1000, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0}; + [4] = {win = 1001, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0}; + [6] = {win = 1003, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0}; + }, win_viewport_margins={ + [2] = { bottom = 0, left = 0, right = 0, top = 0, win = 1000 }, + [4] = { bottom = 1, left = 1, right = 1, top = 1, win = 1001 }, + [6] = { bottom = 1, left = 1, right = 1, top = 1, win = 1003 } + }}) + else + screen:expect({ + grid = [[ + ^ | + {0:~ }{5:╔══════}{11:win_400}{5:═══════╗}{0: }| + {0:~ }{5:║}{7: }{5:║─┐}{0: }| + {0:~ }{5:║}{7:~ }{5:║}{8: }{5:│}{0: }|*2 + {0:~ }{5:╚════════════════════╝}{8: }{5:│}{0: }| + {5:└────────────────────┘} | + ]] + }) + end + exec_lua([[ + local win1, win3 = ... + vim.api.nvim_win_set_config(win1, { zindex = 100, title='win_100' }) + vim.api.nvim_win_set_config(win3, { zindex = 150, title='win_150' }) + ]], win1, win3) + if multigrid then + screen:expect({ + grid = [[ + ## grid 1 + [2:----------------------------------------]|*6 + [3:----------------------------------------]| + ## grid 2 + ^ | + {0:~ }|*5 + ## grid 3 + | + ## grid 4 + {5:╔══════}{11:win_100}{5:═══════╗}| + {5:║}{7: }{5:║}| + {5:║}{7:~ }{5:║}|*2 + {5:╚════════════════════╝}| + ## grid 6 + {5:┌──────}{11:win_150}{5:───────┐}| + {5:│}{8: }{5:│}| + {5:│}{8:~ }{5:│}|*2 + {5:└────────────────────┘}| + ]], + float_pos = { + [4] = {1001, "NW", 1, 1, 5, true, 100}; + [6] = {1003, "NW", 1, 3, 7, true, 150}; + }, + win_viewport = { + [2] = {win = 1000, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0}; + [4] = {win = 1001, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0}; + [6] = {win = 1003, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0}; + }, + win_viewport_margins = { + [2] = { + bottom = 0, + left = 0, + right = 0, + top = 0, + win = 1000 + }, + [4] = { + bottom = 1, + left = 1, + right = 1, + top = 1, + win = 1001 + }, + [6] = { + bottom = 1, + left = 1, + right = 1, + top = 1, + win = 1003 + } + }, + }) + else + screen:expect([[ + ^ | + {0:~ }{5:╔═┌──────}{11:win_150}{5:───────┐}{0: }| + {0:~ }{5:║}{7: }{5:│}{8: }{5:│}{0: }| + {0:~ }{5:║}{7:~}{5:│}{8:~ }{5:│}{0: }|*2 + {0:~ }{5:╚═└────────────────────┘}{0: }| + | + ]]) + end end) it('can use winbar', function() @@ -9523,4 +9854,3 @@ describe('float window', function() with_ext_multigrid(false) end) end) - diff --git a/test/functional/ui/hlstate_spec.lua b/test/functional/ui/hlstate_spec.lua index f8f5ee9488..e10c79fa48 100644 --- a/test/functional/ui/hlstate_spec.lua +++ b/test/functional/ui/hlstate_spec.lua @@ -224,10 +224,10 @@ describe('ext_hlstate detailed highlights', function() [6] = { { foreground = tonumber('0x40ffff'), fg_indexed = true }, { 5, 1 } }, [7] = { {}, { { hi_name = 'MsgArea', ui_name = 'MsgArea', kind = 'ui' } } }, }) - command(("enew | call termopen(['%s'])"):format(testprg('tty-test'))) + command(("enew | call jobstart(['%s'],{'term':v:true})"):format(testprg('tty-test'))) screen:expect([[ ^tty ready | - {1: } | + | |*5 {7: }| ]]) @@ -242,7 +242,7 @@ describe('ext_hlstate detailed highlights', function() screen:expect([[ ^tty ready | x {5:y z} | - {1: } | + | |*4 {7: }| ]]) @@ -250,7 +250,7 @@ describe('ext_hlstate detailed highlights', function() screen:expect([[ ^tty ready | x {2:y }{3:z} | - {1: } | + | |*4 {7: }| ]]) @@ -268,7 +268,7 @@ describe('ext_hlstate detailed highlights', function() else screen:expect([[ ^tty ready | - x {4:y}{2: }{3:z} | + x {2:y }{3:z} | |*5 {7: }| ]]) diff --git a/test/functional/ui/inccommand_user_spec.lua b/test/functional/ui/inccommand_user_spec.lua index 2d26d2c5e0..3eee9a6e07 100644 --- a/test/functional/ui/inccommand_user_spec.lua +++ b/test/functional/ui/inccommand_user_spec.lua @@ -253,6 +253,50 @@ describe("'inccommand' for user commands", function() ]] end) + it("can preview 'nomodifiable' buffer", function() + exec_lua([[ + vim.api.nvim_create_user_command("PreviewTest", function() end, { + preview = function(ev) + vim.bo.modifiable = true + vim.api.nvim_buf_set_lines(0, 0, -1, false, {"cats"}) + return 2 + end, + }) + ]]) + command('set inccommand=split') + + command('set nomodifiable') + eq(false, api.nvim_get_option_value('modifiable', { buf = 0 })) + + feed(':PreviewTest') + + screen:expect([[ + cats | + {1:~ }|*8 + {3:[No Name] [+] }| + | + {1:~ }|*4 + {2:[Preview] }| + :PreviewTest^ | + ]]) + feed('<Esc>') + screen:expect([[ + text on line 1 | + more text on line 2 | + oh no, even more text | + will the text ever stop | + oh well | + did the text stop | + why won't it stop | + make the text stop | + ^ | + {1:~ }|*7 + | + ]]) + + eq(false, api.nvim_get_option_value('modifiable', { buf = 0 })) + end) + it('works with inccommand=nosplit', function() command('set inccommand=nosplit') feed(':Replace text cats') diff --git a/test/functional/ui/input_spec.lua b/test/functional/ui/input_spec.lua index 90e0b3e380..98312c42c9 100644 --- a/test/functional/ui/input_spec.lua +++ b/test/functional/ui/input_spec.lua @@ -368,7 +368,7 @@ describe('input non-printable chars', function() "Xtest-overwrite" | {9:WARNING: The file has been changed since reading it!!!} | {6:Do you really want to write to it (y/n)?}u | - {6:Do you really want to write to it (y/n)?} | + {6:Do you really want to write to it (y/n)?}{18:^E} | {6:Do you really want to write to it (y/n)?}^ | ]]) @@ -379,7 +379,7 @@ describe('input non-printable chars', function() "Xtest-overwrite" | {9:WARNING: The file has been changed since reading it!!!} | {6:Do you really want to write to it (y/n)?}u | - {6:Do you really want to write to it (y/n)?} | + {6:Do you really want to write to it (y/n)?}{18:^E} | {6:Do you really want to write to it (y/n)?}n | {6:Press ENTER or type command to continue}^ | ]]) diff --git a/test/functional/ui/linematch_spec.lua b/test/functional/ui/linematch_spec.lua index b564c01eaa..3593604c49 100644 --- a/test/functional/ui/linematch_spec.lua +++ b/test/functional/ui/linematch_spec.lua @@ -1147,4 +1147,32 @@ describe('regressions', function() }, } end) + + -- oldtest: Test_linematch_3diffs_sanity_check() + it('sanity check with 3 diff buffers', function() + clear() + screen = Screen.new(75, 20) + n.api.nvim_buf_set_lines(0, 0, -1, false, { 'abcd', 'def', 'hij' }) + n.exec('rightbelow vnew') + n.api.nvim_buf_set_lines(0, 0, -1, false, { 'defq', 'hijk', 'nopq' }) + n.exec('rightbelow vnew') + n.api.nvim_buf_set_lines(0, 0, -1, false, { 'hijklm', 'nopqr', 'stuv' }) + n.exec([[ + set diffopt+=linematch:60 + windo diffthis | wincmd t + call feedkeys("Aq\<esc>") + call feedkeys("GAklm\<esc>") + call feedkeys("o") + ]]) + screen:expect([[ + {7: }{22:abcdq }│{7: }{23:----------------------}│{7: }{23:-----------------------}| + {7: }{4:def }│{7: }{4:def}{27:q}{4: }│{7: }{23:-----------------------}| + {7: }{4:hijk}{27:lm}{4: }│{7: }{4:hijk }│{7: }{4:hijk}{27:lm}{4: }| + {7: }{23:----------------------}│{7: }{4:nopq }│{7: }{4:nopq}{27:r}{4: }| + {7: }{4:^ }│{7: }{23:----------------------}│{7: }{27:stuv}{4: }| + {1:~ }│{1:~ }│{1:~ }|*13 + {3:[No Name] [+] }{2:[No Name] [+] [No Name] [+] }| + {5:-- INSERT --} | + ]]) + end) end) diff --git a/test/functional/ui/messages_spec.lua b/test/functional/ui/messages_spec.lua index 734877d262..5c55dfe910 100644 --- a/test/functional/ui/messages_spec.lua +++ b/test/functional/ui/messages_spec.lua @@ -42,104 +42,130 @@ describe('ui/ext_messages', function() it('msg_clear follows msg_show kind of confirm', function() feed('iline 1<esc>') feed(':call confirm("test")<cr>') - screen:expect { + screen:expect({ grid = [[ - line ^1 | - {1:~ }|*4 - ]], + line ^1 | + {1:~ }|*4 + ]], + cmdline = { + { + content = { { '' } }, + hl_id = 10, + pos = 0, + prompt = '[O]k: ', + }, + }, messages = { { - content = { { '\ntest\n[O]k: ', 6, 11 } }, + content = { { '\ntest\n', 6, 10 } }, + history = false, kind = 'confirm', }, }, - } - + }) feed('<cr>') - screen:expect { + screen:expect({ grid = [[ - line ^1 | - {1:~ }|*4 - ]], - } + line ^1 | + {1:~ }|*4 + ]], + cmdline = { { abort = false } }, + }) end) - it('msg_show kind=confirm,confirm_sub,emsg,wmsg,quickfix', function() + it('msg_show kinds', function() feed('iline 1\nline 2<esc>') - -- kind=confirm + -- confirm is now cmdline prompt feed(':echo confirm("test")<cr>') - screen:expect { + screen:expect({ grid = [[ - line 1 | - line ^2 | - {1:~ }|*3 - ]], + line 1 | + line ^2 | + {1:~ }|*3 + ]], + cmdline = { + { + content = { { '' } }, + hl_id = 10, + pos = 0, + prompt = '[O]k: ', + }, + }, messages = { { - content = { { '\ntest\n[O]k: ', 6, 11 } }, + content = { { '\ntest\n', 6, 10 } }, + history = false, kind = 'confirm', }, }, - } - feed('<cr><cr>') - screen:expect { + }) + feed('<cr>') + screen:expect({ grid = [[ - line 1 | - line ^2 | - {1:~ }|*3 - ]], + line 1 | + line ^2 | + {1:~ }|*3 + ]], + cmdline = { { abort = false } }, messages = { { - content = { { '\ntest\n[O]k: ', 6, 11 } }, + content = { { '\ntest\n', 6, 10 } }, + history = false, kind = 'confirm', }, { content = { { '1' } }, + history = false, kind = 'echo', }, { - content = { { 'Press ENTER or type command to continue', 6, 19 } }, + content = { { 'Press ENTER or type command to continue', 6, 18 } }, + history = false, kind = 'return_prompt', }, }, - } - feed('<cr><cr>') + }) + feed('<cr>') - -- kind=confirm_sub + -- :substitute confirm is now cmdline prompt feed(':%s/i/X/gc<cr>') - screen:expect { + screen:expect({ grid = [[ - l{2:i}ne 1 | - l{10:i}ne ^2 | - {1:~ }|*3 - ]], - messages = { + l{2:^i}ne 1 | + l{10:i}ne 2 | + {1:~ }|*3 + ]], + cmdline = { { - content = { { 'replace with X (y/n/a/q/l/^E/^Y)?', 6, 19 } }, - kind = 'confirm_sub', + content = { { '' } }, + hl_id = 18, + pos = 0, + prompt = 'replace with X? (y)es/(n)o/(a)ll/(q)uit/(l)ast/scroll up(^E)/down(^Y)', }, }, - } + }) feed('nq') -- kind=wmsg (editing readonly file) command('write ' .. fname) command('set readonly nohls') feed('G$x') - screen:expect { + screen:expect({ grid = [[ line 1 | - line ^2 | + line^ | {1:~ }|*3 ]], + cmdline = { { abort = false } }, messages = { { - content = { { 'W10: Warning: Changing a readonly file', 19, 27 } }, + content = { { 'W10: Warning: Changing a readonly file', 19, 26 } }, + history = true, kind = 'wmsg', }, }, - } + }) -- kind=wmsg ('wrapscan' after search reaches EOF) feed('uG$/i<cr>') @@ -149,9 +175,11 @@ describe('ui/ext_messages', function() line 2 | {1:~ }|*3 ]], + cmdline = { { abort = false } }, messages = { { - content = { { 'search hit BOTTOM, continuing at TOP', 19, 27 } }, + content = { { 'search hit BOTTOM, continuing at TOP', 19, 26 } }, + history = true, kind = 'wmsg', }, }, @@ -160,22 +188,21 @@ describe('ui/ext_messages', function() -- kind=emsg after :throw feed(':throw "foo"<cr>') screen:expect { - grid = [[ - l^ine 1 | - line 2 | - {1:~ }|*3 - ]], + cmdline = { { abort = false } }, messages = { { - content = { { 'Error detected while processing :', 9, 7 } }, + content = { { 'Error detected while processing :', 9, 6 } }, + history = true, kind = 'emsg', }, { - content = { { 'E605: Exception not caught: foo', 9, 7 } }, - kind = '', + content = { { 'E605: Exception not caught: foo', 9, 6 } }, + history = true, + kind = 'emsg', }, { - content = { { 'Press ENTER or type command to continue', 6, 19 } }, + content = { { 'Press ENTER or type command to continue', 6, 18 } }, + history = false, kind = 'return_prompt', }, }, @@ -191,13 +218,213 @@ describe('ui/ext_messages', function() ^line 2 | {1:~ }|*3 ]], + cmdline = { { abort = false } }, messages = { { content = { { '(2 of 2): line2' } }, + history = true, kind = 'quickfix', }, }, } + + -- search_cmd + feed('?line<cr>') + screen:expect({ + grid = [[ + ^line 1 | + line 2 | + {1:~ }|*3 + ]], + cmdline = { { abort = false } }, + messages = { + { + content = { { '?line ' } }, + history = false, + kind = 'search_cmd', + }, + }, + }) + + -- highlight + feed(':filter character highlight<CR>') + screen:expect({ + cmdline = { { abort = false } }, + messages = { + { + content = { + { '\n@character ' }, + { 'xxx', 26, 155 }, + { ' ' }, + { 'links to', 18, 5 }, + { ' Character\n@character.special ' }, + { 'xxx', 16, 156 }, + { ' ' }, + { 'links to', 18, 5 }, + { ' SpecialChar' }, + }, + history = false, + kind = 'list_cmd', + }, + }, + }) + + -- undo + feed('uu') + screen:expect({ + grid = [[ + ^ | + {1:~ }|*4 + ]], + messages = { + { + content = { { 'Already at oldest change' } }, + history = true, + kind = 'undo', + }, + }, + }) + + feed('<C-r><C-r><C-r>') + screen:expect({ + grid = [[ + line 1 | + line^ | + {1:~ }|*3 + ]], + messages = { + { + content = { { 'Already at newest change' } }, + history = true, + kind = 'undo', + }, + }, + }) + + -- kind=completion + command('set noshowmode') + feed('i<C-n>') + screen:expect({ + messages = { + { + content = { { 'The only match' } }, + history = false, + kind = 'completion', + }, + }, + }) + feed('<Esc>') + command('set showmode') + + -- kind=echoerr for nvim_echo() err + feed(':call nvim_echo([["Error"], ["Message", "Special"]], 1, #{ err:1 })<CR>') + screen:expect({ + cmdline = { { abort = false } }, + messages = { + { + content = { { 'Error', 9, 6 }, { 'Message', 16, 99 } }, + history = true, + kind = 'echoerr', + }, + }, + }) + + -- kind=verbose for nvim_echo() verbose + feed(':call nvim_echo([["Verbose Message"]], 1, #{ verbose:1 })<CR>') + screen:expect({ + cmdline = { { abort = false } }, + messages = { + { + content = { { 'Verbose Message' } }, + history = true, + kind = 'verbose', + }, + }, + }) + + -- kind=verbose for :verbose messages + feed(':1verbose filter Diff[AC] hi<CR>') + screen:expect({ + cmdline = { { abort = false } }, + messages = { + { + content = { + { '\nDiffAdd ' }, + { 'xxx', 22, 30 }, + { ' ' }, + { 'ctermbg=', 18, 5 }, + { '81 ' }, + { 'guibg=', 18, 5 }, + { 'LightBlue' }, + }, + history = false, + kind = 'list_cmd', + }, + { + content = { { '\n\tLast set from Lua (run Nvim with -V1 for more details)' } }, + history = false, + kind = 'verbose', + }, + { + content = { + { '\nDiffChange ' }, + { 'xxx', 4, 31 }, + { ' ' }, + { 'ctermbg=', 18, 5 }, + { '225 ' }, + { 'guibg=', 18, 5 }, + { 'LightMagenta' }, + }, + history = false, + kind = 'list_cmd', + }, + { + content = { { '\n\tLast set from Lua (run Nvim with -V1 for more details)' } }, + history = false, + kind = 'verbose', + }, + { + content = { { 'Press ENTER or type command to continue', 6, 18 } }, + history = false, + kind = 'return_prompt', + }, + }, + }) + + -- kind=shell for :!cmd messages + local cmd = t.is_os('win') and 'echo stdout& echo stderr>&2& exit 3' + or '{ echo stdout; echo stderr >&2; exit 3; }' + feed(('<CR>:!%s<CR>'):format(cmd)) + screen:expect({ + cmdline = { { abort = false } }, + messages = { + { + content = { { (':!%s\r\n[No write since last change]\n'):format(cmd) } }, + history = false, + kind = '', + }, + { + content = { { ('stdout%s\n'):format(t.is_os('win') and '\r' or '') } }, + history = false, + kind = 'shell_out', + }, + { + content = { { ('stderr%s\n'):format(t.is_os('win') and '\r' or ''), 9, 6 } }, + history = false, + kind = 'shell_err', + }, + { + content = { { '\nshell returned 3\n\n' } }, + history = false, + kind = 'shell_ret', + }, + { + content = { { 'Press ENTER or type command to continue', 6, 18 } }, + history = false, + kind = 'return_prompt', + }, + }, + }) end) it(':echoerr', function() @@ -207,10 +434,14 @@ describe('ui/ext_messages', function() ^ | {1:~ }|*4 ]], - messages = { { - content = { { 'raa', 9, 7 } }, - kind = 'echoerr', - } }, + cmdline = { { abort = false } }, + messages = { + { + content = { { 'raa', 9, 6 } }, + history = true, + kind = 'echoerr', + }, + }, } -- cmdline in a later input cycle clears error message @@ -233,17 +464,21 @@ describe('ui/ext_messages', function() ^ | {1:~ }|*4 ]], + cmdline = { { abort = false } }, messages = { { - content = { { 'bork', 9, 7 } }, + content = { { 'bork', 9, 6 } }, + history = true, kind = 'echoerr', }, { - content = { { 'fail', 9, 7 } }, + content = { { 'fail', 9, 6 } }, + history = true, kind = 'echoerr', }, { - content = { { 'Press ENTER or type command to continue', 6, 19 } }, + content = { { 'Press ENTER or type command to continue', 6, 18 } }, + history = false, kind = 'return_prompt', }, }, @@ -255,21 +490,26 @@ describe('ui/ext_messages', function() ^ | {1:~ }|*4 ]], + cmdline = { { abort = false } }, messages = { { - content = { { 'bork', 9, 7 } }, + content = { { 'bork', 9, 6 } }, + history = true, kind = 'echoerr', }, { - content = { { 'fail', 9, 7 } }, + content = { { 'fail', 9, 6 } }, + history = true, kind = 'echoerr', }, { - content = { { 'extrafail', 9, 7 } }, + content = { { 'extrafail', 9, 6 } }, + history = true, kind = 'echoerr', }, { - content = { { 'Press ENTER or type command to continue', 6, 19 } }, + content = { { 'Press ENTER or type command to continue', 6, 18 } }, + history = false, kind = 'return_prompt', }, }, @@ -290,13 +530,17 @@ describe('ui/ext_messages', function() ^ | {1:~ }|*4 ]], - messages = { { - content = { { 'problem', 9, 7 } }, - kind = 'echoerr', - } }, + messages = { + { + content = { { 'problem', 9, 6 } }, + history = true, + kind = 'echoerr', + }, + }, cmdline = { { prompt = 'foo> ', + hl_id = 0, content = { { '' } }, pos = 0, }, @@ -309,6 +553,7 @@ describe('ui/ext_messages', function() ^ | {1:~ }|*4 ]], + cmdline = { { abort = false } }, } eq('solution', eval('x')) @@ -318,16 +563,18 @@ describe('ui/ext_messages', function() ^ | {1:~ }|*4 ]], + cmdline = { { abort = false } }, msg_history = { - { kind = 'echoerr', content = { { 'raa', 9, 7 } } }, - { kind = 'echoerr', content = { { 'bork', 9, 7 } } }, - { kind = 'echoerr', content = { { 'fail', 9, 7 } } }, - { kind = 'echoerr', content = { { 'extrafail', 9, 7 } } }, - { kind = 'echoerr', content = { { 'problem', 9, 7 } } }, + { kind = 'echoerr', content = { { 'raa', 9, 6 } } }, + { kind = 'echoerr', content = { { 'bork', 9, 6 } } }, + { kind = 'echoerr', content = { { 'fail', 9, 6 } } }, + { kind = 'echoerr', content = { { 'extrafail', 9, 6 } } }, + { kind = 'echoerr', content = { { 'problem', 9, 6 } } }, }, messages = { { - content = { { 'Press ENTER or type command to continue', 6, 19 } }, + content = { { 'Press ENTER or type command to continue', 6, 18 } }, + history = false, kind = 'return_prompt', }, }, @@ -350,9 +597,11 @@ describe('ui/ext_messages', function() ^ | {1:~ }|*4 ]], + cmdline = { { abort = false } }, messages = { { - content = { { 'bork\nfail', 9, 7 } }, + content = { { 'bork\nfail', 9, 6 } }, + history = true, kind = 'echoerr', }, }, @@ -364,15 +613,17 @@ describe('ui/ext_messages', function() ^ | {1:~ }|*4 ]], + cmdline = { { abort = false } }, messages = { { - content = { { 'Press ENTER or type command to continue', 6, 19 } }, + content = { { 'Press ENTER or type command to continue', 6, 18 } }, + history = false, kind = 'return_prompt', }, }, msg_history = { { - content = { { 'bork\nfail', 9, 7 } }, + content = { { 'bork\nfail', 9, 6 } }, kind = 'echoerr', }, }, @@ -390,8 +641,9 @@ describe('ui/ext_messages', function() {10:line} 2 | {1:~ }|*3 ]], + cmdline = { { abort = false } }, messages = { - { content = { { '/line W [1/2]' } }, kind = 'search_count' }, + { content = { { '/line W [1/2]' } }, kind = 'search_count', history = false }, }, } @@ -403,35 +655,7 @@ describe('ui/ext_messages', function() {1:~ }|*3 ]], messages = { - { content = { { '/line [2/2]' } }, kind = 'search_count' }, - }, - } - end) - - it(':hi Group output', function() - feed(':hi ErrorMsg<cr>') - screen:expect { - grid = [[ - ^ | - {1:~ }|*4 - ]], - messages = { - { - content = { - { '\nErrorMsg ' }, - { 'xxx', 9, 7 }, - { ' ' }, - { 'ctermfg=', 18, 6 }, - { '15 ' }, - { 'ctermbg=', 18, 6 }, - { '1 ' }, - { 'guifg=', 18, 6 }, - { 'White ' }, - { 'guibg=', 18, 6 }, - { 'Red' }, - }, - kind = '', - }, + { content = { { '/line [2/2]' } }, kind = 'search_count', history = false }, }, } end) @@ -444,11 +668,13 @@ describe('ui/ext_messages', function() ^ | {1:~ }|*4 ]], + cmdline = { { abort = false } }, messages = { - { content = { { 'x #1' } }, kind = '' }, - { content = { { 'y #2' } }, kind = '' }, + { content = { { 'x #1' } }, kind = 'list_cmd', history = false }, + { content = { { 'y #2' } }, kind = 'list_cmd', history = false }, { - content = { { 'Press ENTER or type command to continue', 6, 19 } }, + content = { { 'Press ENTER or type command to continue', 6, 18 } }, + history = false, kind = 'return_prompt', }, }, @@ -463,7 +689,7 @@ describe('ui/ext_messages', function() ^ | {1:~ }|*4 ]], - showmode = { { '-- INSERT --', 5, 12 } }, + showmode = { { '-- INSERT --', 5, 11 } }, } feed('alphpabet<cr>alphanum<cr>') @@ -474,7 +700,7 @@ describe('ui/ext_messages', function() ^ | {1:~ }|*2 ]], - showmode = { { '-- INSERT --', 5, 12 } }, + showmode = { { '-- INSERT --', 5, 11 } }, } feed('<c-x>') @@ -485,7 +711,7 @@ describe('ui/ext_messages', function() ^ | {1:~ }|*2 ]], - showmode = { { '-- ^X mode (^]^D^E^F^I^K^L^N^O^Ps^U^V^Y)', 5, 12 } }, + showmode = { { '-- ^X mode (^]^D^E^F^I^K^L^N^O^Ps^U^V^Y)', 5, 11 } }, } feed('<c-p>') @@ -501,7 +727,7 @@ describe('ui/ext_messages', function() items = { { 'alphpabet', '', '', '' }, { 'alphanum', '', '', '' } }, pos = 1, }, - showmode = { { '-- Keyword Local completion (^N^P) ', 5, 12 }, { 'match 1 of 2', 6, 19 } }, + showmode = { { '-- Keyword Local completion (^N^P) ', 5, 11 }, { 'match 1 of 2', 6, 18 } }, } -- echomsg and showmode don't overwrite each other, this is the same @@ -519,11 +745,14 @@ describe('ui/ext_messages', function() items = { { 'alphpabet', '', '', '' }, { 'alphanum', '', '', '' } }, pos = 1, }, - messages = { { - content = { { 'stuff' } }, - kind = 'echomsg', - } }, - showmode = { { '-- Keyword Local completion (^N^P) ', 5, 12 }, { 'match 1 of 2', 6, 19 } }, + messages = { + { + content = { { 'stuff' } }, + history = true, + kind = 'echomsg', + }, + }, + showmode = { { '-- Keyword Local completion (^N^P) ', 5, 11 }, { 'match 1 of 2', 6, 18 } }, } feed('<c-p>') @@ -539,11 +768,14 @@ describe('ui/ext_messages', function() items = { { 'alphpabet', '', '', '' }, { 'alphanum', '', '', '' } }, pos = 0, }, - messages = { { - content = { { 'stuff' } }, - kind = 'echomsg', - } }, - showmode = { { '-- Keyword Local completion (^N^P) ', 5, 12 }, { 'match 2 of 2', 6, 19 } }, + messages = { + { + content = { { 'stuff' } }, + history = true, + kind = 'echomsg', + }, + }, + showmode = { { '-- Keyword Local completion (^N^P) ', 5, 11 }, { 'match 2 of 2', 6, 18 } }, } feed('<esc>:messages<cr>') @@ -554,13 +786,15 @@ describe('ui/ext_messages', function() alphpabe^t | {1:~ }|*2 ]], + cmdline = { { abort = false } }, msg_history = { { content = { { 'stuff' } }, kind = 'echomsg', } }, messages = { { - content = { { 'Press ENTER or type command to continue', 6, 19 } }, + content = { { 'Press ENTER or type command to continue', 6, 18 } }, + history = false, kind = 'return_prompt', }, }, @@ -574,7 +808,7 @@ describe('ui/ext_messages', function() ^ | {1:~ }|*4 ]], - showmode = { { 'recording @q', 5, 12 } }, + showmode = { { 'recording @q', 5, 11 } }, } feed('i') @@ -583,7 +817,7 @@ describe('ui/ext_messages', function() ^ | {1:~ }|*4 ]], - showmode = { { '-- INSERT --recording @q', 5, 12 } }, + showmode = { { '-- INSERT --recording @q', 5, 11 } }, } feed('<esc>') @@ -592,7 +826,7 @@ describe('ui/ext_messages', function() ^ | {1:~ }|*4 ]], - showmode = { { 'recording @q', 5, 12 } }, + showmode = { { 'recording @q', 5, 11 } }, } feed('q') @@ -611,7 +845,7 @@ describe('ui/ext_messages', function() ^ | {1:~ }|*4 ]], - showmode = { { 'recording @q', 5, 12 } }, + showmode = { { 'recording @q', 5, 11 } }, mode = 'normal', } @@ -621,7 +855,7 @@ describe('ui/ext_messages', function() ^ | {1:~ }|*4 ]], - showmode = { { 'recording @q', 5, 12 } }, + showmode = { { 'recording @q', 5, 11 } }, mode = 'insert', } @@ -631,7 +865,7 @@ describe('ui/ext_messages', function() ^ | {1:~ }|*4 ]], - showmode = { { 'recording @q', 5, 12 } }, + showmode = { { 'recording @q', 5, 11 } }, mode = 'normal', } @@ -653,7 +887,7 @@ describe('ui/ext_messages', function() ^ | {1:~ }|*4 ]], - ruler = { { '0,0-1 All', 9, 62 } }, + ruler = { { '0,0-1 All', 9, 61 } }, }) command('hi clear MsgArea') feed('i') @@ -662,7 +896,7 @@ describe('ui/ext_messages', function() ^ | {1:~ }|*4 ]], - showmode = { { '-- INSERT --', 5, 12 } }, + showmode = { { '-- INSERT --', 5, 11 } }, ruler = { { '0,1 All' } }, } feed('abcde<cr>12345<esc>') @@ -700,7 +934,7 @@ describe('ui/ext_messages', function() {17:123}45 | {1:~ }|*3 ]], - showmode = { { '-- VISUAL BLOCK --', 5, 12 } }, + showmode = { { '-- VISUAL BLOCK --', 5, 11 } }, showcmd = { { '2x3' } }, ruler = { { '1,3 All' } }, }) @@ -752,10 +986,14 @@ describe('ui/ext_messages', function() ^ | {1:~ }|*4 ]], - messages = { { - content = { { 'howdy' } }, - kind = 'echomsg', - } }, + cmdline = { { abort = false } }, + messages = { + { + content = { { 'howdy' } }, + history = true, + kind = 'echomsg', + }, + }, } -- always test a message without kind. If this one gets promoted to a @@ -769,6 +1007,7 @@ describe('ui/ext_messages', function() messages = { { content = { { 'Type :qa and press <Enter> to exit Nvim' } }, + history = true, kind = '', }, }, @@ -780,10 +1019,14 @@ describe('ui/ext_messages', function() ^ | {1:~ }|*4 ]], - messages = { { - content = { { 'bork', 9, 7 } }, - kind = 'echoerr', - } }, + cmdline = { { abort = false } }, + messages = { + { + content = { { 'bork', 9, 6 } }, + history = true, + kind = 'echoerr', + }, + }, } feed(':echo "xyz"<cr>') @@ -792,10 +1035,14 @@ describe('ui/ext_messages', function() ^ | {1:~ }|*4 ]], - messages = { { - content = { { 'xyz' } }, - kind = 'echo', - } }, + cmdline = { { abort = false } }, + messages = { + { + content = { { 'xyz' } }, + history = false, + kind = 'echo', + }, + }, } feed(':call nosuchfunction()<cr>') @@ -804,9 +1051,11 @@ describe('ui/ext_messages', function() ^ | {1:~ }|*4 ]], + cmdline = { { abort = false } }, messages = { { - content = { { 'E117: Unknown function: nosuchfunction', 9, 7 } }, + content = { { 'E117: Unknown function: nosuchfunction', 9, 6 } }, + history = true, kind = 'emsg', }, }, @@ -818,15 +1067,17 @@ describe('ui/ext_messages', function() ^ | {1:~ }|*4 ]], + cmdline = { { abort = false } }, msg_history = { { kind = 'echomsg', content = { { 'howdy' } } }, { kind = '', content = { { 'Type :qa and press <Enter> to exit Nvim' } } }, - { kind = 'echoerr', content = { { 'bork', 9, 7 } } }, - { kind = 'emsg', content = { { 'E117: Unknown function: nosuchfunction', 9, 7 } } }, + { kind = 'echoerr', content = { { 'bork', 9, 6 } } }, + { kind = 'emsg', content = { { 'E117: Unknown function: nosuchfunction', 9, 6 } } }, }, messages = { { - content = { { 'Press ENTER or type command to continue', 6, 19 } }, + content = { { 'Press ENTER or type command to continue', 6, 18 } }, + history = false, kind = 'return_prompt', }, }, @@ -851,11 +1102,14 @@ describe('ui/ext_messages', function() } feed('<cr>') - screen:expect([[ - ^ | - {1:~ }|*3 - | - ]]) + screen:expect({ + grid = [[ + ^ | + {1:~ }|*3 + | + ]], + cmdline = { { abort = false } }, + }) eq(1, eval('&cmdheight')) feed(':set cmdheight=0') @@ -874,10 +1128,13 @@ describe('ui/ext_messages', function() }, } feed('<cr>') - screen:expect([[ - ^ | - {1:~ }|*4 - ]]) + screen:expect({ + grid = [[ + ^ | + {1:~ }|*4 + ]], + cmdline = { { abort = false } }, + }) eq(0, eval('&cmdheight')) end) @@ -888,6 +1145,7 @@ describe('ui/ext_messages', function() ^ | {1:~ }|*4 ]], + cmdline = { { abort = false } }, messages = { { content = { @@ -899,9 +1157,10 @@ stack traceback: [C]: in function 'error' [string ":lua"]:1: in main chunk]], 9, - 7, + 6, }, }, + history = true, kind = 'lua_error', }, }, @@ -916,11 +1175,13 @@ stack traceback: ^ | {1:~ }|*4 ]], + cmdline = { { abort = false } }, messages = { { content = { - { "Error invoking 'test_method' on channel 1:\ncomplete\nerror\n\nmessage", 9, 7 }, + { "Error invoking 'test_method' on channel 1:\ncomplete\nerror\n\nmessage", 9, 6 }, }, + history = true, kind = 'rpc_error', }, }, @@ -940,6 +1201,7 @@ stack traceback: feed(':map<cr>') screen:expect { + cmdline = { { abort = false } }, messages = { { content = { @@ -947,7 +1209,8 @@ stack traceback: { '*', 18, 1 }, { ' k' }, }, - kind = '', + history = false, + kind = 'list_cmd', }, }, } @@ -964,10 +1227,13 @@ stack traceback: ^ | {1:~ }|*6 ]], - messages = { { - content = { { 'wildmenu wildmode' } }, - kind = '', - } }, + messages = { + { + content = { { 'wildmenu wildmode\n' } }, + history = false, + kind = 'wildlist', + }, + }, cmdline = { { firstc = ':', @@ -983,51 +1249,94 @@ stack traceback: feed('ihelllo<esc>') feed('z=') - screen:expect { + screen:expect({ grid = [[ - {100:helllo} | - {1:~ }|*3 - {1:^~ }| - ]], + {100:^helllo} | + {1:~ }|*4 + ]], + cmdline = { + { + content = { { '' } }, + hl_id = 0, + pos = 0, + prompt = 'Type number and <Enter> or click with the mouse (q or empty cancels): ', + }, + }, messages = { { - content = { - { - 'Change "helllo" to:\n 1 "Hello"\n 2 "Hallo"\n 3 "Hullo"\nType number and <Enter> or click with the mouse (q or empty cancels): ', - }, - }, - kind = '', + content = { { 'Change "helllo" to:\n 1 "Hello"\n 2 "Hallo"\n 3 "Hullo"\n' } }, + history = false, + kind = 'list_cmd', }, }, - } + }) feed('1') - screen:expect { + screen:expect({ grid = [[ - {100:helllo} | - {1:~ }|*3 - {1:^~ }| - ]], + {100:^helllo} | + {1:~ }|*4 + ]], + cmdline = { + { + content = { { '1' } }, + hl_id = 0, + pos = 1, + prompt = 'Type number and <Enter> or click with the mouse (q or empty cancels): ', + }, + }, messages = { { - content = { - { - 'Change "helllo" to:\n 1 "Hello"\n 2 "Hallo"\n 3 "Hullo"\nType number and <Enter> or click with the mouse (q or empty cancels): ', - }, - }, - kind = '', + content = { { 'Change "helllo" to:\n 1 "Hello"\n 2 "Hallo"\n 3 "Hullo"\n' } }, + history = false, + kind = 'list_cmd', }, - { content = { { '1' } }, kind = '' }, }, - } + }) feed('<cr>') - screen:expect { + screen:expect({ grid = [[ - ^Hello | - {1:~ }|*4 - ]], - } + ^Hello | + {1:~ }|*4 + ]], + cmdline = { { abort = false } }, + }) + + async_meths.nvim_command("let g:n = inputlist(['input0', 'input1'])") + screen:expect({ + grid = [[ + ^Hello | + {1:~ }|*4 + ]], + cmdline = { + { + content = { { '' } }, + hl_id = 0, + pos = 0, + prompt = 'Type number and <Enter> or click with the mouse (q or empty cancels): ', + }, + }, + messages = { + { + content = { { 'input0\ninput1\n' } }, + history = false, + kind = 'list_cmd', + }, + }, + }) + + feed('42<CR>') + screen:expect({ + grid = [[ + ^Hello | + {1:~ }|*4 + ]], + cmdline = { { + abort = false, + } }, + }) + eq(42, eval('g:n')) end) it('supports nvim_echo messages with multiple attrs', function() @@ -1043,7 +1352,8 @@ stack traceback: ]], messages = { { - content = { { 'wow, ', 10, 9 }, { 'such\n\nvery ', 9, 7 }, { 'color', 8, 13 } }, + content = { { 'wow, ', 10, 8 }, { 'such\n\nvery ', 9, 6 }, { 'color', 8, 12 } }, + history = true, kind = 'echomsg', }, }, @@ -1055,8 +1365,13 @@ stack traceback: ^ | {1:~ }|*4 ]], + cmdline = { { abort = false } }, messages = { - { content = { { '\n 1 %a "[No Name]" line 1' } }, kind = '' }, + { + content = { { '\n 1 %a "[No Name]" line 1' } }, + kind = 'list_cmd', + history = false, + }, }, } @@ -1066,15 +1381,17 @@ stack traceback: ^ | {1:~ }|*4 ]], + cmdline = { { abort = false } }, messages = { { - content = { { 'Press ENTER or type command to continue', 6, 19 } }, + content = { { 'Press ENTER or type command to continue', 6, 18 } }, + history = false, kind = 'return_prompt', }, }, msg_history = { { - content = { { 'wow, ', 10, 9 }, { 'such\n\nvery ', 9, 7 }, { 'color', 8, 13 } }, + content = { { 'wow, ', 10, 8 }, { 'such\n\nvery ', 9, 6 }, { 'color', 8, 12 } }, kind = 'echomsg', }, }, @@ -1093,7 +1410,11 @@ stack traceback: command('write ' .. fname) screen:expect({ messages = { - { content = { { string.format('"%s" [New] 0L, 0B written', fname) } }, kind = '' }, + { + content = { { string.format('"%s" [New] 0L, 0B written', fname) } }, + kind = 'bufwrite', + history = true, + }, }, }) end) @@ -1105,13 +1426,25 @@ stack traceback: screen_showmode(...) showmode = showmode + 1 end + local s1 = [[ + ^ | + {1:~ }|*4 + ]] + screen:expect(s1) + eq(showmode, 0) + feed('i') screen:expect({ - grid = [[ - ^ | - {1:~ }|*4 - ]], + grid = s1, + showmode = { { '-- INSERT --', 5, 11 } }, }) - eq(showmode, 1) + eq(showmode, 2) + command('set noshowmode') + feed('<Esc>') + screen:expect(s1) + eq(showmode, 3) + feed('i') + screen:expect_unchanged() + eq(showmode, 3) end) it('emits single message for multiline print())', function() @@ -1120,6 +1453,7 @@ stack traceback: messages = { { content = { { 'foo\nbar\nbaz' } }, + history = true, kind = 'lua_print', }, }, @@ -1133,6 +1467,7 @@ stack traceback: messages = { { content = { { '{\n foo = "bar"\n}' } }, + history = true, kind = 'lua_print', }, }, @@ -1140,6 +1475,36 @@ stack traceback: exec_lua([[vim.print({ foo = "bar" })]]) screen:expect_unchanged() end) + + it('ruler redraw does not crash due to double grid_line_start()', function() + exec_lua([[ + local ns = vim.api.nvim_create_namespace('') + vim.ui_attach(ns, { ext_messages = true }, function(event, ...) + if event == 'msg_ruler' then + vim.api.nvim__redraw({ flush = true }) + end + end) + vim.o.ruler = true + vim.o.laststatus = 0 + ]]) + feed('i') + n.assert_alive() + end) + + it(':digraph contains newlines', function() + command('digraph') + screen:expect({ + condition = function() + local nl = 0 + eq('list_cmd', screen.messages[1].kind) + for _, chunk in ipairs(screen.messages[1].content) do + nl = nl + (chunk[2]:find('\n') and 1 or 0) + end + eq(682, nl) + screen.messages = {} + end, + }) + end) end) describe('ui/builtin messages', function() @@ -1719,7 +2084,7 @@ describe('ui/ext_messages', function() {1:~ }type :help iccf{18:<Enter>} for information {1: }| {1:~ }|*5 ]] - local showmode = { { '-- INSERT --', 5, 12 } } + local showmode = { { '-- INSERT --', 5, 11 } } screen:expect(introscreen) -- <c-l> (same as :mode) does _not_ clear intro message @@ -1792,9 +2157,11 @@ describe('ui/ext_messages', function() type :help iccf{18:<Enter>} for information | |*5 ]], + cmdline = { { abort = false } }, messages = { { - content = { { 'Press ENTER or type command to continue', 6, 19 } }, + content = { { 'Press ENTER or type command to continue', 6, 18 } }, + history = false, kind = 'return_prompt', }, }, @@ -1874,8 +2241,9 @@ describe('ui/ext_messages', function() {1:~ }|*10 {3:[No Name] }| ]], + cmdline = { { abort = false } }, messages = { - { content = { { ' cmdheight=0' } }, kind = '' }, + { content = { { ' cmdheight=0' } }, kind = 'list_cmd', history = false }, }, }) @@ -1890,8 +2258,9 @@ describe('ui/ext_messages', function() {1:~ }|*9 {3:[No Name] }| ]], + cmdline = { { abort = false } }, messages = { - { content = { { ' laststatus=3' } }, kind = '' }, + { content = { { ' laststatus=3' } }, kind = 'list_cmd', history = false }, }, }) @@ -1910,8 +2279,9 @@ describe('ui/ext_messages', function() {1:~ }|*10 {3:[No Name] }| ]], + cmdline = { { abort = false } }, messages = { - { content = { { ' cmdheight=0' } }, kind = '' }, + { content = { { ' cmdheight=0' } }, kind = 'list_cmd', history = false }, }, }) end) @@ -2015,7 +2385,7 @@ describe('ui/msg_puts_printf', function() ) cmd = cmd .. '"' .. nvim_prog .. '" -u NONE -i NONE -Es -V1' - command([[call termopen(']] .. cmd .. [[')]]) + command([[call jobstart(']] .. cmd .. [[',{'term':v:true})]]) screen:expect([[ ^Exモードに入ります。ノー | マルモードに戻るには "vis| diff --git a/test/functional/ui/mode_spec.lua b/test/functional/ui/mode_spec.lua index 8c6a284cd6..01f4dda227 100644 --- a/test/functional/ui/mode_spec.lua +++ b/test/functional/ui/mode_spec.lua @@ -94,6 +94,46 @@ describe('ui mode_change event', function() } end) + -- oldtest: Test_mouse_shape_indent_norm_with_gq() + it('is restored to Normal mode after "gq" indents using :normal #12309', function() + screen:try_resize(60, 6) + n.exec([[ + func Indent() + exe "normal! \<Ignore>" + return 0 + endfunc + + setlocal indentexpr=Indent() + call setline(1, [repeat('a', 80), repeat('b', 80)]) + ]]) + + feed('ggVG') + screen:expect { + grid = [[ + {17:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}| + {17:aaaaaaaaaaaaaaaaaaaa} | + ^b{17:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb}| + {17:bbbbbbbbbbbbbbbbbbbb} | + {1:~ }| + {5:-- VISUAL LINE --} | + ]], + mode = 'visual', + } + + feed('gq') + screen:expect { + grid = [[ + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + aaaaaaaaaaaaaaaaaaaa | + ^bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb| + bbbbbbbbbbbbbbbbbbbb | + {1:~ }| + | + ]], + mode = 'normal', + } + end) + it('works in insert mode', function() feed('i') screen:expect { diff --git a/test/functional/ui/multigrid_spec.lua b/test/functional/ui/multigrid_spec.lua index 3afda0c4af..cac7174cb6 100644 --- a/test/functional/ui/multigrid_spec.lua +++ b/test/functional/ui/multigrid_spec.lua @@ -1094,7 +1094,7 @@ describe('ext_multigrid', function() end) it('supports mouse', function() - command('autocmd! nvim_popupmenu') -- Delete the default MenuPopup event handler. + command('autocmd! nvim.popupmenu') -- Delete the default MenuPopup event handler. insert('some text\nto be clicked') screen:expect{grid=[[ ## grid 1 diff --git a/test/functional/ui/output_spec.lua b/test/functional/ui/output_spec.lua index b5a09d814c..37e0e1344b 100644 --- a/test/functional/ui/output_spec.lua +++ b/test/functional/ui/output_spec.lua @@ -34,7 +34,7 @@ describe('shell command :!', function() n.nvim_set .. ' notermguicolors', }) screen:expect([[ - {1: } | + ^ | {4:~ }|*4 | {3:-- TERMINAL --} | @@ -78,7 +78,7 @@ describe('shell command :!', function() 29999: foo | 30000: foo | | - {10:Press ENTER or type command to continue}{1: } | + {10:Press ENTER or type command to continue}^ | {3:-- TERMINAL --} | ]], { diff --git a/test/functional/ui/popupmenu_spec.lua b/test/functional/ui/popupmenu_spec.lua index 8fe8975b4a..4c5b1d2bd2 100644 --- a/test/functional/ui/popupmenu_spec.lua +++ b/test/functional/ui/popupmenu_spec.lua @@ -841,7 +841,7 @@ describe('ui/ext_popupmenu', function() aunmenu PopUp " Delete the default MenuPopup event handler. - autocmd! nvim_popupmenu + autocmd! nvim.popupmenu menu PopUp.foo :let g:menustr = 'foo'<CR> menu PopUp.bar :let g:menustr = 'bar'<CR> menu PopUp.baz :let g:menustr = 'baz'<CR> @@ -1162,6 +1162,8 @@ describe('builtin popupmenu', function() [6] = { foreground = Screen.colors.White, background = Screen.colors.Red }, [7] = { background = Screen.colors.Yellow }, -- Search [8] = { foreground = Screen.colors.Red }, + [9] = { foreground = Screen.colors.Yellow, background = Screen.colors.Green }, + [10] = { foreground = Screen.colors.White, background = Screen.colors.Green }, ks = { foreground = Screen.colors.Red, background = Screen.colors.Grey }, kn = { foreground = Screen.colors.Red, background = Screen.colors.Plum1 }, xs = { foreground = Screen.colors.Black, background = Screen.colors.Grey }, @@ -1542,6 +1544,81 @@ describe('builtin popupmenu', function() end) if not multigrid then + describe('popup and preview window do not overlap', function() + before_each(function() + screen:try_resize(53, 20) + end) + + -- oldtest: Test_popup_and_previewwindow_dump_pedit() + it('with :pedit', function() + exec([[ + set previewheight=9 + silent! pedit + call setline(1, map(repeat(["ab"], 10), "v:val .. v:key")) + exec "norm! G\<C-E>\<C-E>" + ]]) + feed('o') + n.poke_eventloop() + feed('<C-X><C-N>') + screen:expect([[ + ab0 | + ab1 | + ab2 | + ab3 | + ab4 | + ab5 | + ab6 | + ab7 | + ab8 | + {s:ab0 }{c: }{3:ew][+] }| + {n:ab1 }{c: } | + {n:ab2 }{c: } | + {n:ab3 }{c: } | + {n:ab4 }{s: } | + {n:ab5 }{s: } | + {n:ab6 }{s: } | + ab0^ | + {1:~ }| + {4:[No Name] [+] }| + {2:-- Keyword Local completion (^N^P) }{5:match 1 of 10} | + ]]) + end) + + -- oldtest: Test_popup_and_previewwindow_dump_pbuffer() + it('with :pbuffer', function() + exec([[ + set previewheight=9 + silent! pbuffer + call setline(1, map(repeat(["ab"], 10), "v:val .. v:key")) + exec "norm! G\<C-E>\<C-E>\<C-E>" + ]]) + feed('o') + n.poke_eventloop() + feed('<C-X><C-N>') + screen:expect([[ + ab0 | + ab1 | + ab2 | + ab3 | + ab4 | + ab5 | + ab6 | + ab7 | + ab8 | + {s:ab0 }{c: }{3:ew][+] }| + {n:ab1 }{c: } | + {n:ab2 }{c: } | + {n:ab3 }{s: } | + {n:ab4 }{s: } | + {n:ab5 }{s: } | + ab0^ | + {1:~ }|*2 + {4:[No Name] [+] }| + {2:-- Keyword Local completion (^N^P) }{5:match 1 of 10} | + ]]) + end) + end) + -- oldtest: Test_pum_with_preview_win() it('preview window opened during completion', function() exec([[ @@ -1603,7 +1680,7 @@ describe('builtin popupmenu', function() end) end - describe('floating window preview #popup', function() + describe('floating window preview popup', function() it('pum popup preview', function() --row must > 10 screen:try_resize(40, 11) @@ -1616,14 +1693,29 @@ describe('builtin popupmenu', function() endfunc set omnifunc=Omni_test set completeopt=menu,popup - funct Set_info() let comp_info = complete_info() if comp_info['selected'] == 2 call nvim__complete_set(comp_info['selected'], {"info": "3info"}) endif endfunc - autocmd CompleteChanged * call Set_info() + funct TsHl() + let comp_info = complete_info() + if get(comp_info, 'previewbufnr', 0) > 0 + call v:lua.vim.treesitter.start(comp_info['preview_bufnr'], 'markdown') + endif + if comp_info['selected'] == 0 + call nvim__complete_set(comp_info['selected'], {"info": "```lua\nfunction test()\n print('foo')\nend\n```"}) + endif + endfunc + augroup Group + au! + autocmd CompleteChanged * :call Set_info() + augroup END + funct TestTs() + autocmd! Group + autocmd CompleteChanged * call TsHl() + endfunc ]]) feed('Gi<C-x><C-o>') --floating preview in right @@ -1684,25 +1776,26 @@ describe('builtin popupmenu', function() } end - -- info window position should be adjusted when new leader add - feed('<C-P>o') + -- delete one character make the pum width smaller than before + -- info window position should be adjusted when popupmenu width changed + feed('<BS>') if multigrid then - screen:expect { + screen:expect({ grid = [[ - ## grid 1 - [2:----------------------------------------]|*10 - [3:----------------------------------------]| - ## grid 2 - o^ | - {1:~ }|*9 - ## grid 3 - {2:-- }{8:Back at original} | - ## grid 4 - {n:1info}| - {n: }| - ## grid 5 - {n:one }| - ]], + ## grid 1 + [2:----------------------------------------]|*10 + [3:----------------------------------------]| + ## grid 2 + on^ | + {1:~ }|*9 + ## grid 3 + {2:-- }{5:match 1 of 3} | + ## grid 4 + {n:1info}| + {n: }| + ## grid 5 + {s:one }| + ]], float_pos = { [5] = { -1, 'NW', 2, 1, 0, false, 100 }, [4] = { 1001, 'NW', 1, 1, 15, false, 50 }, @@ -1713,7 +1806,7 @@ describe('builtin popupmenu', function() topline = 0, botline = 2, curline = 0, - curcol = 1, + curcol = 2, linecount = 1, sum_scroll_delta = 0, }, @@ -1727,22 +1820,88 @@ describe('builtin popupmenu', function() sum_scroll_delta = 0, }, }, - } + win_viewport_margins = { + [2] = { + bottom = 0, + left = 0, + right = 0, + top = 0, + win = 1000, + }, + [4] = { + bottom = 0, + left = 0, + right = 0, + top = 0, + win = 1001, + }, + }, + }) else - screen:expect { + screen:expect({ grid = [[ - o^ | - {n:one 1info}{1: }| - {1:~ }{n: }{1: }| - {1:~ }|*7 - {2:-- }{8:Back at original} | - ]], - } + on^ | + {s:one }{n:1info}{1: }| + {1:~ }{n: }{1: }| + {1:~ }|*7 + {2:-- }{5:match 1 of 3} | + ]], + }) + end + + -- when back to original the preview float should be closed. + feed('<C-P>') + if multigrid then + screen:expect({ + grid = [[ + ## grid 1 + [2:----------------------------------------]|*10 + [3:----------------------------------------]| + ## grid 2 + on^ | + {1:~ }|*9 + ## grid 3 + {2:-- }{8:Back at original} | + ## grid 5 + {n:one }| + ]], + float_pos = { + [5] = { -1, 'NW', 2, 1, 0, false, 100 }, + }, + win_viewport = { + [2] = { + win = 1000, + topline = 0, + botline = 2, + curline = 0, + curcol = 2, + linecount = 1, + sum_scroll_delta = 0, + }, + }, + win_viewport_margins = { + [2] = { + bottom = 0, + left = 0, + right = 0, + top = 0, + win = 1000, + }, + }, + }) + else + screen:expect({ + grid = [[ + on^ | + {n:one }{1: }| + {1:~ }|*8 + {2:-- }{8:Back at original} | + ]], + }) end -- test nvim__complete_set_info - feed('<ESC>cc<C-X><C-O><C-N><C-N>') - vim.uv.sleep(10) + feed('<ESC>S<C-X><C-O><C-N><C-N>') if multigrid then screen:expect { grid = [[ @@ -1758,13 +1917,13 @@ describe('builtin popupmenu', function() {n:one }| {n:two }| {s:looooooooooooooong }| - ## grid 6 + ## grid 7 {n:3info}| {n: }| ]], float_pos = { [5] = { -1, 'NW', 2, 1, 0, false, 100 }, - [6] = { 1002, 'NW', 1, 1, 19, false, 50 }, + [7] = { 1003, 'NW', 1, 1, 19, false, 50 }, }, win_viewport = { [2] = { @@ -1776,8 +1935,8 @@ describe('builtin popupmenu', function() linecount = 1, sum_scroll_delta = 0, }, - [6] = { - win = 1002, + [7] = { + win = 1003, topline = 0, botline = 2, curline = 0, @@ -1819,12 +1978,12 @@ describe('builtin popupmenu', function() {s: one }| {n: two }| {n: looooooooooooooong }| - ## grid 7 + ## grid 8 {n:1info}| {n: }| ]], float_pos = { - [7] = { 1003, 'NW', 1, 1, 14, false, 50 }, + [8] = { 1004, 'NW', 1, 1, 14, false, 50 }, [5] = { -1, 'NW', 2, 1, 19, false, 100 }, }, win_viewport = { @@ -1837,8 +1996,8 @@ describe('builtin popupmenu', function() linecount = 1, sum_scroll_delta = 0, }, - [7] = { - win = 1003, + [8] = { + win = 1004, topline = 0, botline = 2, curline = 0, @@ -1860,6 +2019,90 @@ describe('builtin popupmenu', function() ]], } end + feed('<C-E><Esc>') + + -- works when scroll with treesitter highlight + command('call TestTs()') + feed('S<C-x><C-o>') + if multigrid then + screen:expect({ + grid = [[ + ## grid 1 + [2:----------------------------------------]|*10 + [3:----------------------------------------]| + ## grid 2 + one^ | + {1:~ }|*9 + ## grid 3 + {2:-- }{5:match 1 of 3} | + ## grid 5 + {s:one }| + {n:two }| + {n:looooooooooooooong }| + ## grid 9 + {n:```lua }| + {n:function test()}| + {n: print('foo') }| + {n:end }| + {n:``` }| + {n: }| + ]], + float_pos = { + [5] = { -1, 'NW', 2, 1, 0, false, 100 }, + [9] = { 1005, 'NW', 1, 1, 19, false, 50 }, + }, + win_viewport = { + [2] = { + win = 1000, + topline = 0, + botline = 2, + curline = 0, + curcol = 3, + linecount = 1, + sum_scroll_delta = 0, + }, + [9] = { + win = 1005, + topline = 0, + botline = 6, + curline = 0, + curcol = 0, + linecount = 5, + sum_scroll_delta = 0, + }, + }, + win_viewport_margins = { + [2] = { + bottom = 0, + left = 0, + right = 0, + top = 0, + win = 1000, + }, + [9] = { + bottom = 0, + left = 0, + right = 0, + top = 0, + win = 1005, + }, + }, + }) + else + screen:expect({ + grid = [[ + one^ | + {s:one }{n:```lua }{1: }| + {n:two function test()}{1: }| + {n:looooooooooooooong print('foo') }{1: }| + {1:~ }{n:end }{1: }| + {1:~ }{n:``` }{1: }| + {1:~ }{n: }{1: }| + {1:~ }|*3 + {2:-- }{5:match 1 of 3} | + ]], + }) + end end) end) @@ -3846,7 +4089,7 @@ describe('builtin popupmenu', function() set mouse=a mousemodel=popup " Delete the default MenuPopup event handler. - autocmd! nvim_popupmenu + autocmd! nvim.popupmenu aunmenu PopUp menu PopUp.foo :let g:menustr = 'foo'<CR> menu PopUp.bar :let g:menustr = 'bar'<CR> @@ -4703,7 +4946,7 @@ describe('builtin popupmenu', function() it(':popup command', function() exec([[ " Delete the default MenuPopup event handler. - autocmd! nvim_popupmenu + autocmd! nvim.popupmenu func ChangeMenu() aunmenu PopUp.&Paste @@ -4863,7 +5106,7 @@ describe('builtin popupmenu', function() exec([[ set mousemodel=popup_setpos " Delete the default MenuPopup event handler. - autocmd! nvim_popupmenu + autocmd! nvim.popupmenu aunmenu * source $VIMRUNTIME/menu.vim call setline(1, join(range(20))) @@ -5172,6 +5415,45 @@ describe('builtin popupmenu', function() feed('<C-E><Esc>') end) + -- oldtest: Test_pum_highlights_match_with_abbr() + it('can highlight matched text with abbr', function() + exec([[ + func Omni_test(findstart, base) + if a:findstart + return col(".") + endif + return { + \ 'words': [ + \ { 'word': 'foobar', 'abbr': "foobar\t\t!" }, + \ { 'word': 'foobaz', 'abbr': "foobaz\t\t!" }, + \]} + endfunc + + set omnifunc=Omni_test + set completeopt=menuone,noinsert + hi PmenuMatchSel guifg=Blue guibg=Grey + hi PmenuMatch guifg=Blue guibg=Plum1 + ]]) + feed('i<C-X><C-O>') + screen:expect([[ + ^ | + {s:foobar ! }{1: }| + {n:foobaz ! }{1: }| + {1:~ }|*16 + {2:-- }{5:match 1 of 2} | + ]]) + feed('foo') + screen:expect([[ + foo^ | + {ms:foo}{s:bar ! }{1: }| + {mn:foo}{n:baz ! }{1: }| + {1:~ }|*16 + {2:-- }{5:match 1 of 2} | + ]]) + + feed('<C-E><Esc>') + end) + -- oldtest: Test_pum_user_abbr_hlgroup() it('custom abbr_hlgroup override', function() exec([[ @@ -5419,6 +5701,240 @@ describe('builtin popupmenu', function() ]]) feed('<C-E><ESC>') end) + + -- oldtest: Test_pum_matchins_highlight() + it('with ComplMatchIns highlight', function() + exec([[ + let g:change = 0 + func Omni_test(findstart, base) + if a:findstart + return col(".") + endif + if g:change == 0 + return [#{word: "foo"}, #{word: "bar"}, #{word: "你好"}] + endif + return [#{word: "foo", info: "info"}, #{word: "bar"}, #{word: "你好"}] + endfunc + set omnifunc=Omni_test + hi ComplMatchIns guifg=red + ]]) + + feed('Sαβγ <C-X><C-O>') + screen:expect([[ + αβγ {8:foo}^ | + {1:~ }{s: foo }{1: }| + {1:~ }{n: bar }{1: }| + {1:~ }{n: 你好 }{1: }| + {1:~ }|*15 + {2:-- }{5:match 1 of 3} | + ]]) + feed('<C-E><Esc>') + + feed('Sαβγ <C-X><C-O><C-N>') + screen:expect([[ + αβγ {8:bar}^ | + {1:~ }{n: foo }{1: }| + {1:~ }{s: bar }{1: }| + {1:~ }{n: 你好 }{1: }| + {1:~ }|*15 + {2:-- }{5:match 2 of 3} | + ]]) + feed('<C-E><Esc>') + + feed('Sαβγ <C-X><C-O><C-N><C-N>') + screen:expect([[ + αβγ {8:你好}^ | + {1:~ }{n: foo }{1: }| + {1:~ }{n: bar }{1: }| + {1:~ }{s: 你好 }{1: }| + {1:~ }|*15 + {2:-- }{5:match 3 of 3} | + ]]) + feed('<C-E><Esc>') + + -- restore after accept + feed('Sαβγ <C-X><C-O><C-Y>') + screen:expect([[ + αβγ foo^ | + {1:~ }|*18 + {2:-- INSERT --} | + ]]) + feed('<Esc>') + + -- restore after cancel completion + feed('Sαβγ <C-X><C-O><Space>') + screen:expect([[ + αβγ foo ^ | + {1:~ }|*18 + {2:-- INSERT --} | + ]]) + feed('<Esc>') + + -- text after the inserted text shouldn't be highlighted + feed('0ea <C-X><C-O>') + screen:expect([[ + αβγ {8:foo}^ foo | + {1:~ }{s: foo }{1: }| + {1:~ }{n: bar }{1: }| + {1:~ }{n: 你好 }{1: }| + {1:~ }|*15 + {2:-- }{5:match 1 of 3} | + ]]) + feed('<C-P>') + screen:expect([[ + αβγ ^ foo | + {1:~ }{n: foo }{1: }| + {1:~ }{n: bar }{1: }| + {1:~ }{n: 你好 }{1: }| + {1:~ }|*15 + {2:-- }{8:Back at original} | + ]]) + feed('<C-P>') + screen:expect([[ + αβγ {8:你好}^ foo | + {1:~ }{n: foo }{1: }| + {1:~ }{n: bar }{1: }| + {1:~ }{s: 你好 }{1: }| + {1:~ }|*15 + {2:-- }{5:match 3 of 3} | + ]]) + feed('<C-Y>') + screen:expect([[ + αβγ 你好^ foo | + {1:~ }|*18 + {2:-- INSERT --} | + ]]) + feed('<Esc>') + + feed(':let g:change=1<CR>S<C-X><C-O>') + screen:expect([[ + info | + {1:~ }|*2 + {3:[Scratch] [Preview] }| + {8:foo}^ | + {s:foo }{1: }| + {n:bar }{1: }| + {n:你好 }{1: }| + {1:~ }|*10 + {4:[No Name] [+] }| + {2:-- }{5:match 1 of 3} | + ]]) + feed('<Esc>') + end) + + -- oldtest: Test_pum_matchins_highlight_combine() + it('with ComplMatchIns, Normal and CursorLine highlights', function() + exec([[ + func Omni_test(findstart, base) + if a:findstart + return col(".") + endif + return [#{word: "foo"}, #{word: "bar"}, #{word: "你好"}] + endfunc + set omnifunc=Omni_test + hi Normal guibg=blue + hi CursorLine guibg=green guifg=white + set cursorline + call setline(1, 'aaa bbb') + ]]) + + -- when ComplMatchIns is not set, CursorLine applies normally + feed('0ea <C-X><C-O>') + screen:expect([[ + {10:aaa foo^ bbb }| + {1:~ }{s: foo }{1: }| + {1:~ }{n: bar }{1: }| + {1:~ }{n: 你好 }{1: }| + {1:~ }|*15 + {2:-- }{5:match 1 of 3} | + ]]) + feed('<C-E>') + screen:expect([[ + {10:aaa ^ bbb }| + {1:~ }|*18 + {2:-- INSERT --} | + ]]) + feed('<BS><Esc>') + + -- when ComplMatchIns is set, it is applied over CursorLine + command('hi ComplMatchIns guifg=Yellow') + feed('0ea <C-X><C-O>') + screen:expect([[ + {10:aaa }{9:foo}{10:^ bbb }| + {1:~ }{s: foo }{1: }| + {1:~ }{n: bar }{1: }| + {1:~ }{n: 你好 }{1: }| + {1:~ }|*15 + {2:-- }{5:match 1 of 3} | + ]]) + feed('<C-P>') + screen:expect([[ + {10:aaa ^ bbb }| + {1:~ }{n: foo }{1: }| + {1:~ }{n: bar }{1: }| + {1:~ }{n: 你好 }{1: }| + {1:~ }|*15 + {2:-- }{8:Back at original} | + ]]) + feed('<C-P>') + screen:expect([[ + {10:aaa }{9:你好}{10:^ bbb }| + {1:~ }{n: foo }{1: }| + {1:~ }{n: bar }{1: }| + {1:~ }{s: 你好 }{1: }| + {1:~ }|*15 + {2:-- }{5:match 3 of 3} | + ]]) + feed('<C-E>') + screen:expect([[ + {10:aaa ^ bbb }| + {1:~ }|*18 + {2:-- INSERT --} | + ]]) + feed('<Esc>') + + -- Does not highlight the compl leader + command('set cot+=menuone,noselect') + feed('S<C-X><C-O>') + local pum_start = [[ + {10:^ }| + {n:foo }{1: }| + {n:bar }{1: }| + {n:你好 }{1: }| + {1:~ }|*15 + {2:-- }{8:Back at original} | + ]] + screen:expect(pum_start) + feed('f<C-N>') + screen:expect([[ + {10:f}{9:oo}{10:^ }| + {s:foo }{1: }| + {1:~ }|*17 + {2:-- }{5:match 1 of 3} | + ]]) + feed('<C-E><ESC>') + + command('set cot+=fuzzy') + feed('S<C-X><C-O>') + screen:expect(pum_start) + feed('f<C-N>') + screen:expect([[ + {10:foo^ }| + {s:foo }{1: }| + {1:~ }|*17 + {2:-- }{5:match 1 of 3} | + ]]) + feed('<C-E><Esc>') + + command('set cot-=fuzzy') + feed('Sf<C-N>') + screen:expect([[ + {10:f^ }| + {1:~ }|*18 + {2:-- }{6:Pattern not found} | + ]]) + feed('<C-E><Esc>') + end) end end diff --git a/test/functional/ui/screen.lua b/test/functional/ui/screen.lua index 8e15e6c35f..6a8e7df6a0 100644 --- a/test/functional/ui/screen.lua +++ b/test/functional/ui/screen.lua @@ -37,10 +37,10 @@ -- Tests will often share a group of extra attribute sets to expect(). Those can be -- defined at the beginning of a test: -- --- screen:add_extra_attr_ids { +-- screen:add_extra_attr_ids({ -- [100] = { background = Screen.colors.Plum1, underline = true }, -- [101] = { background = Screen.colors.Red1, bold = true, underline = true }, --- } +-- }) -- -- To help write screen tests, see Screen:snapshot_util(). -- To debug screen tests, see Screen:redraw_debug(). @@ -79,6 +79,7 @@ end --- @field win_position table<integer,table<string,integer>> --- @field float_pos table<integer,table> --- @field cmdline table<integer,table> +--- @field cmdline_hide_level integer? --- @field cmdline_block table[] --- @field hl_groups table<string,integer> --- @field messages table<integer,table> @@ -454,7 +455,7 @@ end --- screen:expect(grid, [attr_ids]) --- screen:expect(condition) --- or keyword args (supports more options): ---- screen:expect{grid=[[...]], cmdline={...}, condition=function() ... end} +--- screen:expect({ grid=[[...]], cmdline={...}, condition=function() ... end }) --- --- @param expected string|function|test.function.ui.screen.Expect --- @param attr_ids? table<integer,table<string,any>> @@ -654,6 +655,12 @@ screen:redraw_debug() to show all intermediate screen states.]] end end + -- Only test the abort state of a cmdline level once. + if self.cmdline_hide_level ~= nil then + self.cmdline[self.cmdline_hide_level] = nil + self.cmdline_hide_level = nil + end + if expected.hl_groups ~= nil then for name, id in pairs(expected.hl_groups) do local expected_hl = attr_state.ids[id] @@ -967,11 +974,11 @@ function Screen:_handle_mode_info_set(cursor_style_enabled, mode_info) self._cursor_style_enabled = cursor_style_enabled for _, item in pairs(mode_info) do -- attr IDs are not stable, but their value should be - if item.attr_id ~= nil then + if item.attr_id ~= nil and self._attr_table[item.attr_id] ~= nil then item.attr = self._attr_table[item.attr_id][1] item.attr_id = nil end - if item.attr_id_lm ~= nil then + if item.attr_id_lm ~= nil and self._attr_table[item.attr_id_lm] ~= nil then item.attr_lm = self._attr_table[item.attr_id_lm][1] item.attr_id_lm = nil end @@ -1296,7 +1303,7 @@ function Screen:_handle_popupmenu_hide() self.popupmenu = nil end -function Screen:_handle_cmdline_show(content, pos, firstc, prompt, indent, level) +function Screen:_handle_cmdline_show(content, pos, firstc, prompt, indent, level, hl_id) if firstc == '' then firstc = nil end @@ -1320,11 +1327,13 @@ function Screen:_handle_cmdline_show(content, pos, firstc, prompt, indent, level firstc = firstc, prompt = prompt, indent = indent, + hl_id = prompt and hl_id, } end -function Screen:_handle_cmdline_hide(level) - self.cmdline[level] = nil +function Screen:_handle_cmdline_hide(level, abort) + self.cmdline[level] = { abort = abort } + self.cmdline_hide_level = level end function Screen:_handle_cmdline_special_char(char, shift, level) @@ -1360,12 +1369,12 @@ function Screen:_handle_wildmenu_hide() self.wildmenu_items, self.wildmenu_pos = nil, nil end -function Screen:_handle_msg_show(kind, chunks, replace_last) +function Screen:_handle_msg_show(kind, chunks, replace_last, history) local pos = #self.messages if not replace_last or pos == 0 then pos = pos + 1 end - self.messages[pos] = { kind = kind, content = chunks } + self.messages[pos] = { kind = kind, content = chunks, history = history } end function Screen:_handle_msg_clear() @@ -1468,7 +1477,9 @@ function Screen:_extstate_repr(attr_state) local cmdline = {} for i, entry in pairs(self.cmdline) do entry = shallowcopy(entry) - entry.content = self:_chunks_repr(entry.content, attr_state) + if entry.content ~= nil then + entry.content = self:_chunks_repr(entry.content, attr_state) + end cmdline[i] = entry end @@ -1479,7 +1490,11 @@ function Screen:_extstate_repr(attr_state) local messages = {} for i, entry in ipairs(self.messages) do - messages[i] = { kind = entry.kind, content = self:_chunks_repr(entry.content, attr_state) } + messages[i] = { + kind = entry.kind, + content = self:_chunks_repr(entry.content, attr_state), + history = entry.history, + } end local msg_history = {} @@ -1713,21 +1728,24 @@ function Screen:_print_snapshot() end end local fn_name = modify_attrs and 'add_extra_attr_ids' or 'set_default_attr_ids' - attrstr = ('screen:' .. fn_name .. ' {\n' .. table.concat(attrstrs, '\n') .. '\n}\n\n') + attrstr = ('screen:' .. fn_name .. '({\n' .. table.concat(attrstrs, '\n') .. '\n})\n\n') end - local result = ('%sscreen:expect({\n grid = [[\n %s\n ]]'):format( - attrstr, - kwargs.grid:gsub('\n', '\n ') - ) + local extstr = '' for _, k in ipairs(ext_keys) do if ext_state[k] ~= nil and not (k == 'win_viewport' and not self.options.ext_multigrid) then - result = result .. ', ' .. k .. '=' .. fmt_ext_state(k, ext_state[k]) + extstr = extstr .. '\n ' .. k .. ' = ' .. fmt_ext_state(k, ext_state[k]) .. ',' end end - result = result .. '\n})' - return result + return ('%sscreen:expect(%s%s%s%s%s'):format( + attrstr, + #extstr > 0 and '{\n grid = [[\n ' or '[[\n', + #extstr > 0 and kwargs.grid:gsub('\n', '\n ') or kwargs.grid, + #extstr > 0 and '\n ]],' or '\n]]', + extstr, + #extstr > 0 and '\n})' or ')' + ) end function Screen:print_snapshot() diff --git a/test/functional/ui/screen_basic_spec.lua b/test/functional/ui/screen_basic_spec.lua index f39e9ecc33..295c40b9b6 100644 --- a/test/functional/ui/screen_basic_spec.lua +++ b/test/functional/ui/screen_basic_spec.lua @@ -2,7 +2,7 @@ local t = require('test.testutil') local n = require('test.functional.testnvim')() local Screen = require('test.functional.ui.screen') -local spawn, set_session, clear = n.spawn, n.set_session, n.clear +local set_session, clear = n.set_session, n.clear local feed, command = n.feed, n.command local exec = n.exec local insert = n.insert @@ -26,7 +26,7 @@ describe('screen', function() } before_each(function() - local screen_nvim = spawn(nvim_argv) + local screen_nvim = n.new_session(false, { args = nvim_argv, merge = false }) set_session(screen_nvim) screen = Screen.new() end) @@ -645,6 +645,59 @@ local function screen_tests(linegrid) | ]]) end) + + it('clamps &cmdheight for current tabpage', function() + command('set cmdheight=10 laststatus=2') + screen:expect([[ + ^ | + {0:~ }|*2 + {1:[No Name] }| + |*10 + ]]) + screen:try_resize(53, 8) + screen:expect([[ + ^ | + {1:[No Name] }| + |*6 + ]]) + eq(6, api.nvim_get_option_value('cmdheight', {})) + end) + + it('clamps &cmdheight for another tabpage #31380', function() + command('tabnew') + command('set cmdheight=9 laststatus=2') + screen:expect([[ + {4: [No Name] }{2: [No Name] }{3: }{4:X}| + ^ | + {0:~ }|*2 + {1:[No Name] }| + |*9 + ]]) + command('tabprev') + screen:expect([[ + {2: [No Name] }{4: [No Name] }{3: }{4:X}| + ^ | + {0:~ }|*10 + {1:[No Name] }| + | + ]]) + screen:try_resize(53, 8) + screen:expect([[ + {2: [No Name] }{4: [No Name] }{3: }{4:X}| + ^ | + {0:~ }|*4 + {1:[No Name] }| + | + ]]) + command('tabnext') + screen:expect([[ + {4: [No Name] }{2: [No Name] }{3: }{4:X}| + ^ | + {1:[No Name] }| + |*5 + ]]) + eq(5, api.nvim_get_option_value('cmdheight', {})) + end) end) describe('press enter', function() @@ -713,7 +766,7 @@ describe('Screen default colors', function() 'colorscheme vim', '--embed', } - local screen_nvim = spawn(nvim_argv) + local screen_nvim = n.new_session(false, { args = nvim_argv, merge = false }) set_session(screen_nvim) screen = Screen.new(53, 14, { rgb = true, ext_termcolors = termcolors or nil }) end diff --git a/test/functional/ui/sign_spec.lua b/test/functional/ui/sign_spec.lua index 7874c04c39..ff03d86979 100644 --- a/test/functional/ui/sign_spec.lua +++ b/test/functional/ui/sign_spec.lua @@ -14,6 +14,10 @@ describe('Signs', function() screen = Screen.new() screen:add_extra_attr_ids { [100] = { bold = true, foreground = Screen.colors.Magenta1 }, + [101] = { foreground = Screen.colors.DarkBlue, background = Screen.colors.Yellow1 }, + [102] = { foreground = Screen.colors.Brown, background = Screen.colors.Yellow }, + [103] = { background = Screen.colors.Yellow, reverse = true }, + [104] = { reverse = true, foreground = Screen.colors.Grey100, background = Screen.colors.Red }, } end) @@ -27,8 +31,8 @@ describe('Signs', function() sign place 2 line=2 name=piet2 buffer=1 ]]) screen:expect([[ - {10:𐌢̀́̂̃̅̄𐌢̀́̂̃̅̄}a | - {10:𠜎̀́̂̃̄̅}b | + {101:𐌢̀́̂̃̅̄𐌢̀́̂̃̅̄}a | + {101:𠜎̀́̂̃̄̅}b | {7: }^ | {1:~ }|*10 | @@ -45,9 +49,9 @@ describe('Signs', function() sign place 3 line=1 name=pietx buffer=1 ]]) screen:expect([[ - {10:>!}a | + {101:>!}a | {7: }b | - {10:>>}c | + {101:>>}c | {7: }^ | {1:~ }|*9 | @@ -80,13 +84,13 @@ describe('Signs', function() ]]) screen:expect([[ {7: }{21:^a }| - {10:>>}b | + {101:>>}b | {7: }c | {7: } | {1:~ }|*2 {3:[No Name] [+] }| {7: }{21:a }| - {10:>>}b | + {101:>>}b | {7: }c | {7: } | {1:~ }| @@ -110,10 +114,10 @@ describe('Signs', function() sign place 6 line=4 name=pietxx buffer=1 ]]) screen:expect([[ - {10:>>}{8: 1 }a | + {101:>>}{8: 1 }a | {7: }{8: 2 }{9:b }| {7: }{13: 3 }c | - {10:>>}{13: 4 }{9:^ }| + {101:>>}{13: 4 }{9:^ }| {1:~ }|*9 | ]]) @@ -132,33 +136,33 @@ describe('Signs', function() set cursorline ]]) screen:expect([[ - {10:>>}a | - {10:>>}b | + {101:>>}a | + {101:>>}b | {9:>>}{21:^c }| {1:~ }|*10 | ]]) feed('k') screen:expect([[ - {10:>>}a | + {101:>>}a | {9:>>}{21:^b }| - {10:>>}c | + {101:>>}c | {1:~ }|*10 | ]]) exec('set nocursorline') screen:expect([[ - {10:>>}a | - {10:>>}^b | - {10:>>}c | + {101:>>}a | + {101:>>}^b | + {101:>>}c | {1:~ }|*10 | ]]) exec('set cursorline cursorlineopt=line') screen:expect([[ - {10:>>}a | - {10:>>}{21:^b }| - {10:>>}c | + {101:>>}a | + {101:>>}{21:^b }| + {101:>>}c | {1:~ }|*10 | ]]) @@ -166,13 +170,14 @@ describe('Signs', function() exec('hi! link SignColumn IncSearch') feed('Go<esc>2G') screen:expect([[ - {10:>>}a | - {9:>>}^b | - {10:>>}c | + {103:>>}a | + {104:>>}^b | + {103:>>}c | {2: } | {1:~ }|*9 | ]]) + -- Check that 'statuscolumn' cursorline/signcolumn highlights are the same (#21726) exec('set statuscolumn=%s') screen:expect_unchanged() @@ -196,7 +201,7 @@ describe('Signs', function() screen:expect([[ {7: }{8: 1 }a | {7: }{8: 2 }b | - WW{10:>>}{8: 3 }c | + {7:WW}{101:>>}{8: 3 }c | {7: }{8: 4 }^ | {1:~ }|*9 | @@ -209,9 +214,9 @@ describe('Signs', function() sign place 3 line=2 name=pietError buffer=1 ]]) screen:expect([[ - {9:XX}{10:>>}{8: 1 }a | - {10:>>}{9:XX}{8: 2 }b | - WW{10:>>}{8: 3 }c | + {9:XX}{101:>>}{8: 1 }a | + {101:>>}{9:XX}{8: 2 }b | + {7:WW}{101:>>}{8: 3 }c | {7: }{8: 4 }^ | {1:~ }|*9 | @@ -220,8 +225,8 @@ describe('Signs', function() exec('set signcolumn=yes:1') screen:expect([[ {9:XX}{8: 1 }a | - {10:>>}{8: 2 }b | - WW{8: 3 }c | + {101:>>}{8: 2 }b | + {7:WW}{8: 3 }c | {7: }{8: 4 }^ | {1:~ }|*9 | @@ -229,9 +234,9 @@ describe('Signs', function() -- "auto:3" accommodates all the signs we defined so far. exec('set signcolumn=auto:3') local s3 = [[ - {9:XX}{10:>>}{7: }{8: 1 }a | - {10:>>}{9:XX}{7: }{8: 2 }b | - WW{10:>>}{9:XX}{8: 3 }c | + {9:XX}{101:>>}{7: }{8: 1 }a | + {101:>>}{9:XX}{7: }{8: 2 }b | + {7:WW}{101:>>}{9:XX}{8: 3 }c | {7: }{8: 4 }^ | {1:~ }|*9 | @@ -240,9 +245,9 @@ describe('Signs', function() -- Check "yes:9". exec('set signcolumn=yes:9') screen:expect([[ - {9:XX}{10:>>}{7: }{8: 1 }a | - {10:>>}{9:XX}{7: }{8: 2 }b | - WW{10:>>}{9:XX}{7: }{8: 3 }c | + {9:XX}{101:>>}{7: }{8: 1 }a | + {101:>>}{9:XX}{7: }{8: 2 }b | + {7:WW}{101:>>}{9:XX}{7: }{8: 3 }c | {7: }{8: 4 }^ | {1:~ }|*9 | @@ -255,8 +260,8 @@ describe('Signs', function() exec('3move1') exec('2d') screen:expect([[ - {9:XX}{10:>>}{8: 1 }a | - {10:>>}{9:XX}{8: 2 }^b | + {9:XX}{101:>>}{8: 1 }a | + {101:>>}{9:XX}{8: 2 }^b | {7: }{8: 3 } | {1:~ }|*10 | @@ -264,8 +269,8 @@ describe('Signs', function() -- character deletion does not delete signs. feed('x') screen:expect([[ - {9:XX}{10:>>}{8: 1 }a | - {10:>>}{9:XX}{8: 2 }^ | + {9:XX}{101:>>}{8: 1 }a | + {101:>>}{9:XX}{8: 2 }^ | {7: }{8: 3 } | {1:~ }|*10 | @@ -301,7 +306,7 @@ describe('Signs', function() exec('sign define pietSearch text=>> texthl=Search') exec('sign place 1 line=1 name=pietSearch buffer=1') screen:expect([[ - {10:>>}{7: }{8: 1 }a | + {101:>>}{7: }{8: 1 }a | {7: }{8: 2 }b | {7: }{8: 3 }c | {7: }{8: 4 }^ | @@ -316,7 +321,7 @@ describe('Signs', function() sign place 4 line=1 name=pietSearch buffer=1 ]]) screen:expect([[ - {10:>>>>>>>>}{8: 1 }a | + {101:>>>>>>>>}{8: 1 }a | {7: }{8: 2 }b | {7: }{8: 3 }c | {7: }{8: 4 }^ | @@ -328,7 +333,7 @@ describe('Signs', function() screen:expect_unchanged() exec('sign unplace 4') screen:expect([[ - {10:>>>>>>}{8: 1 }a | + {101:>>>>>>}{8: 1 }a | {7: }{8: 2 }b | {7: }{8: 3 }c | {7: }{8: 4 }^ | @@ -345,7 +350,7 @@ describe('Signs', function() sign place 8 line=1 name=pietSearch buffer=1 ]]) screen:expect([[ - {10:>>>>>>>>>>}{8: 1 }a | + {101:>>>>>>>>>>}{8: 1 }a | {7: }{8: 2 }b | {7: }{8: 3 }c | {7: }{8: 4 }^ | @@ -375,7 +380,7 @@ describe('Signs', function() -- single column with 1 sign with text and one sign without exec('sign place 1 line=1 name=pietSearch buffer=1') screen:expect([[ - {10:>>}{8: 1 }a | + {101:>>}{8: 1 }a | {7: }{8: 2 }b | {7: }{8: 3 }c | {7: }{8: 4 }^ | @@ -396,7 +401,7 @@ describe('Signs', function() -- line number should be drawn if sign has no text -- no signcolumn, line number for "a" is Search, for "b" is Error, for "c" is LineNr screen:expect([[ - {10: >> }a | + {101: >> }a | {9: 2 }b | {8: 3 }c | {8: 4 }^ | @@ -406,7 +411,7 @@ describe('Signs', function() -- number column on wrapped part of a line should be empty feed('gg100aa<Esc>') screen:expect([[ - {10: >> }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {101: >> }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {9: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {9: }aa^a | {9: 2 }b | @@ -423,7 +428,7 @@ describe('Signs', function() -- number column on virtual lines should be empty screen:expect([[ {8: }VIRT LINES | - {10: >> }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {101: >> }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {9: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {9: }aa^a | {9: 2 }b | @@ -439,7 +444,7 @@ describe('Signs', function() exec('sign place 100000 line=1 name=piet buffer=1') feed(':sign place<cr>') screen:expect([[ - {10:>>} | + {101:>>} | {1:~ }|*6 {3: }| :sign place | @@ -452,7 +457,7 @@ describe('Signs', function() feed('<cr>') screen:expect([[ - {10:>>}^ | + {101:>>}^ | {1:~ }|*12 | ]]) @@ -470,7 +475,7 @@ describe('Signs', function() {7: }a | {7: }^c | {7: }d | - >>e | + {7:>>}e | {1:~ }|*9 | ]]) @@ -498,7 +503,7 @@ describe('Signs', function() {7: }b | {7: }c | {7: }d | - >>e | + {7:>>}e | {1:~ }|*7 | ]]) @@ -550,7 +555,7 @@ describe('Signs', function() exec('silent undo') screen:expect([[ {7: }1 | - S1^2 | + {7:S1}^2 | {7: }3 | {7: }4 | {1:~ }|*9 @@ -575,23 +580,19 @@ describe('Signs', function() sign place 2 line=9 name=S2 ]]) -- Now placed at end of buffer - local s1 = { - grid = [[ - S2^ | - {1:~ }|*12 - | - ]], - } + local s1 = [[ + {7:S2}^ | + {1:~ }|*12 + | + ]] screen:expect(s1) -- Signcolumn tracking used to not count signs placed beyond end of buffer here exec('set signcolumn=auto:9') - screen:expect({ - grid = [[ - S2S1^ | - {1:~ }|*12 - | - ]], - }) + screen:expect([[ + {7:S2S1}^ | + {1:~ }|*12 + | + ]]) -- Unplacing the sign does not crash by decrementing tracked signs below zero exec('sign unplace 1') screen:expect(s1) @@ -607,4 +608,77 @@ describe('Signs', function() eq(6, infos[1].textoff) eq(6, infos[2].textoff) end) + + it('auto width updated in all windows after sign placed in on_win #31438', function() + exec_lua([[ + vim.cmd.call('setline(1, range(1, 500))') + vim.cmd('wincmd s | wincmd v | wincmd j | wincmd v') + + _G.log, _G.needs_clear = {}, false + local ns_id, mark_id = vim.api.nvim_create_namespace('test'), nil + + -- Add decoration which possibly clears all extmarks and adds one on line 499 + local on_win = function(_, winid, bufnr, toprow, botrow) + if _G.needs_clear then + vim.api.nvim_buf_clear_namespace(bufnr, ns_id, 0, -1) + _G.needs_clear = false + end + + if toprow < 499 and 499 <= botrow then + mark_id = vim.api.nvim_buf_set_extmark(bufnr, ns_id, 499, 0, { id = mark_id, sign_text = '!', invalidate = true }) + end + end + vim.api.nvim_set_decoration_provider(ns_id, { on_win = on_win }) + ]]) + screen:expect([[ + 1 │1 | + 2 │2 | + 3 │3 | + 4 │4 | + 5 │5 | + 6 │6 | + {2:[No Name] [+] [No Name] [+] }| + ^1 │1 | + 2 │2 | + 3 │3 | + 4 │4 | + 5 │5 | + {3:[No Name] [+] }{2:[No Name] [+] }| + | + ]]) + feed('G') + screen:expect([[ + {7: }1 │{7: }1 | + {7: }2 │{7: }2 | + {7: }3 │{7: }3 | + {7: }4 │{7: }4 | + {7: }5 │{7: }5 | + {7: }6 │{7: }6 | + {2:[No Name] [+] [No Name] [+] }| + {7: }496 │{7: }1 | + {7: }497 │{7: }2 | + {7: }498 │{7: }3 | + {7: }499 │{7: }4 | + {7:! }^500 │{7: }5 | + {3:[No Name] [+] }{2:[No Name] [+] }| + | + ]]) + feed(':lua log, needs_clear = {}, true<CR>') + screen:expect([[ + {7: }1 │{7: }1 | + {7: }2 │{7: }2 | + {7: }3 │{7: }3 | + {7: }4 │{7: }4 | + {7: }5 │{7: }5 | + {7: }6 │{7: }6 | + {2:[No Name] [+] [No Name] [+] }| + {7: }496 │{7: }1 | + {7: }497 │{7: }2 | + {7: }498 │{7: }3 | + {7: }499 │{7: }4 | + {7:! }^500 │{7: }5 | + {3:[No Name] [+] }{2:[No Name] [+] }| + :lua log, needs_clear = {}, true | + ]]) + end) end) diff --git a/test/functional/ui/statuscolumn_spec.lua b/test/functional/ui/statuscolumn_spec.lua index 268e7173e6..328e212a22 100644 --- a/test/functional/ui/statuscolumn_spec.lua +++ b/test/functional/ui/statuscolumn_spec.lua @@ -13,8 +13,6 @@ local api = n.api local pcall_err = t.pcall_err local assert_alive = n.assert_alive -local mousemodels = { 'extend', 'popup', 'popup_setpos' } - describe('statuscolumn', function() local screen before_each(function() @@ -229,15 +227,28 @@ describe('statuscolumn', function() {1: }{8:8│}aaaaa | | ]]) + -- Last segment and fillchar are highlighted properly + command("set stc=%#Error#%{v:relnum?'Foo':'FooBar'}") + screen:expect([[ + {9:Foo }aaaaa |*4 + {9:FooBar}^aaaaa | + {9:Foo }aaaaa |*8 + | + ]]) end) it('works with wrapped lines, signs and folds', function() - command([[set stc=%C%s%=%{v:virtnum?'':v:lnum}│\ ]]) - command("call setline(1,repeat([repeat('aaaaa',10)],16))") screen:add_extra_attr_ids { [100] = { foreground = Screen.colors.Red, background = Screen.colors.LightGray }, + [101] = { background = Screen.colors.Gray90, bold = true }, + [102] = { foreground = Screen.colors.Brown, background = Screen.colors.Grey }, + [103] = { bold = true, background = Screen.colors.Grey, foreground = Screen.colors.Blue1 }, } - command('hi! CursorLine guifg=Red guibg=NONE') + command([[set cursorline stc=%C%s%=%{v:virtnum?'':v:lnum}│\ ]]) + command("call setline(1,repeat([repeat('aaaaa',10)],16))") + command('hi! CursorLine gui=bold') + command('sign define num1 numhl=Special') + command('sign place 1 line=8 name=num1 buffer=1') screen:expect([[ {8: 4│ }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {8: │ }a | @@ -247,8 +258,8 @@ describe('statuscolumn', function() {8: │ }a | {8: 7│ }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {8: │ }a | - {8: 8│ }^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| - {8: │ }a | + {29: 8│ }{101:^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}| + {29: │ }{101:a }| {8: 9│ }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {8: │ }a | {8:10│ }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{1:@@@}| @@ -256,7 +267,8 @@ describe('statuscolumn', function() ]]) command([[set stc=%C%s%=%l│\ ]]) screen:expect_unchanged() - command('set signcolumn=auto:2 foldcolumn=auto') + command('hi! CursorLine guifg=Red guibg=NONE gui=NONE') + command('set nocursorline signcolumn=auto:2 foldcolumn=auto') command('sign define piet1 text=>> texthl=LineNr') command('sign define piet2 text=>! texthl=NonText') command('sign place 1 line=4 name=piet1 buffer=1') @@ -264,11 +276,11 @@ describe('statuscolumn', function() command('sign place 3 line=6 name=piet1 buffer=1') command('sign place 4 line=6 name=piet2 buffer=1') screen:expect([[ - {8:>>}{7: }{8: 4│ }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {102:>>}{7: }{8: 4│ }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: │ }aaaaa | - {1:>!}{7: }{8: 5│ }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {103:>!}{7: }{8: 5│ }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: │ }aaaaa | - {1:>!}{8:>> 6│ }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {103:>!}{102:>>}{8: 6│ }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: │ }aaaaa | {7: }{8: 7│ }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: │ }aaaaa | @@ -283,11 +295,11 @@ describe('statuscolumn', function() -- Check that alignment works properly with signs after %= command([[set stc=%C%=%{v:virtnum?'':v:lnum}│%s\ ]]) screen:expect([[ - {7: }{8: 4│>>}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {7: }{8: 4│}{102:>>}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: │}{7: }{8: }aaaaaa | - {7: }{8: 5│}{1:>!}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {7: }{8: 5│}{103:>!}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: │}{7: }{8: }aaaaaa | - {7: }{8: 6│}{1:>!}{8:>> }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {7: }{8: 6│}{103:>!}{102:>>}{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: │}{7: }{8: }aaaaaa | {7: }{8: 7│}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: │}{7: }{8: }aaaaaa | @@ -300,11 +312,11 @@ describe('statuscolumn', function() ]]) command('set cursorline') screen:expect([[ - {7: }{8: 4│>>}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {7: }{8: 4│}{102:>>}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: │}{7: }{8: }aaaaaa | - {7: }{8: 5│}{1:>!}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {7: }{8: 5│}{103:>!}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: │}{7: }{8: }aaaaaa | - {7: }{8: 6│}{1:>!}{8:>> }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {7: }{8: 6│}{103:>!}{102:>>}{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: │}{7: }{8: }aaaaaa | {7: }{8: 7│}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: │}{7: }{8: }aaaaaa | @@ -318,11 +330,11 @@ describe('statuscolumn', function() -- v:lnum is the same value on wrapped lines command([[set stc=%C%=%{v:lnum}│%s\ ]]) screen:expect([[ - {7: }{8: 4│>>}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {7: }{8: 4│}{102:>>}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: 4│}{7: }{8: }aaaaaa | - {7: }{8: 5│}{1:>!}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {7: }{8: 5│}{103:>!}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: 5│}{7: }{8: }aaaaaa | - {7: }{8: 6│}{1:>!}{8:>> }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {7: }{8: 6│}{103:>!}{102:>>}{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: 6│}{7: }{8: }aaaaaa | {7: }{8: 7│}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: 7│}{7: }{8: }aaaaaa | @@ -336,11 +348,11 @@ describe('statuscolumn', function() -- v:relnum is the same value on wrapped lines command([[set stc=%C%=\ %{v:relnum}│%s\ ]]) screen:expect([[ - {7: }{8: 4│>>}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {7: }{8: 4│}{102:>>}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: 4│}{7: }{8: }aaaaaaa | - {7: }{8: 3│}{1:>!}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {7: }{8: 3│}{103:>!}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: 3│}{7: }{8: }aaaaaaa | - {7: }{8: 2│}{1:>!}{8:>> }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {7: }{8: 2│}{103:>!}{102:>>}{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: 2│}{7: }{8: }aaaaaaa | {7: }{8: 1│}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: 1│}{7: }{8: }aaaaaaa | @@ -353,11 +365,11 @@ describe('statuscolumn', function() ]]) command([[set stc=%C%=\ %{v:virtnum?'':v:relnum}│%s\ ]]) screen:expect([[ - {7: }{8: 4│>>}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {7: }{8: 4│}{102:>>}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: │}{7: }{8: }aaaaaaa | - {7: }{8: 3│}{1:>!}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {7: }{8: 3│}{103:>!}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: │}{7: }{8: }aaaaaaa | - {7: }{8: 2│}{1:>!}{8:>> }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {7: }{8: 2│}{103:>!}{102:>>}{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: │}{7: }{8: }aaaaaaa | {7: }{8: 1│}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: │}{7: }{8: }aaaaaaa | @@ -378,11 +390,11 @@ describe('statuscolumn', function() command('sign place 10 line=6 name=piet2 buffer=1') command('sign place 11 line=6 name=piet1 buffer=1') screen:expect([[ - {7: }{8: 4│>>}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {7: }{8: 4│}{102:>>}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: │}{7: }{8: }aaaaaaaaaaaaaaaaaaaaa | - {7: }{8: 3│}{1:>!}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {7: }{8: 3│}{103:>!}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: │}{7: }{8: }aaaaaaaaaaaaaaaaaaaaa | - {7: }{8: 2│>>}{1:>!}{8:>>}{1:>!}{8:>>}{1:>!}{8:>>}{1:>!}{8:>> }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {7: }{8: 2│}{102:>>}{103:>!}{102:>>}{103:>!}{102:>>}{103:>!}{102:>>}{103:>!}{102:>>}{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: │}{7: }{8: }aaaaaaaaaaaaaaaaaaaaa | {7: }{8: 1│}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: │}{7: }{8: }aaaaaaaaaaaaaaaaaaaaa | @@ -397,11 +409,11 @@ describe('statuscolumn', function() command('set cpoptions+=n') feed('Hgjg0') screen:expect([[ - {7: }{15: 0│}{8:>>}{7: }{15: }{19:aaaaaaaaaaaaaaaaaaaaaaaaaaaaa}| + {7: }{15: 0│}{102:>>}{7: }{15: }{19:aaaaaaaaaaaaaaaaaaaaaaaaaaaaa}| {7: }{19:^aaaaaaaaaaaaaaaaaaaaa }| - {7: }{8: 3│}{1:>!}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {7: }{8: 3│}{103:>!}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }aaaaaaaaaaaaaaaaaaaaa | - {7: }{8: 2│>>}{1:>!}{8:>>}{1:>!}{8:>>}{1:>!}{8:>>}{1:>!}{8:>> }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {7: }{8: 2│}{102:>>}{103:>!}{102:>>}{103:>!}{102:>>}{103:>!}{102:>>}{103:>!}{102:>>}{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }aaaaaaaaaaaaaaaaaaaaa | {7: }{8: 1│}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }aaaaaaaaaaaaaaaaaaaaa | @@ -416,11 +428,11 @@ describe('statuscolumn', function() command('sign unplace 2') feed('J2gjg0') screen:expect([[ - {7: }{15: 0│}{8:>>}{7: }{15: }{19:aaaaaaaaaaaaaaaaaaaaaaaaaaaaa}| + {7: }{15: 0│}{102:>>}{7: }{15: }{19:aaaaaaaaaaaaaaaaaaaaaaaaaaaaa}| {7: } {19:aaaaaaaaaaaaaaaaaaaaa aaaaaaa}| {7: } {19:aaaaaaaaaaaaaaaaaaaaaaaaaaaaa}| {7: } {19:^aaaaaaaaaaaaaa }| - {7: }{8: 1│>>}{1:>!}{8:>>}{1:>!}{8:>>}{1:>!}{8:>>}{1:>!}{8:>> }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {7: }{8: 1│}{102:>>}{103:>!}{102:>>}{103:>!}{102:>>}{103:>!}{102:>>}{103:>!}{102:>>}{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: } aaaaaaaaaaaaaaaaaaaaa | {7: }{8: 2│}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: } aaaaaaaaaaaaaaaaaaaaa | @@ -434,11 +446,11 @@ describe('statuscolumn', function() command('set nobreakindent') feed('$g0') screen:expect([[ - {7: }{15: 0│}{8:>>}{7: }{15: }{19:aaaaaaaaaaaaaaaaaaaaaaaaaaaaa}| + {7: }{15: 0│}{102:>>}{7: }{15: }{19:aaaaaaaaaaaaaaaaaaaaaaaaaaaaa}| {7: }{19:aaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaa}| {7: }{19:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}| {7: }{19:^aaaa }| - {7: }{8: 1│>>}{1:>!}{8:>>}{1:>!}{8:>>}{1:>!}{8:>>}{1:>!}{8:>> }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {7: }{8: 1│}{102:>>}{103:>!}{102:>>}{103:>!}{102:>>}{103:>!}{102:>>}{103:>!}{102:>>}{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }aaaaaaaaaaaaaaaaaaaaa | {7: }{8: 2│}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }aaaaaaaaaaaaaaaaaaaaa | @@ -460,11 +472,11 @@ describe('statuscolumn', function() ]]) command('set foldcolumn=0 signcolumn=number stc=%l') screen:expect([[ - {8:>>}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | + {102:>>}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | {8: 5}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | {8: }virt_line | {8: }virt_line above | - {8:>>}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | + {102:>>}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | {8: 7}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | {15: 8}{100:^+-- 1 line: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}| {8: 9}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | @@ -580,13 +592,13 @@ describe('statuscolumn', function() command([[set stc=%6s\ %l]]) exec_lua('vim.api.nvim_buf_set_extmark(0, ns, 7, 0, {sign_text = "𒀀"})') screen:expect([[ - {8: 𒀀 8}^aaaaa | + {8: }{7:𒀀 }{8: 8}^aaaaa | {8: }{7: }{8: 9}aaaaa | | ]]) end) - for _, model in ipairs(mousemodels) do + for _, model in ipairs({ 'extend', 'popup', 'popup_setpos' }) do describe('with mousemodel=' .. model, function() before_each(function() command('set mousemodel=' .. model) @@ -645,23 +657,56 @@ describe('statuscolumn', function() -- Check that statusline click doesn't register as statuscolumn click api.nvim_input_mouse('right', 'press', '', 0, 12, 0) eq('', eval('g:testvar')) + -- Check that rightclick still opens popupmenu if there is no clickdef + if model == 'popup' then + api.nvim_set_option_value('statuscolumn', '%0@MyClickFunc@%=%l%TNoClick', {}) + api.nvim_input_mouse('right', 'press', '', 0, 1, 0) + screen:expect([[ + {5:[No Name] }| + {8: 4NoClick}^aaaaa | + {8: 5NoClick}aaaaa | + {8: 6NoClick}aaaaa | + {8: 7NoClick}aaaaa | + {8: 8NoClick}aaaaa | + {8: 9NoClick}aaaaa | + {8:10NoClick}aaaaa | + {8:11NoClick}aaaaa | + {8:12NoClick}aaaaa | + {8:13NoClick}aaaaa | + {8:14NoClick}aaaaa | + {3:[No Name] [+] }| + | + ]]) + api.nvim_input_mouse('right', 'press', '', 0, 1, 3) + screen:expect([[ + {5:[No Name] }| + {8: 4NoClick}^aaaaa | + {8: 5}{4: Inspect } | + {8: 6}{4: } | + {8: 7}{4: Paste } | + {8: 8}{4: Select All } | + {8: 9}{4: } | + {8:10}{4: How-to disable mouse } | + {8:11NoClick}aaaaa | + {8:12NoClick}aaaaa | + {8:13NoClick}aaaaa | + {8:14NoClick}aaaaa | + {3:[No Name] [+] }| + | + ]]) + end end) it('clicks and highlights work with control characters', function() api.nvim_set_option_value('statuscolumn', '\t%#NonText#\1%0@MyClickFunc@\t\1%T\t%##\1', {}) - screen:expect { - grid = [[ - {1:^I}{0:^A^I^A^I}{1:^A}aaaaa |*4 - {1:^I}{0:^A^I^A^I}{1:^A}^aaaaa | - {1:^I}{0:^A^I^A^I}{1:^A}aaaaa |*8 + screen:expect([[ + {8:^I}{1:^A^I^A^I}{8:^A}aaaaa |*4 + {8:^I}{1:^A^I^A^I}{8:^A}^aaaaa | + {8:^I}{1:^A^I^A^I}{8:^A}aaaaa |*8 | - ]], - attr_ids = { - [0] = { foreground = Screen.colors.Blue, bold = true }, -- NonText - [1] = { foreground = Screen.colors.Brown }, -- LineNr - }, - } + ]]) api.nvim_input_mouse('right', 'press', '', 0, 4, 3) + feed('<Esc>') -- Close popupmenu eq('', eval('g:testvar')) api.nvim_input_mouse('left', 'press', '', 0, 5, 8) eq('', eval('g:testvar')) @@ -707,6 +752,36 @@ describe('statuscolumn', function() | ]]) end) + + it('foldcolumn item can be clicked', function() + api.nvim_set_option_value('statuscolumn', '|%C|', {}) + api.nvim_set_option_value('foldcolumn', '2', {}) + api.nvim_set_option_value('mousetime', 0, {}) + feed('ggzfjzfjzo') + local s1 = [[ + {8:|}{7:-+}{8:|}{13:^+--- 2 lines: aaaaa·····························}| + {8:|}{7:│ }{8:|}aaaaa | + {8:|}{7: }{8:|}aaaaa |*11 + | + ]] + screen:expect(s1) + api.nvim_input_mouse('left', 'press', '', 0, 0, 2) + screen:expect([[ + {8:|}{7:--}{8:|}^aaaaa | + {8:|}{7:││}{8:|}aaaaa | + {8:|}{7:│ }{8:|}aaaaa | + {8:|}{7: }{8:|}aaaaa |*10 + | + ]]) + api.nvim_input_mouse('left', 'press', '', 0, 0, 1) + screen:expect(s1) + api.nvim_input_mouse('left', 'press', '', 0, 0, 1) + screen:expect([[ + {8:|}{7:+ }{8:|}{13:^+-- 3 lines: aaaaa······························}| + {8:|}{7: }{8:|}aaaaa |*12 + | + ]]) + end) end) end diff --git a/test/functional/ui/statusline_spec.lua b/test/functional/ui/statusline_spec.lua index 1d0f181244..50e31ac6a9 100644 --- a/test/functional/ui/statusline_spec.lua +++ b/test/functional/ui/statusline_spec.lua @@ -507,268 +507,263 @@ describe('global statusline', function() end) end) -it('statusline does not crash if it has Arabic characters #19447', function() - clear() - api.nvim_set_option_value('statusline', 'غً', {}) - api.nvim_set_option_value('laststatus', 2, {}) - command('redraw!') - assert_alive() -end) +describe('statusline', function() + local screen + before_each(function() + clear() + screen = Screen.new(40, 8) + screen:add_extra_attr_ids { + [100] = { bold = true, reverse = true, foreground = Screen.colors.Blue }, + [101] = { reverse = true, bold = true, foreground = Screen.colors.SlateBlue }, + } + end) -it('statusline is redrawn with :resize from <Cmd> mapping #19629', function() - clear() - local screen = Screen.new(40, 8) - exec([[ - set laststatus=2 - nnoremap <Up> <cmd>resize -1<CR> - nnoremap <Down> <cmd>resize +1<CR> - ]]) - feed('<Up>') - screen:expect([[ - ^ | - {1:~ }|*4 - {3:[No Name] }| - |*2 - ]]) - feed('<Down>') - screen:expect([[ - ^ | - {1:~ }|*5 - {3:[No Name] }| - | - ]]) -end) + it('does not crash if it has Arabic characters #19447', function() + api.nvim_set_option_value('statusline', 'غً', {}) + api.nvim_set_option_value('laststatus', 2, {}) + command('redraw!') + assert_alive() + end) -it('showcmdloc=statusline does not show if statusline is too narrow', function() - clear() - local screen = Screen.new(40, 8) - command('set showcmd') - command('set showcmdloc=statusline') - command('1vsplit') - screen:expect([[ - ^ │ | - {1:~}│{1:~ }|*5 - {3:< }{2:[No Name] }| - | - ]]) - feed('1234') - screen:expect_unchanged() -end) + it('is redrawn with :resize from <Cmd> mapping #19629', function() + exec([[ + set laststatus=2 + nnoremap <Up> <cmd>resize -1<CR> + nnoremap <Down> <cmd>resize +1<CR> + ]]) + feed('<Up>') + screen:expect([[ + ^ | + {1:~ }|*4 + {3:[No Name] }| + |*2 + ]]) + feed('<Down>') + screen:expect([[ + ^ | + {1:~ }|*5 + {3:[No Name] }| + | + ]]) + end) -it('K_EVENT does not trigger a statusline redraw unnecessarily', function() - clear() - local _ = Screen.new(40, 8) - -- does not redraw on vim.schedule (#17937) - command([[ - set laststatus=2 - let g:counter = 0 - func Status() - let g:counter += 1 - lua vim.schedule(function() end) - return g:counter - endfunc - set statusline=%!Status() - ]]) - sleep(50) - eq(1, eval('g:counter < 50'), 'g:counter=' .. eval('g:counter')) - -- also in insert mode - feed('i') - sleep(50) - eq(1, eval('g:counter < 50'), 'g:counter=' .. eval('g:counter')) - -- does not redraw on timer call (#14303) - command([[ - let g:counter = 0 - func Timer(timer) - endfunc - call timer_start(1, 'Timer', {'repeat': 100}) - ]]) - sleep(50) - eq(1, eval('g:counter < 50'), 'g:counter=' .. eval('g:counter')) -end) + it('does not contain showmcd with showcmdloc=statusline when too narrow', function() + command('set showcmd') + command('set showcmdloc=statusline') + command('1vsplit') + screen:expect([[ + ^ │ | + {1:~}│{1:~ }|*5 + {3:< }{2:[No Name] }| + | + ]]) + feed('1234') + screen:expect_unchanged() + end) -it('statusline is redrawn on various state changes', function() - clear() - local screen = Screen.new(40, 4) - - -- recording state change #22683 - command('set ls=2 stl=%{repeat(reg_recording(),5)}') - screen:expect([[ - ^ | - {1:~ }| - {3: }| - | - ]]) - feed('qQ') - screen:expect([[ - ^ | - {1:~ }| - {3:QQQQQ }| - {5:recording @Q} | - ]]) - feed('q') - screen:expect([[ - ^ | - {1:~ }| - {3: }| - | - ]]) - - -- Visual mode change #23932 - command('set ls=2 stl=%{mode(1)}') - screen:expect([[ - ^ | - {1:~ }| - {3:n }| - | - ]]) - feed('v') - screen:expect([[ - ^ | - {1:~ }| - {3:v }| - {5:-- VISUAL --} | - ]]) - feed('V') - screen:expect([[ - ^ | - {1:~ }| - {3:V }| - {5:-- VISUAL LINE --} | - ]]) - feed('<C-V>') - screen:expect([[ - ^ | - {1:~ }| - {3:^V }| - {5:-- VISUAL BLOCK --} | - ]]) - feed('<Esc>') - screen:expect([[ - ^ | - {1:~ }| - {3:n }| - | - ]]) -end) + it('does not redraw unnecessarily after K_EVENT', function() + -- does not redraw on vim.schedule (#17937) + command([[ + set laststatus=2 + let g:counter = 0 + func Status() + let g:counter += 1 + lua vim.schedule(function() end) + return g:counter + endfunc + set statusline=%!Status() + ]]) + sleep(50) + eq(1, eval('g:counter < 50'), 'g:counter=' .. eval('g:counter')) + -- also in insert mode + feed('i') + sleep(50) + eq(1, eval('g:counter < 50'), 'g:counter=' .. eval('g:counter')) + -- does not redraw on timer call (#14303) + command([[ + let g:counter = 0 + func Timer(timer) + endfunc + call timer_start(1, 'Timer', {'repeat': 100}) + ]]) + sleep(50) + eq(1, eval('g:counter < 50'), 'g:counter=' .. eval('g:counter')) + end) -it('ruler is redrawn in cmdline with redrawstatus #22804', function() - clear() - local screen = Screen.new(40, 2) - command([[ - let g:n = 'initial value' - set ls=1 ru ruf=%{g:n} - redraw - let g:n = 'other value' - redrawstatus - ]]) - screen:expect([[ - ^ | - other value | - ]]) -end) + it('is redrawn on various state changes', function() + -- recording state change #22683 + command('set ls=2 stl=%{repeat(reg_recording(),5)}') + local s1 = [[ + ^ | + {1:~ }|*5 + {3: }| + | + ]] + screen:expect(s1) + feed('qQ') + screen:expect([[ + ^ | + {1:~ }|*5 + {3:QQQQQ }| + {5:recording @Q} | + ]]) + feed('q') + screen:expect(s1) + + -- Visual mode change #23932 + command('set ls=2 stl=%{mode(1)}') + local s2 = [[ + ^ | + {1:~ }|*5 + {3:n }| + | + ]] + screen:expect(s2) + feed('v') + screen:expect([[ + ^ | + {1:~ }|*5 + {3:v }| + {5:-- VISUAL --} | + ]]) + feed('V') + screen:expect([[ + ^ | + {1:~ }|*5 + {3:V }| + {5:-- VISUAL LINE --} | + ]]) + feed('<C-V>') + screen:expect([[ + ^ | + {1:~ }|*5 + {3:^V }| + {5:-- VISUAL BLOCK --} | + ]]) + feed('<Esc>') + screen:expect(s2) + end) -it('shows correct ruler in cmdline with no statusline', function() - clear() - local screen = Screen.new(30, 8) - -- Use long ruler to check 'ruler' with 'rulerformat' set has correct width. - command [[ - set ruler rulerformat=%{winnr()}longlonglong ls=0 winwidth=10 - split - wincmd b - vsplit - wincmd t - wincmd | - mode - ]] - -- Window 1 is current. It has a statusline, so cmdline should show the - -- last window's ruler, which has no statusline. - command '1wincmd w' - screen:expect [[ - ^ | - {1:~ }|*2 - {3:[No Name] 1longlonglong }| - │ | - {1:~ }│{1:~ }|*2 - 3longlonglong | - ]] - -- Window 2 is current. It has no statusline, so cmdline should show its - -- ruler instead. - command '2wincmd w' - screen:expect [[ - | - {1:~ }|*2 - {2:[No Name] 1longlonglong }| - ^ │ | - {1:~ }│{1:~ }|*2 - 2longlonglong | - ]] - -- Window 3 is current. Cmdline should again show its ruler. - command '3wincmd w' - screen:expect [[ - | - {1:~ }|*2 - {2:[No Name] 1longlonglong }| - │^ | - {1:~ }│{1:~ }|*2 - 3longlonglong | - ]] -end) + it('ruler is redrawn in cmdline with redrawstatus #22804', function() + command([[ + let g:n = 'initial value' + set ls=1 ru ruf=%{g:n} + redraw + let g:n = 'other value' + redrawstatus + ]]) + screen:expect([[ + ^ | + {1:~ }|*6 + other value | + ]]) + end) -it('uses "stl" and "stlnc" fillchars even if they are the same #19803', function() - clear() - local screen = Screen.new(53, 4) - command('hi clear StatusLine') - command('hi clear StatusLineNC') - command('vsplit') - screen:expect { - grid = [[ - ^ │ | - {1:~ }│{1:~ }| - [No Name] [No Name] | - | - ]], - } -end) + it('hidden moves ruler to cmdline', function() + -- Use long ruler to check 'ruler' with 'rulerformat' set has correct width. + command [[ + set ruler rulerformat=%{winnr()}longlonglong ls=0 winwidth=10 + split + wincmd b + vsplit + wincmd t + wincmd | + mode + ]] + -- Window 1 is current. It has a statusline, so cmdline should show the + -- last window's ruler, which has no statusline. + command '1wincmd w' + screen:expect([[ + ^ | + {1:~ }|*2 + {3:[No Name] 1longlonglong }| + │ | + {1:~ }│{1:~ }|*2 + 3longlonglong | + ]]) + -- Window 2 is current. It has no statusline, so cmdline should show its + -- ruler instead. + command '2wincmd w' + screen:expect([[ + | + {1:~ }|*2 + {2:[No Name] 1longlonglong }| + ^ │ | + {1:~ }│{1:~ }|*2 + 2longlonglong | + ]]) + -- Window 3 is current. Cmdline should again show its ruler. + command '3wincmd w' + screen:expect([[ + | + {1:~ }|*2 + {2:[No Name] 1longlonglong }| + │^ | + {1:~ }│{1:~ }|*2 + 3longlonglong | + ]]) + end) -it('showcmdloc=statusline works with vertical splits', function() - clear() - local screen = Screen.new(53, 4) - command('rightbelow vsplit') - command('set showcmd showcmdloc=statusline') - feed('1234') - screen:expect([[ - │^ | - {1:~ }│{1:~ }| - {2:[No Name] }{3:[No Name] 1234 }| - | - ]]) - feed('<Esc>') - command('set laststatus=3') - feed('1234') - screen:expect([[ - │^ | - {1:~ }│{1:~ }| - {3:[No Name] 1234 }| - | - ]]) -end) + it('uses "stl" and "stlnc" fillchars even if they are the same #19803', function() + command('hi clear StatusLine') + command('hi clear StatusLineNC') + command('vsplit') + screen:expect([[ + ^ │ | + {1:~ }│{1:~ }|*5 + [No Name] [No Name] | + | + ]]) + end) -it('keymap is shown with vertical splits #27269', function() - clear() - local screen = Screen.new(53, 4) - command('setlocal keymap=dvorak') - command('rightbelow vsplit') - screen:expect([[ - │^ | - {1:~ }│{1:~ }| - {2:[No Name] <en-dv> }{3:[No Name] <en-dv> }| - | - ]]) - command('set laststatus=3') - screen:expect([[ - │^ | - {1:~ }│{1:~ }| - {3:[No Name] <en-dv> }| - | - ]]) + it('showcmdloc=statusline works with vertical splits', function() + command('rightbelow vsplit') + command('set showcmd showcmdloc=statusline') + feed('1234') + screen:expect([[ + │^ | + {1:~ }│{1:~ }|*5 + {2:[No Name] }{3:[No Name] 1234 }| + | + ]]) + feed('<Esc>') + command('set laststatus=3') + feed('1234') + screen:expect([[ + │^ | + {1:~ }│{1:~ }|*5 + {3:[No Name] 1234 }| + | + ]]) + end) + + it('keymap is shown with vertical splits #27269', function() + command('setlocal keymap=dvorak') + command('rightbelow vsplit') + screen:expect([[ + │^ | + {1:~ }│{1:~ }|*5 + {2:[No Name] <en-dv> }{3:[No Name] <en-dv> }| + | + ]]) + + command('set laststatus=3') + screen:expect([[ + │^ | + {1:~ }│{1:~ }|*5 + {3:[No Name] <en-dv> }| + | + ]]) + end) + + it("nested call from nvim_eval_statusline() doesn't overwrite items #32259", function() + exec_lua('vim.o.laststatus = 2') + exec_lua([[vim.o.statusline = '%#Special#B:%{nvim_eval_statusline("%f", []).str}']]) + screen:expect([[ + ^ | + {1:~ }|*5 + {101:B:[No Name] }| + | + ]]) + end) end) diff --git a/test/functional/ui/syntax_conceal_spec.lua b/test/functional/ui/syntax_conceal_spec.lua index 57d76e54df..80e38d974a 100644 --- a/test/functional/ui/syntax_conceal_spec.lua +++ b/test/functional/ui/syntax_conceal_spec.lua @@ -198,7 +198,7 @@ describe('Screen', function() end) end) -- a region of text (implicit concealing) - it('cursor position is correct when entering Insert mode with cocu=ni #13916', function() + it('cursor position when entering Insert mode with cocu=ni #13916', function() insert([[foobarfoobarfoobar]]) -- move to end of line feed('$') @@ -217,6 +217,37 @@ describe('Screen', function() {4:-- INSERT --} | ]]) end) + + it('cursor position when scrolling in Normal mode with cocu=n #31271', function() + insert(('foo\n'):rep(9) .. 'foofoobarfoofoo' .. ('\nfoo'):rep(9)) + command('set concealcursor=n') + command('syn match Foo /bar/ conceal cchar=&') + feed('gg5<C-E>10gg$') + screen:expect([[ + foo |*4 + foofoo{1:&}foofo^o | + foo |*4 + | + ]]) + feed('zz') + screen:expect_unchanged() + feed('zt') + screen:expect([[ + foofoo{1:&}foofo^o | + foo |*8 + | + ]]) + feed('zt') + screen:expect_unchanged() + feed('zb') + screen:expect([[ + foo |*8 + foofoo{1:&}foofo^o | + | + ]]) + feed('zb') + screen:expect_unchanged() + end) end) -- match and conceal describe('let the conceal level be', function() diff --git a/test/functional/ui/title_spec.lua b/test/functional/ui/title_spec.lua index 66eb15478b..2de1e71457 100644 --- a/test/functional/ui/title_spec.lua +++ b/test/functional/ui/title_spec.lua @@ -37,6 +37,63 @@ describe('title', function() end) end) + it('is updated in Insert mode', function() + api.nvim_set_option_value('title', true, {}) + screen:expect(function() + eq('[No Name] - Nvim', screen.title) + end) + feed('ifoo') + screen:expect(function() + eq('[No Name] + - Nvim', screen.title) + end) + feed('<Esc>') + api.nvim_set_option_value('titlestring', '%m %f (%{mode(1)}) | nvim', {}) + screen:expect(function() + eq('[+] [No Name] (n) | nvim', screen.title) + end) + feed('i') + screen:expect(function() + eq('[+] [No Name] (i) | nvim', screen.title) + end) + feed('<Esc>') + screen:expect(function() + eq('[+] [No Name] (n) | nvim', screen.title) + end) + end) + + it('is updated in Cmdline mode', function() + api.nvim_set_option_value('title', true, {}) + api.nvim_set_option_value('titlestring', '%f (%{mode(1)}) | nvim', {}) + screen:expect(function() + eq('[No Name] (n) | nvim', screen.title) + end) + feed(':') + screen:expect(function() + eq('[No Name] (c) | nvim', screen.title) + end) + feed('<Esc>') + screen:expect(function() + eq('[No Name] (n) | nvim', screen.title) + end) + end) + + it('is updated in Terminal mode', function() + api.nvim_set_option_value('title', true, {}) + api.nvim_set_option_value('titlestring', '(%{mode(1)}) | nvim', {}) + fn.jobstart({ n.testprg('shell-test'), 'INTERACT' }, { term = true }) + screen:expect(function() + eq('(nt) | nvim', screen.title) + end) + feed('i') + screen:expect(function() + eq('(t) | nvim', screen.title) + end) + feed([[<C-\><C-N>]]) + screen:expect(function() + eq('(nt) | nvim', screen.title) + end) + end) + describe('is not changed by', function() local file1 = is_os('win') and 'C:\\mydir\\myfile1' or '/mydir/myfile1' local file2 = is_os('win') and 'C:\\mydir\\myfile2' or '/mydir/myfile2' diff --git a/test/functional/vimscript/ctx_functions_spec.lua b/test/functional/vimscript/ctx_functions_spec.lua index 873e4f820d..85a74c0ab6 100644 --- a/test/functional/vimscript/ctx_functions_spec.lua +++ b/test/functional/vimscript/ctx_functions_spec.lua @@ -188,7 +188,10 @@ describe('context functions', function() function RestoreFuncs() call ctxpop() endfunction + + let g:sid = expand('<SID>') ]]) + local sid = api.nvim_get_var('sid') eq('Hello, World!', exec_capture([[call Greet('World')]])) eq( @@ -200,11 +203,11 @@ describe('context functions', function() call('DeleteSFuncs') eq( - 'function Greet, line 1: Vim(call):E117: Unknown function: s:greet', + ('function Greet, line 1: Vim(call):E117: Unknown function: %sgreet'):format(sid), pcall_err(command, [[call Greet('World')]]) ) eq( - 'function GreetAll, line 1: Vim(call):E117: Unknown function: s:greet_all', + ('function GreetAll, line 1: Vim(call):E117: Unknown function: %sgreet_all'):format(sid), pcall_err(command, [[call GreetAll('World', 'One', 'Two', 'Three')]]) ) diff --git a/test/functional/vimscript/getchar_spec.lua b/test/functional/vimscript/getchar_spec.lua new file mode 100644 index 0000000000..4ecf082f97 --- /dev/null +++ b/test/functional/vimscript/getchar_spec.lua @@ -0,0 +1,95 @@ +local n = require('test.functional.testnvim')() +local Screen = require('test.functional.ui.screen') + +local clear = n.clear +local exec = n.exec +local feed = n.feed +local async_command = n.async_meths.nvim_command +local poke_eventloop = n.poke_eventloop + +describe('getchar()', function() + before_each(clear) + + -- oldtest: Test_getchar_cursor_position() + it('cursor positioning', function() + local screen = Screen.new(40, 6) + exec([[ + call setline(1, ['foobar', 'foobar', 'foobar']) + call cursor(3, 6) + ]]) + screen:expect([[ + foobar |*2 + fooba^r | + {1:~ }|*2 + | + ]]) + + -- Default: behaves like "msg" when immediately after printing a message, + -- even if :sleep moved cursor elsewhere. + for _, cmd in ipairs({ + 'echo 1234 | call getchar()', + 'echo 1234 | call getchar(-1, {})', + "echo 1234 | call getchar(-1, #{cursor: 'msg'})", + 'echo 1234 | sleep 1m | call getchar()', + 'echo 1234 | sleep 1m | call getchar(-1, {})', + "echo 1234 | sleep 1m | call getchar(-1, #{cursor: 'msg'})", + }) do + async_command(cmd) + screen:expect([[ + foobar |*3 + {1:~ }|*2 + 1234^ | + ]]) + feed('a') + screen:expect([[ + foobar |*2 + fooba^r | + {1:~ }|*2 + 1234 | + ]]) + end + + -- Default: behaves like "keep" when not immediately after printing a message. + for _, cmd in ipairs({ + 'call getchar()', + 'call getchar(-1, {})', + "call getchar(-1, #{cursor: 'keep'})", + "echo 1234 | sleep 1m | call getchar(-1, #{cursor: 'keep'})", + }) do + async_command(cmd) + poke_eventloop() + screen:expect_unchanged() + feed('a') + poke_eventloop() + screen:expect_unchanged() + end + + async_command("call getchar(-1, #{cursor: 'msg'})") + screen:expect([[ + foobar |*3 + {1:~ }|*2 + ^1234 | + ]]) + feed('a') + screen:expect([[ + foobar |*2 + fooba^r | + {1:~ }|*2 + 1234 | + ]]) + + async_command("call getchar(-1, #{cursor: 'hide'})") + screen:expect([[ + foobar |*3 + {1:~ }|*2 + 1234 | + ]]) + feed('a') + screen:expect([[ + foobar |*2 + fooba^r | + {1:~ }|*2 + 1234 | + ]]) + end) +end) diff --git a/test/functional/vimscript/null_spec.lua b/test/functional/vimscript/null_spec.lua index 9a27239a6d..afd50f7cf9 100644 --- a/test/functional/vimscript/null_spec.lua +++ b/test/functional/vimscript/null_spec.lua @@ -116,7 +116,7 @@ describe('NULL', function() null_expr_test( 'is accepted as an empty list by inputlist()', '[feedkeys("\\n"), inputlist(L)]', - 'Type number and <Enter> or click with the mouse (q or empty cancels): ', + '', { 0, 0 } ) null_expr_test( diff --git a/test/functional/vimscript/timer_spec.lua b/test/functional/vimscript/timer_spec.lua index d1b8bfe5d9..3e4e6de35c 100644 --- a/test/functional/vimscript/timer_spec.lua +++ b/test/functional/vimscript/timer_spec.lua @@ -94,12 +94,56 @@ describe('timers', function() assert(0 <= diff and diff <= 4, 'expected (0 <= diff <= 4), got: ' .. tostring(diff)) end) + it('are triggered in inputlist() call #7857', function() + async_meths.nvim_exec2( + [[ + call timer_start(5, 'MyHandler', {'repeat': -1}) + let g:val = 0 + let g:n = inputlist(['input0', 'input1']) + ]], + {} + ) + retry(nil, nil, function() + local val = eval('g:val') + ok(val >= 2, '>= 2', tostring(val)) + eq(0, eval("exists('g:n')")) + end) + feed('42<CR>') + eq(42, eval('g:n')) + end) + + it('are triggered in confirm() call', function() + api.nvim_ui_attach(80, 24, {}) -- needed for confirm() to work + async_meths.nvim_exec2( + [[ + call timer_start(5, 'MyHandler', {'repeat': -1}) + let g:val = 0 + let g:n = confirm('Are you sure?', "&Yes\n&No\n&Cancel") + ]], + {} + ) + retry(nil, nil, function() + local val = eval('g:val') + ok(val >= 2, '>= 2', tostring(val)) + eq(0, eval("exists('g:n')")) + end) + feed('c') + eq(3, eval('g:n')) + end) + it('are triggered in blocking getchar() call', function() - command("call timer_start(5, 'MyHandler', {'repeat': -1})") - async_meths.nvim_command('let g:val = 0 | let g:c = getchar()') + async_meths.nvim_exec2( + [[ + call timer_start(5, 'MyHandler', {'repeat': -1}) + let g:val = 0 + let g:c = getchar() + ]], + {} + ) retry(nil, nil, function() local val = eval('g:val') ok(val >= 2, '>= 2', tostring(val)) + eq(0, eval("exists('g:c')")) eq(0, eval('getchar(1)')) end) feed('c') @@ -126,39 +170,36 @@ describe('timers', function() redraw endfunc ]]) - async_meths.nvim_command('let g:c2 = getchar()') + async_meths.nvim_command("let g:c2 = getchar(-1, {'cursor': 'msg'})") async_meths.nvim_command( 'call timer_start(' .. load_adjust(100) .. ", 'AddItem', {'repeat': -1})" ) screen:expect([[ - ^ITEM 1 | + ITEM 1 | ITEM 2 | {1:~ }|*3 - | + ^ | ]]) async_meths.nvim_command('let g:cont = 1') screen:expect([[ - ^ITEM 1 | + ITEM 1 | ITEM 2 | ITEM 3 | {1:~ }|*2 - | + ^ | ]]) feed('3') eq(51, eval('g:c2')) - screen:expect { - grid = [[ + screen:expect([[ ^ITEM 1 | ITEM 2 | ITEM 3 | {1:~ }|*2 | - ]], - unchanged = true, - } + ]]) end) it('can be stopped', function() |