diff options
| author | Josh Rahm <joshuarahm@gmail.com> | 2024-11-19 22:57:13 +0000 |
|---|---|---|
| committer | Josh Rahm <joshuarahm@gmail.com> | 2024-11-19 22:57:13 +0000 |
| commit | 9be89f131f87608f224f0ee06d199fcd09d32176 (patch) | |
| tree | 11022dcfa9e08cb4ac5581b16734196128688d48 /test/functional | |
| parent | ff7ed8f586589d620a806c3758fac4a47a8e7e15 (diff) | |
| parent | 88085c2e80a7e3ac29aabb6b5420377eed99b8b6 (diff) | |
| download | rneovim-9be89f131f87608f224f0ee06d199fcd09d32176.tar.gz rneovim-9be89f131f87608f224f0ee06d199fcd09d32176.tar.bz2 rneovim-9be89f131f87608f224f0ee06d199fcd09d32176.zip | |
Merge remote-tracking branch 'upstream/master' into mix_20240309
Diffstat (limited to 'test/functional')
147 files changed, 16011 insertions, 7731 deletions
diff --git a/test/functional/api/buffer_spec.lua b/test/functional/api/buffer_spec.lua index cf69958fd8..3775c8c7b7 100644 --- a/test/functional/api/buffer_spec.lua +++ b/test/functional/api/buffer_spec.lua @@ -125,11 +125,6 @@ describe('api/buf', function() it('cursor position is maintained consistently with viewport', function() local screen = Screen.new(20, 12) - screen:set_default_attr_ids { - [1] = { bold = true, foreground = Screen.colors.Blue1 }, - [2] = { reverse = true, bold = true }, - [3] = { reverse = true }, - } screen:attach() local lines = { 'line1', 'line2', 'line3', 'line4', 'line5', 'line6' } @@ -143,11 +138,11 @@ describe('api/buf', function() grid = [[ ^ | {1:~ }|*4 - {2:[No Name] }| + {3:[No Name] }| line5 | line6 | {1:~ }|*2 - {3:[No Name] [+] }| + {2:[No Name] [+] }| | ]], } @@ -158,11 +153,11 @@ describe('api/buf', function() grid = [[ ^ | {1:~ }|*4 - {2:[No Name] }| + {3:[No Name] }| boogalo 5 | line6 | {1:~ }|*2 - {3:[No Name] [+] }| + {2:[No Name] [+] }| | ]], } @@ -172,11 +167,11 @@ describe('api/buf', function() grid = [[ | {1:~ }|*4 - {3:[No Name] }| + {2:[No Name] }| boogalo 5 | ^line6 | {1:~ }|*2 - {2:[No Name] [+] }| + {3:[No Name] [+] }| | ]], } @@ -216,11 +211,6 @@ describe('api/buf', function() local screen before_each(function() screen = Screen.new(20, 12) - screen:set_default_attr_ids { - [1] = { bold = true, foreground = Screen.colors.Blue1 }, - [2] = { reverse = true, bold = true }, - [3] = { reverse = true }, - } screen:attach() api.nvim_buf_set_lines( 0, @@ -243,12 +233,12 @@ describe('api/buf', function() grid = [[ | {1:~ }|*4 - {3:[No Name] }| + {2:[No Name] }| www | xxx | yyy | ^zzz | - {2:[No Name] }| + {3:[No Name] }| | ]], } @@ -258,12 +248,12 @@ describe('api/buf', function() grid = [[ | {1:~ }|*4 - {3:[No Name] }| + {2:[No Name] }| www | xxx | yyy | ^zzz | - {2:[No Name] [+] }| + {3:[No Name] [+] }| | ]], } @@ -274,12 +264,12 @@ describe('api/buf', function() grid = [[ | {1:~ }|*4 - {3:[No Name] }| + {2:[No Name] }| wwweeee | xxx | yyy | ^zzz | - {2:[No Name] [+] }| + {3:[No Name] [+] }| | ]], } @@ -290,12 +280,12 @@ describe('api/buf', function() grid = [[ | {1:~ }|*4 - {3:[No Name] }| + {2:[No Name] }| wwweeee | xxx | yyy | ^zzz | - {2:[No Name] [+] }| + {3:[No Name] [+] }| | ]], unchanged = true, @@ -306,12 +296,12 @@ describe('api/buf', function() grid = [[ | {1:~ }|*4 - {3:[No Name] }| + {2:[No Name] }| wwweeee | xxx | ^yyy | zzz | - {2:[No Name] [+] }| + {3:[No Name] [+] }| | ]], } @@ -321,12 +311,12 @@ describe('api/buf', function() grid = [[ | {1:~ }|*4 - {3:[No Name] }| + {2:[No Name] }| mmmeeeee | wwweeee | xxx | ^yyy | - {2:[No Name] [+] }| + {3:[No Name] [+] }| | ]], } @@ -343,12 +333,12 @@ describe('api/buf', function() grid = [[ ^ | {1:~ }|*4 - {2:[No Name] }| + {3:[No Name] }| www | xxx | yyy | zzz | - {3:[No Name] }| + {2:[No Name] }| | ]], } @@ -358,12 +348,12 @@ describe('api/buf', function() grid = [[ ^ | {1:~ }|*4 - {2:[No Name] }| + {3:[No Name] }| www | xxx | yyy | zzz | - {3:[No Name] [+] }| + {2:[No Name] [+] }| | ]], } @@ -374,12 +364,12 @@ describe('api/buf', function() grid = [[ ^ | {1:~ }|*4 - {2:[No Name] }| + {3:[No Name] }| wwweeee | xxx | yyy | zzz | - {3:[No Name] [+] }| + {2:[No Name] [+] }| | ]], } @@ -389,12 +379,12 @@ describe('api/buf', function() grid = [[ ^ | {1:~ }|*4 - {2:[No Name] }| + {3:[No Name] }| wwweeee | xxx | yyy | zzz | - {3:[No Name] [+] }| + {2:[No Name] [+] }| | ]], unchanged = true, @@ -416,12 +406,12 @@ describe('api/buf', function() ccc | ddd | www | - {2:[No Name] }| + {3:[No Name] }| www | xxx | yyy | zzz | - {3:[No Name] }| + {2:[No Name] }| | ]], } @@ -434,12 +424,12 @@ describe('api/buf', function() ddd | www | xxx | - {2:[No Name] [+] }| + {3:[No Name] [+] }| www | xxx | yyy | zzz | - {3:[No Name] [+] }| + {2:[No Name] [+] }| | ]], } @@ -453,12 +443,12 @@ describe('api/buf', function() ddd | wwweeee | xxx | - {2:[No Name] [+] }| + {3:[No Name] [+] }| wwweeee | xxx | yyy | zzz | - {3:[No Name] [+] }| + {2:[No Name] [+] }| | ]], } @@ -471,12 +461,12 @@ describe('api/buf', function() ddd | mmm | wwweeee | - {2:[No Name] [+] }| + {3:[No Name] [+] }| wwweeee | xxx | yyy | zzz | - {3:[No Name] [+] }| + {2:[No Name] [+] }| | ]], } @@ -745,10 +735,6 @@ describe('api/buf', function() it("set_lines of invisible buffer doesn't move cursor in current window", function() local screen = Screen.new(20, 5) - screen:set_default_attr_ids({ - [1] = { bold = true, foreground = Screen.colors.Blue1 }, - [2] = { bold = true }, - }) screen:attach() insert([[ @@ -771,7 +757,7 @@ describe('api/buf', function() A real window | with proper tex^t | {1:~ }| - {2:-- INSERT --} | + {5:-- INSERT --} | ]]) end) @@ -1756,11 +1742,6 @@ describe('api/buf', function() local screen before_each(function() screen = Screen.new(20, 12) - screen:set_default_attr_ids { - [1] = { bold = true, foreground = Screen.colors.Blue1 }, - [2] = { reverse = true, bold = true }, - [3] = { reverse = true }, - } screen:attach() api.nvim_buf_set_lines( 0, @@ -1783,12 +1764,12 @@ describe('api/buf', function() grid = [[ | {1:~ }|*4 - {3:[No Name] }| + {2:[No Name] }| www | xxx | yyy | ^zzz | - {2:[No Name] }| + {3:[No Name] }| | ]], } @@ -1798,12 +1779,12 @@ describe('api/buf', function() grid = [[ | {1:~ }|*4 - {3:[No Name] }| + {2:[No Name] }| www | xxx | yyy | ^zzz | - {2:[No Name] [+] }| + {3:[No Name] [+] }| | ]], } @@ -1820,12 +1801,12 @@ describe('api/buf', function() grid = [[ ^ | {1:~ }|*4 - {2:[No Name] }| + {3:[No Name] }| www | xxx | yyy | zzz | - {3:[No Name] }| + {2:[No Name] }| | ]], } @@ -1835,12 +1816,12 @@ describe('api/buf', function() grid = [[ ^ | {1:~ }|*4 - {2:[No Name] }| + {3:[No Name] }| www | xxx | yyy | zzz | - {3:[No Name] [+] }| + {2:[No Name] [+] }| | ]], } @@ -1861,12 +1842,12 @@ describe('api/buf', function() ccc | ddd | www | - {2:[No Name] }| + {3:[No Name] }| www | xxx | yyy | zzz | - {3:[No Name] }| + {2:[No Name] }| | ]], } @@ -1879,12 +1860,12 @@ describe('api/buf', function() ddd | www | xxx | - {2:[No Name] [+] }| + {3:[No Name] [+] }| www | xxx | yyy | zzz | - {3:[No Name] [+] }| + {2:[No Name] [+] }| | ]], } @@ -1910,6 +1891,8 @@ describe('api/buf', function() eq({ '' }, get_text(0, 0, 18, 0, 20, {})) eq({ 'ext' }, get_text(0, -2, 1, -2, 4, {})) eq({ 'hello foo!', 'text', 'm' }, get_text(0, 0, 0, 2, 1, {})) + eq({ 'hello foo!' }, get_text(0, 0, -987654321, 0, 987654321, {})) + eq({ '' }, get_text(0, 0, -15, 0, -20, {})) end) it('errors on out-of-range', function() @@ -1923,7 +1906,7 @@ describe('api/buf', function() it('errors when start is greater than end', function() eq("'start' is higher than 'end'", pcall_err(get_text, 0, 1, 0, 0, 0, {})) - eq('start_col must be less than end_col', pcall_err(get_text, 0, 0, 1, 0, 0, {})) + eq('start_col must be less than or equal to end_col', pcall_err(get_text, 0, 0, 1, 0, 0, {})) end) end) diff --git a/test/functional/api/buffer_updates_spec.lua b/test/functional/api/buffer_updates_spec.lua index e030b45396..527394bfd1 100644 --- a/test/functional/api/buffer_updates_spec.lua +++ b/test/functional/api/buffer_updates_spec.lua @@ -27,12 +27,10 @@ end local function sendkeys(keys) api.nvim_input(keys) - -- give nvim some time to process msgpack requests before possibly sending + -- Wait for Nvim to fully process pending input before possibly sending -- more key presses - otherwise they all pile up in the queue and get -- processed at once - local ntime = os.clock() + 0.1 - repeat - until os.clock() > ntime + n.poke_eventloop() end local function open(activate, lines) diff --git a/test/functional/api/extmark_spec.lua b/test/functional/api/extmark_spec.lua index 7b2fe209ba..52d8fd5097 100644 --- a/test/functional/api/extmark_spec.lua +++ b/test/functional/api/extmark_spec.lua @@ -1758,13 +1758,13 @@ describe('API/extmarks', function() command('1d 2') eq(0, #get_extmarks(-1, 0, -1, {})) -- mark is not removed when deleting bytes before the range - set_extmark( - ns, - 3, - 0, - 4, - { invalidate = true, undo_restore = false, hl_group = 'Error', end_col = 7 } - ) + set_extmark(ns, 3, 0, 4, { + invalidate = true, + undo_restore = true, + hl_group = 'Error', + end_col = 7, + right_gravity = false, + }) feed('dw') eq(3, get_extmark_by_id(ns, 3, { details = true })[3].end_col) -- mark is not removed when deleting bytes at the start of the range @@ -1778,15 +1778,18 @@ describe('API/extmarks', function() eq(1, get_extmark_by_id(ns, 3, { details = true })[3].end_col) -- mark is removed when all bytes in the range are deleted feed('hx') - eq({}, get_extmark_by_id(ns, 3, {})) + eq(true, get_extmark_by_id(ns, 3, { details = true })[3].invalid) + -- mark is restored with undo_restore == true if pos did not change + command('undo') + eq(nil, get_extmark_by_id(ns, 3, { details = true })[3].invalid) -- multiline mark is not removed when start of its range is deleted - set_extmark( - ns, - 4, - 1, - 4, - { undo_restore = false, invalidate = true, hl_group = 'Error', end_col = 7, end_row = 3 } - ) + set_extmark(ns, 4, 1, 4, { + undo_restore = false, + invalidate = true, + hl_group = 'Error', + end_col = 7, + end_row = 3, + }) feed('ddDdd') eq({ 0, 0 }, get_extmark_by_id(ns, 4, {})) -- multiline mark is removed when entirety of its range is deleted @@ -1795,10 +1798,15 @@ describe('API/extmarks', function() end) it('can set a URL', function() - set_extmark(ns, 1, 0, 0, { url = 'https://example.com', end_col = 3 }) + local url1 = 'https://example.com' + local url2 = 'http://127.0.0.1' + set_extmark(ns, 1, 0, 0, { url = url1, end_col = 3 }) + set_extmark(ns, 2, 0, 3, { url = url2, hl_group = 'Search', end_col = 5 }) local extmarks = get_extmarks(ns, 0, -1, { details = true }) - eq(1, #extmarks) - eq('https://example.com', extmarks[1][4].url) + eq(2, #extmarks) + eq(url1, extmarks[1][4].url) + eq(url2, extmarks[2][4].url) + eq('Search', extmarks[2][4].hl_group) end) it('respects priority', function() diff --git a/test/functional/api/version_spec.lua b/test/functional/api/version_spec.lua index 5dad9978b7..617786eb2d 100644 --- a/test/functional/api/version_spec.lua +++ b/test/functional/api/version_spec.lua @@ -58,10 +58,18 @@ describe('api metadata', function() return by_name end - -- Remove metadata that is not essential to backwards-compatibility. - local function filter_function_metadata(f) + -- Remove or patch metadata that is not essential to backwards-compatibility. + local function normalize_func_metadata(f) + -- Dictionary was renamed to Dict. That doesn't break back-compat because clients don't actually + -- use the `return_type` field (evidence: "ArrayOf(…)" didn't break clients). + f.return_type = f.return_type:gsub('Dictionary', 'Dict') + f.deprecated_since = nil for idx, _ in ipairs(f.parameters) do + -- Dictionary was renamed to Dict. Doesn't break back-compat because clients don't actually + -- use the `parameters` field of API metadata (evidence: "ArrayOf(…)" didn't break clients). + f.parameters[idx][1] = f.parameters[idx][1]:gsub('Dictionary', 'Dict') + f.parameters[idx][2] = '' -- Remove parameter name. end @@ -141,7 +149,7 @@ describe('api metadata', function() ) end else - eq(filter_function_metadata(f), filter_function_metadata(funcs_new[f.name])) + eq(normalize_func_metadata(f), normalize_func_metadata(funcs_new[f.name])) end end funcs_compat[level] = name_table(old_api[level].functions) diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index fd0535aa51..af4d4854f5 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -367,14 +367,11 @@ describe('API', function() it('displays messages when opts.output=false', function() local screen = Screen.new(40, 8) screen:attach() - screen:set_default_attr_ids({ - [0] = { bold = true, foreground = Screen.colors.Blue }, - }) api.nvim_exec2("echo 'hello'", { output = false }) screen:expect { grid = [[ ^ | - {0:~ }|*6 + {1:~ }|*6 hello | ]], } @@ -383,14 +380,11 @@ describe('API', function() it("doesn't display messages when output=true", function() local screen = Screen.new(40, 6) screen:attach() - screen:set_default_attr_ids({ - [0] = { bold = true, foreground = Screen.colors.Blue }, - }) api.nvim_exec2("echo 'hello'", { output = true }) screen:expect { grid = [[ ^ | - {0:~ }|*4 + {1:~ }|*4 | ]], } @@ -403,7 +397,7 @@ describe('API', function() screen:expect { grid = [[ ^ | - {0:~ }|*4 + {1:~ }|*4 15 | ]], } @@ -699,7 +693,7 @@ describe('API', function() pcall_err(request, 'nvim_call_dict_function', "{ 'f': '' }", 'f', { 1, 2 }) ) eq( - 'dict argument type must be String or Dictionary', + 'dict argument type must be String or Dict', pcall_err(request, 'nvim_call_dict_function', 42, 'f', { 1, 2 }) ) eq( @@ -1307,12 +1301,136 @@ describe('API', function() end) it('crlf=false does not break lines at CR, CRLF', function() api.nvim_paste('line 1\r\n\r\rline 2\nline 3\rline 4\r', false, -1) - expect('line 1\r\n\r\rline 2\nline 3\rline 4\r') + local expected = 'line 1\r\n\r\rline 2\nline 3\rline 4\r' + expect(expected) eq({ 0, 3, 14, 0 }, fn.getpos('.')) + feed('u') -- Undo. + expect('') + feed('.') -- Dot-repeat. + expect(expected) + end) + describe('repeating a paste via redo/recording', function() + -- Test with indent and control chars and multibyte chars containing 0x80 bytes + local text = dedent(([[ + foo + bar + baz + !!!%s!!!%s!!!%s!!! + 最…倒…倀… + ]]):format('\0', '\2\3\6\21\22\23\24\27', '\127')) + before_each(function() + api.nvim_set_option_value('autoindent', true, {}) + end) + local function test_paste_repeat_normal_insert(is_insert) + feed('qr' .. (is_insert and 'i' or '')) + eq('r', fn.reg_recording()) + api.nvim_paste(text, true, -1) + feed(is_insert and '<Esc>' or '') + expect(text) + feed('.') + expect(text:rep(2)) + feed('q') + eq('', fn.reg_recording()) + feed('3.') + expect(text:rep(5)) + feed('2@r') + expect(text:rep(9)) + end + it('works in Normal mode', function() + test_paste_repeat_normal_insert(false) + end) + it('works in Insert mode', function() + test_paste_repeat_normal_insert(true) + end) + local function test_paste_repeat_visual_select(is_select) + insert(('xxx\n'):rep(5)) + feed('ggqr' .. (is_select and 'gH' or 'V')) + api.nvim_paste(text, true, -1) + feed('q') + expect(text .. ('xxx\n'):rep(4)) + feed('2@r') + expect(text:rep(3) .. ('xxx\n'):rep(2)) + end + it('works in Visual mode (recording only)', function() + test_paste_repeat_visual_select(false) + end) + it('works in Select mode (recording only)', function() + test_paste_repeat_visual_select(true) + end) + end) + it('in a mapping recorded in a macro', function() + command([[nnoremap <F2> <Cmd>call nvim_paste('foo', v:false, -1)<CR>]]) + feed('qr<F2>$q') + expect('foo') + feed('@r') -- repeating a macro containing the mapping should only paste once + expect('foofoo') + end) + local function test_paste_cancel_error(is_error) + before_each(function() + exec_lua(([[ + vim.paste = (function(overridden) + return function(lines, phase) + for i, line in ipairs(lines) do + if line == 'CANCEL' then + %s + end + end + return overridden(lines, phase) + end + end)(vim.paste) + ]]):format(is_error and 'error("fake fail")' or 'return false')) + end) + local function check_paste_cancel_error(data, crlf, phase) + if is_error then + eq('fake fail', pcall_err(api.nvim_paste, data, crlf, phase)) + else + eq(false, api.nvim_paste(data, crlf, phase)) + end + end + it('in phase -1', function() + feed('A') + check_paste_cancel_error('CANCEL', true, -1) + feed('<Esc>') + expect('') + feed('.') + expect('') + end) + it('in phase 1', function() + feed('A') + check_paste_cancel_error('CANCEL', true, 1) + feed('<Esc>') + expect('') + feed('.') + expect('') + end) + it('in phase 2', function() + feed('A') + eq(true, api.nvim_paste('aaa', true, 1)) + expect('aaa') + check_paste_cancel_error('CANCEL', true, 2) + feed('<Esc>') + expect('aaa') + feed('.') + expect('aaaaaa') + end) + it('in phase 3', function() + feed('A') + eq(true, api.nvim_paste('aaa', true, 1)) + expect('aaa') + eq(true, api.nvim_paste('bbb', true, 2)) + expect('aaabbb') + check_paste_cancel_error('CANCEL', true, 3) + feed('<Esc>') + expect('aaabbb') + feed('.') + expect('aaabbbaaabbb') + end) + end + describe('vim.paste() cancel', function() + test_paste_cancel_error(false) end) - it('vim.paste() failure', function() - api.nvim_exec_lua('vim.paste = (function(lines, phase) error("fake fail") end)', {}) - eq('fake fail', pcall_err(request, 'nvim_paste', 'line 1\nline 2\nline 3', false, 1)) + describe('vim.paste() error', function() + test_paste_cancel_error(true) end) end) @@ -1441,6 +1559,28 @@ describe('API', function() it('cannot handle NULs', function() eq(0, api.nvim_strwidth('\0abc')) end) + + it('can handle emoji with variant selectors and ZWJ', function() + local selector = '❤️' + eq(2, fn.strchars(selector)) + eq(1, fn.strcharlen(selector)) + eq(2, api.nvim_strwidth(selector)) + + local no_selector = '❤' + eq(1, fn.strchars(no_selector)) + eq(1, fn.strcharlen(no_selector)) + eq(1, api.nvim_strwidth(no_selector)) + + local selector_zwj_selector = '🏳️⚧️' + eq(5, fn.strchars(selector_zwj_selector)) + eq(1, fn.strcharlen(selector_zwj_selector)) + eq(2, api.nvim_strwidth(selector_zwj_selector)) + + local emoji_zwj_emoji = '🧑🌾' + eq(3, fn.strchars(emoji_zwj_emoji)) + eq(1, fn.strcharlen(emoji_zwj_emoji)) + eq(2, api.nvim_strwidth(emoji_zwj_emoji)) + end) end) describe('nvim_get_current_line, nvim_set_current_line', function() @@ -1503,9 +1643,11 @@ describe('API', function() it('nvim_get_vvar, nvim_set_vvar', function() eq('Key is read-only: count', pcall_err(request, 'nvim_set_vvar', 'count', 42)) - eq('Dictionary is locked', pcall_err(request, 'nvim_set_vvar', 'nosuchvar', 42)) + eq('Dict is locked', pcall_err(request, 'nvim_set_vvar', 'nosuchvar', 42)) api.nvim_set_vvar('errmsg', 'set by API') eq('set by API', api.nvim_get_vvar('errmsg')) + api.nvim_set_vvar('completed_item', { word = 'a', user_data = vim.empty_dict() }) + eq({}, api.nvim_get_vvar('completed_item')['user_data']) api.nvim_set_vvar('errmsg', 42) eq('42', eval('v:errmsg')) api.nvim_set_vvar('oldfiles', { 'one', 'two' }) @@ -1533,16 +1675,12 @@ describe('API', function() eq({ 1, 5 }, api.nvim_win_get_cursor(0)) local screen = Screen.new(60, 3) - screen:set_default_attr_ids({ - [0] = { bold = true, foreground = Screen.colors.Blue }, - [1] = { background = Screen.colors.Yellow }, - }) screen:attach() eq(1, eval('v:hlsearch')) screen:expect { grid = [[ - {1:foo} {1:^foo} {1:foo} | - {0:~ }| + {10:foo} {10:^foo} {10:foo} | + {1:~ }| | ]], } @@ -1551,7 +1689,7 @@ describe('API', function() screen:expect { grid = [[ foo ^foo foo | - {0:~ }| + {1:~ }| | ]], } @@ -1559,8 +1697,8 @@ describe('API', function() eq(1, eval('v:hlsearch')) screen:expect { grid = [[ - {1:foo} {1:^foo} {1:foo} | - {0:~ }| + {10:foo} {10:^foo} {10:foo} | + {1:~ }| | ]], } @@ -1581,9 +1719,9 @@ describe('API', function() eq(val2, request('vim_del_var', 'lua')) end) - it('truncates values with NULs in them', function() + it('preserves values with NULs in them', function() api.nvim_set_var('xxx', 'ab\0cd') - eq('ab', api.nvim_get_var('xxx')) + eq('ab\000cd', api.nvim_get_var('xxx')) end) end) @@ -2144,7 +2282,7 @@ describe('API', function() end) describe('nvim_load_context', function() - it('sets current editor state to given context dictionary', function() + it('sets current editor state to given context dict', function() local opts = { types = { 'regs', 'jumps', 'bufs', 'gvars' } } eq({}, parse_context(api.nvim_get_context(opts))) @@ -2160,7 +2298,7 @@ describe('API', function() eq({ 1, 2, 3 }, eval('[g:one, g:Two, g:THREE]')) end) - it('errors when context dictionary is invalid', function() + it('errors when context dict is invalid', function() eq( 'E474: Failed to convert list to msgpack string buffer', pcall_err(api.nvim_load_context, { regs = { {} }, jumps = { {} } }) @@ -2254,12 +2392,6 @@ describe('API', function() before_each(function() screen = Screen.new(40, 8) screen:attach() - screen:set_default_attr_ids({ - [0] = { bold = true, foreground = Screen.colors.Blue }, - [1] = { bold = true, foreground = Screen.colors.SeaGreen }, - [2] = { bold = true, reverse = true }, - [3] = { foreground = Screen.colors.Blue }, - }) end) it('prints long messages correctly #20534', function() @@ -2287,11 +2419,11 @@ describe('API', function() screen:expect { grid = [[ | - {0:~ }|*3 - {2: }| + {1:~ }|*3 + {3: }| | a | - {1:Press ENTER or type command to continue}^ | + {6:Press ENTER or type command to continue}^ | ]], } feed('<CR>') @@ -2299,12 +2431,12 @@ describe('API', function() screen:expect { grid = [[ | - {0:~ }|*2 - {2: }| + {1:~ }|*2 + {3: }| b | | c | - {1:Press ENTER or type command to continue}^ | + {6:Press ENTER or type command to continue}^ | ]], } end) @@ -2314,11 +2446,11 @@ describe('API', function() screen:expect { grid = [[ | - {0:~ }|*3 - {2: }| - aaa{3:^@}bbb{3:^@^@}ccc | - ddd{3:^@^@^@}eee | - {1:Press ENTER or type command to continue}^ | + {1:~ }|*3 + {3: }| + aaa{18:^@}bbb{18:^@^@}ccc | + ddd{18:^@^@^@}eee | + {6:Press ENTER or type command to continue}^ | ]], } end) @@ -2330,20 +2462,14 @@ describe('API', function() before_each(function() screen = Screen.new(40, 8) screen:attach() - screen:set_default_attr_ids({ - [0] = { bold = true, foreground = Screen.colors.Blue }, - [1] = { foreground = Screen.colors.White, background = Screen.colors.Red }, - [2] = { bold = true, foreground = Screen.colors.SeaGreen }, - [3] = { bold = true, reverse = true }, - }) end) it('can show one line', function() async_meths.nvim_err_write('has bork\n') screen:expect([[ ^ | - {0:~ }|*6 - {1:has bork} | + {1:~ }|*6 + {9:has bork} | ]]) end) @@ -2351,11 +2477,11 @@ describe('API', function() async_meths.nvim_err_write('something happened\nvery bad\n') screen:expect([[ | - {0:~ }|*3 + {1:~ }|*3 {3: }| - {1:something happened} | - {1:very bad} | - {2:Press ENTER or type command to continue}^ | + {9:something happened} | + {9:very bad} | + {6:Press ENTER or type command to continue}^ | ]]) end) @@ -2363,13 +2489,13 @@ describe('API', function() async_meths.nvim_err_write('FAILURE\nERROR\nEXCEPTION\nTRACEBACK\n') screen:expect([[ | - {0:~ }| + {1:~ }| {3: }| - {1:FAILURE} | - {1:ERROR} | - {1:EXCEPTION} | - {1:TRACEBACK} | - {2:Press ENTER or type command to continue}^ | + {9:FAILURE} | + {9:ERROR} | + {9:EXCEPTION} | + {9:TRACEBACK} | + {6:Press ENTER or type command to continue}^ | ]]) end) @@ -2379,8 +2505,8 @@ describe('API', function() async_meths.nvim_err_write('fail\n') screen:expect([[ ^ | - {0:~ }|*6 - {1:very fail} | + {1:~ }|*6 + {9:very fail} | ]]) n.poke_eventloop() @@ -2388,11 +2514,11 @@ describe('API', function() async_meths.nvim_err_write('more fail\ntoo fail\n') screen:expect([[ | - {0:~ }|*3 + {1:~ }|*3 {3: }| - {1:more fail} | - {1:too fail} | - {2:Press ENTER or type command to continue}^ | + {9:more fail} | + {9:too fail} | + {6:Press ENTER or type command to continue}^ | ]]) feed('<cr>') -- exit the press ENTER screen end) @@ -2402,11 +2528,11 @@ describe('API', function() screen:expect { grid = [[ | - {0:~ }|*3 + {1:~ }|*3 {3: }| - {1:aaa^@bbb^@^@ccc} | - {1:ddd^@^@^@eee} | - {2:Press ENTER or type command to continue}^ | + {9:aaa^@bbb^@^@ccc} | + {9:ddd^@^@^@eee} | + {6:Press ENTER or type command to continue}^ | ]], } end) @@ -2418,30 +2544,24 @@ describe('API', function() before_each(function() screen = Screen.new(40, 8) screen:attach() - screen:set_default_attr_ids({ - [0] = { bold = true, foreground = Screen.colors.Blue }, - [1] = { foreground = Screen.colors.White, background = Screen.colors.Red }, - [2] = { bold = true, foreground = Screen.colors.SeaGreen }, - [3] = { bold = true, reverse = true }, - }) end) it('shows only one return prompt after all lines are shown', function() async_meths.nvim_err_writeln('FAILURE\nERROR\nEXCEPTION\nTRACEBACK') screen:expect([[ | - {0:~ }| + {1:~ }| {3: }| - {1:FAILURE} | - {1:ERROR} | - {1:EXCEPTION} | - {1:TRACEBACK} | - {2:Press ENTER or type command to continue}^ | + {9:FAILURE} | + {9:ERROR} | + {9:EXCEPTION} | + {9:TRACEBACK} | + {6:Press ENTER or type command to continue}^ | ]]) feed('<CR>') screen:expect([[ ^ | - {0:~ }|*6 + {1:~ }|*6 | ]]) end) @@ -2964,6 +3084,13 @@ describe('API', function() return ('%s(%s)%s'):format(typ, args, rest) end end + + it('does not crash parsing invalid VimL expression #29648', function() + api.nvim_input(':<C-r>=') + api.nvim_input('1bork/') + assert_alive() + end) + require('test.unit.viml.expressions.parser_tests')(it, _check_parsing, hl, fmtn) end) @@ -3102,9 +3229,6 @@ describe('API', function() eq(1, api.nvim_get_current_buf()) local screen = Screen.new(20, 4) - screen:set_default_attr_ids({ - [1] = { bold = true, foreground = Screen.colors.Blue1 }, - }) screen:attach() -- @@ -3201,7 +3325,7 @@ describe('API', function() end) describe('nvim_get_runtime_file', function() - local p = n.alter_slashes + local p = t.fix_slashes it('can find files', function() eq({}, api.nvim_get_runtime_file('bork.borkbork', false)) eq({}, api.nvim_get_runtime_file('bork.borkbork', true)) @@ -3210,36 +3334,36 @@ describe('API', function() local val = api.nvim_get_runtime_file('autoload/remote/*.vim', true) eq(2, #val) if endswith(val[1], 'define.vim') then - ok(endswith(val[1], p 'autoload/remote/define.vim')) - ok(endswith(val[2], p 'autoload/remote/host.vim')) + ok(endswith(p(val[1]), 'autoload/remote/define.vim')) + ok(endswith(p(val[2]), 'autoload/remote/host.vim')) else - ok(endswith(val[1], p 'autoload/remote/host.vim')) - ok(endswith(val[2], p 'autoload/remote/define.vim')) + ok(endswith(p(val[1]), 'autoload/remote/host.vim')) + ok(endswith(p(val[2]), 'autoload/remote/define.vim')) end val = api.nvim_get_runtime_file('autoload/remote/*.vim', false) eq(1, #val) ok( - endswith(val[1], p 'autoload/remote/define.vim') - or endswith(val[1], p 'autoload/remote/host.vim') + endswith(p(val[1]), 'autoload/remote/define.vim') + or endswith(p(val[1]), 'autoload/remote/host.vim') ) val = api.nvim_get_runtime_file('lua', true) eq(1, #val) - ok(endswith(val[1], p 'lua')) + ok(endswith(p(val[1]), 'lua')) val = api.nvim_get_runtime_file('lua/vim', true) eq(1, #val) - ok(endswith(val[1], p 'lua/vim')) + ok(endswith(p(val[1]), 'lua/vim')) end) it('can find directories', function() local val = api.nvim_get_runtime_file('lua/', true) eq(1, #val) - ok(endswith(val[1], p 'lua/')) + ok(endswith(p(val[1]), 'lua/')) val = api.nvim_get_runtime_file('lua/vim/', true) eq(1, #val) - ok(endswith(val[1], p 'lua/vim/')) + ok(endswith(p(val[1]), 'lua/vim/')) eq({}, api.nvim_get_runtime_file('foobarlang/', true)) end) @@ -3253,6 +3377,16 @@ describe('API', function() exc_exec("echo nvim_get_runtime_file('{', v:false)") ) end) + it('preserves order of runtimepath', function() + local vimruntime = fn.getenv('VIMRUNTIME') + local rtp = string.format('%s/syntax,%s/ftplugin', vimruntime, vimruntime) + api.nvim_set_option_value('runtimepath', rtp, {}) + + local val = api.nvim_get_runtime_file('vim.vim', true) + eq(2, #val) + eq(p(val[1]), vimruntime .. '/syntax/vim.vim') + eq(p(val[2]), vimruntime .. '/ftplugin/vim.vim') + end) end) describe('nvim_get_all_options_info', function() @@ -3458,13 +3592,6 @@ describe('API', function() before_each(function() screen = Screen.new(40, 8) screen:attach() - screen:set_default_attr_ids({ - [0] = { bold = true, foreground = Screen.colors.Blue }, - [1] = { bold = true, foreground = Screen.colors.SeaGreen }, - [2] = { bold = true, reverse = true }, - [3] = { foreground = Screen.colors.Brown, bold = true }, -- Statement - [4] = { foreground = Screen.colors.SlateBlue }, -- Special - }) command('highlight Statement gui=bold guifg=Brown') command('highlight Special guifg=SlateBlue') end) @@ -3474,7 +3601,7 @@ describe('API', function() screen:expect { grid = [[ ^ | - {0:~ }|*6 + {1:~ }|*6 msg | ]], } @@ -3489,8 +3616,8 @@ describe('API', function() screen:expect { grid = [[ ^ | - {0:~ }|*6 - msg_a{3:msg_b}{4:msg_c} | + {1:~ }|*6 + msg_a{15:msg_b}{16:msg_c} | ]], } end) @@ -3500,11 +3627,11 @@ describe('API', function() screen:expect { grid = [[ | - {0:~ }|*3 - {2: }| - {3:msg_a} | - {3:msg_a}{4:msg_b} | - {1:Press ENTER or type command to continue}^ | + {1:~ }|*3 + {3: }| + {15:msg_a} | + {15:msg_a}{16:msg_b} | + {6:Press ENTER or type command to continue}^ | ]], } end) @@ -3528,24 +3655,16 @@ describe('API', function() before_each(function() screen = Screen.new(100, 35) screen:attach() - screen:set_default_attr_ids({ - [0] = { bold = true, foreground = Screen.colors.Blue }, - [1] = { background = Screen.colors.Plum1 }, - [2] = { background = tonumber('0xffff40'), bg_indexed = true }, - [3] = { - background = Screen.colors.Plum1, - fg_indexed = true, - foreground = tonumber('0x00e000'), - }, - [4] = { bold = true, reverse = true, background = Screen.colors.Plum1 }, - [5] = { - foreground = Screen.colors.Blue, + screen:add_extra_attr_ids { + [100] = { background = tonumber('0xffff40'), bg_indexed = true }, + [101] = { background = Screen.colors.LightMagenta, - bold = true, + foreground = tonumber('0x00e000'), + fg_indexed = true, }, - [6] = { bold = true }, - [7] = { reverse = true, background = Screen.colors.LightMagenta }, - }) + [102] = { background = Screen.colors.LightMagenta, reverse = true }, + [103] = { background = Screen.colors.LightMagenta, bold = true, reverse = true }, + } end) it('can batch process sequences', function() @@ -3561,38 +3680,38 @@ describe('API', function() screen:expect { grid = [[ ^ | - {0:~}{1::smile }{0: }| - {0:~}{1: }{2:oooo$$$$$$$$$$$$oooo}{1: }{0: }| - {0:~}{1: }{2:oo$$$$$$$$$$$$$$$$$$$$$$$$o}{1: }{0: }| - {0:~}{1: }{2:oo$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$o}{1: }{2:o$}{1: }{2:$$}{1: }{2:o$}{1: }{0: }| - {0:~}{1: }{2:o}{1: }{2:$}{1: }{2:oo}{1: }{2:o$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$o}{1: }{2:$$}{1: }{2:$$}{1: }{2:$$o$}{1: }{0: }| - {0:~}{1: }{2:oo}{1: }{2:$}{1: }{2:$}{1: "}{2:$}{1: }{2:o$$$$$$$$$}{1: }{2:$$$$$$$$$$$$$}{1: }{2:$$$$$$$$$o}{1: }{2:$$$o$$o$}{1: }{0: }| - {0:~}{1: "}{2:$$$$$$o$}{1: }{2:o$$$$$$$$$}{1: }{2:$$$$$$$$$$$}{1: }{2:$$$$$$$$$$o}{1: }{2:$$$$$$$$}{1: }{0: }| - {0:~}{1: }{2:$$$$$$$}{1: }{2:$$$$$$$$$$$}{1: }{2:$$$$$$$$$$$}{1: }{2:$$$$$$$$$$$$$$$$$$$$$$$}{1: }{0: }| - {0:~}{1: }{2:$$$$$$$$$$$$$$$$$$$$$$$}{1: }{2:$$$$$$$$$$$$$}{1: }{2:$$$$$$$$$$$$$$}{1: """}{2:$$$}{1: }{0: }| - {0:~}{1: "}{2:$$$}{1:""""}{2:$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$}{1: "}{2:$$$}{1: }{0: }| - {0:~}{1: }{2:$$$}{1: }{2:o$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$}{1: "}{2:$$$o}{1: }{0: }| - {0:~}{1: }{2:o$$}{1:" }{2:$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$}{1: }{2:$$$o}{1: }{0: }| - {0:~}{1: }{2:$$$}{1: }{2:$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$}{1:" "}{2:$$$$$$ooooo$$$$o}{1: }{0: }| - {0:~}{1: }{2:o$$$oooo$$$$$}{1: }{2:$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$}{1: }{2:o$$$$$$$$$$$$$$$$$}{1: }{0: }| - {0:~}{1: }{2:$$$$$$$$}{1:"}{2:$$$$}{1: }{2:$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$}{1: }{2:$$$$}{1:"""""""" }{0: }| - {0:~}{1: """" }{2:$$$$}{1: "}{2:$$$$$$$$$$$$$$$$$$$$$$$$$$$$}{1:" }{2:o$$$}{1: }{0: }| - {0:~}{1: "}{2:$$$o}{1: """}{2:$$$$$$$$$$$$$$$$$$}{1:"}{2:$$}{1:" }{2:$$$}{1: }{0: }| - {0:~}{1: }{2:$$$o}{1: "}{2:$$}{1:""}{2:$$$$$$}{1:"""" }{2:o$$$}{1: }{0: }| - {0:~}{1: }{2:$$$$o}{1: }{2:o$$$}{1:" }{0: }| - {0:~}{1: "}{2:$$$$o}{1: }{2:o$$$$$$o}{1:"}{2:$$$$o}{1: }{2:o$$$$}{1: }{0: }| - {0:~}{1: "}{2:$$$$$oo}{1: ""}{2:$$$$o$$$$$o}{1: }{2:o$$$$}{1:"" }{0: }| - {0:~}{1: ""}{2:$$$$$oooo}{1: "}{2:$$$o$$$$$$$$$}{1:""" }{0: }| - {0:~}{1: ""}{2:$$$$$$$oo}{1: }{2:$$$$$$$$$$}{1: }{0: }| - {0:~}{1: """"}{2:$$$$$$$$$$$}{1: }{0: }| - {0:~}{1: }{2:$$$$$$$$$$$$}{1: }{0: }| - {0:~}{1: }{2:$$$$$$$$$$}{1:" }{0: }| - {0:~}{1: "}{2:$$$}{1:"""" }{0: }| - {0:~}{1: }{0: }| - {0:~}{3:Press ENTER or type command to continue}{1: }{0: }| - {0:~}{4:term://~/config2/docs/pres//32693:vim --clean +smile 29,39 All}{0: }| - {0:~}{1::call nvim__screenshot("smile2.cat") }{0: }| - {0:~ }|*2 + {1:~}{4::smile }{1: }| + {1:~}{4: }{100:oooo$$$$$$$$$$$$oooo}{4: }{1: }| + {1:~}{4: }{100:oo$$$$$$$$$$$$$$$$$$$$$$$$o}{4: }{1: }| + {1:~}{4: }{100:oo$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$o}{4: }{100:o$}{4: }{100:$$}{4: }{100:o$}{4: }{1: }| + {1:~}{4: }{100:o}{4: }{100:$}{4: }{100:oo}{4: }{100:o$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$o}{4: }{100:$$}{4: }{100:$$}{4: }{100:$$o$}{4: }{1: }| + {1:~}{4: }{100:oo}{4: }{100:$}{4: }{100:$}{4: "}{100:$}{4: }{100:o$$$$$$$$$}{4: }{100:$$$$$$$$$$$$$}{4: }{100:$$$$$$$$$o}{4: }{100:$$$o$$o$}{4: }{1: }| + {1:~}{4: "}{100:$$$$$$o$}{4: }{100:o$$$$$$$$$}{4: }{100:$$$$$$$$$$$}{4: }{100:$$$$$$$$$$o}{4: }{100:$$$$$$$$}{4: }{1: }| + {1:~}{4: }{100:$$$$$$$}{4: }{100:$$$$$$$$$$$}{4: }{100:$$$$$$$$$$$}{4: }{100:$$$$$$$$$$$$$$$$$$$$$$$}{4: }{1: }| + {1:~}{4: }{100:$$$$$$$$$$$$$$$$$$$$$$$}{4: }{100:$$$$$$$$$$$$$}{4: }{100:$$$$$$$$$$$$$$}{4: """}{100:$$$}{4: }{1: }| + {1:~}{4: "}{100:$$$}{4:""""}{100:$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$}{4: "}{100:$$$}{4: }{1: }| + {1:~}{4: }{100:$$$}{4: }{100:o$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$}{4: "}{100:$$$o}{4: }{1: }| + {1:~}{4: }{100:o$$}{4:" }{100:$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$}{4: }{100:$$$o}{4: }{1: }| + {1:~}{4: }{100:$$$}{4: }{100:$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$}{4:" "}{100:$$$$$$ooooo$$$$o}{4: }{1: }| + {1:~}{4: }{100:o$$$oooo$$$$$}{4: }{100:$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$}{4: }{100:o$$$$$$$$$$$$$$$$$}{4: }{1: }| + {1:~}{4: }{100:$$$$$$$$}{4:"}{100:$$$$}{4: }{100:$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$}{4: }{100:$$$$}{4:"""""""" }{1: }| + {1:~}{4: """" }{100:$$$$}{4: "}{100:$$$$$$$$$$$$$$$$$$$$$$$$$$$$}{4:" }{100:o$$$}{4: }{1: }| + {1:~}{4: "}{100:$$$o}{4: """}{100:$$$$$$$$$$$$$$$$$$}{4:"}{100:$$}{4:" }{100:$$$}{4: }{1: }| + {1:~}{4: }{100:$$$o}{4: "}{100:$$}{4:""}{100:$$$$$$}{4:"""" }{100:o$$$}{4: }{1: }| + {1:~}{4: }{100:$$$$o}{4: }{100:o$$$}{4:" }{1: }| + {1:~}{4: "}{100:$$$$o}{4: }{100:o$$$$$$o}{4:"}{100:$$$$o}{4: }{100:o$$$$}{4: }{1: }| + {1:~}{4: "}{100:$$$$$oo}{4: ""}{100:$$$$o$$$$$o}{4: }{100:o$$$$}{4:"" }{1: }| + {1:~}{4: ""}{100:$$$$$oooo}{4: "}{100:$$$o$$$$$$$$$}{4:""" }{1: }| + {1:~}{4: ""}{100:$$$$$$$oo}{4: }{100:$$$$$$$$$$}{4: }{1: }| + {1:~}{4: """"}{100:$$$$$$$$$$$}{4: }{1: }| + {1:~}{4: }{100:$$$$$$$$$$$$}{4: }{1: }| + {1:~}{4: }{100:$$$$$$$$$$}{4:" }{1: }| + {1:~}{4: "}{100:$$$}{4:"""" }{1: }| + {1:~}{4: }{1: }| + {1:~}{101:Press ENTER or type command to continue}{4: }{1: }| + {1:~}{103:term://~/config2/docs/pres//32693:vim --clean +smile 29,39 All}{1: }| + {1:~}{4::call nvim__screenshot("smile2.cat") }{1: }| + {1:~ }|*2 | ]], } @@ -3624,9 +3743,9 @@ describe('API', function() screen:expect { grid = [[ | - {0:~}{1:^ }{0: }| - {0:~}{1: }{0: }|*4 - {0:~ }|*3 + {1:~}{4:^ }{1: }| + {1:~}{4: }{1: }|*4 + {1:~ }|*3 | ]], } @@ -3635,10 +3754,10 @@ describe('API', function() screen:expect { grid = [[ | - {0:~}{7: }{1: }{0: }| - {0:~}{1: }{0: }|*4 - {0:~ }|*3 - {6:-- TERMINAL --} | + {1:~}{102: }{4: }{1: }| + {1:~}{4: }{1: }|*4 + {1:~ }|*3 + {5:-- TERMINAL --} | ]], } @@ -3651,10 +3770,10 @@ describe('API', function() screen:expect { grid = [[ | - {0:~}{1:herrejösses!}{7: }{1: }{0: }| - {0:~}{1: }{0: }|*4 - {0:~ }|*3 - {6:-- TERMINAL --} | + {1:~}{4:herrejösses!}{102: }{4: }{1: }| + {1:~}{4: }{1: }|*4 + {1:~ }|*3 + {5:-- TERMINAL --} | ]], } eq('ba\024blaherrejösses!', exec_lua [[ return stream ]]) @@ -3933,11 +4052,11 @@ describe('API', function() )) eq( { - str = '3 ', - width = 2, + str = ' 3 ', + width = 9, highlights = { { group = 'LineNr', start = 0 }, - { group = 'ErrorMsg', start = 1 }, + { group = 'ErrorMsg', start = 8 }, }, }, api.nvim_eval_statusline('%l%#ErrorMsg# ', { use_statuscol_lnum = 3, highlights = true }) @@ -4472,10 +4591,6 @@ describe('API', function() end) it('does not interfere with printing line in Ex mode #19400', function() local screen = Screen.new(60, 7) - screen:set_default_attr_ids({ - [0] = { bold = true, foreground = Screen.colors.Blue }, -- NonText - [1] = { bold = true, reverse = true }, -- MsgSeparator - }) screen:attach() insert([[ foo @@ -4484,8 +4599,8 @@ describe('API', function() screen:expect([[ foo | bar | - {0:~ }|*2 - {1: }| + {1:~ }|*2 + {3: }| Entering Ex mode. Type "visual" to go to Normal mode. | :1^ | ]]) @@ -4494,7 +4609,7 @@ describe('API', function() screen:expect([[ foo | bar | - {1: }| + {3: }| Entering Ex mode. Type "visual" to go to Normal mode. | :1 | foo | @@ -4934,14 +5049,11 @@ describe('API', function() it("doesn't display messages when output=true", function() local screen = Screen.new(40, 6) screen:attach() - screen:set_default_attr_ids({ - [0] = { bold = true, foreground = Screen.colors.Blue }, - }) api.nvim_cmd({ cmd = 'echo', args = { [['hello']] } }, { output = true }) screen:expect { grid = [[ ^ | - {0:~ }|*4 + {1:~ }|*4 | ]], } @@ -4954,7 +5066,7 @@ describe('API', function() screen:expect { grid = [[ ^ | - {0:~ }|*4 + {1:~ }|*4 15 | ]], } @@ -5020,12 +5132,29 @@ describe('API', function() it('nvim__redraw', function() local screen = Screen.new(60, 5) screen:attach() - local win = api.nvim_get_current_win() eq('at least one action required', pcall_err(api.nvim__redraw, {})) eq('at least one action required', pcall_err(api.nvim__redraw, { buf = 0 })) eq('at least one action required', pcall_err(api.nvim__redraw, { win = 0 })) eq("cannot use both 'buf' and 'win'", pcall_err(api.nvim__redraw, { buf = 0, win = 0 })) + local win = api.nvim_get_current_win() + -- Can move cursor to recently opened window and window is flushed #28868 feed(':echo getchar()<CR>') + local newwin = api.nvim_open_win(0, false, { + relative = 'editor', + width = 1, + height = 1, + row = 1, + col = 10, + }) + api.nvim__redraw({ win = newwin, cursor = true }) + screen:expect({ + grid = [[ + | + {1:~ }{4:^ }{1: }| + {1:~ }|*2 + :echo getchar() | + ]], + }) fn.setline(1, 'foobar') command('vnew') fn.setline(1, 'foobaz') @@ -5034,11 +5163,13 @@ describe('API', function() screen:expect({ grid = [[ foobaz │foobar | - {1:~ }│{1:~ }|*2 + {1:~ }{4:^f}{1: }│{1:~ }| + {1:~ }│{1:~ }| {3:[No Name] [+] }{2:[No Name] [+] }| - ^:echo getchar() | + :echo getchar() | ]], }) + api.nvim_win_close(newwin, true) -- Can update the grid cursor position #20793 api.nvim__redraw({ cursor = true }) screen:expect({ diff --git a/test/functional/api/window_spec.lua b/test/functional/api/window_spec.lua index 15b9b0945c..5ce93f9e04 100644 --- a/test/functional/api/window_spec.lua +++ b/test/functional/api/window_spec.lua @@ -85,7 +85,7 @@ describe('API/win', function() [[ local cmdwin_buf = vim.api.nvim_get_current_buf() local new_win, new_buf = ... - vim.api.nvim_buf_call(new_buf, function() + vim._with({buf = new_buf}, function() vim.api.nvim_win_set_buf(new_win, cmdwin_buf) end) ]], @@ -100,7 +100,7 @@ describe('API/win', function() [[ local cmdwin_win = vim.api.nvim_get_current_win() local new_win, new_buf = ... - vim.api.nvim_win_call(new_win, function() + vim._with({win = new_win}, function() vim.api.nvim_win_set_buf(cmdwin_win, new_buf) end) ]], @@ -164,17 +164,12 @@ describe('API/win', function() eq('typing\n some dumb text', curbuf_contents()) end) - it('does not leak memory when using invalid window ID with invalid pos', function() + it('no memory leak when using invalid window ID with invalid pos', function() eq('Invalid window id: 1', pcall_err(api.nvim_win_set_cursor, 1, { 'b\na' })) end) it('updates the screen, and also when the window is unfocused', function() local screen = Screen.new(30, 9) - screen:set_default_attr_ids({ - [1] = { bold = true, foreground = Screen.colors.Blue }, - [2] = { bold = true, reverse = true }, - [3] = { reverse = true }, - }) screen:attach() insert('prologue') @@ -221,10 +216,10 @@ describe('API/win', function() grid = [[ ^ | {1:~ }|*2 - {2:[No Name] }| + {3:[No Name] }| prologue | |*2 - {3:[No Name] [+] }| + {2:[No Name] [+] }| | ]], } @@ -235,10 +230,10 @@ describe('API/win', function() grid = [[ ^ | {1:~ }|*2 - {2:[No Name] }| + {3:[No Name] }| |*2 epilogue | - {3:[No Name] [+] }| + {2:[No Name] [+] }| | ]], } @@ -249,10 +244,10 @@ describe('API/win', function() grid = [[ ^ | {1:~ }|*2 - {2:[No Name] }| + {3:[No Name] }| prologue | |*2 - {3:[No Name] [+] }| + {2:[No Name] [+] }| | ]], } @@ -286,12 +281,6 @@ describe('API/win', function() it('updates cursorline and statusline ruler in non-current window', function() local screen = Screen.new(60, 8) - screen:set_default_attr_ids({ - [1] = { bold = true, foreground = Screen.colors.Blue }, -- NonText - [2] = { background = Screen.colors.Grey90 }, -- CursorLine - [3] = { bold = true, reverse = true }, -- StatusLine - [4] = { reverse = true }, -- StatusLineNC - }) screen:attach() command('set ruler') command('set cursorline') @@ -306,31 +295,25 @@ describe('API/win', function() aaa │aaa | bbb │bbb | ccc │ccc | - {2:dd^d }│{2:ddd }| + {21:dd^d }│{21:ddd }| {1:~ }│{1:~ }|*2 - {3:[No Name] [+] 4,3 All }{4:[No Name] [+] 4,3 All}| + {3:[No Name] [+] 4,3 All }{2:[No Name] [+] 4,3 All}| | ]]) api.nvim_win_set_cursor(oldwin, { 1, 0 }) screen:expect([[ - aaa │{2:aaa }| + aaa │{21:aaa }| bbb │bbb | ccc │ccc | - {2:dd^d }│ddd | + {21:dd^d }│ddd | {1:~ }│{1:~ }|*2 - {3:[No Name] [+] 4,3 All }{4:[No Name] [+] 1,1 All}| + {3:[No Name] [+] 4,3 All }{2:[No Name] [+] 1,1 All}| | ]]) end) it('updates cursorcolumn in non-current window', function() local screen = Screen.new(60, 8) - screen:set_default_attr_ids({ - [1] = { bold = true, foreground = Screen.colors.Blue }, -- NonText - [2] = { background = Screen.colors.Grey90 }, -- CursorColumn - [3] = { bold = true, reverse = true }, -- StatusLine - [4] = { reverse = true }, -- StatusLineNC - }) screen:attach() command('set cursorcolumn') insert([[ @@ -341,22 +324,22 @@ describe('API/win', function() local oldwin = curwin() command('vsplit') screen:expect([[ - aa{2:a} │aa{2:a} | - bb{2:b} │bb{2:b} | - cc{2:c} │cc{2:c} | + aa{21:a} │aa{21:a} | + bb{21:b} │bb{21:b} | + cc{21:c} │cc{21:c} | dd^d │ddd | {1:~ }│{1:~ }|*2 - {3:[No Name] [+] }{4:[No Name] [+] }| + {3:[No Name] [+] }{2:[No Name] [+] }| | ]]) api.nvim_win_set_cursor(oldwin, { 2, 0 }) screen:expect([[ - aa{2:a} │{2:a}aa | - bb{2:b} │bbb | - cc{2:c} │{2:c}cc | - dd^d │{2:d}dd | + aa{21:a} │{21:a}aa | + bb{21:b} │bbb | + cc{21:c} │{21:c}cc | + dd^d │{21:d}dd | {1:~ }│{1:~ }|*2 - {3:[No Name] [+] }{4:[No Name] [+] }| + {3:[No Name] [+] }{2:[No Name] [+] }| | ]]) end) @@ -655,7 +638,7 @@ describe('API/win', function() feed('q:') exec_lua( [[ - vim.api.nvim_win_call(..., function() + vim._with({win = ...}, function() vim.api.nvim_win_close(0, true) end) ]], @@ -674,7 +657,7 @@ describe('API/win', function() exec_lua( [[ local otherwin, cmdwin = ... - vim.api.nvim_win_call(otherwin, function() + vim._with({win = otherwin}, function() vim.api.nvim_win_close(cmdwin, true) end) ]], @@ -788,7 +771,7 @@ describe('API/win', function() }) exec_lua( [[ - vim.api.nvim_win_call(..., function() + vim._with({win = ...}, function() vim.api.nvim_win_hide(0) end) ]], @@ -807,7 +790,7 @@ describe('API/win', function() exec_lua( [[ local otherwin, cmdwin = ... - vim.api.nvim_win_call(otherwin, function() + vim._with({win = otherwin}, function() vim.api.nvim_win_hide(cmdwin) end) ]], @@ -874,22 +857,6 @@ describe('API/win', function() it('with two diff windows', function() local X = api.nvim_get_vvar('maxcol') local screen = Screen.new(45, 22) - screen:set_default_attr_ids({ - [0] = { foreground = Screen.colors.Blue1, bold = true }, - [1] = { foreground = Screen.colors.Blue4, background = Screen.colors.Grey }, - [2] = { foreground = Screen.colors.Brown }, - [3] = { - foreground = Screen.colors.Blue1, - background = Screen.colors.LightCyan1, - bold = true, - }, - [4] = { background = Screen.colors.LightBlue }, - [5] = { foreground = Screen.colors.Blue4, background = Screen.colors.LightGrey }, - [6] = { background = Screen.colors.Plum1 }, - [7] = { background = Screen.colors.Red, bold = true }, - [8] = { reverse = true }, - [9] = { bold = true, reverse = true }, - }) screen:attach() exec([[ set diffopt+=context:2 number @@ -902,35 +869,35 @@ describe('API/win', function() feed('24gg') screen:expect { grid = [[ - {1: }{2: }{3:----------------}│{1: }{2: 1 }{4:00000001! }| - {1: }{2: }{3:----------------}│{1: }{2: 2 }{4:00000002!! }| - {1: }{2: 1 }00000003!!! │{1: }{2: 3 }00000003!!! | - {1: }{2: 2 }00000004!!!! │{1: }{2: 4 }00000004!!!! | - {1:+ }{2: 3 }{5:+-- 14 lines: 00}│{1:+ }{2: 5 }{5:+-- 14 lines: 00}| - {1: }{2: 17 }00000019!!!!!!!!│{1: }{2: 19 }00000019!!!!!!!!| - {1: }{2: 18 }00000020!!!!!!!!│{1: }{2: 20 }00000020!!!!!!!!| - {1: }{2: }{3:----------------}│{1: }{2: 21 }{4:00000025!!!!!!!!}| - {1: }{2: }{3:----------------}│{1: }{2: 22 }{4:00000026!!!!!!!!}| - {1: }{2: }{3:----------------}│{1: }{2: 23 }{4:00000027!!!!!!!!}| - {1: }{2: 19 }00000028!!!!!!!!│{1: }{2: 24 }^00000028!!!!!!!!| - {1: }{2: 20 }00000029!!!!!!!!│{1: }{2: 25 }00000029!!!!!!!!| - {1:+ }{2: 21 }{5:+-- 14 lines: 00}│{1:+ }{2: 26 }{5:+-- 14 lines: 00}| - {1: }{2: 35 }00000044!!!!!!!!│{1: }{2: 40 }00000044!!!!!!!!| - {1: }{2: 36 }00000045!!!!!!!!│{1: }{2: 41 }00000045!!!!!!!!| - {1: }{2: 37 }{4:00000046!!!!!!!!}│{1: }{2: }{3:----------------}| - {1: }{2: 38 }{4:00000047!!!!!!!!}│{1: }{2: }{3:----------------}| - {1: }{2: 39 }{4:00000048!!!!!!!!}│{1: }{2: }{3:----------------}| - {1: }{2: 40 }{4:00000049!!!!!!!!}│{1: }{2: }{3:----------------}| - {1: }{2: 41 }{4:00000050!!!!!!!!}│{1: }{2: }{3:----------------}| - {8:[No Name] [+] }{9:[No Name] [+] }| + {7: }{8: }{23:----------------}│{7: }{8: 1 }{22:00000001! }| + {7: }{8: }{23:----------------}│{7: }{8: 2 }{22:00000002!! }| + {7: }{8: 1 }00000003!!! │{7: }{8: 3 }00000003!!! | + {7: }{8: 2 }00000004!!!! │{7: }{8: 4 }00000004!!!! | + {7:+ }{8: 3 }{13:+-- 14 lines: 00}│{7:+ }{8: 5 }{13:+-- 14 lines: 00}| + {7: }{8: 17 }00000019!!!!!!!!│{7: }{8: 19 }00000019!!!!!!!!| + {7: }{8: 18 }00000020!!!!!!!!│{7: }{8: 20 }00000020!!!!!!!!| + {7: }{8: }{23:----------------}│{7: }{8: 21 }{22:00000025!!!!!!!!}| + {7: }{8: }{23:----------------}│{7: }{8: 22 }{22:00000026!!!!!!!!}| + {7: }{8: }{23:----------------}│{7: }{8: 23 }{22:00000027!!!!!!!!}| + {7: }{8: 19 }00000028!!!!!!!!│{7: }{8: 24 }^00000028!!!!!!!!| + {7: }{8: 20 }00000029!!!!!!!!│{7: }{8: 25 }00000029!!!!!!!!| + {7:+ }{8: 21 }{13:+-- 14 lines: 00}│{7:+ }{8: 26 }{13:+-- 14 lines: 00}| + {7: }{8: 35 }00000044!!!!!!!!│{7: }{8: 40 }00000044!!!!!!!!| + {7: }{8: 36 }00000045!!!!!!!!│{7: }{8: 41 }00000045!!!!!!!!| + {7: }{8: 37 }{22:00000046!!!!!!!!}│{7: }{8: }{23:----------------}| + {7: }{8: 38 }{22:00000047!!!!!!!!}│{7: }{8: }{23:----------------}| + {7: }{8: 39 }{22:00000048!!!!!!!!}│{7: }{8: }{23:----------------}| + {7: }{8: 40 }{22:00000049!!!!!!!!}│{7: }{8: }{23:----------------}| + {7: }{8: 41 }{22:00000050!!!!!!!!}│{7: }{8: }{23:----------------}| + {2:[No Name] [+] }{3:[No Name] [+] }| | ]], } screen:try_resize(45, 3) screen:expect { grid = [[ - {1: }{2: 19 }00000028!!!!!!!!│{1: }{2: 24 }^00000028!!!!!!!!| - {8:[No Name] [+] }{9:[No Name] [+] }| + {7: }{8: 19 }00000028!!!!!!!!│{7: }{8: 24 }^00000028!!!!!!!!| + {2:[No Name] [+] }{3:[No Name] [+] }| | ]], } @@ -1008,11 +975,6 @@ describe('API/win', function() it('with wrapped lines', function() local X = api.nvim_get_vvar('maxcol') local screen = Screen.new(45, 22) - screen:set_default_attr_ids({ - [0] = { foreground = Screen.colors.Blue1, bold = true }, - [1] = { foreground = Screen.colors.Brown }, - [2] = { background = Screen.colors.Yellow }, - }) screen:attach() exec([[ set number cpoptions+=n @@ -1035,26 +997,26 @@ describe('API/win', function() ) screen:expect { grid = [[ - {1: 1 }^foobar-foobar-foobar-foobar-foobar-foobar| + {8: 1 }^foobar-foobar-foobar-foobar-foobar-foobar| -foobar-foobar-foobar-foobar-foobar-foobar-fo| obar-foobar-foobar-foobar-foobar-foobar-fooba| r-foobar-foobar-foobar-foobar-foobar-foobar-f| oobar-foobar-foobar-foobar-foobar-foobar-foob| ar-foobar-foobar-foobar-foobar- | - {1: 2 }foobar-foobar-foobar-foobar-foobar-foobar| + {8: 2 }foobar-foobar-foobar-foobar-foobar-foobar| -foobar-foobar-foobar-foobar-foobar-foobar-fo| - obar-foobar-fo{2:???????????????}obar-foobar-foob| + obar-foobar-fo{10:???????????????}obar-foobar-foob| ar-foobar-foobar-foobar-foobar-foobar-foobar-| foobar-foobar-foobar-foobar-foobar-foobar-foo| bar-foobar-foobar-foobar-foobar-foobar-foobar| - | - {1: 3 }foobar-foobar-foobar-foobar-foobar-foobar| + {8: 3 }foobar-foobar-foobar-foobar-foobar-foobar| -foobar-foobar-foobar-foobar-foobar-foobar-fo| obar-foobar-foobar-foobar-foobar-foobar-fooba| r-foobar-foobar-foobar-foobar-foobar-foobar-f| - oobar-foobar-foobar-foob{2:!!!!!!!!!!!!!!!!!!!!!}| - {2:!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!}| - {2:!!!!!!!!!}ar-foobar-foobar-foobar-foobar-fooba| + oobar-foobar-foobar-foob{10:!!!!!!!!!!!!!!!!!!!!!}| + {10:!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!}| + {10:!!!!!!!!!}ar-foobar-foobar-foobar-foobar-fooba| r-foobar-foobar- | | ]], @@ -1062,7 +1024,7 @@ describe('API/win', function() screen:try_resize(45, 2) screen:expect { grid = [[ - {1: 1 }^foobar-foobar-foobar-foobar-foobar-foobar| + {8: 1 }^foobar-foobar-foobar-foobar-foobar-foobar| | ]], } @@ -1216,7 +1178,7 @@ describe('API/win', function() exec_lua, [[ local cmdwin_buf = vim.api.nvim_get_current_buf() - vim.api.nvim_buf_call(vim.api.nvim_create_buf(false, true), function() + vim._with({buf = vim.api.nvim_create_buf(false, true)}, function() vim.api.nvim_open_win(cmdwin_buf, false, { relative='editor', row=5, col=5, width=5, height=5, }) @@ -1847,6 +1809,38 @@ describe('API/win', function() eq(topdir .. '/Xacd', fn.getcwd()) end) end) + + it('no memory leak with valid title and invalid footer', function() + eq( + 'title/footer must be string or array', + pcall_err(api.nvim_open_win, 0, false, { + relative = 'editor', + row = 10, + col = 10, + height = 10, + width = 10, + border = 'single', + title = { { 'TITLE' } }, + footer = 0, + }) + ) + end) + + it('no memory leak with invalid title and valid footer', function() + eq( + 'title/footer must be string or array', + pcall_err(api.nvim_open_win, 0, false, { + relative = 'editor', + row = 10, + col = 10, + height = 10, + width = 10, + border = 'single', + title = 0, + footer = { { 'FOOTER' } }, + }) + ) + end) end) describe('set_config', function() @@ -2563,10 +2557,6 @@ describe('API/win', function() it('updates statusline when moving bottom split', function() local screen = Screen.new(10, 10) - screen:set_default_attr_ids({ - [0] = { bold = true, foreground = Screen.colors.Blue }, -- NonText - [1] = { bold = true, reverse = true }, -- StatusLine - }) screen:attach() exec([[ set laststatus=0 @@ -2575,10 +2565,10 @@ describe('API/win', function() ]]) screen:expect([[ ^ | - {0:~ }|*3 - {1:[No Name] }| + {1:~ }|*3 + {3:[No Name] }| | - {0:~ }|*3 + {1:~ }|*3 | ]]) end) @@ -2807,61 +2797,35 @@ describe('API/win', function() border = 'single', }) eq( - 'title/footer cannot be an empty array', - pcall_err(api.nvim_win_set_config, win, { title = {} }) + 'title/footer must be string or array', + pcall_err(api.nvim_win_set_config, win, { title = 0 }) ) command('redraw!') assert_alive() - end) - - it('no crash with invalid footer', function() - local win = api.nvim_open_win(0, true, { - width = 10, - height = 10, - relative = 'editor', - row = 10, - col = 10, - footer = { { 'test' } }, - border = 'single', - }) eq( 'title/footer cannot be an empty array', - pcall_err(api.nvim_win_set_config, win, { footer = {} }) + pcall_err(api.nvim_win_set_config, win, { title = {} }) ) command('redraw!') assert_alive() end) - end) - describe('set_config', function() - it('no crash with invalid title', function() + it('no crash with invalid footer', function() local win = api.nvim_open_win(0, true, { width = 10, height = 10, relative = 'editor', row = 10, col = 10, - title = { { 'test' } }, + footer = { { 'test' } }, border = 'single', }) eq( - 'title/footer cannot be an empty array', - pcall_err(api.nvim_win_set_config, win, { title = {} }) + 'title/footer must be string or array', + pcall_err(api.nvim_win_set_config, win, { footer = 0 }) ) command('redraw!') assert_alive() - end) - - it('no crash with invalid footer', function() - local win = api.nvim_open_win(0, true, { - width = 10, - height = 10, - relative = 'editor', - row = 10, - col = 10, - footer = { { 'test' } }, - border = 'single', - }) eq( 'title/footer cannot be an empty array', pcall_err(api.nvim_win_set_config, win, { footer = {} }) @@ -2869,5 +2833,48 @@ describe('API/win', function() command('redraw!') assert_alive() end) + + describe('no crash or memory leak', function() + local win + + before_each(function() + win = api.nvim_open_win(0, false, { + relative = 'editor', + row = 10, + col = 10, + height = 10, + width = 10, + border = 'single', + title = { { 'OLD_TITLE' } }, + footer = { { 'OLD_FOOTER' } }, + }) + end) + + it('with valid title and invalid footer', function() + eq( + 'title/footer must be string or array', + pcall_err(api.nvim_win_set_config, win, { + title = { { 'NEW_TITLE' } }, + footer = 0, + }) + ) + command('redraw!') + assert_alive() + eq({ { 'OLD_TITLE' } }, api.nvim_win_get_config(win).title) + end) + + it('with invalid title and valid footer', function() + eq( + 'title/footer must be string or array', + pcall_err(api.nvim_win_set_config, win, { + title = 0, + footer = { { 'NEW_FOOTER' } }, + }) + ) + command('redraw!') + assert_alive() + eq({ { 'OLD_FOOTER' } }, api.nvim_win_get_config(win).footer) + end) + end) end) end) diff --git a/test/functional/autocmd/autocmd_oldtest_spec.lua b/test/functional/autocmd/autocmd_oldtest_spec.lua index 1a3b723ac2..5e4beb7684 100644 --- a/test/functional/autocmd/autocmd_oldtest_spec.lua +++ b/test/functional/autocmd/autocmd_oldtest_spec.lua @@ -103,9 +103,9 @@ describe('oldtests', function() it('no ml_get error with TextChanged autocommand and delete', function() local screen = Screen.new(75, 10) screen:attach() - screen:set_default_attr_ids({ - [1] = { background = Screen.colors.Cyan }, - }) + screen:add_extra_attr_ids { + [100] = { background = Screen.colors.Cyan1 }, + } exec([[ set noshowcmd noruler scrolloff=0 source test/old/testdir/samples/matchparen.vim @@ -120,9 +120,9 @@ describe('oldtests', function() } | const auto &themes = _forPeer->owner().cloudThemes(); | const auto theme = themes.themeForEmoji(themeEmoji); | - if (!theme) {1:{} | + if (!theme) {100:{} | return nonCustom; | - {1:^}} | + {100:^}} | 353 fewer lines | ]], } diff --git a/test/functional/autocmd/autocmd_spec.lua b/test/functional/autocmd/autocmd_spec.lua index 5e407a9986..0429cfee89 100644 --- a/test/functional/autocmd/autocmd_spec.lua +++ b/test/functional/autocmd/autocmd_spec.lua @@ -259,15 +259,6 @@ describe('autocmd', function() local screen = Screen.new(50, 10) screen:attach() - screen:set_default_attr_ids({ - [1] = { bold = true, foreground = Screen.colors.Blue1 }, - [2] = { background = Screen.colors.LightMagenta }, - [3] = { - background = Screen.colors.LightMagenta, - bold = true, - foreground = Screen.colors.Blue1, - }, - }) source([[ function! Doit() @@ -292,8 +283,8 @@ describe('autocmd', function() feed(':enew | doautoall User<cr>') screen:expect([[ - {2:bb }| - {3:~ }|*4 + {4:bb }| + {11:~ }|*4 {1:~ }|*4 ^:enew | doautoall User | ]]) @@ -318,8 +309,8 @@ describe('autocmd', function() command('let g:had_value = v:null') feed(':doautoall User<cr>') screen:expect([[ - {2:bb }| - {3:~ }|*4 + {4:bb }| + {11:~ }|*4 {1:~ }|*4 ^:doautoall User | ]]) @@ -343,18 +334,13 @@ describe('autocmd', function() it('`aucmd_win` cannot be changed into a normal window #13699', function() local screen = Screen.new(50, 10) screen:attach() - screen:set_default_attr_ids { - [1] = { bold = true, foreground = Screen.colors.Blue1 }, - [2] = { reverse = true }, - [3] = { bold = true, reverse = true }, - } -- Create specific layout and ensure it's left unchanged. - -- Use nvim_buf_call on a hidden buffer so aucmd_win is used. + -- Use vim._with on a hidden buffer so aucmd_win is used. exec_lua [[ vim.cmd "wincmd s | wincmd _" _G.buf = vim.api.nvim_create_buf(true, true) - vim.api.nvim_buf_call(_G.buf, function() vim.cmd "wincmd J" end) + vim._with({buf = _G.buf}, function() vim.cmd "wincmd J" end) ]] screen:expect [[ ^ | @@ -367,14 +353,14 @@ describe('autocmd', function() -- This used to crash after making aucmd_win a normal window via the above. exec_lua [[ vim.cmd "tabnew | tabclose # | wincmd s | wincmd _" - vim.api.nvim_buf_call(_G.buf, function() vim.cmd "wincmd K" end) + vim._with({buf = _G.buf}, function() vim.cmd "wincmd K" end) ]] assert_alive() screen:expect_unchanged() -- Also check with win_splitmove(). exec_lua [[ - vim.api.nvim_buf_call(_G.buf, function() + vim._with({buf = _G.buf}, function() vim.fn.win_splitmove(vim.fn.winnr(), vim.fn.win_getid(1)) end) ]] @@ -382,11 +368,11 @@ describe('autocmd', function() -- Also check with nvim_win_set_config(). matches( - ': Failed to move window %d+ into split$', + '^Failed to move window %d+ into split$', pcall_err( exec_lua, [[ - vim.api.nvim_buf_call(_G.buf, function() + vim._with({buf = _G.buf}, function() vim.api.nvim_win_set_config(0, { vertical = true, win = vim.fn.win_getid(1) @@ -398,7 +384,7 @@ describe('autocmd', function() screen:expect_unchanged() -- Ensure splitting still works from inside the aucmd_win. - exec_lua [[vim.api.nvim_buf_call(_G.buf, function() vim.cmd "split" end)]] + exec_lua [[vim._with({buf = _G.buf}, function() vim.cmd "split" end)]] screen:expect [[ ^ | {1:~ }| @@ -418,7 +404,7 @@ describe('autocmd', function() 'editor', exec_lua [[ vim.cmd "only" - vim.api.nvim_buf_call(_G.buf, function() + vim._with({buf = _G.buf}, function() _G.config = vim.api.nvim_win_get_config(0) end) return _G.config.relative @@ -463,7 +449,7 @@ describe('autocmd', function() pcall_err( exec_lua, [[ - vim.api.nvim_buf_call(_G.buf, function() + vim._with({buf = _G.buf}, function() local win = vim.api.nvim_get_current_win() vim.api.nvim_win_close(win, true) end) @@ -475,7 +461,7 @@ describe('autocmd', function() pcall_err( exec_lua, [[ - vim.api.nvim_buf_call(_G.buf, function() + vim._with({buf = _G.buf}, function() local win = vim.api.nvim_get_current_win() vim.cmd('tabnext') vim.api.nvim_win_close(win, true) @@ -488,7 +474,7 @@ describe('autocmd', function() pcall_err( exec_lua, [[ - vim.api.nvim_buf_call(_G.buf, function() + vim._with({buf = _G.buf}, function() local win = vim.api.nvim_get_current_win() vim.api.nvim_win_hide(win) end) @@ -500,7 +486,7 @@ describe('autocmd', function() pcall_err( exec_lua, [[ - vim.api.nvim_buf_call(_G.buf, function() + vim._with({buf = _G.buf}, function() local win = vim.api.nvim_get_current_win() vim.cmd('tabnext') vim.api.nvim_win_hide(win) @@ -513,9 +499,6 @@ describe('autocmd', function() it(':doautocmd does not warn "No matching autocommands" #10689', function() local screen = Screen.new(32, 3) screen:attach() - screen:set_default_attr_ids({ - [1] = { bold = true, foreground = Screen.colors.Blue1 }, - }) feed(':doautocmd User Foo<cr>') screen:expect { diff --git a/test/functional/autocmd/cmdline_spec.lua b/test/functional/autocmd/cmdline_spec.lua index ad3bc3576f..ca137debb8 100644 --- a/test/functional/autocmd/cmdline_spec.lua +++ b/test/functional/autocmd/cmdline_spec.lua @@ -61,12 +61,6 @@ describe('cmdline autocommands', function() clear() local screen = Screen.new(72, 8) screen:attach() - screen:set_default_attr_ids({ - [1] = { bold = true, foreground = Screen.colors.Blue1 }, - [2] = { foreground = Screen.colors.Grey100, background = Screen.colors.Red }, - [3] = { bold = true, foreground = Screen.colors.SeaGreen4 }, - [4] = { bold = true, reverse = true }, - }) command("autocmd CmdlineEnter * echoerr 'FAIL'") command("autocmd CmdlineLeave * echoerr 'very error'") @@ -74,22 +68,22 @@ describe('cmdline autocommands', function() screen:expect([[ | {1:~ }|*3 - {4: }| + {3: }| : | - {2:CmdlineEnter Autocommands for "*": Vim(echoerr):FAIL} | + {9:CmdlineEnter Autocommands for "*": Vim(echoerr):FAIL} | :^ | ]]) feed("put ='lorem ipsum'<cr>") screen:expect([[ | - {4: }| + {3: }| : | - {2:CmdlineEnter Autocommands for "*": Vim(echoerr):FAIL} | + {9:CmdlineEnter Autocommands for "*": Vim(echoerr):FAIL} | :put ='lorem ipsum' | - {2:CmdlineLeave Autocommands for "*": Vim(echoerr):very error} | + {9:CmdlineLeave Autocommands for "*": Vim(echoerr):very error} | | - {3:Press ENTER or type command to continue}^ | + {6:Press ENTER or type command to continue}^ | ]]) -- cmdline was still executed @@ -108,11 +102,11 @@ describe('cmdline autocommands', function() screen:expect([[ | lorem ipsum | - {4: }| + {3: }| : | - {2:CmdlineEnter Autocommands for "*": Vim(echoerr):FAIL} | + {9:CmdlineEnter Autocommands for "*": Vim(echoerr):FAIL} | :put ='lorem ipsum' | - {2:CmdlineChanged Autocommands for "*": Vim(echoerr):change erreor} | + {9:CmdlineChanged Autocommands for "*": Vim(echoerr):change erreor} | :put ='lorem ipsum'^ | ]]) @@ -120,37 +114,37 @@ describe('cmdline autocommands', function() screen:expect([[ | lorem ipsum | - {4: }| + {3: }| : | - {2:CmdlineEnter Autocommands for "*": Vim(echoerr):FAIL} | + {9:CmdlineEnter Autocommands for "*": Vim(echoerr):FAIL} | :put ='lorem ipsum' | - {2:CmdlineChanged Autocommands for "*": Vim(echoerr):change erreor} | + {9:CmdlineChanged Autocommands for "*": Vim(echoerr):change erreor} | :put ='lorem ipsum^' | ]]) -- edit still works feed('.') screen:expect([[ - {4: }| + {3: }| : | - {2:CmdlineEnter Autocommands for "*": Vim(echoerr):FAIL} | + {9:CmdlineEnter Autocommands for "*": Vim(echoerr):FAIL} | :put ='lorem ipsum' | - {2:CmdlineChanged Autocommands for "*": Vim(echoerr):change erreor} | + {9:CmdlineChanged Autocommands for "*": Vim(echoerr):change erreor} | :put ='lorem ipsum.' | - {2:CmdlineChanged Autocommands for "*": Vim(echoerr):change erreor} | + {9:CmdlineChanged Autocommands for "*": Vim(echoerr):change erreor} | :put ='lorem ipsum.^' | ]]) feed('<cr>') screen:expect([[ :put ='lorem ipsum' | - {2:CmdlineChanged Autocommands for "*": Vim(echoerr):change erreor} | + {9:CmdlineChanged Autocommands for "*": Vim(echoerr):change erreor} | :put ='lorem ipsum.' | - {2:CmdlineChanged Autocommands for "*": Vim(echoerr):change erreor} | + {9:CmdlineChanged Autocommands for "*": Vim(echoerr):change erreor} | :put ='lorem ipsum.' | - {2:CmdlineLeave Autocommands for "*": Vim(echoerr):very error} | + {9:CmdlineLeave Autocommands for "*": Vim(echoerr):very error} | | - {3:Press ENTER or type command to continue}^ | + {6:Press ENTER or type command to continue}^ | ]]) -- cmdline was still executed diff --git a/test/functional/autocmd/dirchanged_spec.lua b/test/functional/autocmd/dirchanged_spec.lua index 24ac737b5b..1cde0e0552 100644 --- a/test/functional/autocmd/dirchanged_spec.lua +++ b/test/functional/autocmd/dirchanged_spec.lua @@ -9,7 +9,7 @@ local request = n.request local is_os = t.is_os describe('autocmd DirChanged and DirChangedPre', function() - local curdir = vim.uv.cwd():gsub('\\', '/') + local curdir = t.fix_slashes(vim.uv.cwd()) local dirs = { curdir .. '/Xtest-functional-autocmd-dirchanged.dir1', curdir .. '/Xtest-functional-autocmd-dirchanged.dir2', diff --git a/test/functional/autocmd/focus_spec.lua b/test/functional/autocmd/focus_spec.lua index 5163b576db..7f6092bf48 100644 --- a/test/functional/autocmd/focus_spec.lua +++ b/test/functional/autocmd/focus_spec.lua @@ -1,6 +1,6 @@ local t = require('test.testutil') local n = require('test.functional.testnvim')() -local tt = require('test.functional.terminal.testutil') +local tt = require('test.functional.testterm') local clear = n.clear local feed_command = n.feed_command diff --git a/test/functional/autocmd/show_spec.lua b/test/functional/autocmd/show_spec.lua index 7e1818c4fd..10d242527f 100644 --- a/test/functional/autocmd/show_spec.lua +++ b/test/functional/autocmd/show_spec.lua @@ -43,11 +43,9 @@ describe(':autocmd', function() it('should not show group information if interrupted', function() local screen = Screen.new(50, 6) - screen:set_default_attr_ids({ - [1] = { bold = true, foreground = Screen.colors.Blue1 }, -- NonText - [2] = { bold = true, foreground = Screen.colors.SeaGreen }, -- MoreMsg - [3] = { bold = true, foreground = Screen.colors.Magenta }, -- Title - }) + screen:add_extra_attr_ids { + [100] = { foreground = Screen.colors.Magenta, bold = true }, + } screen:attach() exec([[ set more @@ -73,11 +71,11 @@ describe(':autocmd', function() feed(':autocmd<CR>') screen:expect([[ :autocmd | - {3:--- Autocommands ---} | - {3:test_1} {3:BufEnter} | + {100:--- Autocommands ---} | + {100:test_1} {100:BufEnter} | A echo 'A' | B echo 'B' | - {2:-- More --}^ | + {6:-- More --}^ | ]]) feed('q') screen:expect([[ diff --git a/test/functional/autocmd/termxx_spec.lua b/test/functional/autocmd/termxx_spec.lua index a63996ae36..64f16cf779 100644 --- a/test/functional/autocmd/termxx_spec.lua +++ b/test/functional/autocmd/termxx_spec.lua @@ -1,6 +1,6 @@ local t = require('test.testutil') local n = require('test.functional.testnvim')() -local tt = require('test.functional.terminal.testutil') +local tt = require('test.functional.testterm') local uv = vim.uv local clear, command, testprg = n.clear, n.command, n.testprg @@ -199,7 +199,7 @@ end) describe('autocmd TextChangedT', function() clear() - local screen = tt.screen_setup() + local screen = tt.setup_screen() it('works', function() command('autocmd TextChangedT * ++once let g:called = 1') diff --git a/test/functional/core/channels_spec.lua b/test/functional/core/channels_spec.lua index a98e190a60..dee13d19ae 100644 --- a/test/functional/core/channels_spec.lua +++ b/test/functional/core/channels_spec.lua @@ -288,6 +288,37 @@ describe('channels', function() eq({ 'notification', 'exit', { 3, 0 } }, next_msg()) end) + it('stdio channel works with stdout redirected to file #30509', function() + t.write_file( + 'Xstdio_write.vim', + [[ + let chan = stdioopen({}) + call chansend(chan, 'foo') + call chansend(chan, 'bar') + qall! + ]] + ) + local fd = assert(vim.uv.fs_open('Xstdio_redir', 'w', 420)) + local exit_code, exit_signal + local handle = vim.uv.spawn(nvim_prog, { + args = { '-u', 'NONE', '-i', 'NONE', '--headless', '-S', 'Xstdio_write.vim' }, + -- Simulate shell redirection: "nvim ... > Xstdio_redir". #30509 + stdio = { nil, fd, nil }, + }, function(code, signal) + vim.uv.stop() + exit_code, exit_signal = code, signal + end) + finally(function() + handle:close() + vim.uv.fs_close(fd) + os.remove('Xstdio_write.vim') + os.remove('Xstdio_redir') + end) + vim.uv.run('default') + eq({ 0, 0 }, { exit_code, exit_signal }) + eq('foobar', t.read_file('Xstdio_redir')) + end) + it('can use buffered output mode', function() skip(fn.executable('grep') == 0, 'missing "grep" command') source([[ diff --git a/test/functional/core/fileio_spec.lua b/test/functional/core/fileio_spec.lua index 5b0be1e83c..d33710a63d 100644 --- a/test/functional/core/fileio_spec.lua +++ b/test/functional/core/fileio_spec.lua @@ -276,11 +276,6 @@ describe('fileio', function() write_file('Xtest-overwrite-forced', 'foobar') command('set nofixendofline') local screen = Screen.new(40, 4) - screen:set_default_attr_ids({ - [1] = { bold = true, foreground = Screen.colors.Blue1 }, - [2] = { foreground = Screen.colors.Grey100, background = Screen.colors.Red }, - [3] = { bold = true, foreground = Screen.colors.SeaGreen4 }, - }) screen:attach() command('set shortmess-=F') @@ -300,9 +295,9 @@ describe('fileio', function() -- use async feed_command because nvim basically hangs on the prompt feed_command('w') screen:expect([[ - {2:WARNING: The file has been changed since}| - {2: reading it!!!} | - {3:Do you really want to write to it (y/n)?}| + {9:WARNING: The file has been changed since}| + {9: reading it!!!} | + {6:Do you really want to write to it (y/n)?}| ^ | ]]) @@ -326,11 +321,11 @@ end) describe('tmpdir', function() local tmproot_pat = [=[.*[/\\]nvim%.[^/\\]+]=] local testlog = 'Xtest_tmpdir_log' - local os_tmpdir + local os_tmpdir ---@type string before_each(function() -- Fake /tmp dir so that we can mess it up. - os_tmpdir = vim.uv.fs_mkdtemp(vim.fs.dirname(t.tmpname()) .. '/nvim_XXXXXXXXXX') + os_tmpdir = assert(vim.uv.fs_mkdtemp(vim.fs.dirname(t.tmpname(false)) .. '/nvim_XXXXXXXXXX')) end) after_each(function() @@ -419,15 +414,4 @@ describe('tmpdir', function() rm_tmpdir() eq('E5431: tempdir disappeared (3 times)', api.nvim_get_vvar('errmsg')) end) - - it('$NVIM_APPNAME relative path', function() - clear({ - env = { - NVIM_APPNAME = 'a/b', - NVIM_LOG_FILE = testlog, - TMPDIR = os_tmpdir, - }, - }) - matches([=[.*[/\\]a%%b%.[^/\\]+]=], fn.tempname()) - end) end) diff --git a/test/functional/core/job_spec.lua b/test/functional/core/job_spec.lua index e1efc07452..68ac0a50f6 100644 --- a/test/functional/core/job_spec.lua +++ b/test/functional/core/job_spec.lua @@ -1,7 +1,7 @@ local t = require('test.testutil') local n = require('test.functional.testnvim')() local Screen = require('test.functional.ui.screen') -local tt = require('test.functional.terminal.testutil') +local tt = require('test.functional.testterm') local clear = n.clear local eq = t.eq @@ -910,11 +910,6 @@ describe('jobs', function() it('hides cursor and flushes messages before blocking', function() local screen = Screen.new(50, 6) - screen:set_default_attr_ids({ - [0] = { foreground = Screen.colors.Blue, bold = true }, -- NonText - [1] = { bold = true, reverse = true }, -- MsgSeparator - [2] = { bold = true, foreground = Screen.colors.SeaGreen }, -- MoreMsg - }) screen:attach() command([[let g:id = jobstart([v:progpath, '--clean', '--headless'])]]) source([[ @@ -928,8 +923,8 @@ describe('jobs', function() screen:expect { grid = [[ | - {0:~ }|*2 - {1: }| + {1:~ }|*2 + {3: }| aaa | bbb | ]], @@ -938,11 +933,11 @@ describe('jobs', function() screen:expect { grid = [[ | - {1: }| + {3: }| aaa | bbb | ccc | - {2:Press ENTER or type command to continue}^ | + {6:Press ENTER or type command to continue}^ | ]], } feed('<CR>') diff --git a/test/functional/core/log_spec.lua b/test/functional/core/log_spec.lua index cac61cda2d..a952730779 100644 --- a/test/functional/core/log_spec.lua +++ b/test/functional/core/log_spec.lua @@ -1,5 +1,6 @@ local t = require('test.testutil') local n = require('test.functional.testnvim')() +local tt = require('test.functional.testterm') local assert_log = t.assert_log local clear = n.clear @@ -29,10 +30,54 @@ describe('log', function() assert(request('nvim__stats').log_skip <= 13) end) - it('messages are formatted with name or test id', function() + it('TUI client name is "ui"', function() + local function setup(env) + clear() + -- Start Nvim with builtin UI. + local screen = tt.setup_child_nvim({ + '-u', + 'NONE', + '-i', + 'NONE', + '--cmd', + n.nvim_set, + }, { + env = env, + }) + screen:expect([[ + {1: } | + ~ |*4 + | + {3:-- TERMINAL --} | + ]]) + end + + -- Without $NVIM parent. + setup({ + NVIM = '', + NVIM_LISTEN_ADDRESS = '', + NVIM_LOG_FILE = testlog, + __NVIM_TEST_LOG = '1', + }) + -- Example: + -- ERR 2024-09-11T16:40:02.421 ui.47056 ui_client_run:165: test log message + assert_log(' ui%.%d+% +ui_client_run:%d+: test log message', testlog, 100) + + -- With $NVIM parent. + setup({ + NVIM_LOG_FILE = testlog, + __NVIM_TEST_LOG = '1', + }) + -- Example: + -- ERR 2024-09-11T16:41:17.539 ui/c/T2.47826.0 ui_client_run:165: test log message + local tid = _G._nvim_test_id + assert_log(' ui/c/' .. tid .. '%.%d+%.%d +ui_client_run:%d+: test log message', testlog, 100) + end) + + it('formats messages with session name or test id', function() -- Examples: - -- ERR 2022-05-29T12:30:03.800 T2 log_init:110: test log message - -- ERR 2022-05-29T12:30:03.814 T2/child log_init:110: test log message + -- ERR 2024-09-11T16:44:33.794 T3.49429.0 server_init:58: test log message + -- ERR 2024-09-11T16:44:33.823 c/T3.49429.0 server_init:58: test log message clear({ env = { @@ -47,10 +92,10 @@ describe('log', function() exec_lua([[ local j1 = vim.fn.jobstart({ vim.v.progpath, '-es', '-V1', '+foochild', '+qa!' }, vim.empty_dict()) - vim.fn.jobwait({ j1 }, 10000) + vim.fn.jobwait({ j1 }, 5000) ]]) - -- Child Nvim spawned by jobstart() appends "/c" to parent name. - assert_log('%.%d+%.%d/c +server_init:%d+: test log message', testlog, 100) + -- Child Nvim spawned by jobstart() prepends "c/" to parent name. + assert_log('c/' .. tid .. '%.%d+%.%d +server_init:%d+: test log message', testlog, 100) end) end) diff --git a/test/functional/core/main_spec.lua b/test/functional/core/main_spec.lua index 5e903726db..a6e917b4b2 100644 --- a/test/functional/core/main_spec.lua +++ b/test/functional/core/main_spec.lua @@ -193,4 +193,26 @@ describe('command-line option', function() matches('Run "nvim %-V1 %-v"', fn.system({ nvim_prog_abs(), '-v' })) matches('Compilation: .*Run :checkhealth', fn.system({ nvim_prog_abs(), '-V1', '-v' })) end) + + if is_os('win') then + for _, prefix in ipairs({ '~/', '~\\' }) do + it('expands ' .. prefix .. ' on Windows', function() + local fname = os.getenv('USERPROFILE') .. '\\nvim_test.txt' + finally(function() + os.remove(fname) + end) + write_file(fname, 'some text') + eq( + 'some text', + fn.system({ + nvim_prog_abs(), + '-es', + '+%print', + '+q', + prefix .. 'nvim_test.txt', + }):gsub('\n', '') + ) + end) + end + end end) diff --git a/test/functional/vimscript/server_spec.lua b/test/functional/core/server_spec.lua index 4b0dc087f6..0ec11169e9 100644 --- a/test/functional/vimscript/server_spec.lua +++ b/test/functional/core/server_spec.lua @@ -1,10 +1,8 @@ local t = require('test.testutil') local n = require('test.functional.testnvim')() -local assert_log = t.assert_log local eq, neq, eval = t.eq, t.neq, n.eval local clear, fn, api = n.clear, n.fn, n.api -local ok = t.ok local matches = t.matches local pcall_err = t.pcall_err local check_close = n.check_close @@ -20,12 +18,16 @@ local function clear_serverlist() end end -describe('server', function() - after_each(function() - check_close() - os.remove(testlog) - end) +after_each(function() + check_close() + os.remove(testlog) +end) +before_each(function() + os.remove(testlog) +end) + +describe('server', function() it('serverstart() stores sockets in $XDG_RUNTIME_DIR', function() local dir = 'Xtest_xdg_run' mkdir(dir) @@ -39,6 +41,21 @@ describe('server', function() end end) + it('broken $XDG_RUNTIME_DIR is not fatal #30282', function() + clear { + args_rm = { '--listen' }, + env = { NVIM_LOG_FILE = testlog, XDG_RUNTIME_DIR = '/non-existent-dir/subdir//' }, + } + + if is_os('win') then + -- Windows pipes have a special namespace and thus aren't decided by $XDG_RUNTIME_DIR. + matches('nvim', api.nvim_get_vvar('servername')) + else + eq('', api.nvim_get_vvar('servername')) + t.assert_log('Failed to start server%: no such file or directory', testlog, 100) + end + end) + it('serverstart(), serverstop() does not set $NVIM', function() clear() local s = eval('serverstart()') @@ -49,15 +66,6 @@ describe('server', function() eq('', eval('$NVIM_LISTEN_ADDRESS')) end) - it('sets new v:servername if $NVIM_LISTEN_ADDRESS is invalid', function() - clear({ env = { NVIM_LISTEN_ADDRESS = '.' } }) - -- Cleared on startup. - eq('', eval('$NVIM_LISTEN_ADDRESS')) - local servers = fn.serverlist() - eq(1, #servers) - ok(string.len(servers[1]) > 4) -- "~/.local/state/nvim…/…" or "\\.\pipe\…" - end) - it('sets v:servername at startup or if all servers were stopped', function() clear() local initial_server = api.nvim_get_vvar('servername') @@ -89,20 +97,26 @@ describe('server', function() end) it('serverstop() returns false for invalid input', function() - clear { env = { - NVIM_LOG_FILE = testlog, - NVIM_LISTEN_ADDRESS = '.', - } } + clear { + args_rm = { '--listen' }, + env = { + NVIM_LOG_FILE = testlog, + NVIM_LISTEN_ADDRESS = '', + }, + } eq(0, eval("serverstop('')")) eq(0, eval("serverstop('bogus-socket-name')")) - assert_log('Not listening on bogus%-socket%-name', testlog, 10) + t.assert_log('Not listening on bogus%-socket%-name', testlog, 10) end) it('parses endpoints', function() - clear { env = { - NVIM_LOG_FILE = testlog, - NVIM_LISTEN_ADDRESS = '.', - } } + clear { + args_rm = { '--listen' }, + env = { + NVIM_LOG_FILE = testlog, + NVIM_LISTEN_ADDRESS = '', + }, + } clear_serverlist() eq({}, fn.serverlist()) @@ -126,7 +140,7 @@ describe('server', function() if status then table.insert(expected, v4) pcall(fn.serverstart, v4) -- exists already; ignore - assert_log('Failed to start server: address already in use: 127%.0%.0%.1', testlog, 10) + t.assert_log('Failed to start server: address already in use: 127%.0%.0%.1', testlog, 10) end local v6 = '::1:12345' @@ -134,13 +148,13 @@ describe('server', function() if status then table.insert(expected, v6) pcall(fn.serverstart, v6) -- exists already; ignore - assert_log('Failed to start server: address already in use: ::1', testlog, 10) + t.assert_log('Failed to start server: address already in use: ::1', testlog, 10) end eq(expected, fn.serverlist()) clear_serverlist() -- Address without slashes is a "name" which is appended to a generated path. #8519 - matches([[.*[/\\]xtest1%.2%.3%.4[^/\\]*]], fn.serverstart('xtest1.2.3.4')) + matches([[[/\\]xtest1%.2%.3%.4[^/\\]*]], fn.serverstart('xtest1.2.3.4')) clear_serverlist() eq('Vim:Failed to start server: invalid argument', pcall_err(fn.serverstart, '127.0.0.1:65536')) -- invalid port @@ -176,24 +190,89 @@ describe('server', function() end) describe('startup --listen', function() + -- Tests Nvim output when failing to start, with and without "--headless". + -- TODO(justinmk): clear() should have a way to get stdout if Nvim fails to start. + local function _test(args, env, expected) + local function run(cmd) + return n.exec_lua(function(cmd_, env_) + return vim + .system(cmd_, { + text = true, + env = vim.tbl_extend( + 'force', + -- Avoid noise in the logs; we expect failures for these tests. + { NVIM_LOG_FILE = testlog }, + env_ or {} + ), + }) + :wait() + end, cmd, env) --[[@as vim.SystemCompleted]] + end + + local cmd = vim.list_extend({ n.nvim_prog, '+qall!', '--headless' }, args) + local r = run(cmd) + eq(1, r.code) + matches(expected, (r.stderr .. r.stdout):gsub('\\n', ' ')) + + if is_os('win') then + return -- On Windows, output without --headless is garbage. + end + table.remove(cmd, 3) -- Remove '--headless'. + assert(not vim.tbl_contains(cmd, '--headless')) + r = run(cmd) + eq(1, r.code) + matches(expected, (r.stderr .. r.stdout):gsub('\\n', ' ')) + end + it('validates', function() - clear() - local cmd = { unpack(n.nvim_argv) } - table.insert(cmd, '--listen') - matches('nvim.*: Argument missing after: "%-%-listen"', fn.system(cmd)) + clear { env = { NVIM_LOG_FILE = testlog } } + local in_use = n.eval('v:servername') ---@type string Address already used by another server. + + t.assert_nolog('Failed to start server', testlog, 100) + t.assert_nolog('Host lookup failed', testlog, 100) - cmd = { unpack(n.nvim_argv) } - table.insert(cmd, '--listen2') - matches('nvim.*: Garbage after option argument: "%-%-listen2"', fn.system(cmd)) + _test({ '--listen' }, nil, 'nvim.*: Argument missing after: "%-%-listen"') + _test({ '--listen2' }, nil, 'nvim.*: Garbage after option argument: "%-%-listen2"') + _test( + { '--listen', in_use }, + nil, + ('nvim.*: Failed to %%-%%-listen: [^:]+ already [^:]+: "%s"'):format(vim.pesc(in_use)) + ) + _test({ '--listen', '/' }, nil, 'nvim.*: Failed to %-%-listen: [^:]+: "/"') + _test( + { '--listen', 'https://example.com' }, + nil, + ('nvim.*: Failed to %%-%%-listen: %s: "https://example.com"'):format( + is_os('mac') and 'unknown node or service' or 'service not available for socket type' + ) + ) + + t.assert_log('Failed to start server', testlog, 100) + t.assert_log('Host lookup failed', testlog, 100) + + _test( + {}, + { NVIM_LISTEN_ADDRESS = in_use }, + ('nvim.*: Failed $NVIM_LISTEN_ADDRESS: [^:]+ already [^:]+: "%s"'):format(vim.pesc(in_use)) + ) + _test({}, { NVIM_LISTEN_ADDRESS = '/' }, 'nvim.*: Failed $NVIM_LISTEN_ADDRESS: [^:]+: "/"') + _test( + {}, + { NVIM_LISTEN_ADDRESS = 'https://example.com' }, + ('nvim.*: Failed $NVIM_LISTEN_ADDRESS: %s: "https://example.com"'):format( + is_os('mac') and 'unknown node or service' or 'service not available for socket type' + ) + ) end) it('sets v:servername, overrides $NVIM_LISTEN_ADDRESS', function() local addr = (is_os('win') and [[\\.\pipe\Xtest-listen-pipe]] or './Xtest-listen-pipe') clear({ env = { NVIM_LISTEN_ADDRESS = './Xtest-env-pipe' }, args = { '--listen', addr } }) + eq('', eval('$NVIM_LISTEN_ADDRESS')) -- Cleared on startup. eq(addr, api.nvim_get_vvar('servername')) -- Address without slashes is a "name" which is appended to a generated path. #8519 clear({ args = { '--listen', 'test-name' } }) - matches([[.*[/\\]test%-name[^/\\]*]], api.nvim_get_vvar('servername')) + matches([[[/\\]test%-name[^/\\]*]], api.nvim_get_vvar('servername')) end) end) diff --git a/test/functional/core/startup_spec.lua b/test/functional/core/startup_spec.lua index a53625ab1b..f48bcb9360 100644 --- a/test/functional/core/startup_spec.lua +++ b/test/functional/core/startup_spec.lua @@ -27,7 +27,6 @@ local sleep = vim.uv.sleep local startswith = vim.startswith local write_file = t.write_file local api = n.api -local alter_slashes = n.alter_slashes local is_os = t.is_os local dedent = t.dedent local tbl_map = vim.tbl_map @@ -40,22 +39,15 @@ local testlog = 'Xtest-startupspec-log' describe('startup', function() it('--clean', function() clear() - ok( - string.find( - alter_slashes(api.nvim_get_option_value('runtimepath', {})), - fn.stdpath('config'), - 1, - true - ) ~= nil + matches( + vim.pesc(t.fix_slashes(fn.stdpath('config'))), + t.fix_slashes(api.nvim_get_option_value('runtimepath', {})) ) + clear('--clean') ok( - string.find( - alter_slashes(api.nvim_get_option_value('runtimepath', {})), - fn.stdpath('config'), - 1, - true - ) == nil + not t.fix_slashes(api.nvim_get_option_value('runtimepath', {})) + :match(vim.pesc(t.fix_slashes(fn.stdpath('config')))) ) end) @@ -112,6 +104,13 @@ describe('startup', function() |*2 ]]) end) + + it(':filetype detect enables filetype detection with -u NONE', function() + clear() + eq('filetype detection:OFF plugin:OFF indent:OFF', exec_capture('filetype')) + command('filetype detect') + eq('filetype detection:ON plugin:OFF indent:OFF', exec_capture('filetype')) + end) end) describe('startup', function() @@ -400,9 +399,6 @@ describe('startup', function() read_file('Xtest_startup_ttyout') ) end) - if is_os('win') then - assert_log('stream write failed. RPC canceled; closing channel', testlog) - end end) it('input from pipe: has("ttyin")==0 has("ttyout")==1', function() @@ -435,9 +431,6 @@ describe('startup', function() read_file('Xtest_startup_ttyout') ) end) - if is_os('win') then - assert_log('stream write failed. RPC canceled; closing channel', testlog) - end end) it('input from pipe (implicit) #7679', function() @@ -1331,31 +1324,59 @@ describe('runtime:', function() end) it("loads ftdetect/*.{vim,lua} respecting 'rtp' order", function() - local ftdetect_folder = table.concat({ xconfig, 'nvim', 'ftdetect' }, pathsep) - local after_ftdetect_folder = table.concat({ xconfig, 'nvim', 'after', 'ftdetect' }, pathsep) + local rtp_folder = table.concat({ xconfig, 'nvim' }, pathsep) + local after_rtp_folder = table.concat({ rtp_folder, 'after' }, pathsep) + local ftdetect_folder = table.concat({ rtp_folder, 'ftdetect' }, pathsep) + local after_ftdetect_folder = table.concat({ after_rtp_folder, 'ftdetect' }, pathsep) mkdir_p(ftdetect_folder) mkdir_p(after_ftdetect_folder) finally(function() rmdir(ftdetect_folder) rmdir(after_ftdetect_folder) end) + write_file(table.concat({ rtp_folder, 'scripts.vim' }, pathsep), [[let g:aseq ..= 'S']]) + write_file(table.concat({ after_rtp_folder, 'scripts.vim' }, pathsep), [[let g:aseq ..= 's']]) -- A .lua file is loaded after a .vim file if they only differ in extension. -- All files in after/ftdetect/ are loaded after all files in ftdetect/. - write_file(table.concat({ ftdetect_folder, 'new-ft.vim' }, pathsep), [[let g:seq ..= 'A']]) + write_file( + table.concat({ ftdetect_folder, 'new-ft.vim' }, pathsep), + [[ + let g:seq ..= 'A' + autocmd BufRead,BufNewFile FTDETECT let g:aseq ..= 'A' + ]] + ) write_file( table.concat({ ftdetect_folder, 'new-ft.lua' }, pathsep), - [[vim.g.seq = vim.g.seq .. 'B']] + [[ + vim.g.seq = vim.g.seq .. 'B' + vim.api.nvim_create_autocmd({ 'BufRead', 'BufNewFile' }, { + pattern = 'FTDETECT', + command = "let g:aseq ..= 'B'", + }) + ]] ) write_file( table.concat({ after_ftdetect_folder, 'new-ft.vim' }, pathsep), - [[let g:seq ..= 'a']] + [[ + let g:seq ..= 'a' + autocmd BufRead,BufNewFile FTDETECT let g:aseq ..= 'a' + ]] ) write_file( table.concat({ after_ftdetect_folder, 'new-ft.lua' }, pathsep), - [[vim.g.seq = vim.g.seq .. 'b']] + [[ + vim.g.seq = vim.g.seq .. 'b' + vim.api.nvim_create_autocmd({ 'BufRead', 'BufNewFile' }, { + pattern = 'FTDETECT', + command = "let g:aseq ..= 'b'", + }) + ]] ) clear { args_rm = { '-u' }, args = { '--cmd', 'let g:seq = ""' }, env = xenv } eq('ABab', eval('g:seq')) + command('let g:aseq = ""') + command('edit FTDETECT') + eq('SsABab', eval('g:aseq')) end) end) diff --git a/test/functional/editor/completion_spec.lua b/test/functional/editor/completion_spec.lua index 62bb7e19f3..d543de4acd 100644 --- a/test/functional/editor/completion_spec.lua +++ b/test/functional/editor/completion_spec.lua @@ -4,7 +4,7 @@ local Screen = require('test.functional.ui.screen') local assert_alive = n.assert_alive local clear, feed = n.clear, n.feed -local eval, eq, neq = n.eval, t.eq, t.neq +local eval, eq, neq, ok = n.eval, t.eq, t.neq, t.ok local feed_command, source, expect = n.feed_command, n.source, n.expect local fn = n.fn local command = n.command @@ -18,19 +18,10 @@ describe('completion', function() clear() screen = Screen.new(60, 8) screen:attach() - screen:set_default_attr_ids({ - [0] = { bold = true, foreground = Screen.colors.Blue }, - [1] = { background = Screen.colors.LightMagenta }, - [2] = { background = Screen.colors.Grey }, - [3] = { bold = true }, - [4] = { bold = true, foreground = Screen.colors.SeaGreen }, - [5] = { foreground = Screen.colors.Red }, - [6] = { background = Screen.colors.Black }, - [7] = { foreground = Screen.colors.White, background = Screen.colors.Red }, - [8] = { reverse = true }, - [9] = { bold = true, reverse = true }, - [10] = { foreground = Screen.colors.Grey0, background = Screen.colors.Yellow }, - }) + screen:add_extra_attr_ids { + [100] = { foreground = Screen.colors.Gray0, background = Screen.colors.Yellow }, + [101] = { background = Screen.colors.Gray0 }, + } end) describe('v:completed_item', function() @@ -42,15 +33,15 @@ describe('completion', function() screen:expect([[ foo | foo^ | - {0:~ }|*5 - {3:-- Keyword Local completion (^N^P) The only match} | + {1:~ }|*5 + {5:-- Keyword Local completion (^N^P) The only match} | ]]) feed('<C-e>') screen:expect([[ foo | ^ | - {0:~ }|*5 - {3:-- INSERT --} | + {1:~ }|*5 + {5:-- INSERT --} | ]]) feed('<ESC>') eq({}, eval('v:completed_item')) @@ -104,10 +95,10 @@ describe('completion', function() eq('foo', eval('getline(1)')) screen:expect([[ foo^ | - {2:bar foobaz baz }{0: }| - {1:abbr kind menu }{0: }| - {0:~ }|*4 - {3:-- Omni completion (^O^N^P) }{4:match 1 of 2} | + {12:bar foobaz baz }{1: }| + {4:abbr kind menu }{1: }| + {1:~ }|*4 + {5:-- Omni completion (^O^N^P) }{6:match 1 of 2} | ]]) eq({ word = 'foo', @@ -136,24 +127,24 @@ describe('completion', function() screen:expect([[ foo | ^ | - {0:~ }|*5 - {3:-- INSERT --} | + {1:~ }|*5 + {5:-- INSERT --} | ]]) feed('<C-x>') -- the ^X prompt, only test this once screen:expect([[ foo | ^ | - {0:~ }|*5 - {3:-- ^X mode (^]^D^E^F^I^K^L^N^O^Ps^U^V^Y)} | + {1:~ }|*5 + {5:-- ^X mode (^]^D^E^F^I^K^L^N^O^Ps^U^V^Y)} | ]]) feed('<C-n>') screen:expect([[ foo | foo^ | - {2:foo }{0: }| - {0:~ }|*4 - {3:-- Keyword Local completion (^N^P) The only match} | + {12:foo }{1: }| + {1:~ }|*4 + {5:-- Keyword Local completion (^N^P) The only match} | ]]) feed('bar<ESC>') eq('foobar', eval('getline(2)')) @@ -162,9 +153,9 @@ describe('completion', function() foo | foobar | foo^ | - {2:foo }{0: }| - {0:~ }|*3 - {3:-- INSERT --} | + {12:foo }{1: }| + {1:~ }|*3 + {5:-- INSERT --} | ]]) eq('foo', eval('getline(3)')) end) @@ -174,16 +165,16 @@ describe('completion', function() screen:expect([[ foo | ^ | - {2:foo }{0: }| - {0:~ }|*4 - {3:-- Keyword Local completion (^N^P) The only match} | + {12:foo }{1: }| + {1:~ }|*4 + {5:-- Keyword Local completion (^N^P) The only match} | ]]) feed('<C-y>') screen:expect([[ foo | foo^ | - {0:~ }|*5 - {3:-- INSERT --} | + {1:~ }|*5 + {5:-- INSERT --} | ]]) feed('<ESC>') eq('foo', eval('getline(2)')) @@ -191,9 +182,9 @@ describe('completion', function() screen:expect([[ foo |*2 ^ | - {2:foo }{0: }| - {0:~ }|*3 - {3:-- INSERT --} | + {12:foo }{1: }| + {1:~ }|*3 + {5:-- INSERT --} | ]]) feed('<C-y><ESC>') eq('foo', eval('getline(3)')) @@ -204,16 +195,16 @@ describe('completion', function() screen:expect([[ foo | ^ | - {1:foo }{0: }| - {0:~ }|*4 - {3:-- Keyword Local completion (^N^P) }{5:Back at original} | + {4:foo }{1: }| + {1:~ }|*4 + {5:-- Keyword Local completion (^N^P) }{19:Back at original} | ]]) feed('b') screen:expect([[ foo | b^ | - {0:~ }|*5 - {3:-- Keyword Local completion (^N^P) }{5:Back at original} | + {1:~ }|*5 + {5:-- Keyword Local completion (^N^P) }{19:Back at original} | ]]) feed('ar<ESC>') eq('bar', eval('getline(2)')) @@ -222,9 +213,9 @@ describe('completion', function() foo | bar | ^ | - {1:foo }{0: }| - {0:~ }|*3 - {3:-- INSERT --} | + {4:foo }{1: }| + {1:~ }|*3 + {5:-- INSERT --} | ]]) feed('bar<ESC>') eq('bar', eval('getline(3)')) @@ -235,15 +226,15 @@ describe('completion', function() screen:expect([[ foo | ^ | - {1:foo }{0: }| - {0:~ }|*4 - {3:-- Keyword Local completion (^N^P) }{5:Back at original} | + {4:foo }{1: }| + {1:~ }|*4 + {5:-- Keyword Local completion (^N^P) }{19:Back at original} | ]]) feed('<ESC>') screen:expect([[ foo | ^ | - {0:~ }|*5 + {1:~ }|*5 | ]]) eq('', eval('getline(2)')) @@ -252,16 +243,16 @@ describe('completion', function() foo | | ^ | - {1:foo }{0: }| - {0:~ }|*3 - {3:-- INSERT --} | + {4:foo }{1: }| + {1:~ }|*3 + {5:-- INSERT --} | ]]) feed('<ESC>') screen:expect([[ foo | | ^ | - {0:~ }|*4 + {1:~ }|*4 | ]]) eq('', eval('getline(3)')) @@ -336,7 +327,7 @@ describe('completion', function() end end) - describe('refresh:always', function() + describe('with refresh:always and noselect', function() before_each(function() source([[ function! TestCompletion(findstart, base) abort @@ -367,44 +358,44 @@ describe('completion', function() feed('i<C-x><C-u>') screen:expect([[ ^ | - {1:January }{6: }{0: }| - {1:February }{6: }{0: }| - {1:March }{6: }{0: }| - {1:April }{2: }{0: }| - {1:May }{2: }{0: }| - {1:June }{2: }{0: }| - {3:-- User defined completion (^U^N^P) }{5:Back at original} | + {4:January }{101: }{1: }| + {4:February }{101: }{1: }| + {4:March }{101: }{1: }| + {4:April }{12: }{1: }| + {4:May }{12: }{1: }| + {4:June }{12: }{1: }| + {5:-- User defined completion (^U^N^P) }{19:Back at original} | ]]) feed('u') screen:expect([[ u^ | - {1:January }{0: }| - {1:February }{0: }| - {1:June }{0: }| - {1:July }{0: }| - {1:August }{0: }| - {0:~ }| - {3:-- User defined completion (^U^N^P) }{5:Back at original} | + {4:January }{1: }| + {4:February }{1: }| + {4:June }{1: }| + {4:July }{1: }| + {4:August }{1: }| + {1:~ }| + {5:-- User defined completion (^U^N^P) }{19:Back at original} | ]]) feed('g') screen:expect([[ ug^ | - {1:August }{0: }| - {0:~ }|*5 - {3:-- User defined completion (^U^N^P) }{5:Back at original} | + {4:August }{1: }| + {1:~ }|*5 + {5:-- User defined completion (^U^N^P) }{19:Back at original} | ]]) feed('<Down>') screen:expect([[ ug^ | - {2:August }{0: }| - {0:~ }|*5 - {3:-- User defined completion (^U^N^P) The only match} | + {12:August }{1: }| + {1:~ }|*5 + {5:-- User defined completion (^U^N^P) The only match} | ]]) feed('<C-y>') screen:expect([[ August^ | - {0:~ }|*6 - {3:-- INSERT --} | + {1:~ }|*6 + {5:-- INSERT --} | ]]) expect('August') end) @@ -414,45 +405,45 @@ describe('completion', function() screen:expect([[ | Ja^ | - {1:January }{0: }| - {0:~ }|*4 - {3:-- User defined completion (^U^N^P) }{5:Back at original} | + {4:January }{1: }| + {1:~ }|*4 + {5:-- User defined completion (^U^N^P) }{19:Back at original} | ]]) feed('<BS>') screen:expect([[ | J^ | - {1:January }{0: }| - {1:June }{0: }| - {1:July }{0: }| - {0:~ }|*2 - {3:-- User defined completion (^U^N^P) }{5:Back at original} | + {4:January }{1: }| + {4:June }{1: }| + {4:July }{1: }| + {1:~ }|*2 + {5:-- User defined completion (^U^N^P) }{19:Back at original} | ]]) feed('<C-n>') screen:expect([[ | January^ | - {2:January }{0: }| - {1:June }{0: }| - {1:July }{0: }| - {0:~ }|*2 - {3:-- User defined completion (^U^N^P) }{4:match 1 of 3} | + {12:January }{1: }| + {4:June }{1: }| + {4:July }{1: }| + {1:~ }|*2 + {5:-- User defined completion (^U^N^P) }{6:match 1 of 3} | ]]) feed('<C-n>') screen:expect([[ | June^ | - {1:January }{0: }| - {2:June }{0: }| - {1:July }{0: }| - {0:~ }|*2 - {3:-- User defined completion (^U^N^P) }{4:match 2 of 3} | + {4:January }{1: }| + {12:June }{1: }| + {4:July }{1: }| + {1:~ }|*2 + {5:-- User defined completion (^U^N^P) }{6:match 2 of 3} | ]]) feed('<Esc>') screen:expect([[ | Jun^e | - {0:~ }|*5 + {1:~ }|*5 | ]]) feed('.') @@ -460,7 +451,7 @@ describe('completion', function() | June | Jun^e | - {0:~ }|*4 + {1:~ }|*4 | ]]) expect([[ @@ -468,6 +459,67 @@ describe('completion', function() June June]]) end) + + it('Enter does not select original text', function() + feed('iJ<C-x><C-u>') + poke_eventloop() + feed('u') + poke_eventloop() + feed('<CR>') + expect([[ + Ju + ]]) + feed('J<C-x><C-u>') + poke_eventloop() + feed('<CR>') + expect([[ + Ju + J + ]]) + end) + end) + + describe('with noselect but not refresh:always', function() + before_each(function() + source([[ + function! TestCompletion(findstart, base) abort + if a:findstart + let line = getline('.') + let start = col('.') - 1 + while start > 0 && line[start - 1] =~ '\a' + let start -= 1 + endwhile + return start + else + let ret = [] + for m in split("January February March April May June July August September October November December") + if m =~ a:base " match by regex + call add(ret, m) + endif + endfor + return {'words':ret} + endif + endfunction + + set completeopt=menuone,noselect + set completefunc=TestCompletion + ]]) + end) + + it('Enter selects original text after adding leader', function() + feed('iJ<C-x><C-u>') + poke_eventloop() + feed('u') + poke_eventloop() + feed('<CR>') + expect('Ju') + feed('<Esc>') + poke_eventloop() + -- The behavior should be the same when completion has been interrupted, + -- which can happen interactively if the completion function is slow. + feed('SJ<C-x><C-u>u<CR>') + expect('Ju') + end) end) describe('with a lot of items', function() @@ -485,46 +537,46 @@ describe('completion', function() feed('i<C-r>=TestComplete()<CR>') screen:expect([[ ^ | - {1:0 }{6: }{0: }| - {1:1 }{2: }{0: }| - {1:2 }{2: }{0: }| - {1:3 }{2: }{0: }| - {1:4 }{2: }{0: }| - {1:5 }{2: }{0: }| - {3:-- INSERT --} | + {4:0 }{101: }{1: }| + {4:1 }{12: }{1: }| + {4:2 }{12: }{1: }| + {4:3 }{12: }{1: }| + {4:4 }{12: }{1: }| + {4:5 }{12: }{1: }| + {5:-- INSERT --} | ]]) feed('7') screen:expect([[ 7^ | - {1:7 }{6: }{0: }| - {1:70 }{6: }{0: }| - {1:71 }{6: }{0: }| - {1:72 }{2: }{0: }| - {1:73 }{2: }{0: }| - {1:74 }{2: }{0: }| - {3:-- INSERT --} | + {4:7 }{101: }{1: }| + {4:70 }{101: }{1: }| + {4:71 }{101: }{1: }| + {4:72 }{12: }{1: }| + {4:73 }{12: }{1: }| + {4:74 }{12: }{1: }| + {5:-- INSERT --} | ]]) feed('<c-n>') screen:expect([[ 7^ | - {2:7 }{6: }{0: }| - {1:70 }{6: }{0: }| - {1:71 }{6: }{0: }| - {1:72 }{2: }{0: }| - {1:73 }{2: }{0: }| - {1:74 }{2: }{0: }| - {3:-- INSERT --} | + {12:7 }{101: }{1: }| + {4:70 }{101: }{1: }| + {4:71 }{101: }{1: }| + {4:72 }{12: }{1: }| + {4:73 }{12: }{1: }| + {4:74 }{12: }{1: }| + {5:-- INSERT --} | ]]) feed('<c-n>') screen:expect([[ 70^ | - {1:7 }{6: }{0: }| - {2:70 }{6: }{0: }| - {1:71 }{6: }{0: }| - {1:72 }{2: }{0: }| - {1:73 }{2: }{0: }| - {1:74 }{2: }{0: }| - {3:-- INSERT --} | + {4:7 }{101: }{1: }| + {12:70 }{101: }{1: }| + {4:71 }{101: }{1: }| + {4:72 }{12: }{1: }| + {4:73 }{12: }{1: }| + {4:74 }{12: }{1: }| + {5:-- INSERT --} | ]]) end) @@ -532,107 +584,107 @@ describe('completion', function() feed('i<C-r>=TestComplete()<CR>') screen:expect([[ ^ | - {1:0 }{6: }{0: }| - {1:1 }{2: }{0: }| - {1:2 }{2: }{0: }| - {1:3 }{2: }{0: }| - {1:4 }{2: }{0: }| - {1:5 }{2: }{0: }| - {3:-- INSERT --} | + {4:0 }{101: }{1: }| + {4:1 }{12: }{1: }| + {4:2 }{12: }{1: }| + {4:3 }{12: }{1: }| + {4:4 }{12: }{1: }| + {4:5 }{12: }{1: }| + {5:-- INSERT --} | ]]) feed('<PageDown>') screen:expect([[ ^ | - {1:0 }{6: }{0: }| - {1:1 }{2: }{0: }| - {1:2 }{2: }{0: }| - {2:3 }{0: }| - {1:4 }{2: }{0: }| - {1:5 }{2: }{0: }| - {3:-- INSERT --} | + {4:0 }{101: }{1: }| + {4:1 }{12: }{1: }| + {4:2 }{12: }{1: }| + {12:3 }{1: }| + {4:4 }{12: }{1: }| + {4:5 }{12: }{1: }| + {5:-- INSERT --} | ]]) feed('<PageDown>') screen:expect([[ ^ | - {1:5 }{6: }{0: }| - {1:6 }{2: }{0: }| - {2:7 }{0: }| - {1:8 }{2: }{0: }| - {1:9 }{2: }{0: }| - {1:10 }{2: }{0: }| - {3:-- INSERT --} | + {4:5 }{101: }{1: }| + {4:6 }{12: }{1: }| + {12:7 }{1: }| + {4:8 }{12: }{1: }| + {4:9 }{12: }{1: }| + {4:10 }{12: }{1: }| + {5:-- INSERT --} | ]]) feed('<Down>') screen:expect([[ ^ | - {1:5 }{6: }{0: }| - {1:6 }{2: }{0: }| - {1:7 }{2: }{0: }| - {2:8 }{0: }| - {1:9 }{2: }{0: }| - {1:10 }{2: }{0: }| - {3:-- INSERT --} | + {4:5 }{101: }{1: }| + {4:6 }{12: }{1: }| + {4:7 }{12: }{1: }| + {12:8 }{1: }| + {4:9 }{12: }{1: }| + {4:10 }{12: }{1: }| + {5:-- INSERT --} | ]]) feed('<PageUp>') screen:expect([[ ^ | - {1:2 }{6: }{0: }| - {1:3 }{2: }{0: }| - {2:4 }{0: }| - {1:5 }{2: }{0: }| - {1:6 }{2: }{0: }| - {1:7 }{2: }{0: }| - {3:-- INSERT --} | + {4:2 }{101: }{1: }| + {4:3 }{12: }{1: }| + {12:4 }{1: }| + {4:5 }{12: }{1: }| + {4:6 }{12: }{1: }| + {4:7 }{12: }{1: }| + {5:-- INSERT --} | ]]) feed('<PageUp>') -- stop on first item screen:expect([[ ^ | - {2:0 }{6: }{0: }| - {1:1 }{2: }{0: }| - {1:2 }{2: }{0: }| - {1:3 }{2: }{0: }| - {1:4 }{2: }{0: }| - {1:5 }{2: }{0: }| - {3:-- INSERT --} | + {12:0 }{101: }{1: }| + {4:1 }{12: }{1: }| + {4:2 }{12: }{1: }| + {4:3 }{12: }{1: }| + {4:4 }{12: }{1: }| + {4:5 }{12: }{1: }| + {5:-- INSERT --} | ]]) feed('<PageUp>') -- when on first item, unselect screen:expect([[ ^ | - {1:0 }{6: }{0: }| - {1:1 }{2: }{0: }| - {1:2 }{2: }{0: }| - {1:3 }{2: }{0: }| - {1:4 }{2: }{0: }| - {1:5 }{2: }{0: }| - {3:-- INSERT --} | + {4:0 }{101: }{1: }| + {4:1 }{12: }{1: }| + {4:2 }{12: }{1: }| + {4:3 }{12: }{1: }| + {4:4 }{12: }{1: }| + {4:5 }{12: }{1: }| + {5:-- INSERT --} | ]]) feed('<PageUp>') -- when unselected, select last item screen:expect([[ ^ | - {1:95 }{2: }{0: }| - {1:96 }{2: }{0: }| - {1:97 }{2: }{0: }| - {1:98 }{2: }{0: }| - {1:99 }{2: }{0: }| - {2:100 }{6: }{0: }| - {3:-- INSERT --} | + {4:95 }{12: }{1: }| + {4:96 }{12: }{1: }| + {4:97 }{12: }{1: }| + {4:98 }{12: }{1: }| + {4:99 }{12: }{1: }| + {12:100 }{101: }{1: }| + {5:-- INSERT --} | ]]) feed('<PageUp>') screen:expect([[ ^ | - {1:94 }{2: }{0: }| - {1:95 }{2: }{0: }| - {2:96 }{0: }| - {1:97 }{2: }{0: }| - {1:98 }{2: }{0: }| - {1:99 }{6: }{0: }| - {3:-- INSERT --} | + {4:94 }{12: }{1: }| + {4:95 }{12: }{1: }| + {12:96 }{1: }| + {4:97 }{12: }{1: }| + {4:98 }{12: }{1: }| + {4:99 }{101: }{1: }| + {5:-- INSERT --} | ]]) feed('<cr>') screen:expect([[ 96^ | - {0:~ }|*6 - {3:-- INSERT --} | + {1:~ }|*6 + {5:-- INSERT --} | ]]) end) end) @@ -668,9 +720,9 @@ describe('completion', function() screen:expect([[ inc uninc indent unindent | ind^ | - {2:indent }{0: }| - {0:~ }|*4 - {3:-- Keyword Local completion (^N^P) }{4:match 1 of 2} | + {12:indent }{1: }| + {1:~ }|*4 + {5:-- Keyword Local completion (^N^P) }{6:match 1 of 2} | ]]) -- Indents when the item is selected @@ -678,8 +730,8 @@ describe('completion', function() screen:expect([[ inc uninc indent unindent | indent^ | - {0:~ }|*5 - {3:-- INSERT --} | + {1:~ }|*5 + {5:-- INSERT --} | ]]) -- Indents when completion is exited using ESC. feed('<CR>in<C-N><BS>d<Esc>') @@ -687,7 +739,7 @@ describe('completion', function() inc uninc indent unindent | indent | in^d | - {0:~ }|*4 + {1:~ }|*4 | ]]) -- Works for unindenting too. @@ -699,9 +751,9 @@ describe('completion', function() indent | ind | unind^ | - {0:~ }{2: unindent }{0: }| - {0:~ }|*2 - {3:-- Keyword Local completion (^N^P) }{4:match 1 of 2} | + {1:~ }{12: unindent }{1: }| + {1:~ }|*2 + {5:-- Keyword Local completion (^N^P) }{6:match 1 of 2} | ]]) -- Works when going back and forth. feed('<BS>c') @@ -710,9 +762,9 @@ describe('completion', function() indent | ind | uninc^ | - {0:~ }{2: uninc }{0: }| - {0:~ }|*2 - {3:-- Keyword Local completion (^N^P) }{4:match 1 of 2} | + {1:~ }{12: uninc }{1: }| + {1:~ }|*2 + {5:-- Keyword Local completion (^N^P) }{6:match 1 of 2} | ]]) feed('<BS>d') screen:expect([[ @@ -720,9 +772,9 @@ describe('completion', function() indent | ind | unind^ | - {0:~ }{2: unindent }{0: }| - {0:~ }|*2 - {3:-- Keyword Local completion (^N^P) }{4:match 1 of 2} | + {1:~ }{12: unindent }{1: }| + {1:~ }|*2 + {5:-- Keyword Local completion (^N^P) }{6:match 1 of 2} | ]]) feed('<C-N><C-N><C-Y><Esc>') screen:expect([[ @@ -730,7 +782,7 @@ describe('completion', function() indent | ind | uninden^t | - {0:~ }|*3 + {1:~ }|*3 | ]]) end) @@ -741,15 +793,15 @@ describe('completion', function() screen:expect([[ ^foo | bar | - {0:~ }|*5 + {1:~ }|*5 | ]]) feed('A<C-x><C-l>') screen:expect([[ foo^ | bar | - {0:~ }|*5 - {3:-- Whole line completion (^L^N^P) }{7:Pattern not found} | + {1:~ }|*5 + {5:-- Whole line completion (^L^N^P) }{9:Pattern not found} | ]]) eq(-1, eval('foldclosed(1)')) end) @@ -761,10 +813,10 @@ describe('completion', function() screen:expect([[ foobar fooegg | fooegg^ | - {1:foobar }{0: }| - {2:fooegg }{0: }| - {0:~ }|*3 - {3:-- Keyword completion (^N^P) }{4:match 1 of 2} | + {4:foobar }{1: }| + {12:fooegg }{1: }| + {1:~ }|*3 + {5:-- Keyword completion (^N^P) }{6:match 1 of 2} | ]]) assert_alive() @@ -773,10 +825,10 @@ describe('completion', function() grid = [[ foobar fooegg | fooegg^ | - {1:foobar }{0: }| - {2:fooegg }{0: }| - {0:~ }|*3 - {3:-- Keyword completion (^N^P) }{4:match 1 of 2} | + {4:foobar }{1: }| + {12:fooegg }{1: }| + {1:~ }|*3 + {5:-- Keyword completion (^N^P) }{6:match 1 of 2} | ]], unchanged = true, } @@ -786,10 +838,10 @@ describe('completion', function() screen:expect([[ foobar fooegg | foobar^ | - {2:foobar }{0: }| - {1:fooegg }{0: }| - {0:~ }|*3 - {3:-- Keyword completion (^N^P) }{4:match 2 of 2} | + {12:foobar }{1: }| + {4:fooegg }{1: }| + {1:~ }|*3 + {5:-- Keyword completion (^N^P) }{6:match 2 of 2} | ]]) end) @@ -800,7 +852,7 @@ describe('completion', function() screen:expect { grid = [[ | - {0:~ }|*6 + {1:~ }|*6 :lua CURRENT_TESTING_VAR^ | ]], } @@ -813,19 +865,73 @@ describe('completion', function() screen:expect { grid = [[ | - {0:~ }|*5 - {10:CURRENT_TESTING_BAR}{9: CURRENT_TESTING_FOO }| + {1:~ }|*5 + {100:CURRENT_TESTING_BAR}{3: CURRENT_TESTING_FOO }| :lua CURRENT_TESTING_BAR^ | ]], unchanged = true, } end) + it('prefix is not included in completion for cmdline mode', function() + feed(':lua math.a<Tab>') + screen:expect([[ + | + {1:~ }|*5 + {100:abs}{3: acos asin atan atan2 }| + :lua math.abs^ | + ]]) + feed('<Tab>') + screen:expect([[ + | + {1:~ }|*5 + {3:abs }{100:acos}{3: asin atan atan2 }| + :lua math.acos^ | + ]]) + end) + + it('prefix is not included in completion for i_CTRL-X_CTRL-V #19623', function() + feed('ilua math.a<C-X><C-V>') + screen:expect([[ + lua math.abs^ | + {1:~ }{12: abs }{1: }| + {1:~ }{4: acos }{1: }| + {1:~ }{4: asin }{1: }| + {1:~ }{4: atan }{1: }| + {1:~ }{4: atan2 }{1: }| + {1:~ }| + {5:-- Command-line completion (^V^N^P) }{6:match 1 of 5} | + ]]) + feed('<C-V>') + screen:expect([[ + lua math.acos^ | + {1:~ }{4: abs }{1: }| + {1:~ }{12: acos }{1: }| + {1:~ }{4: asin }{1: }| + {1:~ }{4: atan }{1: }| + {1:~ }{4: atan2 }{1: }| + {1:~ }| + {5:-- Command-line completion (^V^N^P) }{6:match 2 of 5} | + ]]) + end) + + it('works when cursor is in the middle of cmdline #29586', function() + feed(':lua math.a(); 1<Left><Left><Left><Left><Left><Tab>') + screen:expect([[ + | + {1:~ }|*5 + {100:abs}{3: acos asin atan atan2 }| + :lua math.abs^(); 1 | + ]]) + end) + it('provides completion from `getcompletion()`', function() eq({ 'vim' }, fn.getcompletion('vi', 'lua')) eq({ 'api' }, fn.getcompletion('vim.ap', 'lua')) eq({ 'tbl_filter' }, fn.getcompletion('vim.tbl_fil', 'lua')) eq({ 'vim' }, fn.getcompletion('print(vi', 'lua')) + eq({ 'abs', 'acos', 'asin', 'atan', 'atan2' }, fn.getcompletion('math.a', 'lua')) + eq({ 'abs', 'acos', 'asin', 'atan', 'atan2' }, fn.getcompletion('lua math.a', 'cmdline')) -- fuzzy completion is not supported, so the result should be the same command('set wildoptions+=fuzzy') eq({ 'vim' }, fn.getcompletion('vi', 'lua')) @@ -841,36 +947,42 @@ describe('completion', function() eq('SpecialKey', fn.getcompletion('set winhighlight=NonText:', 'cmdline')[1]) end) + it('cmdline completion for -complete does not contain spaces', function() + for _, str in ipairs(fn.getcompletion('command -complete=', 'cmdline')) do + ok(not str:find(' '), 'string without spaces', str) + end + end) + describe('from the commandline window', function() it('is cleared after CTRL-C', function() feed('q:') feed('ifoo faa fee f') screen:expect([[ | - {8:[No Name] }| - {0::}foo faa fee f^ | - {0:~ }|*3 - {9:[Command Line] }| - {3:-- INSERT --} | + {2:[No Name] }| + {1::}foo faa fee f^ | + {1:~ }|*3 + {3:[Command Line] }| + {5:-- INSERT --} | ]]) feed('<c-x><c-n>') screen:expect([[ | - {8:[No Name] }| - {0::}foo faa fee foo^ | - {0:~ }{2: foo }{0: }| - {0:~ }{1: faa }{0: }| - {0:~ }{1: fee }{0: }| - {9:[Command Line] }| - {3:-- Keyword Local completion (^N^P) }{4:match 1 of 3} | + {2:[No Name] }| + {1::}foo faa fee foo^ | + {1:~ }{12: foo }{1: }| + {1:~ }{4: faa }{1: }| + {1:~ }{4: fee }{1: }| + {3:[Command Line] }| + {5:-- Keyword Local completion (^N^P) }{6:match 1 of 3} | ]]) feed('<c-c>') screen:expect([[ | - {8:[No Name] }| - {0::}foo faa fee foo | - {0:~ }|*3 - {9:[Command Line] }| + {2:[No Name] }| + {1::}foo faa fee foo | + {1:~ }|*3 + {3:[Command Line] }| :foo faa fee foo^ | ]]) end) @@ -903,9 +1015,9 @@ describe('completion', function() feed('i<C-r>=TestComplete()<CR>') screen:expect([[ ^ | - {1:1 3 2 }{0: }| - {0:~ }|*5 - {3:-- INSERT --} | + {4:1 3 2 }{1: }| + {1:~ }|*5 + {5:-- INSERT --} | ]]) end) end) @@ -918,12 +1030,12 @@ describe('completion', function() grid = [[ *backers.txt* Nvim | Xnull^ | - {2:Xnull }{6: } | - {1:Xoxomoon }{6: } | - {1:Xu }{6: } NVIM REFERENCE MANUAL | - {1:Xpayn }{2: } | - {1:Xinity }{2: } | - {3:-- Keyword Local completion (^N^P) }{4:match 1 of 7} | + {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} | ]], } end) @@ -950,8 +1062,8 @@ describe('completion', function() bar | foobar | f^ | - {0:~ }|*3 - {3:-- Keyword completion (^N^P) }{5:Back at original} | + {1:~ }|*3 + {5:-- Keyword completion (^N^P) }{19:Back at original} | ]], popupmenu = { anchor = { 1, 3, 0 }, @@ -970,8 +1082,8 @@ describe('completion', function() bar | foobar | foob^ | - {0:~ }|*3 - {3:-- Keyword completion (^N^P) }{5:Back at original} | + {1:~ }|*3 + {5:-- Keyword completion (^N^P) }{19:Back at original} | ]], popupmenu = { anchor = { 1, 3, 0 }, @@ -992,10 +1104,10 @@ describe('completion', function() bar | foobar | f^ | - {1:foo }{0: }| - {1:foobar }{0: }| - {0:~ }| - {3:-- Keyword completion (^N^P) }{5:Back at original} | + {4:foo }{1: }| + {4:foobar }{1: }| + {1:~ }| + {5:-- Keyword completion (^N^P) }{19:Back at original} | ]]) eq( { completed_item = {}, width = 15, height = 2, size = 2, col = 0, row = 4, scrollbar = false }, @@ -1007,10 +1119,10 @@ describe('completion', function() bar | foobar | foo^ | - {2:foo }{0: }| - {1:foobar }{0: }| - {0:~ }| - {3:-- Keyword completion (^N^P) }{4:match 1 of 2} | + {12:foo }{1: }| + {4:foobar }{1: }| + {1:~ }| + {5:-- Keyword completion (^N^P) }{6:match 1 of 2} | ]]) eq('foo', eval('g:word')) feed('<C-N>') @@ -1019,10 +1131,10 @@ describe('completion', function() bar | foobar | foobar^ | - {1:foo }{0: }| - {2:foobar }{0: }| - {0:~ }| - {3:-- Keyword completion (^N^P) }{4:match 2 of 2} | + {4:foo }{1: }| + {12:foobar }{1: }| + {1:~ }| + {5:-- Keyword completion (^N^P) }{6:match 2 of 2} | ]]) eq('foobar', eval('g:word')) feed('<up>') @@ -1031,10 +1143,10 @@ describe('completion', function() bar | foobar | foobar^ | - {2:foo }{0: }| - {1:foobar }{0: }| - {0:~ }| - {3:-- Keyword completion (^N^P) }{4:match 1 of 2} | + {12:foo }{1: }| + {4:foobar }{1: }| + {1:~ }| + {5:-- Keyword completion (^N^P) }{6:match 1 of 2} | ]]) eq('foo', eval('g:word')) feed('<down>') @@ -1043,10 +1155,10 @@ describe('completion', function() bar | foobar | foobar^ | - {1:foo }{0: }| - {2:foobar }{0: }| - {0:~ }| - {3:-- Keyword completion (^N^P) }{4:match 2 of 2} | + {4:foo }{1: }| + {12:foobar }{1: }| + {1:~ }| + {5:-- Keyword completion (^N^P) }{6:match 2 of 2} | ]]) eq('foobar', eval('g:word')) feed('<esc>') @@ -1061,11 +1173,11 @@ describe('completion', function() hullo | heeee | hello^ | - {2:hello }{0: }| - {1:hullo }{0: }| - {1:heeee }{0: }| - {0:~ }|*6 - {3:-- }{4:match 1 of 3} | + {12:hello }{1: }| + {4:hullo }{1: }| + {4:heeee }{1: }| + {1:~ }|*6 + {5:-- }{6:match 1 of 3} | ]]) command([[call timer_start(100, { -> execute('stopinsert') })]]) vim.uv.sleep(200) @@ -1075,7 +1187,7 @@ describe('completion', function() hullo | heee^e | hello | - {0:~ }|*9 + {1:~ }|*9 | ]]) end) @@ -1090,9 +1202,9 @@ describe('completion', function() screen:expect([[ ii | ii^ | - {2:ii }{0: }| - {0:~ }|*4 - {3:-- Keyword completion (^N^P) The only match} | + {12:ii }{1: }| + {1:~ }|*4 + {5:-- Keyword completion (^N^P) The only match} | ]]) assert_alive() end) @@ -1129,22 +1241,22 @@ describe('completion', function() screen:expect { grid = [[ foo^ | - {2:foo }{0: }| - {1:bar }{0: }| - {1:foa }{0: }| - {1:.hidden }{0: }| - {0:~ }|*3 - {3:-- }{4:match 1 of 4} | + {12:foo }{1: }| + {4:bar }{1: }| + {4:foa }{1: }| + {4:.hidden }{1: }| + {1:~ }|*3 + {5:-- }{6:match 1 of 4} | ]], } feed('<Esc>ccf<C-n>') screen:expect { grid = [[ foo^ | - {2:foo }{0: }| - {1:foa }{0: }| - {0:~ }|*5 - {3:-- }{4:match 1 of 2} | + {12:foo }{1: }| + {4:foa }{1: }| + {1:~ }|*5 + {5:-- }{6:match 1 of 2} | ]], } end) @@ -1168,10 +1280,10 @@ describe('completion', function() eq(eval('mark'), eval("nvim_buf_get_extmark_by_id(0, ns_id, mark_id, { 'details':1 })")) feed('<Esc>0Yppia<Esc>ggI<C-N>') screen:expect([[ - aaaa{7:^aa}aa | - {2:aaaa } | - {1:aaaaa } | - {3:-- Keyword completion (^N^P) }{4:match 1 of 2} | + aaaa{9:^aa}aa | + {12:aaaa } | + {4:aaaaa } | + {5:-- Keyword completion (^N^P) }{6:match 1 of 2} | ]]) feed('<C-N><C-N><Esc>') eq(eval('mark'), eval("nvim_buf_get_extmark_by_id(0, ns_id, mark_id, { 'details':1 })")) @@ -1180,16 +1292,16 @@ describe('completion', function() feed('<C-N>') screen:expect([[ aaaaa^ | - {1:aaaa } | - {2:aaaaa } | - {3:-- Keyword completion (^N^P) }{4:match 2 of 2} | + {4:aaaa } | + {12:aaaaa } | + {5:-- Keyword completion (^N^P) }{6:match 2 of 2} | ]]) feed('<C-E>') screen:expect([[ - {7:aa}aa^ | + {9:aa}aa^ | aaaa | aaaaa | - {3:-- INSERT --} | + {5:-- INSERT --} | ]]) end) end) diff --git a/test/functional/editor/defaults_spec.lua b/test/functional/editor/defaults_spec.lua new file mode 100644 index 0000000000..47fd177f7b --- /dev/null +++ b/test/functional/editor/defaults_spec.lua @@ -0,0 +1,100 @@ +-- +-- Tests for default autocmds, mappings, commands, and menus. +-- +-- See options/defaults_spec.lua for default options and environment decisions. +-- + +local t = require('test.testutil') +local n = require('test.functional.testnvim')() +local Screen = require('test.functional.ui.screen') + +describe('default', function() + describe('autocommands', function() + it('nvim_terminal.TermClose closes terminal with default shell on success', function() + n.clear() + n.api.nvim_set_option_value('shell', n.testprg('shell-test'), {}) + n.command('set shellcmdflag=EXIT shellredir= shellpipe= shellquote= shellxquote=') + + -- Should not block other events + n.command('let g:n=0') + n.command('au BufEnter * let g:n = g:n + 1') + + n.command('terminal') + t.eq(1, n.eval('get(g:, "n", 0)')) + + t.retry(nil, 1000, function() + t.neq('terminal', n.api.nvim_get_option_value('buftype', { buf = 0 })) + t.eq(2, n.eval('get(g:, "n", 0)')) + end) + end) + end) + + describe('popupmenu', function() + it('can be disabled by user', function() + n.clear { + args = { '+autocmd! nvim_popupmenu', '+aunmenu PopUp' }, + } + local screen = Screen.new(40, 8) + screen:attach() + n.insert([[ + 1 line 1 + 2 https://example.com + 3 line 3 + 4 line 4]]) + + n.api.nvim_input_mouse('right', 'press', '', 0, 1, 4) + screen:expect({ + grid = [[ + 1 line 1 | + 2 ht^tps://example.com | + 3 line 3 | + 4 line 4 | + {1:~ }|*3 + | + ]], + }) + end) + + it('right-click on URL shows "Open in web browser"', function() + n.clear() + local screen = Screen.new(40, 8) + screen:attach() + n.insert([[ + 1 line 1 + 2 https://example.com + 3 line 3 + 4 line 4]]) + + n.api.nvim_input_mouse('right', 'press', '', 0, 3, 4) + screen:expect({ + grid = [[ + 1 line 1 | + 2 https://example.com | + 3 line 3 | + 4 li^ne 4 | + {1:~ }{4: Inspect }{1: }| + {1:~ }{4: }{1: }| + {1:~ }{4: Paste }{1: }| + {4: Select All } | + ]], + }) + + n.api.nvim_input_mouse('right', 'press', '', 0, 1, 4) + screen:expect({ + grid = [[ + 1 line 1 | + 2 ht^tps://example.com | + 3 l{4: Open in web browser } | + 4 l{4: Inspect } | + {1:~ }{4: }{1: }| + {1:~ }{4: Paste }{1: }| + {1:~ }{4: Select All }{1: }| + {4: } | + ]], + }) + end) + end) + + -- describe('key mappings', function() + -- end) +end) diff --git a/test/functional/editor/jump_spec.lua b/test/functional/editor/jump_spec.lua index 880831d9f8..ab4cefaf84 100644 --- a/test/functional/editor/jump_spec.lua +++ b/test/functional/editor/jump_spec.lua @@ -194,7 +194,7 @@ describe("jumpoptions=stack behaves like 'tagstack'", function() end) end) -describe('buffer deletion', function() +describe('buffer deletion with jumpoptions+=clean', function() local base_file = 'Xtest-functional-buffer-deletion' local file1 = base_file .. '1' local file2 = base_file .. '2' @@ -227,6 +227,12 @@ describe('buffer deletion', function() command('edit ' .. file3) end) + after_each(function() + os.remove(file1) + os.remove(file2) + os.remove(file3) + end) + it('deletes jump list entries when the current buffer is deleted', function() command('edit ' .. file1) @@ -319,6 +325,44 @@ describe('buffer deletion', function() end) end) +describe('buffer deletion with jumpoptions-=clean', function() + local base_file = 'Xtest-functional-buffer-deletion' + local file1 = base_file .. '1' + local file2 = base_file .. '2' + local base_content = 'text' + local content1 = base_content .. '1' + local content2 = base_content .. '2' + + before_each(function() + clear() + command('clearjumps') + command('set jumpoptions-=clean') + + write_file(file1, content1, false, false) + write_file(file2, content2, false, false) + + command('edit ' .. file1) + command('edit ' .. file2) + end) + + after_each(function() + os.remove(file1) + os.remove(file2) + end) + + it('Ctrl-O reopens previous buffer with :bunload or :bdelete #28968', function() + eq(file2, fn.bufname('')) + command('bunload') + eq(file1, fn.bufname('')) + feed('<C-O>') + eq(file2, fn.bufname('')) + command('bdelete') + eq(file1, fn.bufname('')) + feed('<C-O>') + eq(file2, fn.bufname('')) + end) +end) + describe('jumpoptions=view', function() local file1 = 'Xtestfile-functional-editor-jumps' local file2 = 'Xtestfile-functional-editor-jumps-2' diff --git a/test/functional/editor/macro_spec.lua b/test/functional/editor/macro_spec.lua index 27c5eddac8..680a70b78a 100644 --- a/test/functional/editor/macro_spec.lua +++ b/test/functional/editor/macro_spec.lua @@ -148,6 +148,23 @@ helloFOO]] eq({ 0, 1, 1, 0 }, fn.getpos('v')) end) + it('can be recorded and replayed in Visual mode when ignorecase', function() + command('set ignorecase') + insert('foo BAR BAR foo BAR foo BAR BAR BAR foo BAR BAR') + feed('0vqifofRq') + eq({ 0, 1, 7, 0 }, fn.getpos('.')) + eq({ 0, 1, 1, 0 }, fn.getpos('v')) + feed('Q') + eq({ 0, 1, 19, 0 }, fn.getpos('.')) + eq({ 0, 1, 1, 0 }, fn.getpos('v')) + feed('Q') + eq({ 0, 1, 27, 0 }, fn.getpos('.')) + eq({ 0, 1, 1, 0 }, fn.getpos('v')) + feed('@i') + eq({ 0, 1, 43, 0 }, fn.getpos('.')) + eq({ 0, 1, 1, 0 }, fn.getpos('v')) + end) + it('can be replayed with @ in blockwise Visual mode', function() insert [[ hello diff --git a/test/functional/editor/meta_key_spec.lua b/test/functional/editor/meta_key_spec.lua index 87fe395608..30027e2fd6 100644 --- a/test/functional/editor/meta_key_spec.lua +++ b/test/functional/editor/meta_key_spec.lua @@ -145,7 +145,8 @@ describe('meta-keys #8226 #13042', function() end) it('ALT/META with vim.on_key()', function() - feed('ifoo<CR>bar<CR>baz<Esc>gg0') + feed('ifoo<CR>bar<CR>baz<Esc>gg0viw"ay') + command('nnoremap … "') exec_lua [[ keys = {} @@ -157,15 +158,15 @@ describe('meta-keys #8226 #13042', function() end) ]] - -- <M-"> is reinterpreted as <Esc>" - feed('qrviw"ayc$FOO.<M-">apq') + -- <M-"> and <M-…> are reinterpreted as <Esc>" and <Esc>… + feed('c$FOO.<M-">apA.<M-…>ap') expect([[ - FOO.foo + FOO.foo.foo bar baz]]) - -- vim.on_key() callback should only receive <Esc>" - eq('qrviw"ayc$FOO.<Esc>"apq', exec_lua [[return table.concat(keys, '')]]) - eq('qrviw"ayc$FOO.<Esc>"apq', exec_lua [[return table.concat(typed, '')]]) + -- vim.on_key() callback should only receive <Esc>" and <Esc>… + eq('c$FOO.<Esc>"apA.<Esc>"ap', exec_lua [[return table.concat(keys, '')]]) + eq('c$FOO.<Esc>"apA.<Esc>…ap', exec_lua [[return table.concat(typed, '')]]) end) end) diff --git a/test/functional/editor/mode_cmdline_spec.lua b/test/functional/editor/mode_cmdline_spec.lua index 70bdc5d4c2..efd7a37c0b 100644 --- a/test/functional/editor/mode_cmdline_spec.lua +++ b/test/functional/editor/mode_cmdline_spec.lua @@ -48,18 +48,14 @@ describe('cmdline', function() it('redraws statusline when toggling overstrike', function() local screen = Screen.new(60, 4) - screen:set_default_attr_ids({ - [0] = { bold = true, foreground = Screen.colors.Blue }, -- NonText - [1] = { reverse = true, bold = true }, -- StatusLine - }) screen:attach() command('set laststatus=2 statusline=%!mode(1)') feed(':') screen:expect { grid = [[ | - {0:~ }| - {1:c }| + {1:~ }| + {3:c }| :^ | ]], } @@ -67,8 +63,8 @@ describe('cmdline', function() screen:expect { grid = [[ | - {0:~ }| - {1:cr }| + {1:~ }| + {3:cr }| :^ | ]], } diff --git a/test/functional/editor/mode_insert_spec.lua b/test/functional/editor/mode_insert_spec.lua index fb3dda4bf4..87d5c46134 100644 --- a/test/functional/editor/mode_insert_spec.lua +++ b/test/functional/editor/mode_insert_spec.lua @@ -180,12 +180,6 @@ describe('insert-mode', function() it('multi-char mapping updates screen properly #25626', function() local screen = Screen.new(60, 6) - screen:set_default_attr_ids({ - [0] = { bold = true, foreground = Screen.colors.Blue }, -- NonText - [1] = { bold = true, reverse = true }, -- StatusLine - [2] = { reverse = true }, -- StatusLineNC - [3] = { bold = true }, -- ModeMsg - }) screen:attach() command('vnew') insert('foo\nfoo\nfoo') @@ -197,10 +191,10 @@ describe('insert-mode', function() grid = [[ foo │ | foo │β^jβ | - foo │{0:~ }| - {0:~ }│{0:~ }| - {2:[No Name] [+] }{1:[No Name] [+] }| - {3:-- INSERT --} | + foo │{1:~ }| + {1:~ }│{1:~ }| + {2:[No Name] [+] }{3:[No Name] [+] }| + {5:-- INSERT --} | ]], } feed('k') @@ -208,9 +202,9 @@ describe('insert-mode', function() grid = [[ foo │ | foo │^βββ | - foo │{0:~ }| - {0:~ }│{0:~ }| - {2:[No Name] [+] }{1:[No Name] [+] }| + foo │{1:~ }| + {1:~ }│{1:~ }| + {2:[No Name] [+] }{3:[No Name] [+] }| | ]], } @@ -357,4 +351,97 @@ describe('insert-mode', function() eq(2, api.nvim_win_get_cursor(0)[1]) end) end) + + it('backspace after replacing multibyte chars', function() + local screen = Screen.new(30, 3) + screen:attach() + api.nvim_buf_set_lines(0, 0, -1, true, { 'test ȧ̟̜̝̅̚m̆̉̐̐̇̈ å' }) + feed('^Rabcdefghi') + screen:expect([[ + abcdefghi^ | + {1:~ }| + {5:-- REPLACE --} | + ]]) + + feed('<bs>') + screen:expect([[ + abcdefgh^å | + {1:~ }| + {5:-- REPLACE --} | + ]]) + + feed('<bs>') + screen:expect([[ + abcdefg^ å | + {1:~ }| + {5:-- REPLACE --} | + ]]) + + feed('<bs>') + screen:expect([[ + abcdef^m̆̉̐̐̇̈ å | + {1:~ }| + {5:-- REPLACE --} | + ]]) + + feed('<bs>') + screen:expect([[ + abcde^ȧ̟̜̝̅̚m̆̉̐̐̇̈ å | + {1:~ }| + {5:-- REPLACE --} | + ]]) + + feed('<bs>') + screen:expect([[ + abcd^ ȧ̟̜̝̅̚m̆̉̐̐̇̈ å | + {1:~ }| + {5:-- REPLACE --} | + ]]) + + feed('<esc>') + + api.nvim_buf_set_lines(0, 0, -1, true, { 'wow 🧑🌾🏳️⚧️x' }) + feed('^Rabcd') + + screen:expect([[ + abcd^🧑🌾🏳️⚧️x | + {1:~ }| + {5:-- REPLACE --} | + ]]) + + feed('e') + screen:expect([[ + abcde^🏳️⚧️x | + {1:~ }| + {5:-- REPLACE --} | + ]]) + + feed('f') + screen:expect([[ + abcdef^x | + {1:~ }| + {5:-- REPLACE --} | + ]]) + + feed('<bs>') + screen:expect([[ + abcde^🏳️⚧️x | + {1:~ }| + {5:-- REPLACE --} | + ]]) + + feed('<bs>') + screen:expect([[ + abcd^🧑🌾🏳️⚧️x | + {1:~ }| + {5:-- REPLACE --} | + ]]) + + feed('<bs>') + screen:expect([[ + abc^ 🧑🌾🏳️⚧️x | + {1:~ }| + {5:-- REPLACE --} | + ]]) + end) end) diff --git a/test/functional/editor/tabpage_spec.lua b/test/functional/editor/tabpage_spec.lua index 0b26494436..c20a6e5cb5 100644 --- a/test/functional/editor/tabpage_spec.lua +++ b/test/functional/editor/tabpage_spec.lua @@ -102,14 +102,9 @@ describe('tabpage', function() it('switching tabpage after setting laststatus=3 #19591', function() local screen = Screen.new(40, 8) - screen:set_default_attr_ids({ - [0] = { bold = true, foreground = Screen.colors.Blue }, - [1] = { bold = true, reverse = true }, -- StatusLine - [2] = { reverse = true }, -- TabLineFill - [3] = { bold = true }, -- TabLineSel - [4] = { background = Screen.colors.LightGrey, underline = true }, -- TabLine - [5] = { bold = true, foreground = Screen.colors.Magenta }, - }) + screen:add_extra_attr_ids { + [100] = { bold = true, foreground = Screen.colors.Fuchsia }, + } screen:attach() command('tabnew') @@ -118,18 +113,18 @@ describe('tabpage', function() command('tabnext') feed('<C-G>') screen:expect([[ - {4: [No Name] }{3: [No Name] }{2: }{4:X}| + {24: [No Name] }{5: [No Name] }{2: }{24:X}| ^ | - {0:~ }|*4 - {1:[No Name] }| + {1:~ }|*4 + {3:[No Name] }| "[No Name]" --No lines in buffer-- | ]]) command('vnew') screen:expect([[ - {4: [No Name] }{3: }{5:2}{3: [No Name] }{2: }{4:X}| + {24: [No Name] }{5: }{100:2}{5: [No Name] }{2: }{24:X}| ^ │ | - {0:~ }│{0:~ }|*4 - {1:[No Name] }| + {1:~ }│{1:~ }|*4 + {3:[No Name] }| "[No Name]" --No lines in buffer-- | ]]) end) diff --git a/test/functional/ex_cmds/append_spec.lua b/test/functional/ex_cmds/append_spec.lua index 80fdcb3134..df62aecc7f 100644 --- a/test/functional/ex_cmds/append_spec.lua +++ b/test/functional/ex_cmds/append_spec.lua @@ -23,7 +23,7 @@ local cmdtest = function(cmd, prep, ret1) end it(cmd .. 's' .. prep .. ' the current line by default', function() - command(cmd .. '\nabc\ndef\n') + command(cmd .. '\nabc\ndef') eq(ret1, buffer_contents()) end) -- Used to crash because this invokes history processing which uses diff --git a/test/functional/ex_cmds/excmd_spec.lua b/test/functional/ex_cmds/excmd_spec.lua index 20ebb2dedb..923bb99eeb 100644 --- a/test/functional/ex_cmds/excmd_spec.lua +++ b/test/functional/ex_cmds/excmd_spec.lua @@ -29,13 +29,13 @@ describe('Ex cmds', function() ':tabnext 9999999999999999999999999999999999999999', 'Vim(tabnext):E475: Invalid argument: 9999999999999999999999999999999999999999' ) - check_excmd_err( - ':N 9999999999999999999999999999999999999999', - 'Vim(Next):E939: Positive count required' + eq( + 'Vim(Next):E163: There is only one file to edit', + pcall_err(command, ':N 9999999999999999999999999999999999999999') ) check_excmd_err( ':bdelete 9999999999999999999999999999999999999999', - 'Vim(bdelete):E939: Positive count required' + 'Vim(bdelete):E516: No buffers were deleted' ) eq( 'Vim(menu):E329: No menu "9999999999999999999999999999999999999999"', diff --git a/test/functional/ex_cmds/mksession_spec.lua b/test/functional/ex_cmds/mksession_spec.lua index 9b24854362..8f09e802eb 100644 --- a/test/functional/ex_cmds/mksession_spec.lua +++ b/test/functional/ex_cmds/mksession_spec.lua @@ -170,7 +170,7 @@ describe(':mksession', function() skip(is_os('win'), 'causes rmdir() to fail') local cwd_dir = fn.fnamemodify('.', ':p:~'):gsub([[[\/]*$]], '') - cwd_dir = cwd_dir:gsub([[\]], '/') -- :mksession always uses unix slashes. + cwd_dir = t.fix_slashes(cwd_dir) -- :mksession always uses unix slashes. local session_path = cwd_dir .. '/' .. session_file command('cd ' .. tab_dir) diff --git a/test/functional/ex_cmds/profile_spec.lua b/test/functional/ex_cmds/profile_spec.lua index 57e5c6b2dc..365583948b 100644 --- a/test/functional/ex_cmds/profile_spec.lua +++ b/test/functional/ex_cmds/profile_spec.lua @@ -6,17 +6,11 @@ require('os') local eval = n.eval local command = n.command local eq, neq = t.eq, t.neq -local tempfile = t.tmpname() +local tempfile = t.tmpname(false) local source = n.source local matches = t.matches local read_file = t.read_file --- tmpname() also creates the file on POSIX systems. Remove it again. --- We just need the name, ignoring any race conditions. -if uv.fs_stat(tempfile).uid then - os.remove(tempfile) -end - local function assert_file_exists(filepath) neq(nil, uv.fs_stat(filepath).uid) end @@ -31,6 +25,7 @@ describe(':profile', function() after_each(function() n.expect_exit(command, 'qall!') if uv.fs_stat(tempfile).uid ~= nil then + -- Delete the tempfile. We just need the name, ignoring any race conditions. os.remove(tempfile) end end) diff --git a/test/functional/ex_cmds/quickfix_commands_spec.lua b/test/functional/ex_cmds/quickfix_commands_spec.lua index 3df41b015e..9c47b1f05f 100644 --- a/test/functional/ex_cmds/quickfix_commands_spec.lua +++ b/test/functional/ex_cmds/quickfix_commands_spec.lua @@ -185,6 +185,9 @@ describe('quickfix', function() it('BufAdd does not cause E16 when reusing quickfix buffer #18135', function() local file = file_base .. '_reuse_qfbuf_BufAdd' write_file(file, ('\n'):rep(100) .. 'foo') + finally(function() + os.remove(file) + end) source([[ set grepprg=internal autocmd BufAdd * call and(0, 0) @@ -192,7 +195,24 @@ describe('quickfix', function() ]]) command('grep foo ' .. file) command('grep foo ' .. file) - os.remove(file) + end) + + it('jump message does not scroll with cmdheight=0 and shm+=O #29597', function() + local screen = Screen.new(40, 6) + screen:attach() + command('set cmdheight=0') + local file = file_base .. '_reuse_qfbuf_BufAdd' + write_file(file, 'foobar') + finally(function() + os.remove(file) + end) + command('vimgrep /foo/gj ' .. file) + feed(':cc<CR>') + screen:expect([[ + ^foobar | + {1:~ }|*4 + (1 of 1): foobar | + ]]) end) end) diff --git a/test/functional/fixtures/fake-lsp-server.lua b/test/functional/fixtures/fake-lsp-server.lua index f806869b40..f813927f77 100644 --- a/test/functional/fixtures/fake-lsp-server.lua +++ b/test/functional/fixtures/fake-lsp-server.lua @@ -939,6 +939,48 @@ function tests.basic_formatting() } end +function tests.range_formatting() + skeleton { + on_init = function() + return { + capabilities = { + documentFormattingProvider = true, + documentRangeFormattingProvider = true, + }, + } + end, + body = function() + notify('start') + expect_request('textDocument/rangeFormatting', function() + return nil, {} + end) + notify('shutdown') + end, + } +end + +function tests.ranges_formatting() + skeleton { + on_init = function() + return { + capabilities = { + documentFormattingProvider = true, + documentRangeFormattingProvider = { + rangesSupport = true, + }, + }, + } + end, + body = function() + notify('start') + expect_request('textDocument/rangesFormatting', function() + return nil, {} + end) + notify('shutdown') + end, + } +end + function tests.set_defaults_all_capabilities() skeleton { on_init = function(_) diff --git a/test/functional/legacy/crash_spec.lua b/test/functional/legacy/crash_spec.lua index 04f77c7d4f..b63b3146f4 100644 --- a/test/functional/legacy/crash_spec.lua +++ b/test/functional/legacy/crash_spec.lua @@ -1,9 +1,14 @@ +local t = require('test.testutil') local n = require('test.functional.testnvim')() local assert_alive = n.assert_alive local clear = n.clear local command = n.command +local eq = t.eq +local eval = n.eval +local exec = n.exec local feed = n.feed +local pcall_err = t.pcall_err before_each(clear) @@ -32,3 +37,29 @@ it('no crash with very long option error message', function() pcall(command, 'source test/old/testdir/crash/poc_did_set_langmap') assert_alive() end) + +it('no crash when closing window with tag in loclist', function() + exec([[ + new + lexpr ['foo'] + lopen + let g:qf_bufnr = bufnr() + lclose + call settagstack(1, #{items: [#{tagname: 'foo', from: [g:qf_bufnr, 1, 1, 0]}]}) + ]]) + eq(1, eval('bufexists(g:qf_bufnr)')) + command('1close') + eq(0, eval('bufexists(g:qf_bufnr)')) + assert_alive() +end) + +it('no crash when writing "Untitled" file fails', function() + t.mkdir('Untitled') + finally(function() + vim.uv.fs_rmdir('Untitled') + end) + feed('ifoobar') + command('set bufhidden=unload') + eq('Vim(enew):E502: "Untitled" is a directory', pcall_err(command, 'confirm enew')) + assert_alive() +end) diff --git a/test/functional/legacy/edit_spec.lua b/test/functional/legacy/edit_spec.lua index f3d18a2541..2d98188f9b 100644 --- a/test/functional/legacy/edit_spec.lua +++ b/test/functional/legacy/edit_spec.lua @@ -44,6 +44,12 @@ describe('edit', function() {1:~ }|*4 =^ | ]]) + feed([['r'<CR><Esc>]]) + expect('r') + -- Test for inserting null and empty list + feed('a<C-R>=v:_null_list<CR><Esc>') + feed('a<C-R>=[]<CR><Esc>') + expect('r') end) -- oldtest: Test_edit_ctrl_r_failed() diff --git a/test/functional/legacy/ex_mode_spec.lua b/test/functional/legacy/ex_mode_spec.lua index 574c3e4069..e033898d7a 100644 --- a/test/functional/legacy/ex_mode_spec.lua +++ b/test/functional/legacy/ex_mode_spec.lua @@ -48,7 +48,7 @@ describe('Ex mode', function() command('set noincsearch nohlsearch inccommand=') local screen = Screen.new(60, 6) screen:attach() - command([[call setline(1, ['foo foo', 'foo foo', 'foo foo'])]]) + command([[call setline(1, repeat(['foo foo'], 4))]]) command([[set number]]) feed('gQ') screen:expect([[ @@ -110,12 +110,24 @@ describe('Ex mode', function() :^ | ]]) + -- The printed line should overwrite the colon + feed('<CR>') + screen:expect([[ + {8: 2 }foo foo | + ^^^q | + {8: 2 }foo foo | + {8: 3 }foo foo | + {8: 4 }foo foo | + :^ | + ]]) + feed(':vi<CR>') screen:expect([[ {8: 1 }foo bar | {8: 2 }foo foo | - {8: 3 }^foo foo | - {1:~ }|*2 + {8: 3 }foo foo | + {8: 4 }^foo foo | + {1:~ }| | ]]) end) diff --git a/test/functional/legacy/excmd_spec.lua b/test/functional/legacy/excmd_spec.lua index de3d498f27..753a45ee05 100644 --- a/test/functional/legacy/excmd_spec.lua +++ b/test/functional/legacy/excmd_spec.lua @@ -5,45 +5,14 @@ local Screen = require('test.functional.ui.screen') local clear = n.clear local command = n.command local exec = n.exec -local exec_lua = n.exec_lua local expect_exit = n.expect_exit local feed = n.feed local fn = n.fn -local api = n.api local read_file = t.read_file -local source = n.source local eq = t.eq local write_file = t.write_file local is_os = t.is_os -local function sizeoflong() - if not exec_lua('return pcall(require, "ffi")') then - pending('missing LuaJIT FFI') - end - return exec_lua('return require("ffi").sizeof(require("ffi").typeof("long"))') -end - -describe('Ex command', function() - before_each(clear) - after_each(function() - eq({}, api.nvim_get_vvar('errors')) - end) - - it('checks for address line overflow', function() - if sizeoflong() < 8 then - pending('Skipped: only works with 64 bit long ints') - end - - source [[ - new - call setline(1, 'text') - call assert_fails('|.44444444444444444444444', 'E1247:') - call assert_fails('|.9223372036854775806', 'E1247:') - bwipe! - ]] - end) -end) - describe(':confirm command dialog', function() local screen diff --git a/test/functional/legacy/matchparen_spec.lua b/test/functional/legacy/matchparen_spec.lua index 3841761515..df0d80f0ab 100644 --- a/test/functional/legacy/matchparen_spec.lua +++ b/test/functional/legacy/matchparen_spec.lua @@ -120,8 +120,7 @@ describe('matchparen', function() ]]) feed('i<C-X><C-N><C-N>') - screen:expect { - grid = [[ + screen:expect([[ aa | aaa | aaaa | @@ -131,7 +130,79 @@ describe('matchparen', function() {4: aaaa }{1: }| {1:~ }| {5:-- }{6:match 2 of 3} | - ]], - } + ]]) + end) + + -- oldtest: Test_matchparen_mbyte() + it("works with multibyte chars in 'matchpairs'", function() + local screen = Screen.new(30, 10) + screen:set_default_attr_ids({ + [0] = { bold = true, foreground = Screen.colors.Blue }, + [1] = { background = Screen.colors.Cyan }, + [2] = { bold = true }, + }) + screen:attach() + + exec([[ + source $VIMRUNTIME/plugin/matchparen.vim + call setline(1, ['aaaaaaaa(', 'bbbb)cc']) + set matchpairs+=(:) + ]]) + + screen:expect([[ + ^aaaaaaaa( | + bbbb)cc | + {0:~ }|*7 + | + ]]) + feed('$') + screen:expect([[ + aaaaaaaa{1:^(} | + bbbb{1:)}cc | + {0:~ }|*7 + | + ]]) + feed('j') + screen:expect([[ + aaaaaaaa( | + bbbb)c^c | + {0:~ }|*7 + | + ]]) + feed('2h') + screen:expect([[ + aaaaaaaa{1:(} | + bbbb{1:^)}cc | + {0:~ }|*7 + | + ]]) + feed('0') + screen:expect([[ + aaaaaaaa( | + ^bbbb)cc | + {0:~ }|*7 + | + ]]) + feed('kA') + screen:expect([[ + aaaaaaaa{1:(}^ | + bbbb{1:)}cc | + {0:~ }|*7 + {2:-- INSERT --} | + ]]) + feed('<Down>') + screen:expect([[ + aaaaaaaa( | + bbbb)cc^ | + {0:~ }|*7 + {2:-- INSERT --} | + ]]) + feed('<C-W>') + screen:expect([[ + aaaaaaaa{1:(} | + bbbb{1:)}^ | + {0:~ }|*7 + {2:-- INSERT --} | + ]]) end) end) diff --git a/test/functional/legacy/put_spec.lua b/test/functional/legacy/put_spec.lua index 587424da10..8b9b495679 100644 --- a/test/functional/legacy/put_spec.lua +++ b/test/functional/legacy/put_spec.lua @@ -1,52 +1,11 @@ -local t = require('test.testutil') local n = require('test.functional.testnvim')() local Screen = require('test.functional.ui.screen') local clear = n.clear -local exec_lua = n.exec_lua -local api = n.api local source = n.source -local eq = t.eq - -local function sizeoflong() - if not exec_lua('return pcall(require, "ffi")') then - pending('missing LuaJIT FFI') - end - return exec_lua('return require("ffi").sizeof(require("ffi").typeof("long"))') -end describe('put', function() before_each(clear) - after_each(function() - eq({}, api.nvim_get_vvar('errors')) - end) - - it('very large count 64-bit', function() - if sizeoflong() < 8 then - pending('Skipped: only works with 64 bit long ints') - end - - source [[ - new - let @" = repeat('x', 100) - call assert_fails('norm 999999999p', 'E1240:') - bwipe! - ]] - end) - - it('very large count (visual block) 64-bit', function() - if sizeoflong() < 8 then - pending('Skipped: only works with 64 bit long ints') - end - - source [[ - new - call setline(1, repeat('x', 100)) - exe "norm \<C-V>$y" - call assert_fails('norm 999999999p', 'E1240:') - bwipe! - ]] - end) -- oldtest: Test_put_other_window() it('above topline in buffer in two splits', function() diff --git a/test/functional/legacy/scroll_opt_spec.lua b/test/functional/legacy/scroll_opt_spec.lua index 97578067d5..ec1dc59d53 100644 --- a/test/functional/legacy/scroll_opt_spec.lua +++ b/test/functional/legacy/scroll_opt_spec.lua @@ -375,6 +375,108 @@ describe('smoothscroll', function() screen:expect_unchanged() end) + -- oldtest: Test_smoothscroll_diff_change_line() + it('works in diff mode when changing line', function() + screen:try_resize(55, 20) + exec([[ + set diffopt+=followwrap smoothscroll + call setline(1, repeat(' abc', &columns)) + call setline(2, 'bar') + call setline(3, repeat(' abc', &columns)) + vnew + call setline(1, repeat(' abc', &columns)) + call setline(2, 'foo') + call setline(3, 'bar') + call setline(4, repeat(' abc', &columns)) + windo exe "normal! 2gg5\<C-E>" + windo diffthis + ]]) + + screen:expect([[ + {1:<<<}bc abc abc abc abc abc a│{1:<<<}bc abc abc abc abc abc a| + {7: }bc abc abc abc abc abc ab│{7: }bc abc abc abc abc abc ab| + {7: }c abc abc abc abc abc abc│{7: }c abc abc abc abc abc abc| + {7: } abc abc abc abc abc │{7: } abc abc abc abc abc | + {7: }{22:foo }│{7: }{23:-------------------------}| + {7: }bar │{7: }^bar | + {7: } abc abc abc abc abc abc │{7: } abc abc abc abc abc abc | + {7: }abc abc abc abc abc abc a│{7: }abc abc abc abc abc abc a| + {7: }bc abc abc abc abc abc ab│{7: }bc abc abc abc abc abc ab| + {7: }c abc abc abc abc abc abc│{7: }c abc abc abc abc abc abc| + {7: } abc abc abc abc abc abc │{7: } abc abc abc abc abc abc | + {7: }abc abc abc abc abc abc a│{7: }abc abc abc abc abc abc a| + {7: }bc abc abc abc abc abc ab│{7: }bc abc abc abc abc abc ab| + {7: }c abc abc abc abc abc abc│{7: }c abc abc abc abc abc abc| + {7: } abc abc abc abc abc │{7: } abc abc abc abc abc | + {1:~ }│{1:~ }|*3 + {2:[No Name] [+] }{3:[No Name] [+] }| + | + ]]) + feed('Abar') + screen:expect([[ + {1:<<<}bc abc abc abc abc abc a│{1:<<<}bc abc abc abc abc abc a| + {7: }bc abc abc abc abc abc ab│{7: }bc abc abc abc abc abc ab| + {7: }c abc abc abc abc abc abc│{7: }c abc abc abc abc abc abc| + {7: } abc abc abc abc abc │{7: } abc abc abc abc abc | + {7: }{22:foo }│{7: }{23:-------------------------}| + {7: }bar │{7: }barbar^ | + {7: } abc abc abc abc abc abc │{7: } abc abc abc abc abc abc | + {7: }abc abc abc abc abc abc a│{7: }abc abc abc abc abc abc a| + {7: }bc abc abc abc abc abc ab│{7: }bc abc abc abc abc abc ab| + {7: }c abc abc abc abc abc abc│{7: }c abc abc abc abc abc abc| + {7: } abc abc abc abc abc abc │{7: } abc abc abc abc abc abc | + {7: }abc abc abc abc abc abc a│{7: }abc abc abc abc abc abc a| + {7: }bc abc abc abc abc abc ab│{7: }bc abc abc abc abc abc ab| + {7: }c abc abc abc abc abc abc│{7: }c abc abc abc abc abc abc| + {7: } abc abc abc abc abc │{7: } abc abc abc abc abc | + {1:~ }│{1:~ }|*3 + {2:[No Name] [+] }{3:[No Name] [+] }| + {5:-- INSERT --} | + ]]) + feed('<Esc>') + screen:expect([[ + {1:<<<}bc abc abc abc abc abc a│{1:<<<}bc abc abc abc abc abc a| + {7: }bc abc abc abc abc abc ab│{7: }bc abc abc abc abc abc ab| + {7: }c abc abc abc abc abc abc│{7: }c abc abc abc abc abc abc| + {7: } abc abc abc abc abc │{7: } abc abc abc abc abc | + {7: }{27:foo}{4: }│{7: }{27:barba^r}{4: }| + {7: }{22:bar }│{7: }{23:-------------------------}| + {7: } abc abc abc abc abc abc │{7: } abc abc abc abc abc abc | + {7: }abc abc abc abc abc abc a│{7: }abc abc abc abc abc abc a| + {7: }bc abc abc abc abc abc ab│{7: }bc abc abc abc abc abc ab| + {7: }c abc abc abc abc abc abc│{7: }c abc abc abc abc abc abc| + {7: } abc abc abc abc abc abc │{7: } abc abc abc abc abc abc | + {7: }abc abc abc abc abc abc a│{7: }abc abc abc abc abc abc a| + {7: }bc abc abc abc abc abc ab│{7: }bc abc abc abc abc abc ab| + {7: }c abc abc abc abc abc abc│{7: }c abc abc abc abc abc abc| + {7: } abc abc abc abc abc │{7: } abc abc abc abc abc | + {1:~ }│{1:~ }|*3 + {2:[No Name] [+] }{3:[No Name] [+] }| + | + ]]) + feed('yyp') + screen:expect([[ + {1:<<<}bc abc abc abc abc abc a│{1:<<<}bc abc abc abc abc abc a| + {7: }bc abc abc abc abc abc ab│{7: }bc abc abc abc abc abc ab| + {7: }c abc abc abc abc abc abc│{7: }c abc abc abc abc abc abc| + {7: } abc abc abc abc abc │{7: } abc abc abc abc abc | + {7: }{27:foo}{4: }│{7: }{27:barbar}{4: }| + {7: }{4:bar }│{7: }{4:^bar}{27:bar}{4: }| + {7: } abc abc abc abc abc abc │{7: } abc abc abc abc abc abc | + {7: }abc abc abc abc abc abc a│{7: }abc abc abc abc abc abc a| + {7: }bc abc abc abc abc abc ab│{7: }bc abc abc abc abc abc ab| + {7: }c abc abc abc abc abc abc│{7: }c abc abc abc abc abc abc| + {7: } abc abc abc abc abc abc │{7: } abc abc abc abc abc abc | + {7: }abc abc abc abc abc abc a│{7: }abc abc abc abc abc abc a| + {7: }bc abc abc abc abc abc ab│{7: }bc abc abc abc abc abc ab| + {7: }c abc abc abc abc abc abc│{7: }c abc abc abc abc abc abc| + {7: } abc abc abc abc abc │{7: } abc abc abc abc abc | + {1:~ }│{1:~ }|*3 + {2:[No Name] [+] }{3:[No Name] [+] }| + | + ]]) + end) + -- oldtest: Test_smoothscroll_wrap_scrolloff_zero() it("works with zero 'scrolloff'", function() screen:try_resize(40, 8) @@ -1204,16 +1306,15 @@ describe('smoothscroll', function() set smoothscroll scrolloff=3 call setline(1, ['one', 'two long '->repeat(100), 'three', 'four', 'five', 'six']) ]]) - --FIXME: incorrect screen due to reset_skipcol()/curs_columns() shenanigans feed(':norm j721|<CR>') screen:expect([[ - two long two long two long two long two | + {1:<<<}two long two long two long two long t| + wo long two long two long two long two l| + ong two long two long two long two long | + ^two long two long two long two long two | long two long two long two long two long| two long two long two long two long two| - ^ long two long two long two long two lon| - g two long two long two long two long tw| - o long two long two long two long two lo| - ng two long two long two long two long t| + long two long two long two long two lon| :norm j721| | ]]) feed('gj') @@ -1272,15 +1373,14 @@ describe('smoothscroll', function() :norm j721| | ]]) feed('gk') - --FIXME: incorrect screen due to reset_skipcol()/curs_columns() shenanigans screen:expect([[ + {1:<<<}long two long two long two long two l| + ong two long two long two long two long | two long two long two long two long two | long two long two long two long two long| two long two long two long two long two| long two long two long two long two lon| - g two long two long two long two long tw| - o long two long two long two long two lo| - ^ng two long two long two long two long t| + ^g two long two long | :norm j721| | ]]) end) diff --git a/test/functional/legacy/signs_spec.lua b/test/functional/legacy/signs_spec.lua index 614673ee3c..27145f4c91 100644 --- a/test/functional/legacy/signs_spec.lua +++ b/test/functional/legacy/signs_spec.lua @@ -1,13 +1,14 @@ -- Tests for signs local n = require('test.functional.testnvim')() +local Screen = require('test.functional.ui.screen') -local clear, command, expect = n.clear, n.command, n.expect +local clear, command, exec, expect, feed = n.clear, n.command, n.exec, n.expect, n.feed describe('signs', function() - setup(clear) + before_each(clear) - it('is working', function() + it('are working', function() command('sign define JumpSign text=x') command([[exe 'sign place 42 line=2 name=JumpSign buffer=' . bufnr('')]]) -- Split the window to the bottom to verify :sign-jump will stay in the current @@ -21,4 +22,60 @@ describe('signs', function() 2]]) end) + + -- oldtest: Test_sign_cursor_position() + it('are drawn correctly', function() + local screen = Screen.new(75, 6) + screen:attach() + exec([[ + call setline(1, [repeat('x', 75), 'mmmm', 'yyyy']) + call cursor(2,1) + sign define s1 texthl=Search text==> + sign define s2 linehl=Pmenu + redraw + sign place 10 line=2 name=s1 + ]]) + screen:expect([[ + {7: }xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx| + {7: }xx | + {10:=>}^mmmm | + {7: }yyyy | + {1:~ }| + | + ]]) + + -- Change the sign text + command('sign define s1 text=-)') + screen:expect([[ + {7: }xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx| + {7: }xx | + {10:-)}^mmmm | + {7: }yyyy | + {1:~ }| + | + ]]) + + -- Also place a line HL sign + command('sign place 11 line=2 name=s2') + screen:expect([[ + {7: }xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx| + {7: }xx | + {10:-)}{4:^mmmm }| + {7: }yyyy | + {1:~ }| + | + ]]) + + -- update cursor position calculation + feed('lh') + command('sign unplace 11') + command('sign unplace 10') + screen:expect([[ + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx| + ^mmmm | + yyyy | + {1:~ }|*2 + | + ]]) + end) end) diff --git a/test/functional/lua/api_spec.lua b/test/functional/lua/api_spec.lua index 56969150bd..741ed0cf6e 100644 --- a/test/functional/lua/api_spec.lua +++ b/test/functional/lua/api_spec.lua @@ -145,10 +145,10 @@ describe('luaeval(vim.api.…)', function() eq(true, fn.luaeval('vim.api.nvim__id(vim.api.nvim__id)(true)')) eq( 42, - exec_lua [[ - local f = vim.api.nvim__id({42, vim.api.nvim__id}) - return f[2](f[1]) - ]] + exec_lua(function() + local f = vim.api.nvim__id({ 42, vim.api.nvim__id }) + return f[2](f[1]) + end) ) end) @@ -224,42 +224,39 @@ describe('luaeval(vim.api.…)', function() end) it('correctly converts dictionaries with type_idx to API objects', function() - eq( - 4, - eval([[type(luaeval('vim.api.nvim__id_dictionary({[vim.type_idx]=vim.types.dictionary})'))]]) - ) + eq(4, eval([[type(luaeval('vim.api.nvim__id_dict({[vim.type_idx]=vim.types.dictionary})'))]])) - eq({}, fn.luaeval('vim.api.nvim__id_dictionary({[vim.type_idx]=vim.types.dictionary})')) + eq({}, fn.luaeval('vim.api.nvim__id_dict({[vim.type_idx]=vim.types.dictionary})')) eq( { v = { 42 } }, fn.luaeval( - 'vim.api.nvim__id_dictionary({v={[vim.type_idx]=vim.types.array, [vim.val_idx]=10, [5]=1, foo=2, [1]=42}})' + 'vim.api.nvim__id_dict({v={[vim.type_idx]=vim.types.array, [vim.val_idx]=10, [5]=1, foo=2, [1]=42}})' ) ) eq( { foo = 2 }, fn.luaeval( - 'vim.api.nvim__id_dictionary({[vim.type_idx]=vim.types.dictionary, [vim.val_idx]=10, [5]=1, foo=2, [1]=42})' + 'vim.api.nvim__id_dict({[vim.type_idx]=vim.types.dictionary, [vim.val_idx]=10, [5]=1, foo=2, [1]=42})' ) ) eq( { v = 10 }, fn.luaeval( - 'vim.api.nvim__id_dictionary({v={[vim.type_idx]=vim.types.float, [vim.val_idx]=10, [5]=1, foo=2, [1]=42}})' + 'vim.api.nvim__id_dict({v={[vim.type_idx]=vim.types.float, [vim.val_idx]=10, [5]=1, foo=2, [1]=42}})' ) ) eq( { v = {} }, fn.luaeval( - 'vim.api.nvim__id_dictionary({v={[vim.type_idx]=vim.types.array, [vim.val_idx]=10, [5]=1, foo=2}})' + 'vim.api.nvim__id_dict({v={[vim.type_idx]=vim.types.array, [vim.val_idx]=10, [5]=1, foo=2}})' ) ) - -- If API requests dictionary, then empty table will be the one. This is not + -- If API requests dict, then empty table will be the one. This is not -- the case normally because empty table is an empty array. - eq({}, fn.luaeval('vim.api.nvim__id_dictionary({})')) - eq(4, eval([[type(luaeval('vim.api.nvim__id_dictionary({})'))]])) + eq({}, fn.luaeval('vim.api.nvim__id_dict({})')) + eq(4, eval([[type(luaeval('vim.api.nvim__id_dict({})'))]])) end) it('converts booleans in positional args', function() @@ -365,12 +362,12 @@ describe('luaeval(vim.api.…)', function() eq( [[Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Invalid 'dct': Expected Lua table]], - remove_trace(exc_exec([[call luaeval("vim.api.nvim__id_dictionary(1)")]])) + remove_trace(exc_exec([[call luaeval("vim.api.nvim__id_dict(1)")]])) ) eq( [[Vim(call):E5108: Error executing lua [string "luaeval()"]:1: Invalid 'dct': Expected Dict-like Lua table]], remove_trace( - exc_exec([[call luaeval("vim.api.nvim__id_dictionary({[vim.type_idx]=vim.types.array})")]]) + exc_exec([[call luaeval("vim.api.nvim__id_dict({[vim.type_idx]=vim.types.array})")]]) ) ) diff --git a/test/functional/lua/buffer_updates_spec.lua b/test/functional/lua/buffer_updates_spec.lua index d4af7e4732..f277868b1c 100644 --- a/test/functional/lua/buffer_updates_spec.lua +++ b/test/functional/lua/buffer_updates_spec.lua @@ -27,30 +27,35 @@ local origlines = { before_each(function() clear() - exec_lua [[ - local evname = ... + exec_lua(function() local events = {} - function test_register(bufnr, evname, id, changedtick, utf_sizes, preview) + function _G.test_register(bufnr, evname, id, changedtick, utf_sizes, preview) local function callback(...) - table.insert(events, {id, ...}) - if test_unreg == id then + table.insert(events, { id, ... }) + if _G.test_unreg == id then return true end end - local opts = {[evname]=callback, on_detach=callback, on_reload=callback, utf_sizes=utf_sizes, preview=preview} + local opts = { + [evname] = callback, + on_detach = callback, + on_reload = callback, + utf_sizes = utf_sizes, + preview = preview, + } if changedtick then opts.on_changedtick = callback end vim.api.nvim_buf_attach(bufnr, false, opts) end - function get_events() + function _G.get_events() local ret_events = events events = {} return ret_events end - ]] + end) end) describe('lua buffer event callbacks: on_lines', function() @@ -257,13 +262,13 @@ describe('lua buffer event callbacks: on_lines', function() it('has valid cursor position while shifting', function() api.nvim_buf_set_lines(0, 0, -1, true, { 'line1' }) - exec_lua([[ + exec_lua(function() vim.api.nvim_buf_attach(0, false, { on_lines = function() vim.api.nvim_set_var('listener_cursor_line', vim.api.nvim_win_get_cursor(0)[1]) end, }) - ]]) + end) feed('>>') eq(1, api.nvim_get_var('listener_cursor_line')) end) @@ -277,19 +282,19 @@ describe('lua buffer event callbacks: on_lines', function() end) it('does not SEGFAULT when accessing window buffer info in on_detach #14998', function() - local code = [[ + local code = function() local buf = vim.api.nvim_create_buf(false, false) - vim.cmd"split" + vim.cmd 'split' vim.api.nvim_win_set_buf(0, buf) vim.api.nvim_buf_attach(buf, false, { - on_detach = function(_, buf) + on_detach = function(_, buf0) vim.fn.tabpagebuflist() - vim.fn.win_findbuf(buf) - end + vim.fn.win_findbuf(buf0) + end, }) - ]] + end exec_lua(code) command('q!') @@ -302,13 +307,13 @@ describe('lua buffer event callbacks: on_lines', function() it('#12718 lnume', function() api.nvim_buf_set_lines(0, 0, -1, true, { '1', '2', '3' }) - exec_lua([[ + exec_lua(function() vim.api.nvim_buf_attach(0, false, { on_lines = function(...) vim.api.nvim_set_var('linesev', { ... }) end, }) - ]]) + end) feed('1G0') feed('y<C-v>2j') feed('G0') @@ -326,13 +331,13 @@ describe('lua buffer event callbacks: on_lines', function() end) it('nvim_buf_call() from callback does not cause wrong Normal mode CTRL-A #16729', function() - exec_lua([[ + exec_lua(function() vim.api.nvim_buf_attach(0, false, { - on_lines = function(...) + on_lines = function() vim.api.nvim_buf_call(0, function() end) end, }) - ]]) + end) feed('itest123<Esc><C-A>') eq('test124', api.nvim_get_current_line()) end) @@ -342,19 +347,19 @@ describe('lua buffer event callbacks: on_lines', function() screen:attach() api.nvim_buf_set_lines(0, 0, -1, true, { 'aaa', 'bbb', 'ccc' }) - exec_lua([[ + exec_lua(function() local ns = vim.api.nvim_create_namespace('') vim.api.nvim_buf_attach(0, false, { on_lines = function(_, _, _, row, _, end_row) vim.api.nvim_buf_clear_namespace(0, ns, row, end_row) for i = row, end_row - 1 do - local id = vim.api.nvim_buf_set_extmark(0, ns, i, 0, { - virt_text = {{ 'NEW' .. tostring(i), 'WarningMsg' }}, + vim.api.nvim_buf_set_extmark(0, ns, i, 0, { + virt_text = { { 'NEW' .. tostring(i), 'WarningMsg' } }, }) end end, }) - ]]) + end) feed('o') screen:expect({ @@ -379,6 +384,25 @@ describe('lua buffer event callbacks: on_lines', function() ]], }) end) + + it('line lengths are correct when pressing TAB with folding #29119', function() + api.nvim_buf_set_lines(0, 0, -1, true, { 'a', 'b' }) + + exec_lua(function() + _G.res = {} + vim.o.foldmethod = 'indent' + vim.o.softtabstop = -1 + vim.api.nvim_buf_attach(0, false, { + on_lines = function(_, bufnr, _, row, _, end_row) + local lines = vim.api.nvim_buf_get_lines(bufnr, row, end_row, true) + table.insert(_G.res, lines) + end, + }) + end) + + feed('i<Tab>') + eq({ '\ta' }, exec_lua('return _G.res[#_G.res]')) + end) end) describe('lua: nvim_buf_attach on_bytes', function() diff --git a/test/functional/lua/command_line_completion_spec.lua b/test/functional/lua/command_line_completion_spec.lua index 2ba432133b..f8786a45bb 100644 --- a/test/functional/lua/command_line_completion_spec.lua +++ b/test/functional/lua/command_line_completion_spec.lua @@ -5,12 +5,14 @@ local clear = n.clear local eq = t.eq local exec_lua = n.exec_lua +--- @return { [1]: string[], [2]: integer } local get_completions = function(input, env) - return exec_lua('return {vim._expand_pat(...)}', input, env) + return exec_lua('return { vim._expand_pat(...) }', input, env) end +--- @return { [1]: string[], [2]: integer } local get_compl_parts = function(parts) - return exec_lua('return {vim._expand_pat_get_parts(...)}', parts) + return exec_lua('return { vim._expand_pat_get_parts(...) }', parts) end before_each(clear) @@ -123,6 +125,171 @@ describe('nlua_expand_pat', function() ) end) + describe('should complete vim.fn', function() + it('correctly works for simple completion', function() + local actual = get_completions('vim.fn.did') + local expected = { + { 'did_filetype' }, + #'vim.fn.', + } + eq(expected, actual) + end) + it('should not suggest items with #', function() + exec_lua [[ + -- ensure remote#host#... functions exist + vim.cmd [=[ + runtime! autoload/remote/host.vim + ]=] + -- make a dummy call to ensure vim.fn contains an entry: remote#host#... + vim.fn['remote#host#IsRunning']('python3') + ]] + local actual = get_completions('vim.fn.remo') + local expected = { + { 'remove' }, -- there should be no completion "remote#host#..." + #'vim.fn.', + } + eq(expected, actual) + end) + end) + + describe('should complete for variable accessors for', function() + it('vim.v', function() + local actual = get_completions('vim.v.t_') + local expected = { + { 't_blob', 't_bool', 't_dict', 't_float', 't_func', 't_list', 't_number', 't_string' }, + #'vim.v.', + } + eq(expected, actual) + end) + + it('vim.g', function() + exec_lua [[ + vim.cmd [=[ + let g:nlua_foo = 'completion' + let g:nlua_foo_bar = 'completion' + let g:nlua_foo#bar = 'nocompletion' " should be excluded from lua completion + ]=] + ]] + local actual = get_completions('vim.g.nlua') + local expected = { + { 'nlua_foo', 'nlua_foo_bar' }, + #'vim.g.', + } + eq(expected, actual) + end) + + it('vim.b', function() + exec_lua [[ + vim.b.nlua_foo_buf = 'bar' + vim.b.some_other_vars = 'bar' + ]] + local actual = get_completions('vim.b.nlua') + local expected = { + { 'nlua_foo_buf' }, + #'vim.b.', + } + eq(expected, actual) + end) + + it('vim.w', function() + exec_lua [[ + vim.w.nlua_win_var = 42 + ]] + local actual = get_completions('vim.w.nlua') + local expected = { + { 'nlua_win_var' }, + #'vim.w.', + } + eq(expected, actual) + end) + + it('vim.t', function() + exec_lua [[ + vim.t.nlua_tab_var = 42 + ]] + local actual = get_completions('vim.t.') + local expected = { + { 'nlua_tab_var' }, + #'vim.t.', + } + eq(expected, actual) + end) + end) + + describe('should complete for option accessors for', function() + -- for { vim.o, vim.go, vim.opt, vim.opt_local, vim.opt_global } + local test_opt = function(accessor) + do + local actual = get_completions(accessor .. '.file') + local expected = { + 'fileencoding', + 'fileencodings', + 'fileformat', + 'fileformats', + 'fileignorecase', + 'filetype', + } + eq({ expected, #accessor + 1 }, actual, accessor .. '.file') + end + do + local actual = get_completions(accessor .. '.winh') + local expected = { + 'winheight', + 'winhighlight', + } + eq({ expected, #accessor + 1 }, actual, accessor .. '.winh') + end + end + + test_opt('vim.o') + test_opt('vim.go') + test_opt('vim.opt') + test_opt('vim.opt_local') + test_opt('vim.opt_global') + + it('vim.o, suggesting all the known options', function() + local completions = get_completions('vim.o.')[1] ---@type string[] + eq( + exec_lua [[ + return vim.tbl_count(vim.api.nvim_get_all_options_info()) + ]], + #completions + ) + end) + + it('vim.bo', function() + do + local actual = get_completions('vim.bo.file') + local compls = { + -- should contain buffer options only + 'fileencoding', + 'fileformat', + 'filetype', + } + eq({ compls, #'vim.bo.' }, actual) + end + do + local actual = get_completions('vim.bo.winh') + local compls = {} + eq({ compls, #'vim.bo.' }, actual) + end + end) + + it('vim.wo', function() + do + local actual = get_completions('vim.wo.file') + local compls = {} + eq({ compls, #'vim.wo.' }, actual) + end + do + local actual = get_completions('vim.wo.winh') + -- should contain window options only + local compls = { 'winhighlight' } + eq({ compls, #'vim.wo.' }, actual) + end + end) + end) + it('should return everything if the input is of length 0', function() eq({ { 'other', 'vim' }, 0 }, get_completions('', { vim = true, other = true })) end) diff --git a/test/functional/lua/commands_spec.lua b/test/functional/lua/commands_spec.lua index 57b084d3d6..456ee13da2 100644 --- a/test/functional/lua/commands_spec.lua +++ b/test/functional/lua/commands_spec.lua @@ -178,13 +178,15 @@ describe(':lua', function() eq('hello', exec_capture(':lua = x()')) exec_lua('x = {a = 1, b = 2}') eq('{\n a = 1,\n b = 2\n}', exec_capture(':lua =x')) - exec_lua([[function x(success) - if success then - return true, "Return value" - else - return false, nil, "Error message" + exec_lua(function() + function _G.x(success) + if success then + return true, 'Return value' + else + return false, nil, 'Error message' + end end - end]]) + end) eq( dedent [[ true diff --git a/test/functional/lua/deprecated_spec.lua b/test/functional/lua/deprecated_spec.lua new file mode 100644 index 0000000000..fee34336cc --- /dev/null +++ b/test/functional/lua/deprecated_spec.lua @@ -0,0 +1,22 @@ +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('deprecated lua code', function() + before_each(clear) + + describe('vim.treesitter.get_parser()', function() + it('returns nil for versions >= 0.12', function() + local result = exec_lua(function() + if vim.version.ge(vim.version(), '0.12') then + return vim.treesitter.get_parser(0, 'borklang') + end + return nil + end) + eq(nil, result) + end) + end) +end) diff --git a/test/functional/lua/diagnostic_spec.lua b/test/functional/lua/diagnostic_spec.lua index 05082bc132..4ae1146703 100644 --- a/test/functional/lua/diagnostic_spec.lua +++ b/test/functional/lua/diagnostic_spec.lua @@ -1,7 +1,6 @@ local t = require('test.testutil') local n = require('test.functional.testnvim')() -local NIL = vim.NIL local command = n.command local clear = n.clear local exec_lua = n.exec_lua @@ -15,10 +14,10 @@ describe('vim.diagnostic', function() before_each(function() clear() - exec_lua [[ + exec_lua(function() require('vim.diagnostic') - function make_diagnostic(msg, lnum, col, end_lnum, end_col, severity, source, code) + local function make_diagnostic(msg, lnum, col, end_lnum, end_col, severity, source, code) return { lnum = lnum, col = col, @@ -31,54 +30,97 @@ describe('vim.diagnostic', function() } end - function make_error(msg, lnum, col, end_lnum, end_col, source, code) - return make_diagnostic(msg, lnum, col, end_lnum, end_col, vim.diagnostic.severity.ERROR, source, code) + function _G.make_error(msg, lnum, col, end_lnum, end_col, source, code) + return make_diagnostic( + msg, + lnum, + col, + end_lnum, + end_col, + vim.diagnostic.severity.ERROR, + source, + code + ) end - function make_warning(msg, lnum, col, end_lnum, end_col, source, code) - return make_diagnostic(msg, lnum, col, end_lnum, end_col, vim.diagnostic.severity.WARN, source, code) + function _G.make_warning(msg, lnum, col, end_lnum, end_col, source, code) + return make_diagnostic( + msg, + lnum, + col, + end_lnum, + end_col, + vim.diagnostic.severity.WARN, + source, + code + ) end - function make_info(msg, lnum, col, end_lnum, end_col, source, code) - return make_diagnostic(msg, lnum, col, end_lnum, end_col, vim.diagnostic.severity.INFO, source, code) + function _G.make_info(msg, lnum, col, end_lnum, end_col, source, code) + return make_diagnostic( + msg, + lnum, + col, + end_lnum, + end_col, + vim.diagnostic.severity.INFO, + source, + code + ) end - function make_hint(msg, lnum, col, end_lnum, end_col, source, code) - return make_diagnostic(msg, lnum, col, end_lnum, end_col, vim.diagnostic.severity.HINT, source, code) + function _G.make_hint(msg, lnum, col, end_lnum, end_col, source, code) + return make_diagnostic( + msg, + lnum, + col, + end_lnum, + end_col, + vim.diagnostic.severity.HINT, + source, + code + ) end - function count_diagnostics(bufnr, severity, namespace) - return #vim.diagnostic.get(bufnr, {severity = severity, namespace = namespace}) + function _G.count_diagnostics(bufnr, severity, namespace) + return #vim.diagnostic.get(bufnr, { severity = severity, namespace = namespace }) end - function count_extmarks(bufnr, namespace) + function _G.count_extmarks(bufnr, namespace) local ns = vim.diagnostic.get_namespace(namespace) local extmarks = 0 if ns.user_data.virt_text_ns then - extmarks = extmarks + #vim.api.nvim_buf_get_extmarks(bufnr, ns.user_data.virt_text_ns, 0, -1, {}) + extmarks = extmarks + + #vim.api.nvim_buf_get_extmarks(bufnr, ns.user_data.virt_text_ns, 0, -1, {}) end if ns.user_data.underline_ns then - extmarks = extmarks + #vim.api.nvim_buf_get_extmarks(bufnr, ns.user_data.underline_ns, 0, -1, {}) + extmarks = extmarks + + #vim.api.nvim_buf_get_extmarks(bufnr, ns.user_data.underline_ns, 0, -1, {}) end return extmarks end - function get_virt_text_extmarks(ns) - local ns = vim.diagnostic.get_namespace(ns) + function _G.get_virt_text_extmarks(ns) + ns = vim.diagnostic.get_namespace(ns) local virt_text_ns = ns.user_data.virt_text_ns - return vim.api.nvim_buf_get_extmarks(diagnostic_bufnr, virt_text_ns, 0, -1, {details = true}) + return vim.api.nvim_buf_get_extmarks( + _G.diagnostic_bufnr, + virt_text_ns, + 0, + -1, + { details = true } + ) end - ]] - - exec_lua([[ - diagnostic_ns = vim.api.nvim_create_namespace("diagnostic_spec") - other_ns = vim.api.nvim_create_namespace("other_namespace") - diagnostic_bufnr = vim.api.nvim_create_buf(true, false) - local lines = {"1st line of text", "2nd line of text", "wow", "cool", "more", "lines"} - vim.fn.bufload(diagnostic_bufnr) - vim.api.nvim_buf_set_lines(diagnostic_bufnr, 0, 1, false, lines) - return diagnostic_bufnr - ]]) + end) + + exec_lua(function() + _G.diagnostic_ns = vim.api.nvim_create_namespace('diagnostic_spec') + _G.other_ns = vim.api.nvim_create_namespace('other_namespace') + _G.diagnostic_bufnr = vim.api.nvim_create_buf(true, false) + local lines = { '1st line of text', '2nd line of text', 'wow', 'cool', 'more', 'lines' } + vim.fn.bufload(_G.diagnostic_bufnr) + vim.api.nvim_buf_set_lines(_G.diagnostic_bufnr, 0, 1, false, lines) + end) end) it('creates highlight groups', function() @@ -115,27 +157,28 @@ describe('vim.diagnostic', function() end) it('retrieves diagnostics from all buffers and namespaces', function() - local result = exec_lua [[ + local result = exec_lua(function() local other_bufnr = vim.api.nvim_create_buf(true, false) - local lines = vim.api.nvim_buf_get_lines(diagnostic_bufnr, 0, -1, true) + local lines = vim.api.nvim_buf_get_lines(_G.diagnostic_bufnr, 0, -1, true) vim.api.nvim_buf_set_lines(other_bufnr, 0, 1, false, lines) - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { - make_error('Diagnostic #1', 1, 1, 1, 1), - make_error('Diagnostic #2', 2, 1, 2, 1), + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_error('Diagnostic #1', 1, 1, 1, 1), + _G.make_error('Diagnostic #2', 2, 1, 2, 1), }) - vim.diagnostic.set(other_ns, other_bufnr, { - make_error('Diagnostic #3', 3, 1, 3, 1), + vim.diagnostic.set(_G.other_ns, other_bufnr, { + _G.make_error('Diagnostic #3', 3, 1, 3, 1), }) return vim.diagnostic.get() - ]] + end) eq(3, #result) eq( 2, - exec_lua( - [[return #vim.tbl_filter(function(d) return d.bufnr == diagnostic_bufnr end, ...)]], - result - ) + exec_lua(function() + return #vim.tbl_filter(function(d) + return d.bufnr == _G.diagnostic_bufnr + end, result) + end) ) eq('Diagnostic #1', result[1].message) end) @@ -143,155 +186,171 @@ describe('vim.diagnostic', function() it('removes diagnostics from the cache when a buffer is removed', function() eq( 2, - exec_lua [[ - vim.api.nvim_win_set_buf(0, diagnostic_bufnr) - local other_bufnr = vim.fn.bufadd('test | test') - local lines = vim.api.nvim_buf_get_lines(diagnostic_bufnr, 0, -1, true) - vim.api.nvim_buf_set_lines(other_bufnr, 0, 1, false, lines) - vim.cmd('bunload! ' .. other_bufnr) - - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { - make_error('Diagnostic #1', 1, 1, 1, 1), - make_error('Diagnostic #2', 2, 1, 2, 1), - }) - vim.diagnostic.set(diagnostic_ns, other_bufnr, { - make_error('Diagnostic #3', 3, 1, 3, 1), - }) - vim.api.nvim_set_current_buf(other_bufnr) - vim.opt_local.buflisted = true - vim.cmd('bwipeout!') - return #vim.diagnostic.get() - ]] + exec_lua(function() + vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr) + local other_bufnr = vim.fn.bufadd('test | test') + local lines = vim.api.nvim_buf_get_lines(_G.diagnostic_bufnr, 0, -1, true) + vim.api.nvim_buf_set_lines(other_bufnr, 0, 1, false, lines) + vim.cmd('bunload! ' .. other_bufnr) + + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_error('Diagnostic #1', 1, 1, 1, 1), + _G.make_error('Diagnostic #2', 2, 1, 2, 1), + }) + vim.diagnostic.set(_G.diagnostic_ns, other_bufnr, { + _G.make_error('Diagnostic #3', 3, 1, 3, 1), + }) + vim.api.nvim_set_current_buf(other_bufnr) + vim.opt_local.buflisted = true + vim.cmd('bwipeout!') + return #vim.diagnostic.get() + end) ) eq( 2, - exec_lua [[ - vim.api.nvim_set_current_buf(diagnostic_bufnr) - vim.opt_local.buflisted = false - return #vim.diagnostic.get() - ]] + exec_lua(function() + vim.api.nvim_set_current_buf(_G.diagnostic_bufnr) + vim.opt_local.buflisted = false + return #vim.diagnostic.get() + end) ) eq( 0, - exec_lua [[ - vim.cmd('bwipeout!') - return #vim.diagnostic.get() - ]] + exec_lua(function() + vim.cmd('bwipeout!') + return #vim.diagnostic.get() + end) ) end) it('removes diagnostic from stale cache on reset', function() - local diagnostics = exec_lua [[ - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { - make_error('Diagnostic #1', 1, 1, 1, 1), - make_error('Diagnostic #2', 2, 1, 2, 1), + local diagnostics = exec_lua(function() + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_error('Diagnostic #1', 1, 1, 1, 1), + _G.make_error('Diagnostic #2', 2, 1, 2, 1), }) - local other_bufnr = vim.fn.bufadd('test | test') - vim.cmd('noautocmd bwipeout! ' .. diagnostic_bufnr) - return vim.diagnostic.get(diagnostic_bufnr) - ]] + vim.fn.bufadd('test | test') + vim.cmd('noautocmd bwipeout! ' .. _G.diagnostic_bufnr) + return vim.diagnostic.get(_G.diagnostic_bufnr) + end) eq(2, #diagnostics) - diagnostics = exec_lua [[ + diagnostics = exec_lua(function() vim.diagnostic.reset() return vim.diagnostic.get() - ]] + end) eq(0, #diagnostics) end) it('always returns a copy of diagnostic tables', function() - local result = exec_lua [[ - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { - make_error('Diagnostic #1', 1, 1, 1, 1), + local result = exec_lua(function() + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_error('Diagnostic #1', 1, 1, 1, 1), }) local diag = vim.diagnostic.get() diag[1].col = 10000 return vim.diagnostic.get()[1].col == 10000 - ]] + end) eq(false, result) end) it('resolves buffer number 0 to the current buffer', function() eq( 2, - exec_lua [[ - vim.api.nvim_set_current_buf(diagnostic_bufnr) - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { - make_error('Diagnostic #1', 1, 1, 1, 1), - make_error('Diagnostic #2', 2, 1, 2, 1), - }) - return #vim.diagnostic.get(0) - ]] + exec_lua(function() + vim.api.nvim_set_current_buf(_G.diagnostic_bufnr) + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_error('Diagnostic #1', 1, 1, 1, 1), + _G.make_error('Diagnostic #2', 2, 1, 2, 1), + }) + return #vim.diagnostic.get(0) + end) ) end) it('saves and count a single error', function() eq( 1, - exec_lua [[ - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { - make_error('Diagnostic #1', 1, 1, 1, 1), - }) - return count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.ERROR, diagnostic_ns) - ]] + exec_lua(function() + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_error('Diagnostic #1', 1, 1, 1, 1), + }) + return _G.count_diagnostics( + _G.diagnostic_bufnr, + vim.diagnostic.severity.ERROR, + _G.diagnostic_ns + ) + end) ) end) it('saves and count multiple errors', function() eq( 2, - exec_lua [[ - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { - make_error('Diagnostic #1', 1, 1, 1, 1), - make_error('Diagnostic #2', 2, 1, 2, 1), - }) - return count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.ERROR, diagnostic_ns) - ]] + exec_lua(function() + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_error('Diagnostic #1', 1, 1, 1, 1), + _G.make_error('Diagnostic #2', 2, 1, 2, 1), + }) + return _G.count_diagnostics( + _G.diagnostic_bufnr, + vim.diagnostic.severity.ERROR, + _G.diagnostic_ns + ) + end) ) end) it('saves and count from multiple namespaces', function() eq( { 1, 1, 2 }, - exec_lua [[ - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { - make_error('Diagnostic From Server 1', 1, 1, 1, 1), - }) - vim.diagnostic.set(other_ns, diagnostic_bufnr, { - make_error('Diagnostic From Server 2', 1, 1, 1, 1), - }) - return { - -- First namespace - count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.ERROR, diagnostic_ns), - -- Second namespace - count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.ERROR, other_ns), - -- All namespaces - count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.ERROR), - } - ]] + exec_lua(function() + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_error('Diagnostic From Server 1', 1, 1, 1, 1), + }) + vim.diagnostic.set(_G.other_ns, _G.diagnostic_bufnr, { + _G.make_error('Diagnostic From Server 2', 1, 1, 1, 1), + }) + return { + -- First namespace + _G.count_diagnostics( + _G.diagnostic_bufnr, + vim.diagnostic.severity.ERROR, + _G.diagnostic_ns + ), + -- Second namespace + _G.count_diagnostics(_G.diagnostic_bufnr, vim.diagnostic.severity.ERROR, _G.other_ns), + -- All namespaces + _G.count_diagnostics(_G.diagnostic_bufnr, vim.diagnostic.severity.ERROR), + } + end) ) end) it('saves and count from multiple namespaces with respect to severity', function() eq( { 3, 0, 3 }, - exec_lua [[ - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { - make_error('Diagnostic From Server 1:1', 1, 1, 1, 1), - make_error('Diagnostic From Server 1:2', 2, 2, 2, 2), - make_error('Diagnostic From Server 1:3', 2, 3, 3, 2), - }) - vim.diagnostic.set(other_ns, diagnostic_bufnr, { - make_warning('Warning From Server 2', 3, 3, 3, 3), - }) - return { - -- Namespace 1 - count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.ERROR, diagnostic_ns), - -- Namespace 2 - count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.ERROR, other_ns), - -- All namespaces - count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.ERROR), - } - ]] + exec_lua(function() + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_error('Diagnostic From Server 1:1', 1, 1, 1, 1), + _G.make_error('Diagnostic From Server 1:2', 2, 2, 2, 2), + _G.make_error('Diagnostic From Server 1:3', 2, 3, 3, 2), + }) + vim.diagnostic.set(_G.other_ns, _G.diagnostic_bufnr, { + _G.make_warning('Warning From Server 2', 3, 3, 3, 3), + }) + return { + -- Namespace 1 + _G.count_diagnostics( + _G.diagnostic_bufnr, + vim.diagnostic.severity.ERROR, + _G.diagnostic_ns + ), + -- Namespace 2 + _G.count_diagnostics(_G.diagnostic_bufnr, vim.diagnostic.severity.ERROR, _G.other_ns), + -- All namespaces + _G.count_diagnostics(_G.diagnostic_bufnr, vim.diagnostic.severity.ERROR), + } + end) ) end) @@ -304,160 +363,190 @@ describe('vim.diagnostic', function() local all_highlights = { 1, 1, 2, 4, 2 } eq( all_highlights, - exec_lua [[ - local ns_1_diags = { - make_error("Error 1", 1, 1, 1, 5), - make_warning("Warning on Server 1", 2, 1, 2, 3), - } - local ns_2_diags = { - make_warning("Warning 1", 2, 1, 2, 3), - } + exec_lua(function() + local ns_1_diags = { + _G.make_error('Error 1', 1, 1, 1, 5), + _G.make_warning('Warning on Server 1', 2, 1, 2, 3), + } + local ns_2_diags = { + _G.make_warning('Warning 1', 2, 1, 2, 3), + } - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, ns_1_diags) - vim.diagnostic.set(other_ns, diagnostic_bufnr, ns_2_diags) + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, ns_1_diags) + vim.diagnostic.set(_G.other_ns, _G.diagnostic_bufnr, ns_2_diags) - return { - count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.ERROR, diagnostic_ns), - count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.WARN, other_ns), - count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.WARN), - count_extmarks(diagnostic_bufnr, diagnostic_ns), - count_extmarks(diagnostic_bufnr, other_ns), - } - ]] + return { + _G.count_diagnostics( + _G.diagnostic_bufnr, + vim.diagnostic.severity.ERROR, + _G.diagnostic_ns + ), + _G.count_diagnostics(_G.diagnostic_bufnr, vim.diagnostic.severity.WARN, _G.other_ns), + _G.count_diagnostics(_G.diagnostic_bufnr, vim.diagnostic.severity.WARN), + _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns), + _G.count_extmarks(_G.diagnostic_bufnr, _G.other_ns), + } + end) ) -- Clear diagnostics from namespace 1, and make sure we have the right amount of stuff for namespace 2 eq( { 1, 1, 2, 0, 2 }, - exec_lua [[ - vim.diagnostic.enable(false, { bufnr = diagnostic_bufnr, ns_id = diagnostic_ns }) - return { - count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.ERROR, diagnostic_ns), - count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.WARN, other_ns), - count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.WARN), - count_extmarks(diagnostic_bufnr, diagnostic_ns), - count_extmarks(diagnostic_bufnr, other_ns), - } - ]] + exec_lua(function() + vim.diagnostic.enable(false, { bufnr = _G.diagnostic_bufnr, ns_id = _G.diagnostic_ns }) + return { + _G.count_diagnostics( + _G.diagnostic_bufnr, + vim.diagnostic.severity.ERROR, + _G.diagnostic_ns + ), + _G.count_diagnostics(_G.diagnostic_bufnr, vim.diagnostic.severity.WARN, _G.other_ns), + _G.count_diagnostics(_G.diagnostic_bufnr, vim.diagnostic.severity.WARN), + _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns), + _G.count_extmarks(_G.diagnostic_bufnr, _G.other_ns), + } + end) ) -- Show diagnostics from namespace 1 again eq( all_highlights, - exec_lua([[ - vim.diagnostic.enable(true, { bufnr = diagnostic_bufnr, ns_id = diagnostic_ns }) - return { - count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.ERROR, diagnostic_ns), - count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.WARN, other_ns), - count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.WARN), - count_extmarks(diagnostic_bufnr, diagnostic_ns), - count_extmarks(diagnostic_bufnr, other_ns), - } - ]]) + exec_lua(function() + vim.diagnostic.enable(true, { bufnr = _G.diagnostic_bufnr, ns_id = _G.diagnostic_ns }) + return { + _G.count_diagnostics( + _G.diagnostic_bufnr, + vim.diagnostic.severity.ERROR, + _G.diagnostic_ns + ), + _G.count_diagnostics(_G.diagnostic_bufnr, vim.diagnostic.severity.WARN, _G.other_ns), + _G.count_diagnostics(_G.diagnostic_bufnr, vim.diagnostic.severity.WARN), + _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns), + _G.count_extmarks(_G.diagnostic_bufnr, _G.other_ns), + } + end) ) end) it('does not display diagnostics when disabled', function() eq( { 0, 2 }, - exec_lua [[ - local ns_1_diags = { - make_error("Error 1", 1, 1, 1, 5), - make_warning("Warning on Server 1", 2, 1, 2, 3), - } - local ns_2_diags = { - make_warning("Warning 1", 2, 1, 2, 3), - } + exec_lua(function() + local ns_1_diags = { + _G.make_error('Error 1', 1, 1, 1, 5), + _G.make_warning('Warning on Server 1', 2, 1, 2, 3), + } + local ns_2_diags = { + _G.make_warning('Warning 1', 2, 1, 2, 3), + } - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, ns_1_diags) - vim.diagnostic.set(other_ns, diagnostic_bufnr, ns_2_diags) + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, ns_1_diags) + vim.diagnostic.set(_G.other_ns, _G.diagnostic_bufnr, ns_2_diags) - vim.diagnostic.enable(false, { bufnr = diagnostic_bufnr, ns_id = diagnostic_ns }) + vim.diagnostic.enable(false, { bufnr = _G.diagnostic_bufnr, ns_id = _G.diagnostic_ns }) - return { - count_extmarks(diagnostic_bufnr, diagnostic_ns), - count_extmarks(diagnostic_bufnr, other_ns), - } - ]] + return { + _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns), + _G.count_extmarks(_G.diagnostic_bufnr, _G.other_ns), + } + end) ) eq( { 4, 0 }, - exec_lua [[ - vim.diagnostic.enable(true, { bufnr = diagnostic_bufnr, ns_id = diagnostic_ns }) - vim.diagnostic.enable(false, { bufnr = diagnostic_bufnr, ns_id = other_ns }) + exec_lua(function() + vim.diagnostic.enable(true, { bufnr = _G.diagnostic_bufnr, ns_id = _G.diagnostic_ns }) + vim.diagnostic.enable(false, { bufnr = _G.diagnostic_bufnr, ns_id = _G.other_ns }) - return { - count_extmarks(diagnostic_bufnr, diagnostic_ns), - count_extmarks(diagnostic_bufnr, other_ns), - } - ]] + return { + _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns), + _G.count_extmarks(_G.diagnostic_bufnr, _G.other_ns), + } + end) ) end) describe('show() and hide()', function() it('works', function() - local result = exec_lua [[ + local result = exec_lua(function() local other_bufnr = vim.api.nvim_create_buf(true, false) - vim.api.nvim_win_set_buf(0, diagnostic_bufnr) + vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr) local result = {} vim.diagnostic.config({ underline = false, virtual_text = true }) local ns_1_diags = { - make_error("Error 1", 1, 1, 1, 5), - make_warning("Warning on Server 1", 2, 1, 2, 5), + _G.make_error('Error 1', 1, 1, 1, 5), + _G.make_warning('Warning on Server 1', 2, 1, 2, 5), } local ns_2_diags = { - make_warning("Warning 1", 2, 1, 2, 5), + _G.make_warning('Warning 1', 2, 1, 2, 5), } local other_buffer_diags = { - make_info("This is interesting", 0, 0, 0, 0) + _G.make_info('This is interesting', 0, 0, 0, 0), } - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, ns_1_diags) - vim.diagnostic.set(other_ns, diagnostic_bufnr, ns_2_diags) - vim.diagnostic.set(diagnostic_ns, other_bufnr, other_buffer_diags) + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, ns_1_diags) + vim.diagnostic.set(_G.other_ns, _G.diagnostic_bufnr, ns_2_diags) + vim.diagnostic.set(_G.diagnostic_ns, other_bufnr, other_buffer_diags) -- All buffers and namespaces - table.insert(result, count_extmarks(diagnostic_bufnr, diagnostic_ns) + - count_extmarks(diagnostic_bufnr, other_ns) + - count_extmarks(other_bufnr, diagnostic_ns)) + table.insert( + result, + _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns) + + _G.count_extmarks(_G.diagnostic_bufnr, _G.other_ns) + + _G.count_extmarks(other_bufnr, _G.diagnostic_ns) + ) -- Hide one namespace - vim.diagnostic.hide(diagnostic_ns) - table.insert(result, count_extmarks(diagnostic_bufnr, diagnostic_ns) + - count_extmarks(diagnostic_bufnr, other_ns) + - count_extmarks(other_bufnr, diagnostic_ns)) + vim.diagnostic.hide(_G.diagnostic_ns) + table.insert( + result, + _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns) + + _G.count_extmarks(_G.diagnostic_bufnr, _G.other_ns) + + _G.count_extmarks(other_bufnr, _G.diagnostic_ns) + ) -- Show one namespace - vim.diagnostic.show(diagnostic_ns) - table.insert(result, count_extmarks(diagnostic_bufnr, diagnostic_ns) + - count_extmarks(diagnostic_bufnr, other_ns) + - count_extmarks(other_bufnr, diagnostic_ns)) + vim.diagnostic.show(_G.diagnostic_ns) + table.insert( + result, + _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns) + + _G.count_extmarks(_G.diagnostic_bufnr, _G.other_ns) + + _G.count_extmarks(other_bufnr, _G.diagnostic_ns) + ) -- Hide one buffer vim.diagnostic.hide(nil, other_bufnr) - table.insert(result, count_extmarks(diagnostic_bufnr, diagnostic_ns) + - count_extmarks(diagnostic_bufnr, other_ns) + - count_extmarks(other_bufnr, diagnostic_ns)) + table.insert( + result, + _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns) + + _G.count_extmarks(_G.diagnostic_bufnr, _G.other_ns) + + _G.count_extmarks(other_bufnr, _G.diagnostic_ns) + ) -- Hide everything vim.diagnostic.hide() - table.insert(result, count_extmarks(diagnostic_bufnr, diagnostic_ns) + - count_extmarks(diagnostic_bufnr, other_ns) + - count_extmarks(other_bufnr, diagnostic_ns)) + table.insert( + result, + _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns) + + _G.count_extmarks(_G.diagnostic_bufnr, _G.other_ns) + + _G.count_extmarks(other_bufnr, _G.diagnostic_ns) + ) -- Show one buffer - vim.diagnostic.show(nil, diagnostic_bufnr) - table.insert(result, count_extmarks(diagnostic_bufnr, diagnostic_ns) + - count_extmarks(diagnostic_bufnr, other_ns) + - count_extmarks(other_bufnr, diagnostic_ns)) + vim.diagnostic.show(nil, _G.diagnostic_bufnr) + table.insert( + result, + _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns) + + _G.count_extmarks(_G.diagnostic_bufnr, _G.other_ns) + + _G.count_extmarks(other_bufnr, _G.diagnostic_ns) + ) return result - ]] + end) eq(4, result[1]) eq(1, result[2]) @@ -468,13 +557,17 @@ describe('vim.diagnostic', function() end) it("doesn't error after bwipeout on buffer", function() - exec_lua [[ - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, {{ lnum = 0, end_lnum = 0, col = 0, end_col = 0 }}) - vim.cmd("bwipeout! " .. diagnostic_bufnr) + exec_lua(function() + vim.diagnostic.set( + _G.diagnostic_ns, + _G.diagnostic_bufnr, + { { lnum = 0, end_lnum = 0, col = 0, end_col = 0 } } + ) + vim.cmd('bwipeout! ' .. _G.diagnostic_bufnr) - vim.diagnostic.show(diagnostic_ns) - vim.diagnostic.hide(diagnostic_ns) - ]] + vim.diagnostic.show(_G.diagnostic_ns) + vim.diagnostic.hide(_G.diagnostic_ns) + end) end) end) @@ -505,52 +598,64 @@ describe('vim.diagnostic', function() end) it('without arguments', function() - local result = exec_lua [[ - vim.api.nvim_win_set_buf(0, diagnostic_bufnr) + local result = exec_lua(function() + vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr) local result = {} vim.diagnostic.config({ underline = false, virtual_text = true }) local ns_1_diags = { - make_error("Error 1", 1, 1, 1, 5), - make_warning("Warning on Server 1", 2, 1, 2, 5), + _G.make_error('Error 1', 1, 1, 1, 5), + _G.make_warning('Warning on Server 1', 2, 1, 2, 5), } local ns_2_diags = { - make_warning("Warning 1", 2, 1, 2, 5), + _G.make_warning('Warning 1', 2, 1, 2, 5), } - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, ns_1_diags) - vim.diagnostic.set(other_ns, diagnostic_bufnr, ns_2_diags) + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, ns_1_diags) + vim.diagnostic.set(_G.other_ns, _G.diagnostic_bufnr, ns_2_diags) - table.insert(result, count_extmarks(diagnostic_bufnr, diagnostic_ns) + - count_extmarks(diagnostic_bufnr, other_ns)) + table.insert( + result, + _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns) + + _G.count_extmarks(_G.diagnostic_bufnr, _G.other_ns) + ) vim.diagnostic.enable(false) - table.insert(result, count_extmarks(diagnostic_bufnr, diagnostic_ns) + - count_extmarks(diagnostic_bufnr, other_ns)) + table.insert( + result, + _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns) + + _G.count_extmarks(_G.diagnostic_bufnr, _G.other_ns) + ) -- Create a new buffer local other_bufnr = vim.api.nvim_create_buf(true, false) local other_buffer_diags = { - make_info("This is interesting", 0, 0, 0, 0) + _G.make_info('This is interesting', 0, 0, 0, 0), } - vim.diagnostic.set(diagnostic_ns, other_bufnr, other_buffer_diags) + vim.diagnostic.set(_G.diagnostic_ns, other_bufnr, other_buffer_diags) - table.insert(result, count_extmarks(diagnostic_bufnr, diagnostic_ns) + - count_extmarks(diagnostic_bufnr, other_ns) + - count_extmarks(other_bufnr, diagnostic_ns)) + table.insert( + result, + _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns) + + _G.count_extmarks(_G.diagnostic_bufnr, _G.other_ns) + + _G.count_extmarks(other_bufnr, _G.diagnostic_ns) + ) vim.diagnostic.enable() - table.insert(result, count_extmarks(diagnostic_bufnr, diagnostic_ns) + - count_extmarks(diagnostic_bufnr, other_ns) + - count_extmarks(other_bufnr, diagnostic_ns)) + table.insert( + result, + _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns) + + _G.count_extmarks(_G.diagnostic_bufnr, _G.other_ns) + + _G.count_extmarks(other_bufnr, _G.diagnostic_ns) + ) return result - ]] + end) eq(3, result[1]) eq(0, result[2]) @@ -559,54 +664,66 @@ describe('vim.diagnostic', function() end) it('with buffer argument', function() - local result = exec_lua [[ + local result = exec_lua(function() local other_bufnr = vim.api.nvim_create_buf(true, false) - vim.api.nvim_win_set_buf(0, diagnostic_bufnr) + vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr) local result = {} vim.diagnostic.config({ underline = false, virtual_text = true }) local ns_1_diags = { - make_error("Error 1", 1, 1, 1, 5), - make_warning("Warning on Server 1", 2, 1, 2, 5), + _G.make_error('Error 1', 1, 1, 1, 5), + _G.make_warning('Warning on Server 1', 2, 1, 2, 5), } local ns_2_diags = { - make_warning("Warning 1", 2, 1, 2, 5), + _G.make_warning('Warning 1', 2, 1, 2, 5), } local other_buffer_diags = { - make_info("This is interesting", 0, 0, 0, 0) + _G.make_info('This is interesting', 0, 0, 0, 0), } - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, ns_1_diags) - vim.diagnostic.set(other_ns, diagnostic_bufnr, ns_2_diags) - vim.diagnostic.set(diagnostic_ns, other_bufnr, other_buffer_diags) + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, ns_1_diags) + vim.diagnostic.set(_G.other_ns, _G.diagnostic_bufnr, ns_2_diags) + vim.diagnostic.set(_G.diagnostic_ns, other_bufnr, other_buffer_diags) - table.insert(result, count_extmarks(diagnostic_bufnr, diagnostic_ns) + - count_extmarks(diagnostic_bufnr, other_ns) + - count_extmarks(other_bufnr, diagnostic_ns)) + table.insert( + result, + _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns) + + _G.count_extmarks(_G.diagnostic_bufnr, _G.other_ns) + + _G.count_extmarks(other_bufnr, _G.diagnostic_ns) + ) - vim.diagnostic.enable(false, { bufnr = diagnostic_bufnr }) + vim.diagnostic.enable(false, { bufnr = _G.diagnostic_bufnr }) - table.insert(result, count_extmarks(diagnostic_bufnr, diagnostic_ns) + - count_extmarks(diagnostic_bufnr, other_ns) + - count_extmarks(other_bufnr, diagnostic_ns)) + table.insert( + result, + _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns) + + _G.count_extmarks(_G.diagnostic_bufnr, _G.other_ns) + + _G.count_extmarks(other_bufnr, _G.diagnostic_ns) + ) - vim.diagnostic.enable(true, { bufnr = diagnostic_bufnr }) + vim.diagnostic.enable(true, { bufnr = _G.diagnostic_bufnr }) - table.insert(result, count_extmarks(diagnostic_bufnr, diagnostic_ns) + - count_extmarks(diagnostic_bufnr, other_ns) + - count_extmarks(other_bufnr, diagnostic_ns)) + table.insert( + result, + _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns) + + _G.count_extmarks(_G.diagnostic_bufnr, _G.other_ns) + + _G.count_extmarks(other_bufnr, _G.diagnostic_ns) + ) vim.diagnostic.enable(false, { bufnr = other_bufnr }) - table.insert(result, count_extmarks(diagnostic_bufnr, diagnostic_ns) + - count_extmarks(diagnostic_bufnr, other_ns) + - count_extmarks(other_bufnr, diagnostic_ns)) + table.insert( + result, + _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns) + + _G.count_extmarks(_G.diagnostic_bufnr, _G.other_ns) + + _G.count_extmarks(other_bufnr, _G.diagnostic_ns) + ) return result - ]] + end) eq(4, result[1]) eq(1, result[2]) @@ -615,44 +732,56 @@ describe('vim.diagnostic', function() end) it('with a namespace argument', function() - local result = exec_lua [[ - vim.api.nvim_win_set_buf(0, diagnostic_bufnr) + local result = exec_lua(function() + vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr) local result = {} vim.diagnostic.config({ underline = false, virtual_text = true }) local ns_1_diags = { - make_error("Error 1", 1, 1, 1, 5), - make_warning("Warning on Server 1", 2, 1, 2, 5), + _G.make_error('Error 1', 1, 1, 1, 5), + _G.make_warning('Warning on Server 1', 2, 1, 2, 5), } local ns_2_diags = { - make_warning("Warning 1", 2, 1, 2, 5), + _G.make_warning('Warning 1', 2, 1, 2, 5), } - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, ns_1_diags) - vim.diagnostic.set(other_ns, diagnostic_bufnr, ns_2_diags) + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, ns_1_diags) + vim.diagnostic.set(_G.other_ns, _G.diagnostic_bufnr, ns_2_diags) - table.insert(result, count_extmarks(diagnostic_bufnr, diagnostic_ns) + - count_extmarks(diagnostic_bufnr, other_ns)) + table.insert( + result, + _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns) + + _G.count_extmarks(_G.diagnostic_bufnr, _G.other_ns) + ) - vim.diagnostic.enable(false, { ns_id = diagnostic_ns }) + vim.diagnostic.enable(false, { ns_id = _G.diagnostic_ns }) - table.insert(result, count_extmarks(diagnostic_bufnr, diagnostic_ns) + - count_extmarks(diagnostic_bufnr, other_ns)) + table.insert( + result, + _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns) + + _G.count_extmarks(_G.diagnostic_bufnr, _G.other_ns) + ) - vim.diagnostic.enable(true, { ns_id = diagnostic_ns }) + vim.diagnostic.enable(true, { ns_id = _G.diagnostic_ns }) - table.insert(result, count_extmarks(diagnostic_bufnr, diagnostic_ns) + - count_extmarks(diagnostic_bufnr, other_ns)) + table.insert( + result, + _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns) + + _G.count_extmarks(_G.diagnostic_bufnr, _G.other_ns) + ) - vim.diagnostic.enable(false, { ns_id = other_ns }) + vim.diagnostic.enable(false, { ns_id = _G.other_ns }) - table.insert(result, count_extmarks(diagnostic_bufnr, diagnostic_ns) + - count_extmarks(diagnostic_bufnr, other_ns)) + table.insert( + result, + _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns) + + _G.count_extmarks(_G.diagnostic_bufnr, _G.other_ns) + ) return result - ]] + end) eq(3, result[1]) eq(1, result[2]) @@ -662,84 +791,93 @@ describe('vim.diagnostic', function() --- @return table local function test_enable(legacy) - local result = exec_lua( - [[ - local legacy = ... + return exec_lua(function() local other_bufnr = vim.api.nvim_create_buf(true, false) - vim.api.nvim_win_set_buf(0, diagnostic_bufnr) + vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr) local result = {} vim.diagnostic.config({ underline = false, virtual_text = true }) local ns_1_diags = { - make_error("Error 1", 1, 1, 1, 5), - make_warning("Warning on Server 1", 2, 1, 2, 5), + _G.make_error('Error 1', 1, 1, 1, 5), + _G.make_warning('Warning on Server 1', 2, 1, 2, 5), } local ns_2_diags = { - make_warning("Warning 1", 2, 1, 2, 5), + _G.make_warning('Warning 1', 2, 1, 2, 5), } local other_buffer_diags = { - make_info("This is interesting", 0, 0, 0, 0) + _G.make_info('This is interesting', 0, 0, 0, 0), } - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, ns_1_diags) - vim.diagnostic.set(other_ns, diagnostic_bufnr, ns_2_diags) - vim.diagnostic.set(diagnostic_ns, other_bufnr, other_buffer_diags) + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, ns_1_diags) + vim.diagnostic.set(_G.other_ns, _G.diagnostic_bufnr, ns_2_diags) + vim.diagnostic.set(_G.diagnostic_ns, other_bufnr, other_buffer_diags) - table.insert(result, count_extmarks(diagnostic_bufnr, diagnostic_ns) + - count_extmarks(diagnostic_bufnr, other_ns) + - count_extmarks(other_bufnr, diagnostic_ns)) + table.insert( + result, + _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns) + + _G.count_extmarks(_G.diagnostic_bufnr, _G.other_ns) + + _G.count_extmarks(other_bufnr, _G.diagnostic_ns) + ) if legacy then - vim.diagnostic.disable(diagnostic_bufnr, diagnostic_ns) + vim.diagnostic.disable(_G.diagnostic_bufnr, _G.diagnostic_ns) else - vim.diagnostic.enable(false, { bufnr = diagnostic_bufnr, ns_id = diagnostic_ns }) + vim.diagnostic.enable(false, { bufnr = _G.diagnostic_bufnr, ns_id = _G.diagnostic_ns }) end - table.insert(result, count_extmarks(diagnostic_bufnr, diagnostic_ns) + - count_extmarks(diagnostic_bufnr, other_ns) + - count_extmarks(other_bufnr, diagnostic_ns)) + table.insert( + result, + _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns) + + _G.count_extmarks(_G.diagnostic_bufnr, _G.other_ns) + + _G.count_extmarks(other_bufnr, _G.diagnostic_ns) + ) if legacy then - vim.diagnostic.disable(diagnostic_bufnr, other_ns) + vim.diagnostic.disable(_G.diagnostic_bufnr, _G.other_ns) else - vim.diagnostic.enable(false, { bufnr = diagnostic_bufnr, ns_id = other_ns }) + vim.diagnostic.enable(false, { bufnr = _G.diagnostic_bufnr, ns_id = _G.other_ns }) end - table.insert(result, count_extmarks(diagnostic_bufnr, diagnostic_ns) + - count_extmarks(diagnostic_bufnr, other_ns) + - count_extmarks(other_bufnr, diagnostic_ns)) + table.insert( + result, + _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns) + + _G.count_extmarks(_G.diagnostic_bufnr, _G.other_ns) + + _G.count_extmarks(other_bufnr, _G.diagnostic_ns) + ) if legacy then - vim.diagnostic.enable(diagnostic_bufnr, diagnostic_ns) + vim.diagnostic.enable(_G.diagnostic_bufnr, _G.diagnostic_ns) else - vim.diagnostic.enable(true, { bufnr = diagnostic_bufnr, ns_id = diagnostic_ns }) + vim.diagnostic.enable(true, { bufnr = _G.diagnostic_bufnr, ns_id = _G.diagnostic_ns }) end - table.insert(result, count_extmarks(diagnostic_bufnr, diagnostic_ns) + - count_extmarks(diagnostic_bufnr, other_ns) + - count_extmarks(other_bufnr, diagnostic_ns)) + table.insert( + result, + _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns) + + _G.count_extmarks(_G.diagnostic_bufnr, _G.other_ns) + + _G.count_extmarks(other_bufnr, _G.diagnostic_ns) + ) if legacy then -- Should have no effect - vim.diagnostic.disable(other_bufnr, other_ns) + vim.diagnostic.disable(other_bufnr, _G.other_ns) else -- Should have no effect - vim.diagnostic.enable(false, { bufnr = other_bufnr, ns_id = other_ns }) + vim.diagnostic.enable(false, { bufnr = other_bufnr, ns_id = _G.other_ns }) end - table.insert(result, count_extmarks(diagnostic_bufnr, diagnostic_ns) + - count_extmarks(diagnostic_bufnr, other_ns) + - count_extmarks(other_bufnr, diagnostic_ns)) + table.insert( + result, + _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns) + + _G.count_extmarks(_G.diagnostic_bufnr, _G.other_ns) + + _G.count_extmarks(other_bufnr, _G.diagnostic_ns) + ) return result - ]], - legacy - ) - - return result + end) end it('with both buffer and namespace arguments', function() @@ -772,442 +910,600 @@ describe('vim.diagnostic', function() local all_highlights = { 1, 1, 2, 4, 2 } eq( all_highlights, - exec_lua [[ - local ns_1_diags = { - make_error("Error 1", 1, 1, 1, 5), - make_warning("Warning on Server 1", 2, 1, 2, 3), - } - local ns_2_diags = { - make_warning("Warning 1", 2, 1, 2, 3), - } + exec_lua(function() + local ns_1_diags = { + _G.make_error('Error 1', 1, 1, 1, 5), + _G.make_warning('Warning on Server 1', 2, 1, 2, 3), + } + local ns_2_diags = { + _G.make_warning('Warning 1', 2, 1, 2, 3), + } - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, ns_1_diags) - vim.diagnostic.set(other_ns, diagnostic_bufnr, ns_2_diags) + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, ns_1_diags) + vim.diagnostic.set(_G.other_ns, _G.diagnostic_bufnr, ns_2_diags) - return { - count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.ERROR, diagnostic_ns), - count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.WARN, other_ns), - count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.WARN), - count_extmarks(diagnostic_bufnr, diagnostic_ns), - count_extmarks(diagnostic_bufnr, other_ns), - } - ]] + return { + _G.count_diagnostics( + _G.diagnostic_bufnr, + vim.diagnostic.severity.ERROR, + _G.diagnostic_ns + ), + _G.count_diagnostics(_G.diagnostic_bufnr, vim.diagnostic.severity.WARN, _G.other_ns), + _G.count_diagnostics(_G.diagnostic_bufnr, vim.diagnostic.severity.WARN), + _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns), + _G.count_extmarks(_G.diagnostic_bufnr, _G.other_ns), + } + end) ) -- Reset diagnostics from namespace 1 - exec_lua([[ vim.diagnostic.reset(diagnostic_ns) ]]) + exec_lua([[ vim.diagnostic.reset( _G.diagnostic_ns) ]]) -- Make sure we have the right diagnostic count eq( { 0, 1, 1, 0, 2 }, - exec_lua [[ - local diagnostic_count = {} - vim.wait(100, function () diagnostic_count = { - count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.ERROR, diagnostic_ns), - count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.WARN, other_ns), - count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.WARN), - count_extmarks(diagnostic_bufnr, diagnostic_ns), - count_extmarks(diagnostic_bufnr, other_ns), - } end ) - return diagnostic_count - ]] + exec_lua(function() + local diagnostic_count = {} + vim.wait(100, function() + diagnostic_count = { + _G.count_diagnostics( + _G.diagnostic_bufnr, + vim.diagnostic.severity.ERROR, + _G.diagnostic_ns + ), + _G.count_diagnostics(_G.diagnostic_bufnr, vim.diagnostic.severity.WARN, _G.other_ns), + _G.count_diagnostics(_G.diagnostic_bufnr, vim.diagnostic.severity.WARN), + _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns), + _G.count_extmarks(_G.diagnostic_bufnr, _G.other_ns), + } + end) + return diagnostic_count + end) ) -- Reset diagnostics from namespace 2 - exec_lua([[ vim.diagnostic.reset(other_ns) ]]) + exec_lua([[ vim.diagnostic.reset(_G.other_ns) ]]) -- Make sure we have the right diagnostic count eq( { 0, 0, 0, 0, 0 }, - exec_lua [[ - local diagnostic_count = {} - vim.wait(100, function () diagnostic_count = { - count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.ERROR, diagnostic_ns), - count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.WARN, other_ns), - count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.WARN), - count_extmarks(diagnostic_bufnr, diagnostic_ns), - count_extmarks(diagnostic_bufnr, other_ns), - } end ) - return diagnostic_count - ]] + exec_lua(function() + local diagnostic_count = {} + vim.wait(100, function() + diagnostic_count = { + _G.count_diagnostics( + _G.diagnostic_bufnr, + vim.diagnostic.severity.ERROR, + _G.diagnostic_ns + ), + _G.count_diagnostics(_G.diagnostic_bufnr, vim.diagnostic.severity.WARN, _G.other_ns), + _G.count_diagnostics(_G.diagnostic_bufnr, vim.diagnostic.severity.WARN), + _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns), + _G.count_extmarks(_G.diagnostic_bufnr, _G.other_ns), + } + end) + return diagnostic_count + end) ) end) it("doesn't error after bwipeout called on buffer", function() - exec_lua [[ - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, {{ lnum = 0, end_lnum = 0, col = 0, end_col = 0 }}) - vim.cmd("bwipeout! " .. diagnostic_bufnr) + exec_lua(function() + vim.diagnostic.set( + _G.diagnostic_ns, + _G.diagnostic_bufnr, + { { lnum = 0, end_lnum = 0, col = 0, end_col = 0 } } + ) + vim.cmd('bwipeout! ' .. _G.diagnostic_bufnr) - vim.diagnostic.reset(diagnostic_ns) - ]] + vim.diagnostic.reset(_G.diagnostic_ns) + end) end) end) - describe('get_next_pos()', function() + describe('get_next()', function() it('can find the next pos with only one namespace', function() eq( { 1, 1 }, - exec_lua [[ - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { - make_error('Diagnostic #1', 1, 1, 1, 1), - }) - vim.api.nvim_win_set_buf(0, diagnostic_bufnr) - return vim.diagnostic.get_next_pos() - ]] + exec_lua(function() + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_error('Diagnostic #1', 1, 1, 1, 1), + }) + vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr) + local next = vim.diagnostic.get_next() + return { next.lnum, next.col } + end) ) end) it('can find next pos with two errors', function() eq( { 4, 4 }, - exec_lua [[ - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { - make_error('Diagnostic #1', 1, 1, 1, 1), - make_error('Diagnostic #2', 4, 4, 4, 4), - }) - vim.api.nvim_win_set_buf(0, diagnostic_bufnr) - vim.api.nvim_win_set_cursor(0, {3, 1}) - return vim.diagnostic.get_next_pos { namespace = diagnostic_ns } - ]] + exec_lua(function() + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_error('Diagnostic #1', 1, 1, 1, 1), + _G.make_error('Diagnostic #2', 4, 4, 4, 4), + }) + vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr) + vim.api.nvim_win_set_cursor(0, { 3, 1 }) + local next = vim.diagnostic.get_next({ namespace = _G.diagnostic_ns }) + return { next.lnum, next.col } + end) ) end) it('can cycle when position is past error', function() eq( { 1, 1 }, - exec_lua [[ - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { - make_error('Diagnostic #1', 1, 1, 1, 1), - }) - vim.api.nvim_win_set_buf(0, diagnostic_bufnr) - vim.api.nvim_win_set_cursor(0, {3, 1}) - return vim.diagnostic.get_next_pos { namespace = diagnostic_ns } - ]] + exec_lua(function() + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_error('Diagnostic #1', 1, 1, 1, 1), + }) + vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr) + vim.api.nvim_win_set_cursor(0, { 3, 1 }) + local next = vim.diagnostic.get_next({ namespace = _G.diagnostic_ns }) + return { next.lnum, next.col } + end) ) end) it('will not cycle when wrap is off', function() eq( - false, - exec_lua [[ - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { - make_error('Diagnostic #1', 1, 1, 1, 1), - }) - vim.api.nvim_win_set_buf(0, diagnostic_bufnr) - vim.api.nvim_win_set_cursor(0, {3, 1}) - return vim.diagnostic.get_next_pos { namespace = diagnostic_ns, wrap = false } - ]] + nil, + exec_lua(function() + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_error('Diagnostic #1', 1, 1, 1, 1), + }) + vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr) + vim.api.nvim_win_set_cursor(0, { 3, 1 }) + local next = vim.diagnostic.get_next({ namespace = _G.diagnostic_ns, wrap = false }) + return next + end) ) end) it('can cycle even from the last line', function() eq( { 4, 4 }, - exec_lua [[ - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { - make_error('Diagnostic #2', 4, 4, 4, 4), - }) - vim.api.nvim_win_set_buf(0, diagnostic_bufnr) - vim.api.nvim_win_set_cursor(0, {vim.api.nvim_buf_line_count(0), 1}) - return vim.diagnostic.get_prev_pos { namespace = diagnostic_ns } - ]] + exec_lua(function() + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_error('Diagnostic #2', 4, 4, 4, 4), + }) + vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr) + vim.api.nvim_win_set_cursor(0, { vim.api.nvim_buf_line_count(0), 1 }) + local prev = vim.diagnostic.get_prev({ namespace = _G.diagnostic_ns }) + return { prev.lnum, prev.col } + end) ) end) it('works with diagnostics past the end of the line #16349', function() eq( { 4, 0 }, - exec_lua [[ - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { - make_error('Diagnostic #1', 3, 9001, 3, 9001), - make_error('Diagnostic #2', 4, 0, 4, 0), - }) - vim.api.nvim_win_set_buf(0, diagnostic_bufnr) - vim.api.nvim_win_set_cursor(0, {1, 1}) - vim.diagnostic.goto_next { float = false } - return vim.diagnostic.get_next_pos { namespace = diagnostic_ns } - ]] + exec_lua(function() + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_error('Diagnostic #1', 3, 9001, 3, 9001), + _G.make_error('Diagnostic #2', 4, 0, 4, 0), + }) + vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr) + vim.api.nvim_win_set_cursor(0, { 1, 1 }) + vim.diagnostic.jump({ count = 1, float = false }) + local next = vim.diagnostic.get_next({ namespace = _G.diagnostic_ns }) + return { next.lnum, next.col } + end) ) end) it('works with diagnostics before the start of the line', function() eq( { 4, 0 }, - exec_lua [[ - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { - make_error('Diagnostic #1', 3, 9001, 3, 9001), - make_error('Diagnostic #2', 4, -1, 4, -1), + exec_lua(function() + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_error('Diagnostic #1', 3, 9001, 3, 9001), + _G.make_error('Diagnostic #2', 4, -1, 4, -1), }) - vim.api.nvim_win_set_buf(0, diagnostic_bufnr) - vim.api.nvim_win_set_cursor(0, {1, 1}) - vim.diagnostic.goto_next { float = false } - return vim.diagnostic.get_next_pos { namespace = diagnostic_ns } - ]] + vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr) + vim.api.nvim_win_set_cursor(0, { 1, 1 }) + vim.diagnostic.jump({ count = 1, float = false }) + local next = vim.diagnostic.get_next({ namespace = _G.diagnostic_ns }) + return { next.lnum, next.col } + end) ) end) it('jumps to diagnostic with highest severity', function() - exec_lua([[ - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { - make_info('Info', 1, 0, 1, 1), - make_error('Error', 2, 0, 2, 1), - make_warning('Warning', 3, 0, 3, 1), - make_error('Error', 4, 0, 4, 1), + exec_lua(function() + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_info('Info', 1, 0, 1, 1), + _G.make_error('Error', 2, 0, 2, 1), + _G.make_warning('Warning', 3, 0, 3, 1), + _G.make_error('Error', 4, 0, 4, 1), }) - vim.api.nvim_win_set_buf(0, diagnostic_bufnr) - vim.api.nvim_win_set_cursor(0, {1, 0}) - ]]) + vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr) + vim.api.nvim_win_set_cursor(0, { 1, 0 }) + end) eq( { 3, 0 }, - exec_lua([[ - vim.diagnostic.goto_next({_highest = true}) - return vim.api.nvim_win_get_cursor(0) - ]]) + exec_lua(function() + vim.diagnostic.jump({ count = 1, _highest = true }) + return vim.api.nvim_win_get_cursor(0) + end) ) eq( { 5, 0 }, - exec_lua([[ - vim.diagnostic.goto_next({_highest = true}) - return vim.api.nvim_win_get_cursor(0) - ]]) - ) - - exec_lua([[ - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { - make_info('Info', 1, 0, 1, 1), - make_hint('Hint', 2, 0, 2, 1), - make_warning('Warning', 3, 0, 3, 1), - make_hint('Hint', 4, 0, 4, 1), - make_warning('Warning', 5, 0, 5, 1), + exec_lua(function() + vim.diagnostic.jump({ count = 1, _highest = true }) + return vim.api.nvim_win_get_cursor(0) + end) + ) + + exec_lua(function() + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_info('Info', 1, 0, 1, 1), + _G.make_hint('Hint', 2, 0, 2, 1), + _G.make_warning('Warning', 3, 0, 3, 1), + _G.make_hint('Hint', 4, 0, 4, 1), + _G.make_warning('Warning', 5, 0, 5, 1), }) - vim.api.nvim_win_set_buf(0, diagnostic_bufnr) - vim.api.nvim_win_set_cursor(0, {1, 0}) - ]]) + vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr) + vim.api.nvim_win_set_cursor(0, { 1, 0 }) + end) eq( { 4, 0 }, - exec_lua([[ - vim.diagnostic.goto_next({_highest = true}) - return vim.api.nvim_win_get_cursor(0) - ]]) + exec_lua(function() + vim.diagnostic.jump({ count = 1, _highest = true }) + return vim.api.nvim_win_get_cursor(0) + end) ) eq( { 6, 0 }, - exec_lua([[ - vim.diagnostic.goto_next({_highest = true}) - return vim.api.nvim_win_get_cursor(0) - ]]) + exec_lua(function() + vim.diagnostic.jump({ count = 1, _highest = true }) + return vim.api.nvim_win_get_cursor(0) + end) ) end) it('jumps to next diagnostic if severity is non-nil', function() - exec_lua([[ - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { - make_info('Info', 1, 0, 1, 1), - make_error('Error', 2, 0, 2, 1), - make_warning('Warning', 3, 0, 3, 1), - make_error('Error', 4, 0, 4, 1), + exec_lua(function() + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_info('Info', 1, 0, 1, 1), + _G.make_error('Error', 2, 0, 2, 1), + _G.make_warning('Warning', 3, 0, 3, 1), + _G.make_error('Error', 4, 0, 4, 1), }) - vim.api.nvim_win_set_buf(0, diagnostic_bufnr) - vim.api.nvim_win_set_cursor(0, {1, 0}) - ]]) + vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr) + vim.api.nvim_win_set_cursor(0, { 1, 0 }) + end) eq( { 2, 0 }, - exec_lua([[ - vim.diagnostic.goto_next() - return vim.api.nvim_win_get_cursor(0) - ]]) + exec_lua(function() + vim.diagnostic.jump({ count = 1 }) + return vim.api.nvim_win_get_cursor(0) + end) ) eq( { 3, 0 }, - exec_lua([[ - vim.diagnostic.goto_next() - return vim.api.nvim_win_get_cursor(0) - ]]) + exec_lua(function() + vim.diagnostic.jump({ count = 1 }) + return vim.api.nvim_win_get_cursor(0) + end) ) eq( { 4, 0 }, - exec_lua([[ - vim.diagnostic.goto_next() - return vim.api.nvim_win_get_cursor(0) - ]]) + exec_lua(function() + vim.diagnostic.jump({ count = 1 }) + return vim.api.nvim_win_get_cursor(0) + end) ) end) end) - describe('get_prev_pos()', function() - it('can find the prev pos with only one namespace', function() + describe('get_prev()', function() + it('can find the previous diagnostic with only one namespace', function() eq( { 1, 1 }, - exec_lua [[ - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { - make_error('Diagnostic #1', 1, 1, 1, 1), - }) - vim.api.nvim_win_set_buf(0, diagnostic_bufnr) - vim.api.nvim_win_set_cursor(0, {3, 1}) - return vim.diagnostic.get_prev_pos() - ]] + exec_lua(function() + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_error('Diagnostic #1', 1, 1, 1, 1), + }) + vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr) + vim.api.nvim_win_set_cursor(0, { 3, 1 }) + local prev = vim.diagnostic.get_prev() + return { prev.lnum, prev.col } + end) ) end) - it('can find prev pos with two errors', function() + it('can find the previous diagnostic with two errors', function() eq( { 1, 1 }, - exec_lua [[ - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { - make_error('Diagnostic #1', 1, 1, 1, 1), - make_error('Diagnostic #2', 4, 4, 4, 4), - }) - vim.api.nvim_win_set_buf(0, diagnostic_bufnr) - vim.api.nvim_win_set_cursor(0, {3, 1}) - return vim.diagnostic.get_prev_pos { namespace = diagnostic_ns } - ]] + exec_lua(function() + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_error('Diagnostic #1', 1, 1, 1, 1), + _G.make_error('Diagnostic #2', 4, 4, 4, 4), + }) + vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr) + vim.api.nvim_win_set_cursor(0, { 3, 1 }) + local prev = vim.diagnostic.get_prev({ namespace = _G.diagnostic_ns }) + return { prev.lnum, prev.col } + end) ) end) it('can cycle when position is past error', function() eq( { 4, 4 }, - exec_lua [[ - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { - make_error('Diagnostic #2', 4, 4, 4, 4), - }) - vim.api.nvim_win_set_buf(0, diagnostic_bufnr) - vim.api.nvim_win_set_cursor(0, {3, 1}) - return vim.diagnostic.get_prev_pos { namespace = diagnostic_ns } - ]] + exec_lua(function() + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_error('Diagnostic #2', 4, 4, 4, 4), + }) + vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr) + vim.api.nvim_win_set_cursor(0, { 3, 1 }) + local prev = vim.diagnostic.get_prev({ namespace = _G.diagnostic_ns }) + return { prev.lnum, prev.col } + end) ) end) it('respects wrap parameter', function() eq( - false, - exec_lua [[ - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { - make_error('Diagnostic #2', 4, 4, 4, 4), - }) - vim.api.nvim_win_set_buf(0, diagnostic_bufnr) - vim.api.nvim_win_set_cursor(0, {3, 1}) - return vim.diagnostic.get_prev_pos { namespace = diagnostic_ns, wrap = false} - ]] + nil, + exec_lua(function() + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_error('Diagnostic #2', 4, 4, 4, 4), + }) + vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr) + vim.api.nvim_win_set_cursor(0, { 3, 1 }) + local prev = vim.diagnostic.get_prev({ namespace = _G.diagnostic_ns, wrap = false }) + return prev + end) ) end) it('works on blank line #28397', function() eq( { 0, 2 }, - exec_lua [[ - local test_bufnr = vim.api.nvim_create_buf(true, false) - vim.api.nvim_buf_set_lines(test_bufnr, 0, -1, false, { - 'first line', - '', - '', - 'end line', - }) - vim.diagnostic.set(diagnostic_ns, test_bufnr, { - make_info('Diagnostic #1', 0, 2, 0, 2), - make_info('Diagnostic #2', 2, 0, 2, 0), - make_info('Diagnostic #3', 2, 0, 2, 0), + exec_lua(function() + local test_bufnr = vim.api.nvim_create_buf(true, false) + vim.api.nvim_buf_set_lines(test_bufnr, 0, -1, false, { + 'first line', + '', + '', + 'end line', + }) + vim.diagnostic.set(_G.diagnostic_ns, test_bufnr, { + _G.make_info('Diagnostic #1', 0, 2, 0, 2), + _G.make_info('Diagnostic #2', 2, 0, 2, 0), + _G.make_info('Diagnostic #3', 2, 0, 2, 0), + }) + vim.api.nvim_win_set_buf(0, test_bufnr) + vim.api.nvim_win_set_cursor(0, { 3, 0 }) + return vim.diagnostic.get_prev_pos { namespace = _G.diagnostic_ns } + end) + ) + end) + end) + + describe('jump()', function() + before_each(function() + exec_lua(function() + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_error('Diagnostic #1', 0, 0, 0, 2), + _G.make_error('Diagnostic #2', 1, 1, 1, 4), + _G.make_warning('Diagnostic #3', 2, -1, 2, -1), + _G.make_info('Diagnostic #4', 3, 0, 3, 3), }) - vim.api.nvim_win_set_buf(0, test_bufnr) - vim.api.nvim_win_set_cursor(0, {3, 0}) - return vim.diagnostic.get_prev_pos { namespace = diagnostic_ns} - ]] + vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr) + end) + end) + + it('can move forward', function() + eq( + { 2, 1 }, + exec_lua(function() + vim.api.nvim_win_set_cursor(0, { 1, 0 }) + vim.diagnostic.jump({ count = 1 }) + return vim.api.nvim_win_get_cursor(0) + end) + ) + + eq( + { 4, 0 }, + exec_lua(function() + vim.api.nvim_win_set_cursor(0, { 1, 0 }) + vim.diagnostic.jump({ count = 3 }) + return vim.api.nvim_win_get_cursor(0) + end) + ) + + eq( + { 4, 0 }, + exec_lua(function() + vim.api.nvim_win_set_cursor(0, { 1, 0 }) + vim.diagnostic.jump({ count = math.huge, wrap = false }) + return vim.api.nvim_win_get_cursor(0) + end) + ) + end) + + it('can move backward', function() + eq( + { 3, 0 }, + exec_lua(function() + vim.api.nvim_win_set_cursor(0, { 4, 0 }) + vim.diagnostic.jump({ count = -1 }) + return vim.api.nvim_win_get_cursor(0) + end) + ) + + eq( + { 1, 0 }, + exec_lua(function() + vim.api.nvim_win_set_cursor(0, { 4, 0 }) + vim.diagnostic.jump({ count = -3 }) + return vim.api.nvim_win_get_cursor(0) + end) + ) + + eq( + { 1, 0 }, + exec_lua(function() + vim.api.nvim_win_set_cursor(0, { 4, 0 }) + vim.diagnostic.jump({ count = -math.huge, wrap = false }) + return vim.api.nvim_win_get_cursor(0) + end) + ) + end) + + it('can filter by severity', function() + eq( + { 3, 0 }, + exec_lua(function() + vim.api.nvim_win_set_cursor(0, { 1, 0 }) + vim.diagnostic.jump({ count = 1, severity = vim.diagnostic.severity.WARN }) + return vim.api.nvim_win_get_cursor(0) + end) + ) + + eq( + { 3, 0 }, + exec_lua(function() + vim.api.nvim_win_set_cursor(0, { 1, 0 }) + vim.diagnostic.jump({ count = 9999, severity = vim.diagnostic.severity.WARN }) + return vim.api.nvim_win_get_cursor(0) + end) + ) + end) + + it('can wrap', function() + eq( + { 1, 0 }, + exec_lua(function() + vim.api.nvim_win_set_cursor(0, { 4, 0 }) + vim.diagnostic.jump({ count = 1, wrap = true }) + return vim.api.nvim_win_get_cursor(0) + end) + ) + + eq( + { 4, 0 }, + exec_lua(function() + vim.api.nvim_win_set_cursor(0, { 1, 0 }) + vim.diagnostic.jump({ count = -1, wrap = true }) + return vim.api.nvim_win_get_cursor(0) + end) ) end) end) describe('get()', function() it('returns an empty table when no diagnostics are present', function() - eq({}, exec_lua [[return vim.diagnostic.get(diagnostic_bufnr, {namespace=diagnostic_ns})]]) + eq( + {}, + exec_lua [[return vim.diagnostic.get( _G.diagnostic_bufnr, {namespace=diagnostic_ns})]] + ) end) it('returns all diagnostics when no severity is supplied', function() eq( 2, - exec_lua [[ - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { - make_error("Error 1", 1, 1, 1, 5), - make_warning("Warning on Server 1", 1, 1, 2, 3), - }) + exec_lua(function() + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_error('Error 1', 1, 1, 1, 5), + _G.make_warning('Warning on Server 1', 1, 1, 2, 3), + }) - return #vim.diagnostic.get(diagnostic_bufnr) - ]] + return #vim.diagnostic.get(_G.diagnostic_bufnr) + end) ) end) it('returns only requested diagnostics when severity range is supplied', function() eq( { 2, 3, 2 }, - exec_lua [[ - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { - make_error("Error 1", 1, 1, 1, 5), - make_warning("Warning on Server 1", 1, 1, 2, 3), - make_info("Ignored information", 1, 1, 2, 3), - make_hint("Here's a hint", 1, 1, 2, 3), - }) + exec_lua(function() + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_error('Error 1', 1, 1, 1, 5), + _G.make_warning('Warning on Server 1', 1, 1, 2, 3), + _G.make_info('Ignored information', 1, 1, 2, 3), + _G.make_hint("Here's a hint", 1, 1, 2, 3), + }) - return { - #vim.diagnostic.get(diagnostic_bufnr, { severity = {min=vim.diagnostic.severity.WARN} }), - #vim.diagnostic.get(diagnostic_bufnr, { severity = {max=vim.diagnostic.severity.WARN} }), - #vim.diagnostic.get(diagnostic_bufnr, { - severity = { - min=vim.diagnostic.severity.INFO, - max=vim.diagnostic.severity.WARN, - } - }), - } - ]] + return { + #vim.diagnostic.get( + _G.diagnostic_bufnr, + { severity = { min = vim.diagnostic.severity.WARN } } + ), + #vim.diagnostic.get( + _G.diagnostic_bufnr, + { severity = { max = vim.diagnostic.severity.WARN } } + ), + #vim.diagnostic.get(_G.diagnostic_bufnr, { + severity = { + min = vim.diagnostic.severity.INFO, + max = vim.diagnostic.severity.WARN, + }, + }), + } + end) ) end) it('returns only requested diagnostics when severities are supplied', function() eq( { 1, 1, 2 }, - exec_lua [[ - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { - make_error("Error 1", 1, 1, 1, 5), - make_warning("Warning on Server 1", 1, 1, 2, 3), - make_info("Ignored information", 1, 1, 2, 3), - make_hint("Here's a hint", 1, 1, 2, 3), - }) + exec_lua(function() + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_error('Error 1', 1, 1, 1, 5), + _G.make_warning('Warning on Server 1', 1, 1, 2, 3), + _G.make_info('Ignored information', 1, 1, 2, 3), + _G.make_hint("Here's a hint", 1, 1, 2, 3), + }) - return { - #vim.diagnostic.get(diagnostic_bufnr, { severity = {vim.diagnostic.severity.WARN} }), - #vim.diagnostic.get(diagnostic_bufnr, { severity = {vim.diagnostic.severity.ERROR} }), - #vim.diagnostic.get(diagnostic_bufnr, { - severity = { - vim.diagnostic.severity.INFO, - vim.diagnostic.severity.WARN, - } - }), - } - ]] + return { + #vim.diagnostic.get( + _G.diagnostic_bufnr, + { severity = { vim.diagnostic.severity.WARN } } + ), + #vim.diagnostic.get( + _G.diagnostic_bufnr, + { severity = { vim.diagnostic.severity.ERROR } } + ), + #vim.diagnostic.get(_G.diagnostic_bufnr, { + severity = { + vim.diagnostic.severity.INFO, + vim.diagnostic.severity.WARN, + }, + }), + } + end) ) end) it('allows filtering by line', function() eq( 2, - exec_lua [[ - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { - make_error("Error 1", 1, 1, 1, 5), - make_warning("Warning on Server 1", 1, 1, 2, 3), - make_info("Ignored information", 1, 1, 2, 3), - make_error("Error On Other Line", 3, 1, 3, 5), - }) + exec_lua(function() + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_error('Error 1', 1, 1, 1, 5), + _G.make_warning('Warning on Server 1', 1, 1, 2, 3), + _G.make_info('Ignored information', 1, 1, 2, 3), + _G.make_error('Error On Other Line', 3, 1, 3, 5), + }) - return #vim.diagnostic.get(diagnostic_bufnr, {lnum = 2}) - ]] + return #vim.diagnostic.get(_G.diagnostic_bufnr, { lnum = 2 }) + end) ) end) end) @@ -1215,118 +1511,144 @@ describe('vim.diagnostic', function() describe('count', function() it('returns actually present severity counts', function() eq( - exec_lua [[return { - [vim.diagnostic.severity.ERROR] = 4, - [vim.diagnostic.severity.WARN] = 3, - [vim.diagnostic.severity.INFO] = 2, - [vim.diagnostic.severity.HINT] = 1, - }]], - exec_lua [[ - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { - make_error("Error 1", 1, 1, 1, 2), - make_error("Error 2", 1, 3, 1, 4), - make_error("Error 3", 1, 5, 1, 6), - make_error("Error 4", 1, 7, 1, 8), - make_warning("Warning 1", 2, 1, 2, 2), - make_warning("Warning 2", 2, 3, 2, 4), - make_warning("Warning 3", 2, 5, 2, 6), - make_info("Info 1", 3, 1, 3, 2), - make_info("Info 2", 3, 3, 3, 4), - make_hint("Hint 1", 4, 1, 4, 2), + exec_lua(function() + return { + [vim.diagnostic.severity.ERROR] = 4, + [vim.diagnostic.severity.WARN] = 3, + [vim.diagnostic.severity.INFO] = 2, + [vim.diagnostic.severity.HINT] = 1, + } + end), + exec_lua(function() + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_error('Error 1', 1, 1, 1, 2), + _G.make_error('Error 2', 1, 3, 1, 4), + _G.make_error('Error 3', 1, 5, 1, 6), + _G.make_error('Error 4', 1, 7, 1, 8), + _G.make_warning('Warning 1', 2, 1, 2, 2), + _G.make_warning('Warning 2', 2, 3, 2, 4), + _G.make_warning('Warning 3', 2, 5, 2, 6), + _G.make_info('Info 1', 3, 1, 3, 2), + _G.make_info('Info 2', 3, 3, 3, 4), + _G.make_hint('Hint 1', 4, 1, 4, 2), }) - return vim.diagnostic.count(diagnostic_bufnr) - ]] - ) - eq( - exec_lua [[return { - [vim.diagnostic.severity.ERROR] = 2, - [vim.diagnostic.severity.INFO] = 1, - }]], - exec_lua [[ - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { - make_error("Error 1", 1, 1, 1, 2), - make_error("Error 2", 1, 3, 1, 4), - make_info("Info 1", 3, 1, 3, 2), + return vim.diagnostic.count(_G.diagnostic_bufnr) + end) + ) + eq( + exec_lua(function() + return { + [vim.diagnostic.severity.ERROR] = 2, + [vim.diagnostic.severity.INFO] = 1, + } + end), + exec_lua(function() + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_error('Error 1', 1, 1, 1, 2), + _G.make_error('Error 2', 1, 3, 1, 4), + _G.make_info('Info 1', 3, 1, 3, 2), }) - return vim.diagnostic.count(diagnostic_bufnr) - ]] + return vim.diagnostic.count(_G.diagnostic_bufnr) + end) ) end) it('returns only requested diagnostics count when severity range is supplied', function() eq( - exec_lua [[return { - { [vim.diagnostic.severity.ERROR] = 1, [vim.diagnostic.severity.WARN] = 1 }, - { [vim.diagnostic.severity.WARN] = 1, [vim.diagnostic.severity.INFO] = 1, [vim.diagnostic.severity.HINT] = 1 }, - { [vim.diagnostic.severity.WARN] = 1, [vim.diagnostic.severity.INFO] = 1 }, - }]], - exec_lua [[ - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { - make_error("Error 1", 1, 1, 1, 5), - make_warning("Warning on Server 1", 1, 1, 2, 3), - make_info("Ignored information", 1, 1, 2, 3), - make_hint("Here's a hint", 1, 1, 2, 3), + exec_lua(function() + return { + { [vim.diagnostic.severity.ERROR] = 1, [vim.diagnostic.severity.WARN] = 1 }, + { + [vim.diagnostic.severity.WARN] = 1, + [vim.diagnostic.severity.INFO] = 1, + [vim.diagnostic.severity.HINT] = 1, + }, + { [vim.diagnostic.severity.WARN] = 1, [vim.diagnostic.severity.INFO] = 1 }, + } + end), + exec_lua(function() + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_error('Error 1', 1, 1, 1, 5), + _G.make_warning('Warning on Server 1', 1, 1, 2, 3), + _G.make_info('Ignored information', 1, 1, 2, 3), + _G.make_hint("Here's a hint", 1, 1, 2, 3), }) return { - vim.diagnostic.count(diagnostic_bufnr, { severity = {min=vim.diagnostic.severity.WARN} }), - vim.diagnostic.count(diagnostic_bufnr, { severity = {max=vim.diagnostic.severity.WARN} }), - vim.diagnostic.count(diagnostic_bufnr, { + vim.diagnostic.count( + _G.diagnostic_bufnr, + { severity = { min = vim.diagnostic.severity.WARN } } + ), + vim.diagnostic.count( + _G.diagnostic_bufnr, + { severity = { max = vim.diagnostic.severity.WARN } } + ), + vim.diagnostic.count(_G.diagnostic_bufnr, { severity = { - min=vim.diagnostic.severity.INFO, - max=vim.diagnostic.severity.WARN, - } + min = vim.diagnostic.severity.INFO, + max = vim.diagnostic.severity.WARN, + }, }), } - ]] + end) ) end) it('returns only requested diagnostics when severities are supplied', function() eq( - exec_lua [[return { - { [vim.diagnostic.severity.WARN] = 1 }, - { [vim.diagnostic.severity.ERROR] = 1 }, - { [vim.diagnostic.severity.WARN] = 1, [vim.diagnostic.severity.INFO] = 1 }, - }]], - exec_lua [[ - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { - make_error("Error 1", 1, 1, 1, 5), - make_warning("Warning on Server 1", 1, 1, 2, 3), - make_info("Ignored information", 1, 1, 2, 3), - make_hint("Here's a hint", 1, 1, 2, 3), + exec_lua(function() + return { + { [vim.diagnostic.severity.WARN] = 1 }, + { [vim.diagnostic.severity.ERROR] = 1 }, + { [vim.diagnostic.severity.WARN] = 1, [vim.diagnostic.severity.INFO] = 1 }, + } + end), + exec_lua(function() + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_error('Error 1', 1, 1, 1, 5), + _G.make_warning('Warning on Server 1', 1, 1, 2, 3), + _G.make_info('Ignored information', 1, 1, 2, 3), + _G.make_hint("Here's a hint", 1, 1, 2, 3), }) return { - vim.diagnostic.count(diagnostic_bufnr, { severity = {vim.diagnostic.severity.WARN} }), - vim.diagnostic.count(diagnostic_bufnr, { severity = {vim.diagnostic.severity.ERROR} }), - vim.diagnostic.count(diagnostic_bufnr, { + vim.diagnostic.count( + _G.diagnostic_bufnr, + { severity = { vim.diagnostic.severity.WARN } } + ), + vim.diagnostic.count( + _G.diagnostic_bufnr, + { severity = { vim.diagnostic.severity.ERROR } } + ), + vim.diagnostic.count(_G.diagnostic_bufnr, { severity = { vim.diagnostic.severity.INFO, vim.diagnostic.severity.WARN, - } + }, }), } - ]] + end) ) end) it('allows filtering by line', function() eq( - exec_lua [[return { - [vim.diagnostic.severity.WARN] = 1, - [vim.diagnostic.severity.INFO] = 1, - }]], - exec_lua [[ - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { - make_error("Error 1", 1, 1, 1, 5), - make_warning("Warning on Server 1", 1, 1, 2, 3), - make_info("Ignored information", 1, 1, 2, 3), - make_error("Error On Other Line", 3, 1, 3, 5), + exec_lua(function() + return { + [vim.diagnostic.severity.WARN] = 1, + [vim.diagnostic.severity.INFO] = 1, + } + end), + exec_lua(function() + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_error('Error 1', 1, 1, 1, 5), + _G.make_warning('Warning on Server 1', 1, 1, 2, 3), + _G.make_info('Ignored information', 1, 1, 2, 3), + _G.make_error('Error On Other Line', 3, 1, 3, 5), }) - return vim.diagnostic.count(diagnostic_bufnr, {lnum = 2}) - ]] + return vim.diagnostic.count(_G.diagnostic_bufnr, { lnum = 2 }) + end) ) end) end) @@ -1335,137 +1657,138 @@ describe('vim.diagnostic', function() it('works with global, namespace, and ephemeral options', function() eq( 1, - exec_lua [[ - vim.diagnostic.config({ - virtual_text = false, - }) + exec_lua(function() + vim.diagnostic.config({ + virtual_text = false, + }) - vim.diagnostic.config({ - virtual_text = true, - underline = false, - }, diagnostic_ns) + vim.diagnostic.config({ + virtual_text = true, + underline = false, + }, _G.diagnostic_ns) - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { - make_error('Some Error', 4, 4, 4, 4), - }) + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_error('Some Error', 4, 4, 4, 4), + }) - return count_extmarks(diagnostic_bufnr, diagnostic_ns) - ]] + return _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns) + end) ) eq( 1, - exec_lua [[ - vim.diagnostic.config({ - virtual_text = false, - }) + exec_lua(function() + vim.diagnostic.config({ + virtual_text = false, + }) - vim.diagnostic.config({ - virtual_text = false, - underline = false, - }, diagnostic_ns) + vim.diagnostic.config({ + virtual_text = false, + underline = false, + }, _G.diagnostic_ns) - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { - make_error('Some Error', 4, 4, 4, 4), - }, {virtual_text = true}) + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_error('Some Error', 4, 4, 4, 4), + }, { virtual_text = true }) - return count_extmarks(diagnostic_bufnr, diagnostic_ns) - ]] + return _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns) + end) ) eq( 0, - exec_lua [[ - vim.diagnostic.config({ - virtual_text = false, - }) + exec_lua(function() + vim.diagnostic.config({ + virtual_text = false, + }) - vim.diagnostic.config({ - virtual_text = {severity=vim.diagnostic.severity.ERROR}, - underline = false, - }, diagnostic_ns) + vim.diagnostic.config({ + virtual_text = { severity = vim.diagnostic.severity.ERROR }, + underline = false, + }, _G.diagnostic_ns) - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { - make_warning('Some Warning', 4, 4, 4, 4), - }, {virtual_text = true}) + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_warning('Some Warning', 4, 4, 4, 4), + }, { virtual_text = true }) - return count_extmarks(diagnostic_bufnr, diagnostic_ns) - ]] + return _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns) + end) ) eq( 1, - exec_lua [[ - vim.diagnostic.config({ - virtual_text = false, - }) + exec_lua(function() + vim.diagnostic.config({ + virtual_text = false, + }) - vim.diagnostic.config({ - virtual_text = {severity=vim.diagnostic.severity.ERROR}, - underline = false, - }, diagnostic_ns) + vim.diagnostic.config({ + virtual_text = { severity = vim.diagnostic.severity.ERROR }, + underline = false, + }, _G.diagnostic_ns) - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { - make_warning('Some Warning', 4, 4, 4, 4), - }, { - virtual_text = {} -- An empty table uses default values - }) + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_warning('Some Warning', 4, 4, 4, 4), + }, { + virtual_text = {}, -- An empty table uses default values + }) - return count_extmarks(diagnostic_bufnr, diagnostic_ns) - ]] + return _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns) + end) ) end) it('can use functions for config values', function() - exec_lua [[ + exec_lua(function() vim.diagnostic.config({ - virtual_text = function() return true end, - }, diagnostic_ns) - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { - make_error('Delayed Diagnostic', 4, 4, 4, 4), + virtual_text = function() + return true + end, + }, _G.diagnostic_ns) + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_error('Delayed Diagnostic', 4, 4, 4, 4), }) - ]] + end) eq( 1, - exec_lua [[return count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.ERROR, diagnostic_ns)]] + exec_lua [[return _G.count_diagnostics( _G.diagnostic_bufnr, vim.diagnostic.severity.ERROR, _G.diagnostic_ns)]] ) - eq(2, exec_lua [[return count_extmarks(diagnostic_bufnr, diagnostic_ns)]]) + eq(2, exec_lua [[return _G.count_extmarks( _G.diagnostic_bufnr, _G.diagnostic_ns)]]) -- Now, don't enable virtual text. -- We should have one less extmark displayed. - exec_lua [[ + exec_lua(function() vim.diagnostic.config({ - virtual_text = function() return false end, - }, diagnostic_ns) - ]] + virtual_text = function() + return false + end, + }, _G.diagnostic_ns) + end) eq( 1, - exec_lua [[return count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.ERROR, diagnostic_ns)]] + exec_lua [[return _G.count_diagnostics( _G.diagnostic_bufnr, vim.diagnostic.severity.ERROR, _G.diagnostic_ns)]] ) - eq(1, exec_lua [[return count_extmarks(diagnostic_bufnr, diagnostic_ns)]]) + eq(1, exec_lua [[return _G.count_extmarks( _G.diagnostic_bufnr, _G.diagnostic_ns)]]) end) it('allows filtering by severity', function() local get_extmark_count_with_severity = function(min_severity) - return exec_lua( - [[ + return exec_lua(function() vim.diagnostic.config({ underline = false, virtual_text = { - severity = {min=...}, + severity = { min = min_severity }, }, }) - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { - make_warning('Delayed Diagnostic', 4, 4, 4, 4), + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_warning('Delayed Diagnostic', 4, 4, 4, 4), }) - return count_extmarks(diagnostic_bufnr, diagnostic_ns) - ]], - min_severity - ) + return _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns) + end) end -- No messages with Error or higher @@ -1477,152 +1800,158 @@ describe('vim.diagnostic', function() end) it('allows sorting by severity', function() - exec_lua [[ + exec_lua(function() vim.diagnostic.config({ underline = false, signs = true, virtual_text = true, }) - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { - make_warning('Warning', 4, 4, 4, 4), - make_error('Error', 4, 4, 4, 4), - make_info('Info', 4, 4, 4, 4), + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_warning('Warning', 4, 4, 4, 4), + _G.make_error('Error', 4, 4, 4, 4), + _G.make_info('Info', 4, 4, 4, 4), }) - function get_virt_text_and_signs(severity_sort) + function _G.get_virt_text_and_signs(severity_sort) vim.diagnostic.config({ severity_sort = severity_sort, }) - local virt_text = get_virt_text_extmarks(diagnostic_ns)[1][4].virt_text + local virt_text = _G.get_virt_text_extmarks(_G.diagnostic_ns)[1][4].virt_text local virt_texts = {} for i = 2, #virt_text - 1 do - table.insert(virt_texts, (string.gsub(virt_text[i][2], "DiagnosticVirtualText", ""))) + table.insert(virt_texts, (string.gsub(virt_text[i][2], 'DiagnosticVirtualText', ''))) end - local ns = vim.diagnostic.get_namespace(diagnostic_ns) + local ns = vim.diagnostic.get_namespace(_G.diagnostic_ns) local sign_ns = ns.user_data.sign_ns local signs = {} - local all_signs = vim.api.nvim_buf_get_extmarks(diagnostic_bufnr, sign_ns, 0, -1, {type = 'sign', details = true}) + local all_signs = vim.api.nvim_buf_get_extmarks( + _G.diagnostic_bufnr, + sign_ns, + 0, + -1, + { type = 'sign', details = true } + ) table.sort(all_signs, function(a, b) return a[1] > b[1] end) for _, v in ipairs(all_signs) do - local s = v[4].sign_hl_group:gsub('DiagnosticSign', "") + local s = v[4].sign_hl_group:gsub('DiagnosticSign', '') if not vim.tbl_contains(signs, s) then signs[#signs + 1] = s end end - return {virt_texts, signs} + return { virt_texts, signs } end - ]] + end) - local result = exec_lua [[return get_virt_text_and_signs(false)]] + local result = exec_lua [[return _G.get_virt_text_and_signs(false)]] -- Virt texts are defined lowest priority to highest, signs from -- highest to lowest eq({ 'Warn', 'Error', 'Info' }, result[1]) eq({ 'Info', 'Error', 'Warn' }, result[2]) - result = exec_lua [[return get_virt_text_and_signs(true)]] + result = exec_lua [[return _G.get_virt_text_and_signs(true)]] eq({ 'Info', 'Warn', 'Error' }, result[1]) eq({ 'Error', 'Warn', 'Info' }, result[2]) - result = exec_lua [[return get_virt_text_and_signs({ reverse = true })]] + result = exec_lua [[return _G.get_virt_text_and_signs({ reverse = true })]] eq({ 'Error', 'Warn', 'Info' }, result[1]) eq({ 'Info', 'Warn', 'Error' }, result[2]) end) it('can show diagnostic sources in virtual text', function() - local result = exec_lua [[ + local result = exec_lua(function() local diagnostics = { - make_error('Some error', 0, 0, 0, 0, 'source x'), + _G.make_error('Some error', 0, 0, 0, 0, 'source x'), } - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics, { + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, diagnostics, { underline = false, virtual_text = { prefix = '', source = 'always', - } + }, }) - local extmarks = get_virt_text_extmarks(diagnostic_ns) + local extmarks = _G.get_virt_text_extmarks(_G.diagnostic_ns) local virt_text = extmarks[1][4].virt_text[3][1] return virt_text - ]] + end) eq(' source x: Some error', result) - result = exec_lua [[ + result = exec_lua(function() vim.diagnostic.config({ underline = false, virtual_text = { prefix = '', source = 'if_many', - } - }, diagnostic_ns) + }, + }, _G.diagnostic_ns) - local extmarks = get_virt_text_extmarks(diagnostic_ns) + local extmarks = _G.get_virt_text_extmarks(_G.diagnostic_ns) local virt_text = extmarks[1][4].virt_text[3][1] return virt_text - ]] + end) eq(' Some error', result) - result = exec_lua [[ + result = exec_lua(function() local diagnostics = { - make_error('Some error', 0, 0, 0, 0, 'source x'), - make_error('Another error', 1, 1, 1, 1, 'source y'), + _G.make_error('Some error', 0, 0, 0, 0, 'source x'), + _G.make_error('Another error', 1, 1, 1, 1, 'source y'), } - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics, { + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, diagnostics, { underline = false, virtual_text = { prefix = '', source = 'if_many', - } + }, }) - local extmarks = get_virt_text_extmarks(diagnostic_ns) - local virt_text = {extmarks[1][4].virt_text[3][1], extmarks[2][4].virt_text[3][1]} + local extmarks = _G.get_virt_text_extmarks(_G.diagnostic_ns) + local virt_text = { extmarks[1][4].virt_text[3][1], extmarks[2][4].virt_text[3][1] } return virt_text - ]] + end) eq(' source x: Some error', result[1]) eq(' source y: Another error', result[2]) end) it('supports a format function for diagnostic messages', function() - local result = exec_lua [[ + local result = exec_lua(function() vim.diagnostic.config({ underline = false, virtual_text = { prefix = '', format = function(diagnostic) if diagnostic.severity == vim.diagnostic.severity.ERROR then - return string.format("🔥 %s", diagnostic.message) + return string.format('🔥 %s', diagnostic.message) end - return string.format("👀 %s", diagnostic.message) + return string.format('👀 %s', diagnostic.message) end, - } + }, }) - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { - make_warning('Warning', 0, 0, 0, 0), - make_error('Error', 1, 0, 1, 0), + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_warning('Warning', 0, 0, 0, 0), + _G.make_error('Error', 1, 0, 1, 0), }) - local extmarks = get_virt_text_extmarks(diagnostic_ns) - return {extmarks[1][4].virt_text, extmarks[2][4].virt_text} - ]] + local extmarks = _G.get_virt_text_extmarks(_G.diagnostic_ns) + return { extmarks[1][4].virt_text, extmarks[2][4].virt_text } + end) eq(' 👀 Warning', result[1][3][1]) eq(' 🔥 Error', result[2][3][1]) end) it('includes source for formatted diagnostics', function() - local result = exec_lua [[ + local result = exec_lua(function() vim.diagnostic.config({ underline = false, virtual_text = { @@ -1630,21 +1959,21 @@ describe('vim.diagnostic', function() source = 'always', format = function(diagnostic) if diagnostic.severity == vim.diagnostic.severity.ERROR then - return string.format("🔥 %s", diagnostic.message) + return string.format('🔥 %s', diagnostic.message) end - return string.format("👀 %s", diagnostic.message) + return string.format('👀 %s', diagnostic.message) end, - } + }, }) - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { - make_warning('Warning', 0, 0, 0, 0, 'some_linter'), - make_error('Error', 1, 0, 1, 0, 'another_linter'), + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_warning('Warning', 0, 0, 0, 0, 'some_linter'), + _G.make_error('Error', 1, 0, 1, 0, 'another_linter'), }) - local extmarks = get_virt_text_extmarks(diagnostic_ns) - return {extmarks[1][4].virt_text, extmarks[2][4].virt_text} - ]] + local extmarks = _G.get_virt_text_extmarks(_G.diagnostic_ns) + return { extmarks[1][4].virt_text, extmarks[2][4].virt_text } + end) eq(' some_linter: 👀 Warning', result[1][3][1]) eq(' another_linter: 🔥 Error', result[2][3][1]) end) @@ -1652,90 +1981,94 @@ describe('vim.diagnostic', function() it('can add a prefix to virtual text', function() eq( 'E Some error', - exec_lua [[ - local diagnostics = { - make_error('Some error', 0, 0, 0, 0), - } - - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics, { - underline = false, - virtual_text = { - prefix = 'E', - suffix = '', + exec_lua(function() + local diagnostics = { + _G.make_error('Some error', 0, 0, 0, 0), } - }) - local extmarks = get_virt_text_extmarks(diagnostic_ns) - local prefix = extmarks[1][4].virt_text[2][1] - local message = extmarks[1][4].virt_text[3][1] - return prefix .. message - ]] + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, diagnostics, { + underline = false, + virtual_text = { + prefix = 'E', + suffix = '', + }, + }) + + local extmarks = _G.get_virt_text_extmarks(_G.diagnostic_ns) + local prefix = extmarks[1][4].virt_text[2][1] + local message = extmarks[1][4].virt_text[3][1] + return prefix .. message + end) ) eq( '[(1/1) err-code] Some error', - exec_lua [[ - local diagnostics = { - make_error('Some error', 0, 0, 0, 0, nil, 'err-code'), - } - - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics, { - underline = false, - virtual_text = { - prefix = function(diag, i, total) return string.format('[(%d/%d) %s]', i, total, diag.code) end, - suffix = '', + exec_lua(function() + local diagnostics = { + _G.make_error('Some error', 0, 0, 0, 0, nil, 'err-code'), } - }) - local extmarks = get_virt_text_extmarks(diagnostic_ns) - local prefix = extmarks[1][4].virt_text[2][1] - local message = extmarks[1][4].virt_text[3][1] - return prefix .. message - ]] + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, diagnostics, { + underline = false, + virtual_text = { + prefix = function(diag, i, total) + return string.format('[(%d/%d) %s]', i, total, diag.code) + end, + suffix = '', + }, + }) + + local extmarks = _G.get_virt_text_extmarks(_G.diagnostic_ns) + local prefix = extmarks[1][4].virt_text[2][1] + local message = extmarks[1][4].virt_text[3][1] + return prefix .. message + end) ) end) it('can add a suffix to virtual text', function() eq( ' Some error ✘', - exec_lua [[ - local diagnostics = { - make_error('Some error', 0, 0, 0, 0), - } - - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics, { - underline = false, - virtual_text = { - prefix = '', - suffix = ' ✘', + exec_lua(function() + local diagnostics = { + _G.make_error('Some error', 0, 0, 0, 0), } - }) - local extmarks = get_virt_text_extmarks(diagnostic_ns) - local virt_text = extmarks[1][4].virt_text[3][1] - return virt_text - ]] + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, diagnostics, { + underline = false, + virtual_text = { + prefix = '', + suffix = ' ✘', + }, + }) + + local extmarks = _G.get_virt_text_extmarks(_G.diagnostic_ns) + local virt_text = extmarks[1][4].virt_text[3][1] + return virt_text + end) ) eq( ' Some error [err-code]', - exec_lua [[ - local diagnostics = { - make_error('Some error', 0, 0, 0, 0, nil, 'err-code'), - } - - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics, { - underline = false, - virtual_text = { - prefix = '', - suffix = function(diag) return string.format(' [%s]', diag.code) end, + exec_lua(function() + local diagnostics = { + _G.make_error('Some error', 0, 0, 0, 0, nil, 'err-code'), } - }) - local extmarks = get_virt_text_extmarks(diagnostic_ns) - local virt_text = extmarks[1][4].virt_text[3][1] - return virt_text - ]] + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, diagnostics, { + underline = false, + virtual_text = { + prefix = '', + suffix = function(diag) + return string.format(' [%s]', diag.code) + end, + }, + }) + + local extmarks = _G.get_virt_text_extmarks(_G.diagnostic_ns) + local virt_text = extmarks[1][4].virt_text[3][1] + return virt_text + end) ) end) end) @@ -1749,80 +2082,80 @@ describe('vim.diagnostic', function() end) it('can perform updates after insert_leave', function() - exec_lua [[vim.api.nvim_set_current_buf(diagnostic_bufnr)]] + exec_lua [[vim.api.nvim_set_current_buf( _G.diagnostic_bufnr)]] api.nvim_input('o') eq({ mode = 'i', blocking = false }, api.nvim_get_mode()) -- Save the diagnostics - exec_lua [[ + exec_lua(function() vim.diagnostic.config({ update_in_insert = false, }) - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { - make_error('Delayed Diagnostic', 4, 4, 4, 4), + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_error('Delayed Diagnostic', 4, 4, 4, 4), }) - ]] + end) -- No diagnostics displayed yet. eq({ mode = 'i', blocking = false }, api.nvim_get_mode()) eq( 1, - exec_lua [[return count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.ERROR, diagnostic_ns)]] + exec_lua [[return _G.count_diagnostics( _G.diagnostic_bufnr, vim.diagnostic.severity.ERROR, _G.diagnostic_ns)]] ) - eq(0, exec_lua [[return count_extmarks(diagnostic_bufnr, diagnostic_ns)]]) + eq(0, exec_lua [[return _G.count_extmarks( _G.diagnostic_bufnr, _G.diagnostic_ns)]]) api.nvim_input('<esc>') eq({ mode = 'n', blocking = false }, api.nvim_get_mode()) eq( 1, - exec_lua [[return count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.ERROR, diagnostic_ns)]] + exec_lua [[return _G.count_diagnostics( _G.diagnostic_bufnr, vim.diagnostic.severity.ERROR, _G.diagnostic_ns)]] ) - eq(2, exec_lua [[return count_extmarks(diagnostic_bufnr, diagnostic_ns)]]) + eq(2, exec_lua [[return _G.count_extmarks( _G.diagnostic_bufnr, _G.diagnostic_ns)]]) end) it('does not perform updates when not needed', function() - exec_lua [[vim.api.nvim_set_current_buf(diagnostic_bufnr)]] + exec_lua [[vim.api.nvim_set_current_buf( _G.diagnostic_bufnr)]] api.nvim_input('o') eq({ mode = 'i', blocking = false }, api.nvim_get_mode()) -- Save the diagnostics - exec_lua [[ + exec_lua(function() vim.diagnostic.config({ update_in_insert = false, virtual_text = true, }) - DisplayCount = 0 + _G.DisplayCount = 0 local set_virtual_text = vim.diagnostic.handlers.virtual_text.show vim.diagnostic.handlers.virtual_text.show = function(...) - DisplayCount = DisplayCount + 1 + _G.DisplayCount = _G.DisplayCount + 1 return set_virtual_text(...) end - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { - make_error('Delayed Diagnostic', 4, 4, 4, 4), + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_error('Delayed Diagnostic', 4, 4, 4, 4), }) - ]] + end) -- No diagnostics displayed yet. eq({ mode = 'i', blocking = false }, api.nvim_get_mode()) eq( 1, - exec_lua [[return count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.ERROR, diagnostic_ns)]] + exec_lua [[return _G.count_diagnostics( _G.diagnostic_bufnr, vim.diagnostic.severity.ERROR, _G.diagnostic_ns)]] ) - eq(0, exec_lua [[return count_extmarks(diagnostic_bufnr, diagnostic_ns)]]) - eq(0, exec_lua [[return DisplayCount]]) + eq(0, exec_lua [[return _G.count_extmarks( _G.diagnostic_bufnr, _G.diagnostic_ns)]]) + eq(0, exec_lua [[return _G.DisplayCount]]) api.nvim_input('<esc>') eq({ mode = 'n', blocking = false }, api.nvim_get_mode()) eq( 1, - exec_lua [[return count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.ERROR, diagnostic_ns)]] + exec_lua [[return _G.count_diagnostics( _G.diagnostic_bufnr, vim.diagnostic.severity.ERROR, _G.diagnostic_ns)]] ) - eq(2, exec_lua [[return count_extmarks(diagnostic_bufnr, diagnostic_ns)]]) - eq(1, exec_lua [[return DisplayCount]]) + eq(2, exec_lua [[return _G.count_extmarks( _G.diagnostic_bufnr, _G.diagnostic_ns)]]) + eq(1, exec_lua [[return _G.DisplayCount]]) -- Go in and out of insert mode one more time. api.nvim_input('o') @@ -1832,52 +2165,51 @@ describe('vim.diagnostic', function() eq({ mode = 'n', blocking = false }, api.nvim_get_mode()) -- Should not have set the virtual text again. - eq(1, exec_lua [[return DisplayCount]]) + eq(1, exec_lua [[return _G.DisplayCount]]) end) it('never sets virtual text, in combination with insert leave', function() - exec_lua [[vim.api.nvim_set_current_buf(diagnostic_bufnr)]] + exec_lua [[vim.api.nvim_set_current_buf( _G.diagnostic_bufnr)]] api.nvim_input('o') eq({ mode = 'i', blocking = false }, api.nvim_get_mode()) -- Save the diagnostics - exec_lua [[ + exec_lua(function() vim.diagnostic.config({ update_in_insert = false, virtual_text = false, }) - - DisplayCount = 0 + _G.DisplayCount = 0 local set_virtual_text = vim.diagnostic.handlers.virtual_text.show vim.diagnostic.handlers.virtual_text.show = function(...) - DisplayCount = DisplayCount + 1 + _G.DisplayCount = _G.DisplayCount + 1 return set_virtual_text(...) end - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { - make_error('Delayed Diagnostic', 4, 4, 4, 4), + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_error('Delayed Diagnostic', 4, 4, 4, 4), }) - ]] + end) -- No diagnostics displayed yet. eq({ mode = 'i', blocking = false }, api.nvim_get_mode()) eq( 1, - exec_lua [[return count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.ERROR, diagnostic_ns)]] + exec_lua [[return _G.count_diagnostics( _G.diagnostic_bufnr, vim.diagnostic.severity.ERROR, _G.diagnostic_ns)]] ) - eq(0, exec_lua [[return count_extmarks(diagnostic_bufnr, diagnostic_ns)]]) - eq(0, exec_lua [[return DisplayCount]]) + eq(0, exec_lua [[return _G.count_extmarks( _G.diagnostic_bufnr, _G.diagnostic_ns)]]) + eq(0, exec_lua [[return _G.DisplayCount]]) api.nvim_input('<esc>') eq({ mode = 'n', blocking = false }, api.nvim_get_mode()) eq( 1, - exec_lua [[return count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.ERROR, diagnostic_ns)]] + exec_lua [[return _G.count_diagnostics( _G.diagnostic_bufnr, vim.diagnostic.severity.ERROR, _G.diagnostic_ns)]] ) - eq(1, exec_lua [[return count_extmarks(diagnostic_bufnr, diagnostic_ns)]]) - eq(0, exec_lua [[return DisplayCount]]) + eq(1, exec_lua [[return _G.count_extmarks( _G.diagnostic_bufnr, _G.diagnostic_ns)]]) + eq(0, exec_lua [[return _G.DisplayCount]]) -- Go in and out of insert mode one more time. api.nvim_input('o') @@ -1887,124 +2219,136 @@ describe('vim.diagnostic', function() eq({ mode = 'n', blocking = false }, api.nvim_get_mode()) -- Should not have set the virtual text still. - eq(0, exec_lua [[return DisplayCount]]) + eq(0, exec_lua [[return _G.DisplayCount]]) end) it('can perform updates while in insert mode, if desired', function() - exec_lua [[vim.api.nvim_set_current_buf(diagnostic_bufnr)]] + exec_lua [[vim.api.nvim_set_current_buf( _G.diagnostic_bufnr)]] api.nvim_input('o') eq({ mode = 'i', blocking = false }, api.nvim_get_mode()) -- Save the diagnostics - exec_lua [[ + exec_lua(function() vim.diagnostic.config({ update_in_insert = true, }) - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { - make_error('Delayed Diagnostic', 4, 4, 4, 4), + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_error('Delayed Diagnostic', 4, 4, 4, 4), }) - ]] + end) -- Diagnostics are displayed, because the user wanted them that way! eq({ mode = 'i', blocking = false }, api.nvim_get_mode()) eq( 1, - exec_lua [[return count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.ERROR, diagnostic_ns)]] + exec_lua [[return _G.count_diagnostics( _G.diagnostic_bufnr, vim.diagnostic.severity.ERROR, _G.diagnostic_ns)]] ) - eq(2, exec_lua [[return count_extmarks(diagnostic_bufnr, diagnostic_ns)]]) + eq(2, exec_lua [[return _G.count_extmarks( _G.diagnostic_bufnr, _G.diagnostic_ns)]]) api.nvim_input('<esc>') eq({ mode = 'n', blocking = false }, api.nvim_get_mode()) eq( 1, - exec_lua [[return count_diagnostics(diagnostic_bufnr, vim.diagnostic.severity.ERROR, diagnostic_ns)]] + exec_lua [[return _G.count_diagnostics( _G.diagnostic_bufnr, vim.diagnostic.severity.ERROR, _G.diagnostic_ns)]] ) - eq(2, exec_lua [[return count_extmarks(diagnostic_bufnr, diagnostic_ns)]]) + eq(2, exec_lua [[return _G.count_extmarks( _G.diagnostic_bufnr, _G.diagnostic_ns)]]) end) it('can set diagnostics without displaying them', function() eq( 0, - exec_lua [[ - vim.diagnostic.enable(false, { bufnr = diagnostic_bufnr, ns_id = diagnostic_ns }) - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { - make_error('Diagnostic From Server 1:1', 1, 1, 1, 1), - }) - return count_extmarks(diagnostic_bufnr, diagnostic_ns) - ]] + exec_lua(function() + vim.diagnostic.enable(false, { bufnr = _G.diagnostic_bufnr, ns_id = _G.diagnostic_ns }) + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_error('Diagnostic From Server 1:1', 1, 1, 1, 1), + }) + return _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns) + end) ) eq( 2, - exec_lua [[ - vim.diagnostic.enable(true, { bufnr = diagnostic_bufnr, ns_id = diagnostic_ns }) - return count_extmarks(diagnostic_bufnr, diagnostic_ns) - ]] + exec_lua(function() + vim.diagnostic.enable(true, { bufnr = _G.diagnostic_bufnr, ns_id = _G.diagnostic_ns }) + return _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns) + end) ) end) it('can set display options', function() eq( 0, - exec_lua [[ - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { - make_error('Diagnostic From Server 1:1', 1, 1, 1, 1), - }, { virtual_text = false, underline = false }) - return count_extmarks(diagnostic_bufnr, diagnostic_ns) - ]] + exec_lua(function() + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_error('Diagnostic From Server 1:1', 1, 1, 1, 1), + }, { virtual_text = false, underline = false }) + return _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns) + end) ) eq( 1, - exec_lua [[ - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { - make_error('Diagnostic From Server 1:1', 1, 1, 1, 1), - }, { virtual_text = true, underline = false }) - return count_extmarks(diagnostic_bufnr, diagnostic_ns) - ]] + exec_lua(function() + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_error('Diagnostic From Server 1:1', 1, 1, 1, 1), + }, { virtual_text = true, underline = false }) + return _G.count_extmarks(_G.diagnostic_bufnr, _G.diagnostic_ns) + end) ) end) it('sets and clears signs #26193 #26555', function() do - local result = exec_lua [[ + local result = exec_lua(function() vim.diagnostic.config({ signs = true, }) local diagnostics = { - make_error('Error', 1, 1, 1, 2), - make_warning('Warning', 3, 3, 3, 3), + _G.make_error('Error', 1, 1, 1, 2), + _G.make_warning('Warning', 3, 3, 3, 3), } - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics) + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, diagnostics) - local ns = vim.diagnostic.get_namespace(diagnostic_ns) + local ns = vim.diagnostic.get_namespace(_G.diagnostic_ns) local sign_ns = ns.user_data.sign_ns - local signs = vim.api.nvim_buf_get_extmarks(diagnostic_bufnr, sign_ns, 0, -1, {type ='sign', details = true}) + local signs = vim.api.nvim_buf_get_extmarks( + _G.diagnostic_bufnr, + sign_ns, + 0, + -1, + { type = 'sign', details = true } + ) local result = {} for _, s in ipairs(signs) do result[#result + 1] = { lnum = s[2] + 1, name = s[4].sign_hl_group } end return result - ]] + end) eq({ 2, 'DiagnosticSignError' }, { result[1].lnum, result[1].name }) eq({ 4, 'DiagnosticSignWarn' }, { result[2].lnum, result[2].name }) end do - local result = exec_lua [[ - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, {}) + local result = exec_lua(function() + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, {}) - local ns = vim.diagnostic.get_namespace(diagnostic_ns) + local ns = vim.diagnostic.get_namespace(_G.diagnostic_ns) local sign_ns = ns.user_data.sign_ns - return vim.api.nvim_buf_get_extmarks(diagnostic_bufnr, sign_ns, 0, -1, {type ='sign', details = true}) - ]] + return vim.api.nvim_buf_get_extmarks( + _G.diagnostic_bufnr, + sign_ns, + 0, + -1, + { type = 'sign', details = true } + ) + end) eq({}, result) end @@ -2019,22 +2363,28 @@ describe('vim.diagnostic', function() n.command('sign define DiagnosticSignInfo text= texthl= linehl=Underlined numhl=Underlined') n.command('sign define DiagnosticSignHint text= texthl= linehl=Underlined numhl=Underlined') - local result = exec_lua [[ + local result = exec_lua(function() vim.diagnostic.config({ signs = true, }) local diagnostics = { - make_error('Error', 1, 1, 1, 2), - make_warning('Warning', 3, 3, 3, 3), + _G.make_error('Error', 1, 1, 1, 2), + _G.make_warning('Warning', 3, 3, 3, 3), } - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics) + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, diagnostics) - local ns = vim.diagnostic.get_namespace(diagnostic_ns) + local ns = vim.diagnostic.get_namespace(_G.diagnostic_ns) local sign_ns = ns.user_data.sign_ns - local signs = vim.api.nvim_buf_get_extmarks(diagnostic_bufnr, sign_ns, 0, -1, {type ='sign', details = true}) + local signs = vim.api.nvim_buf_get_extmarks( + _G.diagnostic_bufnr, + sign_ns, + 0, + -1, + { type = 'sign', details = true } + ) local result = {} for _, s in ipairs(signs) do result[#result + 1] = { @@ -2046,7 +2396,7 @@ describe('vim.diagnostic', function() } end return result - ]] + end) eq({ lnum = 2, @@ -2070,65 +2420,67 @@ describe('vim.diagnostic', function() it('can display a header', function() eq( { 'Diagnostics:', '1. Syntax error' }, - exec_lua [[ - local diagnostics = { - make_error("Syntax error", 0, 1, 0, 3), - } - vim.api.nvim_win_set_buf(0, diagnostic_bufnr) - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics) - local float_bufnr, winnr = vim.diagnostic.open_float() - local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) - vim.api.nvim_win_close(winnr, true) - return lines - ]] + exec_lua(function() + local diagnostics = { + _G.make_error('Syntax error', 0, 1, 0, 3), + } + vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr) + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, diagnostics) + local float_bufnr, winnr = vim.diagnostic.open_float() + local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) + vim.api.nvim_win_close(winnr, true) + return lines + end) ) eq( { "We're no strangers to love...", '1. Syntax error' }, - exec_lua [[ - local diagnostics = { - make_error("Syntax error", 0, 1, 0, 3), - } - vim.api.nvim_win_set_buf(0, diagnostic_bufnr) - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics) - local float_bufnr, winnr = vim.diagnostic.open_float({header = "We're no strangers to love..."}) - local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) - vim.api.nvim_win_close(winnr, true) - return lines - ]] + exec_lua(function() + local diagnostics = { + _G.make_error('Syntax error', 0, 1, 0, 3), + } + vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr) + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, diagnostics) + local float_bufnr, winnr = + vim.diagnostic.open_float({ header = "We're no strangers to love..." }) + local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) + vim.api.nvim_win_close(winnr, true) + return lines + end) ) eq( { 'You know the rules', '1. Syntax error' }, - exec_lua [[ - local diagnostics = { - make_error("Syntax error", 0, 1, 0, 3), - } - vim.api.nvim_win_set_buf(0, diagnostic_bufnr) - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics) - local float_bufnr, winnr = vim.diagnostic.open_float({header = {'You know the rules', 'Search'}}) - local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) - vim.api.nvim_win_close(winnr, true) - return lines - ]] + exec_lua(function() + local diagnostics = { + _G.make_error('Syntax error', 0, 1, 0, 3), + } + vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr) + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, diagnostics) + local float_bufnr, winnr = + vim.diagnostic.open_float({ header = { 'You know the rules', 'Search' } }) + local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) + vim.api.nvim_win_close(winnr, true) + return lines + end) ) end) it('can show diagnostics from the whole buffer', function() eq( { '1. Syntax error', '2. Some warning' }, - exec_lua [[ - local diagnostics = { - make_error("Syntax error", 0, 1, 0, 3), - make_warning("Some warning", 1, 1, 1, 3), - } - vim.api.nvim_win_set_buf(0, diagnostic_bufnr) - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics) - local float_bufnr, winnr = vim.diagnostic.open_float({header = false, scope="buffer"}) - local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) - vim.api.nvim_win_close(winnr, true) - return lines - ]] + exec_lua(function() + local diagnostics = { + _G.make_error('Syntax error', 0, 1, 0, 3), + _G.make_warning('Some warning', 1, 1, 1, 3), + } + vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr) + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, diagnostics) + local float_bufnr, winnr = vim.diagnostic.open_float({ header = false, scope = 'buffer' }) + local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) + vim.api.nvim_win_close(winnr, true) + return lines + end) ) end) @@ -2136,37 +2488,70 @@ describe('vim.diagnostic', function() -- Using cursor position eq( { '1. Some warning' }, - exec_lua [[ - local diagnostics = { - make_error("Syntax error", 0, 1, 0, 3), - make_warning("Some warning", 1, 1, 1, 3), - } - vim.api.nvim_win_set_buf(0, diagnostic_bufnr) - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics) - vim.api.nvim_win_set_cursor(0, {2, 1}) - local float_bufnr, winnr = vim.diagnostic.open_float({header=false}) - local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) - vim.api.nvim_win_close(winnr, true) - return lines - ]] + exec_lua(function() + local diagnostics = { + _G.make_error('Syntax error', 0, 1, 0, 3), + _G.make_warning('Some warning', 1, 1, 1, 3), + } + vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr) + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, diagnostics) + vim.api.nvim_win_set_cursor(0, { 2, 1 }) + local float_bufnr, winnr = vim.diagnostic.open_float({ header = false }) + local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) + vim.api.nvim_win_close(winnr, true) + return lines + end) ) -- With specified position eq( { '1. Some warning' }, - exec_lua [[ - local diagnostics = { - make_error("Syntax error", 0, 1, 0, 3), - make_warning("Some warning", 1, 1, 1, 3), - } - vim.api.nvim_win_set_buf(0, diagnostic_bufnr) - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics) - vim.api.nvim_win_set_cursor(0, {1, 1}) - local float_bufnr, winnr = vim.diagnostic.open_float({header=false, pos=1}) - local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) - vim.api.nvim_win_close(winnr, true) - return lines - ]] + exec_lua(function() + local diagnostics = { + _G.make_error('Syntax error', 0, 1, 0, 3), + _G.make_warning('Some warning', 1, 1, 1, 3), + } + vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr) + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, diagnostics) + vim.api.nvim_win_set_cursor(0, { 1, 1 }) + local float_bufnr, winnr = vim.diagnostic.open_float({ header = false, pos = 1 }) + local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) + vim.api.nvim_win_close(winnr, true) + return lines + end) + ) + + -- End position is exclusive + eq( + nil, + exec_lua(function() + local diagnostics = { + _G.make_error('Syntax error', 1, 1, 2, 0), + } + vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr) + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, diagnostics) + vim.api.nvim_win_set_cursor(0, { 1, 1 }) + local _, winnr = vim.diagnostic.open_float(0, { header = false, pos = { 2, 0 } }) + return winnr + end) + ) + + -- Works when width == 0 + eq( + { '1. Syntax error' }, + exec_lua(function() + local diagnostics = { + _G.make_error('Syntax error', 2, 0, 2, 0), + } + vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr) + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, diagnostics) + vim.api.nvim_win_set_cursor(0, { 1, 1 }) + local float_bufnr, winnr = + vim.diagnostic.open_float(0, { header = false, pos = { 2, 1 } }) + local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) + vim.api.nvim_win_close(winnr, true) + return lines + end) ) end) @@ -2174,55 +2559,94 @@ describe('vim.diagnostic', function() -- Using cursor position eq( { 'Syntax error' }, - exec_lua [[ - local diagnostics = { - make_error("Syntax error", 1, 1, 1, 2), - make_warning("Some warning", 1, 3, 1, 4), - } - vim.api.nvim_win_set_buf(0, diagnostic_bufnr) - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics) - vim.api.nvim_win_set_cursor(0, {2, 2}) - local float_bufnr, winnr = vim.diagnostic.open_float({header=false, scope="cursor"}) - local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) - vim.api.nvim_win_close(winnr, true) - return lines - ]] + exec_lua(function() + local diagnostics = { + _G.make_error('Syntax error', 1, 1, 1, 3), + _G.make_warning('Some warning', 1, 3, 1, 4), + } + vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr) + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, diagnostics) + vim.api.nvim_win_set_cursor(0, { 2, 2 }) + local float_bufnr, winnr = vim.diagnostic.open_float({ header = false, scope = 'cursor' }) + local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) + vim.api.nvim_win_close(winnr, true) + return lines + end) ) -- With specified position eq( { 'Some warning' }, - exec_lua [[ - local diagnostics = { - make_error("Syntax error", 1, 1, 1, 2), - make_warning("Some warning", 1, 3, 1, 4), - } - vim.api.nvim_win_set_buf(0, diagnostic_bufnr) - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics) - vim.api.nvim_win_set_cursor(0, {1, 1}) - local float_bufnr, winnr = vim.diagnostic.open_float({header=false, scope="cursor", pos={1,3}}) - local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) - vim.api.nvim_win_close(winnr, true) - return lines - ]] + exec_lua(function() + local diagnostics = { + _G.make_error('Syntax error', 1, 1, 1, 3), + _G.make_warning('Some warning', 1, 3, 1, 4), + } + vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr) + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, diagnostics) + vim.api.nvim_win_set_cursor(0, { 1, 1 }) + local float_bufnr, winnr = + vim.diagnostic.open_float({ header = false, scope = 'cursor', pos = { 1, 3 } }) + local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) + vim.api.nvim_win_close(winnr, true) + return lines + end) ) -- With column position past the end of the line. #16062 eq( { 'Syntax error' }, - exec_lua [[ - local first_line_len = #vim.api.nvim_buf_get_lines(diagnostic_bufnr, 0, 1, true)[1] - local diagnostics = { - make_error("Syntax error", 0, first_line_len + 1, 1, 0), - } - vim.api.nvim_win_set_buf(0, diagnostic_bufnr) - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics) - vim.api.nvim_win_set_cursor(0, {1, 1}) - local float_bufnr, winnr = vim.diagnostic.open_float({header=false, scope="cursor", pos={0,first_line_len}}) - local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) - vim.api.nvim_win_close(winnr, true) - return lines - ]] + exec_lua(function() + local first_line_len = #vim.api.nvim_buf_get_lines(_G.diagnostic_bufnr, 0, 1, true)[1] + local diagnostics = { + _G.make_error('Syntax error', 0, first_line_len + 1, 1, 0), + } + vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr) + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, diagnostics) + vim.api.nvim_win_set_cursor(0, { 1, 1 }) + local float_bufnr, winnr = vim.diagnostic.open_float({ + header = false, + scope = 'cursor', + pos = { 0, first_line_len }, + }) + local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) + vim.api.nvim_win_close(winnr, true) + return lines + end) + ) + + -- End position is exclusive + eq( + nil, + exec_lua(function() + local diagnostics = { + _G.make_error('Syntax error', 1, 1, 1, 3), + } + vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr) + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, diagnostics) + vim.api.nvim_win_set_cursor(0, { 1, 1 }) + local _, winnr = + vim.diagnostic.open_float(0, { header = false, scope = 'cursor', pos = { 1, 3 } }) + return winnr + end) + ) + + -- Works when width == 0 + eq( + { 'Syntax error' }, + exec_lua(function() + local diagnostics = { + _G.make_error('Syntax error', 2, 0, 2, 0), + } + vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr) + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, diagnostics) + vim.api.nvim_win_set_cursor(0, { 1, 1 }) + local float_bufnr, winnr = + vim.diagnostic.open_float({ header = false, scope = 'cursor', pos = { 2, 1 } }) + local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) + vim.api.nvim_win_close(winnr, true) + return lines + end) ) end) @@ -2234,17 +2658,17 @@ describe('vim.diagnostic', function() -- 1. <msg> eq( 2, - exec_lua [[ - local diagnostics = { - make_error("Syntax error", 0, 1, 0, 3), - } - vim.api.nvim_win_set_buf(0, diagnostic_bufnr) - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics) - local float_bufnr, winnr = vim.diagnostic.open_float(diagnostic_bufnr) - local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) - vim.api.nvim_win_close(winnr, true) - return #lines - ]] + exec_lua(function() + local diagnostics = { + _G.make_error('Syntax error', 0, 1, 0, 3), + } + vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr) + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, diagnostics) + local float_bufnr, winnr = vim.diagnostic.open_float(_G.diagnostic_bufnr) + local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) + vim.api.nvim_win_close(winnr, true) + return #lines + end) ) end ) @@ -2252,43 +2676,44 @@ describe('vim.diagnostic', function() it('only reports diagnostics from the current buffer when bufnr is omitted #15710', function() eq( 2, - exec_lua [[ - local other_bufnr = vim.api.nvim_create_buf(true, false) - local buf_1_diagnostics = { - make_error("Syntax error", 0, 1, 0, 3), - } - local buf_2_diagnostics = { - make_warning("Some warning", 0, 1, 0, 3), - } - vim.api.nvim_win_set_buf(0, diagnostic_bufnr) - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, buf_1_diagnostics) - vim.diagnostic.set(other_ns, other_bufnr, buf_2_diagnostics) - local float_bufnr, winnr = vim.diagnostic.open_float() - local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) - vim.api.nvim_win_close(winnr, true) - return #lines - ]] + exec_lua(function() + local other_bufnr = vim.api.nvim_create_buf(true, false) + local buf_1_diagnostics = { + _G.make_error('Syntax error', 0, 1, 0, 3), + } + local buf_2_diagnostics = { + _G.make_warning('Some warning', 0, 1, 0, 3), + } + vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr) + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, buf_1_diagnostics) + vim.diagnostic.set(_G.other_ns, other_bufnr, buf_2_diagnostics) + local float_bufnr, winnr = vim.diagnostic.open_float() + local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) + vim.api.nvim_win_close(winnr, true) + return #lines + end) ) end) it('allows filtering by namespace', function() eq( 2, - exec_lua [[ - local ns_1_diagnostics = { - make_error("Syntax error", 0, 1, 0, 3), - } - local ns_2_diagnostics = { - make_warning("Some warning", 0, 1, 0, 3), - } - vim.api.nvim_win_set_buf(0, diagnostic_bufnr) - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, ns_1_diagnostics) - vim.diagnostic.set(other_ns, diagnostic_bufnr, ns_2_diagnostics) - local float_bufnr, winnr = vim.diagnostic.open_float(diagnostic_bufnr, {namespace = diagnostic_ns}) - local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) - vim.api.nvim_win_close(winnr, true) - return #lines - ]] + exec_lua(function() + local ns_1_diagnostics = { + _G.make_error('Syntax error', 0, 1, 0, 3), + } + local ns_2_diagnostics = { + _G.make_warning('Some warning', 0, 1, 0, 3), + } + vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr) + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, ns_1_diagnostics) + vim.diagnostic.set(_G.other_ns, _G.diagnostic_bufnr, ns_2_diagnostics) + local float_bufnr, winnr = + vim.diagnostic.open_float(_G.diagnostic_bufnr, { namespace = _G.diagnostic_ns }) + local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) + vim.api.nvim_win_close(winnr, true) + return #lines + end) ) end) @@ -2299,17 +2724,18 @@ describe('vim.diagnostic', function() -- 1. <msg> eq( 1, - exec_lua [[ - local diagnostics = { - make_error("Syntax error", 0, 1, 0, 3), - } - vim.api.nvim_win_set_buf(0, diagnostic_bufnr) - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics) - local float_bufnr, winnr = vim.diagnostic.open_float(diagnostic_bufnr, {header = false}) - local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) - vim.api.nvim_win_close(winnr, true) - return #lines - ]] + exec_lua(function() + local diagnostics = { + _G.make_error('Syntax error', 0, 1, 0, 3), + } + vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr) + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, diagnostics) + local float_bufnr, winnr = + vim.diagnostic.open_float(_G.diagnostic_bufnr, { header = false }) + local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) + vim.api.nvim_win_close(winnr, true) + return #lines + end) ) end ) @@ -2317,138 +2743,141 @@ describe('vim.diagnostic', function() it('clamps diagnostic line numbers within the valid range', function() eq( 1, - exec_lua [[ - local diagnostics = { - make_error("Syntax error", 6, 0, 6, 0), - } - vim.api.nvim_win_set_buf(0, diagnostic_bufnr) - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics) - local float_bufnr, winnr = vim.diagnostic.open_float(diagnostic_bufnr, {header = false, pos = 5}) - local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) - vim.api.nvim_win_close(winnr, true) - return #lines - ]] + exec_lua(function() + local diagnostics = { + _G.make_error('Syntax error', 6, 0, 6, 0), + } + vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr) + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, diagnostics) + local float_bufnr, winnr = + vim.diagnostic.open_float(_G.diagnostic_bufnr, { header = false, pos = 5 }) + local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) + vim.api.nvim_win_close(winnr, true) + return #lines + end) ) end) it('can show diagnostic source', function() - exec_lua [[vim.api.nvim_win_set_buf(0, diagnostic_bufnr)]] + exec_lua [[vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr)]] eq( { '1. Syntax error' }, - exec_lua [[ - local diagnostics = { - make_error("Syntax error", 0, 1, 0, 3, "source x"), - } - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics) - local float_bufnr, winnr = vim.diagnostic.open_float(diagnostic_bufnr, { - header = false, - source = "if_many", - }) - local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) - vim.api.nvim_win_close(winnr, true) - return lines - ]] + exec_lua(function() + local diagnostics = { + _G.make_error('Syntax error', 0, 1, 0, 3, 'source x'), + } + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, diagnostics) + local float_bufnr, winnr = vim.diagnostic.open_float(_G.diagnostic_bufnr, { + header = false, + source = 'if_many', + }) + local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) + vim.api.nvim_win_close(winnr, true) + return lines + end) ) eq( { '1. source x: Syntax error' }, - exec_lua [[ - local float_bufnr, winnr = vim.diagnostic.open_float(diagnostic_bufnr, { - header = false, - source = "always", - }) - local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) - vim.api.nvim_win_close(winnr, true) - return lines - ]] + exec_lua(function() + local float_bufnr, winnr = vim.diagnostic.open_float(_G.diagnostic_bufnr, { + header = false, + source = 'always', + }) + local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) + vim.api.nvim_win_close(winnr, true) + return lines + end) ) eq( { '1. source x: Syntax error', '2. source y: Another error' }, - exec_lua [[ - local diagnostics = { - make_error("Syntax error", 0, 1, 0, 3, "source x"), - make_error("Another error", 0, 1, 0, 3, "source y"), - } - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics) - local float_bufnr, winnr = vim.diagnostic.open_float(diagnostic_bufnr, { - header = false, - source = "if_many", - }) - local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) - vim.api.nvim_win_close(winnr, true) - return lines - ]] + exec_lua(function() + local diagnostics = { + _G.make_error('Syntax error', 0, 1, 0, 3, 'source x'), + _G.make_error('Another error', 0, 1, 0, 3, 'source y'), + } + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, diagnostics) + local float_bufnr, winnr = vim.diagnostic.open_float(_G.diagnostic_bufnr, { + header = false, + source = 'if_many', + }) + local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) + vim.api.nvim_win_close(winnr, true) + return lines + end) ) end) it('respects severity_sort', function() - exec_lua [[vim.api.nvim_win_set_buf(0, diagnostic_bufnr)]] + exec_lua [[vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr)]] eq( { '1. Syntax error', '2. Info', '3. Error', '4. Warning' }, - exec_lua [[ - local diagnostics = { - make_error("Syntax error", 0, 1, 0, 3), - make_info('Info', 0, 3, 0, 4), - make_error('Error', 0, 2, 0, 2), - make_warning('Warning', 0, 0, 0, 1), - } + exec_lua(function() + local diagnostics = { + _G.make_error('Syntax error', 0, 1, 0, 3), + _G.make_info('Info', 0, 3, 0, 4), + _G.make_error('Error', 0, 2, 0, 2), + _G.make_warning('Warning', 0, 0, 0, 1), + } - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics) + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, diagnostics) - vim.diagnostic.config({severity_sort = false}) + vim.diagnostic.config({ severity_sort = false }) - local float_bufnr, winnr = vim.diagnostic.open_float(diagnostic_bufnr, { header = false }) - local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) - vim.api.nvim_win_close(winnr, true) - return lines - ]] + local float_bufnr, winnr = + vim.diagnostic.open_float(_G.diagnostic_bufnr, { header = false }) + local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) + vim.api.nvim_win_close(winnr, true) + return lines + end) ) eq( { '1. Syntax error', '2. Error', '3. Warning', '4. Info' }, - exec_lua [[ - vim.diagnostic.config({severity_sort = true}) - local float_bufnr, winnr = vim.diagnostic.open_float(diagnostic_bufnr, { header = false }) - local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) - vim.api.nvim_win_close(winnr, true) - return lines - ]] + exec_lua(function() + vim.diagnostic.config({ severity_sort = true }) + local float_bufnr, winnr = + vim.diagnostic.open_float(_G.diagnostic_bufnr, { header = false }) + local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) + vim.api.nvim_win_close(winnr, true) + return lines + end) ) eq( { '1. Info', '2. Warning', '3. Error', '4. Syntax error' }, - exec_lua [[ - vim.diagnostic.config({severity_sort = { reverse = true } }) - local float_bufnr, winnr = vim.diagnostic.open_float(diagnostic_bufnr, { header = false }) - local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) - vim.api.nvim_win_close(winnr, true) - return lines - ]] + exec_lua(function() + vim.diagnostic.config({ severity_sort = { reverse = true } }) + local float_bufnr, winnr = + vim.diagnostic.open_float(_G.diagnostic_bufnr, { header = false }) + local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) + vim.api.nvim_win_close(winnr, true) + return lines + end) ) end) it('can filter by severity', function() local count_diagnostics_with_severity = function(min_severity, max_severity) - return exec_lua( - [[ - local min_severity, max_severity = ... + return exec_lua(function() vim.diagnostic.config({ float = { - severity = {min=min_severity, max=max_severity}, + severity = { min = min_severity, max = max_severity }, }, }) - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { - make_error("Syntax error", 0, 1, 0, 3), - make_info('Info', 0, 3, 0, 4), - make_error('Error', 0, 2, 0, 2), - make_warning('Warning', 0, 0, 0, 1), + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_error('Syntax error', 0, 1, 0, 3), + _G.make_info('Info', 0, 3, 0, 4), + _G.make_error('Error', 0, 2, 0, 2), + _G.make_warning('Warning', 0, 0, 0, 1), }) - local float_bufnr, winnr = vim.diagnostic.open_float(diagnostic_bufnr, { header = false }) + local float_bufnr, winnr = + vim.diagnostic.open_float(_G.diagnostic_bufnr, { header = false }) if not float_bufnr then return 0 end @@ -2456,10 +2885,7 @@ describe('vim.diagnostic', function() local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) vim.api.nvim_win_close(winnr, true) return #lines - ]], - min_severity, - max_severity - ) + end) end eq(2, count_diagnostics_with_severity('ERROR')) @@ -2473,83 +2899,84 @@ describe('vim.diagnostic', function() -- Default is to add a number eq( { '1. Syntax error', '2. Some warning' }, - exec_lua [[ - local diagnostics = { - make_error("Syntax error", 0, 1, 0, 3), - make_warning("Some warning", 1, 1, 1, 3), - } - vim.api.nvim_win_set_buf(0, diagnostic_bufnr) - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics) - local float_bufnr, winnr = vim.diagnostic.open_float({header = false, scope = "buffer"}) - local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) - vim.api.nvim_win_close(winnr, true) - return lines - ]] + exec_lua(function() + local diagnostics = { + _G.make_error('Syntax error', 0, 1, 0, 3), + _G.make_warning('Some warning', 1, 1, 1, 3), + } + vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr) + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, diagnostics) + local float_bufnr, winnr = vim.diagnostic.open_float({ header = false, scope = 'buffer' }) + local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) + vim.api.nvim_win_close(winnr, true) + return lines + end) ) eq( { 'Syntax error', 'Some warning' }, - exec_lua [[ - local diagnostics = { - make_error("Syntax error", 0, 1, 0, 3), - make_warning("Some warning", 1, 1, 1, 3), - } - vim.api.nvim_win_set_buf(0, diagnostic_bufnr) - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics) - local float_bufnr, winnr = vim.diagnostic.open_float({header = false, scope = "buffer", prefix = ""}) - local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) - vim.api.nvim_win_close(winnr, true) - return lines - ]] + exec_lua(function() + local diagnostics = { + _G.make_error('Syntax error', 0, 1, 0, 3), + _G.make_warning('Some warning', 1, 1, 1, 3), + } + vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr) + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, diagnostics) + local float_bufnr, winnr = + vim.diagnostic.open_float({ header = false, scope = 'buffer', prefix = '' }) + local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) + vim.api.nvim_win_close(winnr, true) + return lines + end) ) eq( { '1. Syntax error', '2. Some warning' }, - exec_lua [[ - local diagnostics = { - make_error("Syntax error", 0, 1, 0, 3), - make_warning("Some warning", 0, 1, 0, 3), - } - vim.api.nvim_win_set_buf(0, diagnostic_bufnr) - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics) - local float_bufnr, winnr = vim.diagnostic.open_float({ - header = false, - prefix = function(_, i, total) - -- Only show a number if there is more than one diagnostic - if total > 1 then - return string.format("%d. ", i) - end - return "" - end, - }) - local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) - vim.api.nvim_win_close(winnr, true) - return lines - ]] + exec_lua(function() + local diagnostics = { + _G.make_error('Syntax error', 0, 1, 0, 3), + _G.make_warning('Some warning', 0, 1, 0, 3), + } + vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr) + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, diagnostics) + local float_bufnr, winnr = vim.diagnostic.open_float({ + header = false, + prefix = function(_, i, total) + -- Only show a number if there is more than one diagnostic + if total > 1 then + return string.format('%d. ', i) + end + return '' + end, + }) + local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) + vim.api.nvim_win_close(winnr, true) + return lines + end) ) eq( { 'Syntax error' }, - exec_lua [[ - local diagnostics = { - make_error("Syntax error", 0, 1, 0, 3), - } - vim.api.nvim_win_set_buf(0, diagnostic_bufnr) - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics) - local float_bufnr, winnr = vim.diagnostic.open_float({ - header = false, - prefix = function(_, i, total) - -- Only show a number if there is more than one diagnostic - if total > 1 then - return string.format("%d. ", i) - end - return "" - end, - }) - local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) - vim.api.nvim_win_close(winnr, true) - return lines - ]] + exec_lua(function() + local diagnostics = { + _G.make_error('Syntax error', 0, 1, 0, 3), + } + vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr) + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, diagnostics) + local float_bufnr, winnr = vim.diagnostic.open_float({ + header = false, + prefix = function(_, i, total) + -- Only show a number if there is more than one diagnostic + if total > 1 then + return string.format('%d. ', i) + end + return '' + end, + }) + local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) + vim.api.nvim_win_close(winnr, true) + return lines + end) ) eq( @@ -2562,50 +2989,51 @@ describe('vim.diagnostic', function() -- Default is to render the diagnostic error code eq( { '1. Syntax error [code-x]', '2. Some warning [code-y]' }, - exec_lua [[ - local diagnostics = { - make_error("Syntax error", 0, 1, 0, 3, nil, "code-x"), - make_warning("Some warning", 1, 1, 1, 3, nil, "code-y"), - } - vim.api.nvim_win_set_buf(0, diagnostic_bufnr) - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics) - local float_bufnr, winnr = vim.diagnostic.open_float({header = false, scope = "buffer"}) - local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) - vim.api.nvim_win_close(winnr, true) - return lines - ]] + exec_lua(function() + local diagnostics = { + _G.make_error('Syntax error', 0, 1, 0, 3, nil, 'code-x'), + _G.make_warning('Some warning', 1, 1, 1, 3, nil, 'code-y'), + } + vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr) + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, diagnostics) + local float_bufnr, winnr = vim.diagnostic.open_float({ header = false, scope = 'buffer' }) + local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) + vim.api.nvim_win_close(winnr, true) + return lines + end) ) eq( { '1. Syntax error', '2. Some warning' }, - exec_lua [[ - local diagnostics = { - make_error("Syntax error", 0, 1, 0, 3, nil, "code-x"), - make_warning("Some warning", 1, 1, 1, 3, nil, "code-y"), - } - vim.api.nvim_win_set_buf(0, diagnostic_bufnr) - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics) - local float_bufnr, winnr = vim.diagnostic.open_float({header = false, scope = "buffer", suffix = ""}) - local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) - vim.api.nvim_win_close(winnr, true) - return lines - ]] + exec_lua(function() + local diagnostics = { + _G.make_error('Syntax error', 0, 1, 0, 3, nil, 'code-x'), + _G.make_warning('Some warning', 1, 1, 1, 3, nil, 'code-y'), + } + vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr) + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, diagnostics) + local float_bufnr, winnr = + vim.diagnostic.open_float({ header = false, scope = 'buffer', suffix = '' }) + local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) + vim.api.nvim_win_close(winnr, true) + return lines + end) ) -- Suffix is rendered on the last line of a multiline diagnostic eq( { '1. Syntax error', ' More context [code-x]' }, - exec_lua [[ - local diagnostics = { - make_error("Syntax error\nMore context", 0, 1, 0, 3, nil, "code-x"), - } - vim.api.nvim_win_set_buf(0, diagnostic_bufnr) - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics) - local float_bufnr, winnr = vim.diagnostic.open_float({header = false, scope = "buffer"}) - local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) - vim.api.nvim_win_close(winnr, true) - return lines - ]] + exec_lua(function() + local diagnostics = { + _G.make_error('Syntax error\nMore context', 0, 1, 0, 3, nil, 'code-x'), + } + vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr) + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, diagnostics) + local float_bufnr, winnr = vim.diagnostic.open_float({ header = false, scope = 'buffer' }) + local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) + vim.api.nvim_win_close(winnr, true) + return lines + end) ) eq( @@ -2617,96 +3045,134 @@ describe('vim.diagnostic', function() it('works with the old signature', function() eq( { '1. Syntax error' }, - exec_lua [[ - local diagnostics = { - make_error("Syntax error", 0, 1, 0, 3), - } - vim.api.nvim_win_set_buf(0, diagnostic_bufnr) - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics) - local float_bufnr, winnr = vim.diagnostic.open_float(0, { header = false }) - local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) - vim.api.nvim_win_close(winnr, true) - return lines - ]] + exec_lua(function() + local diagnostics = { + _G.make_error('Syntax error', 0, 1, 0, 3), + } + vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr) + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, diagnostics) + local float_bufnr, winnr = vim.diagnostic.open_float(0, { header = false }) + local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) + vim.api.nvim_win_close(winnr, true) + return lines + end) ) end) it('works for multi-line diagnostics #21949', function() - -- open float failed non diagnostic lnum - eq( - vim.NIL, - exec_lua [[ + -- create diagnostic + exec_lua(function() local diagnostics = { - make_error("Error in two lines lnum is 1 and end_lnum is 2", 1, 1, 2, 3), + _G.make_error('Error in two lines lnum is 1 and end_lnum is 2', 1, 1, 2, 3), } - local winids = {} - vim.api.nvim_win_set_buf(0, diagnostic_bufnr) - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, diagnostics) - local _, winnr = vim.diagnostic.open_float(0, { header = false }) - return winnr - ]] + vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr) + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, diagnostics) + end) + + -- open float failed non diagnostic lnum + eq( + nil, + exec_lua(function() + vim.api.nvim_win_set_cursor(0, { 1, 0 }) + local _, winnr = vim.diagnostic.open_float(0, { header = false }) + return winnr + end) + ) + eq( + nil, + exec_lua(function() + vim.api.nvim_win_set_cursor(0, { 1, 0 }) + local _, winnr = vim.diagnostic.open_float(0, { header = false, scope = 'cursor' }) + return winnr + end) ) -- can open a float window on lnum 1 eq( { '1. Error in two lines lnum is 1 and end_lnum is 2' }, - exec_lua [[ - vim.api.nvim_win_set_cursor(0, {2, 0}) - local float_bufnr, winnr = vim.diagnostic.open_float(0, { header = false }) - local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) - vim.api.nvim_win_close(winnr, true) - return lines - ]] + exec_lua(function() + vim.api.nvim_win_set_cursor(0, { 2, 0 }) + local float_bufnr, winnr = vim.diagnostic.open_float(0, { header = false }) + local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) + vim.api.nvim_win_close(winnr, true) + return lines + end) + ) + + -- can open a cursor-scoped float window on lnum 1 + eq( + { 'Error in two lines lnum is 1 and end_lnum is 2' }, + exec_lua(function() + vim.api.nvim_win_set_cursor(0, { 2, 1 }) + local float_bufnr, winnr = + vim.diagnostic.open_float(0, { header = false, scope = 'cursor' }) + local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) + vim.api.nvim_win_close(winnr, true) + return lines + end) ) -- can open a float window on end_lnum 2 eq( { '1. Error in two lines lnum is 1 and end_lnum is 2' }, - exec_lua [[ - vim.api.nvim_win_set_cursor(0, {3, 0}) - local float_bufnr, winnr = vim.diagnostic.open_float(0, { header = false }) - local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) - vim.api.nvim_win_close(winnr, true) - return lines - ]] + exec_lua(function() + vim.api.nvim_win_set_cursor(0, { 3, 0 }) + local float_bufnr, winnr = vim.diagnostic.open_float(0, { header = false }) + local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) + vim.api.nvim_win_close(winnr, true) + return lines + end) + ) + + -- can open a cursor-scoped float window on end_lnum 2 + eq( + { 'Error in two lines lnum is 1 and end_lnum is 2' }, + exec_lua(function() + vim.api.nvim_win_set_cursor(0, { 3, 2 }) + local float_bufnr, winnr = + vim.diagnostic.open_float(0, { header = false, scope = 'cursor' }) + local lines = vim.api.nvim_buf_get_lines(float_bufnr, 0, -1, false) + vim.api.nvim_win_close(winnr, true) + return lines + end) ) end) end) describe('setloclist()', function() it('sets diagnostics in lnum order', function() - local loc_list = exec_lua [[ - vim.api.nvim_win_set_buf(0, diagnostic_bufnr) + local loc_list = exec_lua(function() + vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr) - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { - make_error('Farther Diagnostic', 4, 4, 4, 4), - make_error('Lower Diagnostic', 1, 1, 1, 1), + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_error('Farther Diagnostic', 4, 4, 4, 4), + _G.make_error('Lower Diagnostic', 1, 1, 1, 1), }) vim.diagnostic.setloclist() return vim.fn.getloclist(0) - ]] + end) assert(loc_list[1].lnum < loc_list[2].lnum) end) it('sets diagnostics in lnum order, regardless of namespace', function() - local loc_list = exec_lua [[ - vim.api.nvim_win_set_buf(0, diagnostic_bufnr) + local loc_list = exec_lua(function() + vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr) - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { - make_error('Lower Diagnostic', 1, 1, 1, 1), + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_error('Lower Diagnostic', 1, 1, 1, 1), }) - vim.diagnostic.set(other_ns, diagnostic_bufnr, { - make_warning('Farther Diagnostic', 4, 4, 4, 4), + vim.diagnostic.set(_G.other_ns, _G.diagnostic_bufnr, { + _G.make_warning('Farther Diagnostic', 4, 4, 4, 4), }) vim.diagnostic.setloclist() return vim.fn.getloclist(0) - ]] + end) assert(loc_list[1].lnum < loc_list[2].lnum) end) @@ -2725,22 +3191,23 @@ describe('vim.diagnostic', function() } eq( diagnostic, - exec_lua( - [[ - return vim.diagnostic.match(..., "^(%w+): [^:]+:(%d+):(%d+):(.+)$", {"severity", "lnum", "col", "message"}) - ]], - msg - ) + exec_lua(function() + return vim.diagnostic.match( + msg, + '^(%w+): [^:]+:(%d+):(%d+):(.+)$', + { 'severity', 'lnum', 'col', 'message' } + ) + end) ) end) it('returns nil if the pattern fails to match', function() eq( - NIL, - exec_lua [[ - local msg = "The answer to life, the universe, and everything is" - return vim.diagnostic.match(msg, "This definitely will not match", {}) - ]] + nil, + exec_lua(function() + local msg = 'The answer to life, the universe, and everything is' + return vim.diagnostic.match(msg, 'This definitely will not match', {}) + end) ) end) @@ -2756,12 +3223,15 @@ describe('vim.diagnostic', function() } eq( diagnostic, - exec_lua( - [[ - return vim.diagnostic.match(..., "^[^:]+:(%d+):(.+)$", {"lnum", "message"}, nil, {severity = vim.diagnostic.severity.INFO}) - ]], - msg - ) + exec_lua(function() + return vim.diagnostic.match( + msg, + '^[^:]+:(%d+):(.+)$', + { 'lnum', 'message' }, + nil, + { severity = vim.diagnostic.severity.INFO } + ) + end) ) end) @@ -2777,38 +3247,40 @@ describe('vim.diagnostic', function() } eq( diagnostic, - exec_lua( - [[ - return vim.diagnostic.match(..., "^(%d+):(%w+):(.+)$", {"lnum", "severity", "message"}, {FATAL = vim.diagnostic.severity.ERROR}) - ]], - msg - ) + exec_lua(function() + return vim.diagnostic.match( + msg, + '^(%d+):(%w+):(.+)$', + { 'lnum', 'severity', 'message' }, + { FATAL = vim.diagnostic.severity.ERROR } + ) + end) ) end) end) describe('toqflist() and fromqflist()', function() it('works', function() - local result = exec_lua [[ - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { - make_error('Error 1', 0, 1, 0, 1), - make_error('Error 2', 1, 1, 1, 1), - make_warning('Warning', 2, 2, 2, 2), - }) + local result = exec_lua(function() + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_error('Error 1', 0, 1, 0, 1), + _G.make_error('Error 2', 1, 1, 1, 1), + _G.make_warning('Warning', 2, 2, 2, 2), + }) - local diagnostics = vim.diagnostic.get(diagnostic_bufnr) - vim.fn.setqflist(vim.diagnostic.toqflist(diagnostics)) - local list = vim.fn.getqflist() - local new_diagnostics = vim.diagnostic.fromqflist(list) + local diagnostics = vim.diagnostic.get(_G.diagnostic_bufnr) + vim.fn.setqflist(vim.diagnostic.toqflist(diagnostics)) + local list = vim.fn.getqflist() + local new_diagnostics = vim.diagnostic.fromqflist(list) - -- Remove namespace since it isn't present in the return value of - -- fromlist() - for _, v in ipairs(diagnostics) do - v.namespace = nil - end + -- Remove namespace since it isn't present in the return value of + -- fromlist() + for _, v in ipairs(diagnostics) do + v.namespace = nil + end - return {diagnostics, new_diagnostics} - ]] + return { diagnostics, new_diagnostics } + end) eq(result[1], result[2]) end) end) @@ -2828,179 +3300,181 @@ describe('vim.diagnostic', function() it('can add new handlers', function() eq( true, - exec_lua [[ - local handler_called = false - vim.diagnostic.handlers.test = { - show = function(namespace, bufnr, diagnostics, opts) - assert(namespace == diagnostic_ns) - assert(bufnr == diagnostic_bufnr) - assert(#diagnostics == 1) - assert(opts.test.some_opt == 42) - handler_called = true - end, - } + exec_lua(function() + local handler_called = false + vim.diagnostic.handlers.test = { + show = function(namespace, bufnr, diagnostics, opts) + assert(namespace == _G.diagnostic_ns) + assert(bufnr == _G.diagnostic_bufnr) + assert(#diagnostics == 1) + assert(opts.test.some_opt == 42) + handler_called = true + end, + } - vim.diagnostic.config({test = {some_opt = 42}}) - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { - make_warning("Warning", 0, 0, 0, 0), - }) - return handler_called - ]] + vim.diagnostic.config({ test = { some_opt = 42 } }) + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_warning('Warning', 0, 0, 0, 0), + }) + return handler_called + end) ) end) it('can disable handlers by setting the corresponding option to false', function() eq( false, - exec_lua [[ - local handler_called = false - vim.diagnostic.handlers.test = { - show = function(namespace, bufnr, diagnostics, opts) - handler_called = true - end, - } + exec_lua(function() + local handler_called = false + vim.diagnostic.handlers.test = { + show = function(_, _, _, _) + handler_called = true + end, + } - vim.diagnostic.config({test = false}) - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { - make_warning("Warning", 0, 0, 0, 0), - }) - return handler_called - ]] + vim.diagnostic.config({ test = false }) + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_warning('Warning', 0, 0, 0, 0), + }) + return handler_called + end) ) end) it("always calls a handler's hide function if defined", function() eq( { false, true }, - exec_lua [[ - local hide_called = false - local show_called = false - vim.diagnostic.handlers.test = { - show = function(namespace, bufnr, diagnostics, opts) - show_called = true - end, - hide = function(namespace, bufnr) - assert(namespace == diagnostic_ns) - assert(bufnr == diagnostic_bufnr) - hide_called = true - end, - } + exec_lua(function() + local hide_called = false + local show_called = false + vim.diagnostic.handlers.test = { + show = function(_, _, _, _) + show_called = true + end, + hide = function(namespace, bufnr) + assert(namespace == _G.diagnostic_ns) + assert(bufnr == _G.diagnostic_bufnr) + hide_called = true + end, + } - vim.diagnostic.config({test = false}) - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { - make_warning("Warning", 0, 0, 0, 0), - }) - vim.diagnostic.hide(diagnostic_ns, diagnostic_bufnr) - return {show_called, hide_called} - ]] + vim.diagnostic.config({ test = false }) + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_warning('Warning', 0, 0, 0, 0), + }) + vim.diagnostic.hide(_G.diagnostic_ns, _G.diagnostic_bufnr) + return { show_called, hide_called } + end) ) end) it('triggers the autocommand when diagnostics are set', function() eq( { true, true }, - exec_lua [[ - -- Set a different buffer as current to test that <abuf> is being set properly in - -- DiagnosticChanged callbacks - local tmp = vim.api.nvim_create_buf(false, true) - vim.api.nvim_set_current_buf(tmp) - - local triggered = {} - vim.api.nvim_create_autocmd('DiagnosticChanged', { - callback = function(args) - triggered = {args.buf, #args.data.diagnostics} - end, - }) - vim.api.nvim_buf_set_name(diagnostic_bufnr, "test | test") - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { - make_error('Diagnostic', 0, 0, 0, 0) - }) - return { - triggered[1] == diagnostic_bufnr, - triggered[2] == 1, - } - ]] + exec_lua(function() + -- Set a different buffer as current to test that <abuf> is being set properly in + -- DiagnosticChanged callbacks + local tmp = vim.api.nvim_create_buf(false, true) + vim.api.nvim_set_current_buf(tmp) + + local triggered = {} + vim.api.nvim_create_autocmd('DiagnosticChanged', { + callback = function(args) + triggered = { args.buf, #args.data.diagnostics } + end, + }) + vim.api.nvim_buf_set_name(_G.diagnostic_bufnr, 'test | test') + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_error('Diagnostic', 0, 0, 0, 0), + }) + return { + triggered[1] == _G.diagnostic_bufnr, + triggered[2] == 1, + } + end) ) end) it('triggers the autocommand when diagnostics are cleared', function() eq( true, - exec_lua [[ - local tmp = vim.api.nvim_create_buf(false, true) - vim.api.nvim_set_current_buf(tmp) - vim.g.diagnostic_autocmd_triggered = 0 - vim.cmd('autocmd DiagnosticChanged * let g:diagnostic_autocmd_triggered = +expand("<abuf>")') - vim.api.nvim_buf_set_name(diagnostic_bufnr, "test | test") - vim.diagnostic.reset(diagnostic_ns, diagnostic_bufnr) - return vim.g.diagnostic_autocmd_triggered == diagnostic_bufnr - ]] + exec_lua(function() + local tmp = vim.api.nvim_create_buf(false, true) + vim.api.nvim_set_current_buf(tmp) + vim.g.diagnostic_autocmd_triggered = 0 + vim.cmd( + 'autocmd DiagnosticChanged * let g:diagnostic_autocmd_triggered = +expand("<abuf>")' + ) + vim.api.nvim_buf_set_name(_G.diagnostic_bufnr, 'test | test') + vim.diagnostic.reset(_G.diagnostic_ns, _G.diagnostic_bufnr) + return vim.g.diagnostic_autocmd_triggered == _G.diagnostic_bufnr + end) ) end) it('is_enabled', function() eq( { false, false, false, false, false }, - exec_lua [[ - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { - make_error('Diagnostic #1', 1, 1, 1, 1), - }) - vim.api.nvim_set_current_buf(diagnostic_bufnr) - vim.diagnostic.enable(false) - return { - vim.diagnostic.is_enabled(), - vim.diagnostic.is_enabled{ bufnr = 0 }, - vim.diagnostic.is_enabled{ bufnr = diagnostic_bufnr }, - vim.diagnostic.is_enabled{ bufnr = diagnostic_bufnr, ns_id = diagnostic_ns }, - vim.diagnostic.is_enabled{ bufnr = 0, ns_id = diagnostic_ns }, - } - ]] + exec_lua(function() + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_error('Diagnostic #1', 1, 1, 1, 1), + }) + vim.api.nvim_set_current_buf(_G.diagnostic_bufnr) + vim.diagnostic.enable(false) + return { + vim.diagnostic.is_enabled(), + vim.diagnostic.is_enabled { bufnr = 0 }, + vim.diagnostic.is_enabled { bufnr = _G.diagnostic_bufnr }, + vim.diagnostic.is_enabled { bufnr = _G.diagnostic_bufnr, ns_id = _G.diagnostic_ns }, + vim.diagnostic.is_enabled { bufnr = 0, ns_id = _G.diagnostic_ns }, + } + end) ) eq( { true, true, true, true, true }, - exec_lua [[ - vim.diagnostic.enable() - return { - vim.diagnostic.is_enabled(), - vim.diagnostic.is_enabled{ bufnr = 0 }, - vim.diagnostic.is_enabled{ bufnr = diagnostic_bufnr }, - vim.diagnostic.is_enabled{ bufnr = diagnostic_bufnr, ns_id = diagnostic_ns }, - vim.diagnostic.is_enabled{ bufnr = 0, ns_id = diagnostic_ns }, - } - ]] + exec_lua(function() + vim.diagnostic.enable() + return { + vim.diagnostic.is_enabled(), + vim.diagnostic.is_enabled { bufnr = 0 }, + vim.diagnostic.is_enabled { bufnr = _G.diagnostic_bufnr }, + vim.diagnostic.is_enabled { bufnr = _G.diagnostic_bufnr, ns_id = _G.diagnostic_ns }, + vim.diagnostic.is_enabled { bufnr = 0, ns_id = _G.diagnostic_ns }, + } + end) ) end) it('is_disabled (deprecated)', function() eq( { true, true, true, true }, - exec_lua [[ - vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, { - make_error('Diagnostic #1', 1, 1, 1, 1), - }) - vim.api.nvim_set_current_buf(diagnostic_bufnr) - vim.diagnostic.disable() - return { - vim.diagnostic.is_disabled(), - vim.diagnostic.is_disabled(diagnostic_bufnr), - vim.diagnostic.is_disabled(diagnostic_bufnr, diagnostic_ns), - vim.diagnostic.is_disabled(_, diagnostic_ns), - } - ]] + exec_lua(function() + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_error('Diagnostic #1', 1, 1, 1, 1), + }) + vim.api.nvim_set_current_buf(_G.diagnostic_bufnr) + vim.diagnostic.disable() + return { + vim.diagnostic.is_disabled(), + vim.diagnostic.is_disabled(_G.diagnostic_bufnr), + vim.diagnostic.is_disabled(_G.diagnostic_bufnr, _G.diagnostic_ns), + vim.diagnostic.is_disabled(0, _G.diagnostic_ns), + } + end) ) eq( { false, false, false, false }, - exec_lua [[ - vim.diagnostic.enable() - return { - vim.diagnostic.is_disabled(), - vim.diagnostic.is_disabled(diagnostic_bufnr), - vim.diagnostic.is_disabled(diagnostic_bufnr, diagnostic_ns), - vim.diagnostic.is_disabled(_, diagnostic_ns), - } - ]] + exec_lua(function() + vim.diagnostic.enable() + return { + vim.diagnostic.is_disabled(), + vim.diagnostic.is_disabled(_G.diagnostic_bufnr), + vim.diagnostic.is_disabled(_G.diagnostic_bufnr, _G.diagnostic_ns), + vim.diagnostic.is_disabled(0, _G.diagnostic_ns), + } + end) ) end) end) diff --git a/test/functional/lua/ffi_spec.lua b/test/functional/lua/ffi_spec.lua index 85ca264107..96f5812493 100644 --- a/test/functional/lua/ffi_spec.lua +++ b/test/functional/lua/ffi_spec.lua @@ -15,27 +15,27 @@ describe('ffi.cdef', function() eq( 12, - exec_lua [=[ - local ffi = require('ffi') + exec_lua(function() + local ffi = require('ffi') - ffi.cdef [[ + ffi.cdef [[ typedef struct window_S win_T; int win_col_off(win_T *wp); extern win_T *curwin; ]] - vim.cmd('set number numberwidth=4 signcolumn=yes:4') + vim.cmd('set number numberwidth=4 signcolumn=yes:4') - return ffi.C.win_col_off(ffi.C.curwin) - ]=] + return ffi.C.win_col_off(ffi.C.curwin) + end) ) eq( 20, - exec_lua [=[ - local ffi = require('ffi') + exec_lua(function() + local ffi = require('ffi') - ffi.cdef[[ + ffi.cdef [[ typedef struct {} stl_hlrec_t; typedef struct {} StlClickRecord; typedef struct {} statuscol_T; @@ -58,32 +58,32 @@ describe('ffi.cdef', function() ); ]] - return ffi.C.build_stl_str_hl( - ffi.C.find_window_by_handle(0, ffi.new('Error')), - ffi.new('char[1024]'), - 1024, - ffi.cast('char*', 'StatusLineOfLength20'), - -1, - 0, - 0, - 0, - nil, - nil, - nil - ) - ]=] + return ffi.C.build_stl_str_hl( + ffi.C.find_window_by_handle(0, ffi.new('Error')), + ffi.new('char[1024]'), + 1024, + ffi.cast('char*', 'StatusLineOfLength20'), + -1, + 0, + 0, + 0, + nil, + nil, + nil + ) + end) ) -- Check that extern symbols are exported and accessible eq( true, - exec_lua [[ - local ffi = require('ffi') + exec_lua(function() + local ffi = require('ffi') - ffi.cdef('uint64_t display_tick;') + ffi.cdef('uint64_t display_tick;') - return ffi.C.display_tick >= 0 - ]] + return ffi.C.display_tick >= 0 + end) ) end) end) diff --git a/test/functional/lua/filetype_spec.lua b/test/functional/lua/filetype_spec.lua index 7db04e6f6b..574c837f92 100644 --- a/test/functional/lua/filetype_spec.lua +++ b/test/functional/lua/filetype_spec.lua @@ -18,90 +18,82 @@ describe('vim.filetype', function() before_each(function() clear() - exec_lua [[ + exec_lua(function() local bufnr = vim.api.nvim_create_buf(true, false) vim.api.nvim_set_current_buf(bufnr) - ]] + end) end) it('works with extensions', function() eq( 'radicalscript', - exec_lua [[ - vim.filetype.add({ - extension = { - rs = 'radicalscript', - }, - }) - return vim.filetype.match({ filename = 'main.rs' }) - ]] + exec_lua(function() + vim.filetype.add({ + extension = { + rs = 'radicalscript', + }, + }) + return vim.filetype.match({ filename = 'main.rs' }) + end) ) end) it('prioritizes filenames over extensions', function() eq( 'somethingelse', - exec_lua [[ - vim.filetype.add({ - extension = { - rs = 'radicalscript', - }, - filename = { - ['main.rs'] = 'somethingelse', - }, - }) - return vim.filetype.match({ filename = 'main.rs' }) - ]] + exec_lua(function() + vim.filetype.add({ + extension = { + rs = 'radicalscript', + }, + filename = { + ['main.rs'] = 'somethingelse', + }, + }) + return vim.filetype.match({ filename = 'main.rs' }) + end) ) end) it('works with filenames', function() eq( 'nim', - exec_lua [[ - vim.filetype.add({ - filename = { - ['s_O_m_e_F_i_l_e'] = 'nim', - }, - }) - return vim.filetype.match({ filename = 's_O_m_e_F_i_l_e' }) - ]] + exec_lua(function() + vim.filetype.add({ + filename = { + ['s_O_m_e_F_i_l_e'] = 'nim', + }, + }) + return vim.filetype.match({ filename = 's_O_m_e_F_i_l_e' }) + end) ) eq( 'dosini', - exec_lua( - [[ - local root = ... - vim.filetype.add({ - filename = { - ['config'] = 'toml', - [root .. '/.config/fun/config'] = 'dosini', - }, - }) - return vim.filetype.match({ filename = root .. '/.config/fun/config' }) - ]], - root - ) + exec_lua(function() + vim.filetype.add({ + filename = { + ['config'] = 'toml', + [root .. '/.config/fun/config'] = 'dosini', + }, + }) + return vim.filetype.match({ filename = root .. '/.config/fun/config' }) + end) ) end) it('works with patterns', function() eq( 'markdown', - exec_lua( - [[ - local root = ... - vim.env.HOME = '/a-funky+home%dir' - vim.filetype.add({ - pattern = { - ['~/blog/.*%.txt'] = 'markdown', - } - }) - return vim.filetype.match({ filename = '~/blog/why_neovim_is_awesome.txt' }) - ]], - root - ) + exec_lua(function() + vim.env.HOME = '/a-funky+home%dir' + vim.filetype.add({ + pattern = { + ['~/blog/.*%.txt'] = 'markdown', + }, + }) + return vim.filetype.match({ filename = '~/blog/why_neovim_is_awesome.txt' }) + end) ) end) @@ -110,43 +102,43 @@ describe('vim.filetype', function() command('file relevant_to_me') eq( 'foss', - exec_lua [[ - vim.filetype.add({ - pattern = { - ["relevant_to_(%a+)"] = function(path, bufnr, capture) - if capture == "me" then - return "foss" - end - end, - } - }) - return vim.filetype.match({ buf = 0 }) - ]] + exec_lua(function() + vim.filetype.add({ + pattern = { + ['relevant_to_(%a+)'] = function(_, _, capture) + if capture == 'me' then + return 'foss' + end + end, + }, + }) + return vim.filetype.match({ buf = 0 }) + end) ) end) it('works with contents #22180', function() eq( 'sh', - exec_lua [[ - -- Needs to be set so detect#sh doesn't fail - vim.g.ft_ignore_pat = '\\.\\(Z\\|gz\\|bz2\\|zip\\|tgz\\)$' - return vim.filetype.match({ contents = { '#!/usr/bin/env bash' } }) - ]] + exec_lua(function() + -- Needs to be set so detect#sh doesn't fail + vim.g.ft_ignore_pat = '\\.\\(Z\\|gz\\|bz2\\|zip\\|tgz\\)$' + return (vim.filetype.match({ contents = { '#!/usr/bin/env bash' } })) + end) ) end) it('considers extension mappings when matching from hashbang', function() eq( 'fooscript', - exec_lua [[ - vim.filetype.add({ - extension = { - foo = 'fooscript', - } - }) - return vim.filetype.match({ contents = { '#!/usr/bin/env foo' } }) - ]] + exec_lua(function() + vim.filetype.add({ + extension = { + foo = 'fooscript', + }, + }) + return vim.filetype.match({ contents = { '#!/usr/bin/env foo' } }) + end) ) end) @@ -160,7 +152,12 @@ describe('vim.filetype', function() xml = { formatexpr = 'xmlformat#Format()' }, } do for option, value in pairs(opts) do - eq(value, exec_lua([[ return vim.filetype.get_option(...) ]], ft, option)) + eq( + value, + exec_lua(function() + return vim.filetype.get_option(ft, option) + end) + ) end end end) diff --git a/test/functional/lua/fs_spec.lua b/test/functional/lua/fs_spec.lua index aba02ab01b..f0d49205e7 100644 --- a/test/functional/lua/fs_spec.lua +++ b/test/functional/lua/fs_spec.lua @@ -141,19 +141,14 @@ describe('vim.fs', function() it('works', function() eq( true, - exec_lua( - [[ - local dir, nvim = ... - for name, type in vim.fs.dir(dir) do - if name == nvim and type == 'file' then - return true + exec_lua(function() + for name, type in vim.fs.dir(nvim_dir) do + if name == nvim_prog_basename and type == 'file' then + return true + end end - end - return false - ]], - nvim_dir, - nvim_prog_basename - ) + return false + end) ) end) @@ -172,14 +167,12 @@ describe('vim.fs', function() io.open('testd/a/b/c/c4', 'w'):close() local function run(dir, depth, skip) - local r = exec_lua( - [[ - local dir, depth, skip = ... + return exec_lua(function() local r = {} local skip_f if skip then - skip_f = function(n) - if vim.tbl_contains(skip or {}, n) then + skip_f = function(n0) + if vim.tbl_contains(skip or {}, n0) then return false end end @@ -188,12 +181,7 @@ describe('vim.fs', function() r[name] = type_ end return r - ]], - dir, - depth, - skip - ) - return r + end) end local exp = {} @@ -263,13 +251,12 @@ describe('vim.fs', function() opts = { path = test_source_path .. '/contrib', limit = math.huge } eq( - exec_lua( - [[ - local dir = ... - return vim.tbl_map(vim.fs.basename, vim.fn.glob(dir..'/contrib/*', false, true)) - ]], - test_source_path - ), + exec_lua(function() + return vim.tbl_map( + vim.fs.basename, + vim.fn.glob(test_source_path .. '/contrib/*', false, true) + ) + end), vim.tbl_map( vim.fs.basename, vim.fs.find(function(_, d) @@ -299,11 +286,11 @@ describe('vim.fs', function() it('works with a function', function() ---@type string - local result = exec_lua([[ - return vim.fs.root(0, function(name, path) + local result = exec_lua(function() + return vim.fs.root(0, function(name, _) return name:match('%.txt$') end) - ]]) + end) eq(vim.fs.joinpath(test_source_path, 'test/functional/fixtures'), result) end) @@ -352,13 +339,10 @@ describe('vim.fs', function() local xdg_config_home = test_build_dir .. '/.config' eq( xdg_config_home .. '/nvim', - exec_lua( - [[ - vim.env.XDG_CONFIG_HOME = ... - return vim.fs.normalize('$XDG_CONFIG_HOME/nvim') - ]], - xdg_config_home - ) + exec_lua(function() + vim.env.XDG_CONFIG_HOME = xdg_config_home + return vim.fs.normalize('$XDG_CONFIG_HOME/nvim') + end) ) end) diff --git a/test/functional/lua/glob_spec.lua b/test/functional/lua/glob_spec.lua index 56cd4c9bb5..8302c7334d 100644 --- a/test/functional/lua/glob_spec.lua +++ b/test/functional/lua/glob_spec.lua @@ -2,21 +2,15 @@ local t = require('test.testutil') local n = require('test.functional.testnvim')() local eq = t.eq -local exec_lua = n.exec_lua describe('glob', function() before_each(n.clear) after_each(n.clear) - local match = function(...) - return exec_lua( - [[ - local pattern = select(1, ...) - local str = select(2, ...) - return require("vim.glob").to_lpeg(pattern):match(str) ~= nil - ]], - ... - ) + local match = function(pattern, str) + return n.exec_lua(function() + return require('vim.glob').to_lpeg(pattern):match(str) ~= nil + end) end describe('glob matching', function() @@ -161,7 +155,7 @@ describe('glob', function() eq(false, match('{ab,cd}', 'a')) eq(true, match('{ab,cd}', 'cd')) eq(true, match('{a,b,c}', 'c')) - eq(true, match('{a,{b,c}}', 'c')) + eq(false, match('{a,{b,c}}', 'c')) -- {} cannot nest end) it('should match [] groups', function() @@ -205,6 +199,19 @@ describe('glob', function() eq(true, match('[!a-zA-Z0-9]', '!')) end) + it('should handle long patterns', function() + -- lpeg has a recursion limit of 200 by default, make sure the grammar does trigger it on + -- strings longer than that + local fill_200 = + 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + eq(200, fill_200:len()) + local long_lit = fill_200 .. 'a' + eq(false, match(long_lit, 'b')) + eq(true, match(long_lit, long_lit)) + local long_pat = fill_200 .. 'a/**/*.c' + eq(true, match(long_pat, fill_200 .. 'a/b/c/d.c')) + end) + it('should match complex patterns', function() eq(false, match('**/*.{c,h}', '')) eq(false, match('**/*.{c,h}', 'c')) @@ -223,6 +230,17 @@ describe('glob', function() eq(true, match('{[0-9],[a-z]}', '0')) eq(true, match('{[0-9],[a-z]}', 'a')) eq(false, match('{[0-9],[a-z]}', 'A')) + + -- glob is from willRename filter in typescript-language-server + -- https://github.com/typescript-language-server/typescript-language-server/blob/b224b878652438bcdd639137a6b1d1a6630129e4/src/lsp-server.ts#L266 + eq(true, match('**/*.{ts,js,jsx,tsx,mjs,mts,cjs,cts}', 'test.js')) + eq(true, match('**/*.{ts,js,jsx,tsx,mjs,mts,cjs,cts}', 'test.ts')) + eq(true, match('**/*.{ts,js,jsx,tsx,mjs,mts,cjs,cts}', 'test.mts')) + eq(true, match('**/*.{ts,js,jsx,tsx,mjs,mts,cjs,cts}', 'test.mjs')) + eq(true, match('**/*.{ts,js,jsx,tsx,mjs,mts,cjs,cts}', 'test.cjs')) + eq(true, match('**/*.{ts,js,jsx,tsx,mjs,mts,cjs,cts}', 'test.cts')) + eq(true, match('**/*.{ts,js,jsx,tsx,mjs,mts,cjs,cts}', 'test.jsx')) + eq(true, match('**/*.{ts,js,jsx,tsx,mjs,mts,cjs,cts}', 'test.tsx')) end) end) end) diff --git a/test/functional/lua/highlight_spec.lua b/test/functional/lua/highlight_spec.lua index c9f2d0a47f..c048949df8 100644 --- a/test/functional/lua/highlight_spec.lua +++ b/test/functional/lua/highlight_spec.lua @@ -1,14 +1,112 @@ local t = require('test.testutil') local n = require('test.functional.testnvim')() +local Screen = require('test.functional.ui.screen') local exec_lua = n.exec_lua local eq = t.eq -local neq = t.neq local eval = n.eval local command = n.command local clear = n.clear local api = n.api +describe('vim.highlight.range', function() + local screen + + before_each(function() + clear() + screen = Screen.new(60, 6) + screen:add_extra_attr_ids({ + [100] = { foreground = Screen.colors.Blue, background = Screen.colors.Yellow, bold = true }, + }) + screen:attach() + api.nvim_set_option_value('list', true, {}) + api.nvim_set_option_value('listchars', 'eol:$', {}) + api.nvim_buf_set_lines(0, 0, -1, true, { + 'asdfghjkl', + '«口=口»', + 'qwertyuiop', + '口口=口口', + 'zxcvbnm', + }) + end) + + it('works with charwise selection', function() + exec_lua(function() + local ns = vim.api.nvim_create_namespace('') + vim.highlight.range(0, ns, 'Search', { 1, 5 }, { 3, 10 }) + end) + screen:expect([[ + ^asdfghjkl{1:$} | + «口{10:=口»}{100:$} | + {10:qwertyuiop}{100:$} | + {10:口口=口}口{1:$} | + zxcvbnm{1:$} | + | + ]]) + end) + + it('works with linewise selection', function() + exec_lua(function() + local ns = vim.api.nvim_create_namespace('') + vim.highlight.range(0, ns, 'Search', { 0, 0 }, { 4, 0 }, { regtype = 'V' }) + end) + screen:expect([[ + {10:^asdfghjkl}{100:$} | + {10:«口=口»}{100:$} | + {10:qwertyuiop}{100:$} | + {10:口口=口口}{100:$} | + {10:zxcvbnm}{100:$} | + | + ]]) + end) + + it('works with blockwise selection', function() + exec_lua(function() + local ns = vim.api.nvim_create_namespace('') + vim.highlight.range(0, ns, 'Search', { 0, 0 }, { 4, 4 }, { regtype = '\022' }) + end) + screen:expect([[ + {10:^asdf}ghjkl{1:$} | + {10:«口=}口»{1:$} | + {10:qwer}tyuiop{1:$} | + {10:口口}=口口{1:$} | + {10:zxcv}bnm{1:$} | + | + ]]) + end) + + it('works with blockwise selection with width', function() + exec_lua(function() + local ns = vim.api.nvim_create_namespace('') + vim.highlight.range(0, ns, 'Search', { 0, 4 }, { 4, 7 }, { regtype = '\0226' }) + end) + screen:expect([[ + ^asdf{10:ghjkl}{1:$} | + «口={10:口»}{1:$} | + qwer{10:tyuiop}{1:$} | + 口口{10:=口口}{1:$} | + zxcv{10:bnm}{1:$} | + | + ]]) + end) + + it('can use -1 or v:maxcol to indicate end of line', function() + exec_lua(function() + local ns = vim.api.nvim_create_namespace('') + vim.highlight.range(0, ns, 'Search', { 0, 4 }, { 1, -1 }, {}) + vim.highlight.range(0, ns, 'Search', { 2, 6 }, { 3, vim.v.maxcol }, {}) + end) + screen:expect([[ + ^asdf{10:ghjkl}{100:$} | + {10:«口=口»}{100:$} | + qwerty{10:uiop}{100:$} | + {10:口口=口口}{1:$} | + zxcvbnm{1:$} | + | + ]]) + end) +end) + describe('vim.highlight.on_yank', function() before_each(function() clear() @@ -16,53 +114,62 @@ describe('vim.highlight.on_yank', function() it('does not show errors even if buffer is wiped before timeout', function() command('new') - exec_lua([[ - vim.highlight.on_yank({timeout = 10, on_macro = true, event = {operator = "y", regtype = "v"}}) + exec_lua(function() + vim.highlight.on_yank({ + timeout = 10, + on_macro = true, + event = { operator = 'y', regtype = 'v' }, + }) vim.cmd('bwipeout!') - ]]) + end) vim.uv.sleep(10) n.feed('<cr>') -- avoid hang if error message exists eq('', eval('v:errmsg')) end) it('does not close timer twice', function() - exec_lua([[ - vim.highlight.on_yank({timeout = 10, on_macro = true, event = {operator = "y"}}) + exec_lua(function() + vim.highlight.on_yank({ timeout = 10, on_macro = true, event = { operator = 'y' } }) vim.uv.sleep(10) vim.schedule(function() - vim.highlight.on_yank({timeout = 0, on_macro = true, event = {operator = "y"}}) + vim.highlight.on_yank({ timeout = 0, on_macro = true, event = { operator = 'y' } }) end) - ]]) + end) eq('', eval('v:errmsg')) end) it('does not show in another window', function() command('vsplit') - exec_lua([[ - vim.api.nvim_buf_set_mark(0,"[",1,1,{}) - vim.api.nvim_buf_set_mark(0,"]",1,1,{}) - vim.highlight.on_yank({timeout = math.huge, on_macro = true, event = {operator = "y"}}) - ]]) - neq({}, api.nvim__win_get_ns(0)) + exec_lua(function() + vim.api.nvim_buf_set_mark(0, '[', 1, 1, {}) + vim.api.nvim_buf_set_mark(0, ']', 1, 1, {}) + vim.highlight.on_yank({ timeout = math.huge, on_macro = true, event = { operator = 'y' } }) + end) + local ns = api.nvim_create_namespace('hlyank') + local win = api.nvim_get_current_win() + eq({ win }, api.nvim__ns_get(ns).wins) command('wincmd w') - eq({}, api.nvim__win_get_ns(0)) + eq({ win }, api.nvim__ns_get(ns).wins) end) it('removes old highlight if new one is created before old one times out', function() command('vnew') - exec_lua([[ - vim.api.nvim_buf_set_mark(0,"[",1,1,{}) - vim.api.nvim_buf_set_mark(0,"]",1,1,{}) - vim.highlight.on_yank({timeout = math.huge, on_macro = true, event = {operator = "y"}}) - ]]) - neq({}, api.nvim__win_get_ns(0)) + exec_lua(function() + vim.api.nvim_buf_set_mark(0, '[', 1, 1, {}) + vim.api.nvim_buf_set_mark(0, ']', 1, 1, {}) + vim.highlight.on_yank({ timeout = math.huge, on_macro = true, event = { operator = 'y' } }) + end) + local ns = api.nvim_create_namespace('hlyank') + eq(api.nvim_get_current_win(), api.nvim__ns_get(ns).wins[1]) command('wincmd w') - exec_lua([[ - vim.api.nvim_buf_set_mark(0,"[",1,1,{}) - vim.api.nvim_buf_set_mark(0,"]",1,1,{}) - vim.highlight.on_yank({timeout = math.huge, on_macro = true, event = {operator = "y"}}) - ]]) + exec_lua(function() + vim.api.nvim_buf_set_mark(0, '[', 1, 1, {}) + vim.api.nvim_buf_set_mark(0, ']', 1, 1, {}) + vim.highlight.on_yank({ timeout = math.huge, on_macro = true, event = { operator = 'y' } }) + end) + local win = api.nvim_get_current_win() + eq({ win }, api.nvim__ns_get(ns).wins) command('wincmd w') - eq({}, api.nvim__win_get_ns(0)) + eq({ win }, api.nvim__ns_get(ns).wins) end) end) diff --git a/test/functional/lua/inspector_spec.lua b/test/functional/lua/inspector_spec.lua index 8fadba6ee8..3a1263f6a3 100644 --- a/test/functional/lua/inspector_spec.lua +++ b/test/functional/lua/inspector_spec.lua @@ -12,22 +12,21 @@ describe('vim.inspect_pos', function() end) it('it returns items', function() - local ret = exec_lua([[ + local buf, items, other_buf_syntax = exec_lua(function() local buf = vim.api.nvim_create_buf(true, false) local buf1 = vim.api.nvim_create_buf(true, false) - local ns1 = vim.api.nvim_create_namespace("ns1") - local ns2 = vim.api.nvim_create_namespace("") + local ns1 = vim.api.nvim_create_namespace('ns1') + local ns2 = vim.api.nvim_create_namespace('') vim.api.nvim_set_current_buf(buf) - vim.api.nvim_buf_set_lines(0, 0, -1, false, {"local a = 123"}) - vim.api.nvim_buf_set_lines(buf1, 0, -1, false, {"--commentline"}) + vim.api.nvim_buf_set_lines(0, 0, -1, false, { 'local a = 123' }) + vim.api.nvim_buf_set_lines(buf1, 0, -1, false, { '--commentline' }) vim.bo[buf].filetype = 'lua' vim.bo[buf1].filetype = 'lua' - vim.api.nvim_buf_set_extmark(buf, ns1, 0, 10, { hl_group = "Normal" }) - vim.api.nvim_buf_set_extmark(buf, ns2, 0, 10, { hl_group = "Normal" }) - vim.cmd("syntax on") - return {buf, vim.inspect_pos(0, 0, 10), vim.inspect_pos(buf1, 0, 10).syntax } - ]]) - local buf, items, other_buf_syntax = unpack(ret) + vim.api.nvim_buf_set_extmark(buf, ns1, 0, 10, { hl_group = 'Normal' }) + vim.api.nvim_buf_set_extmark(buf, ns2, 0, 10, { hl_group = 'Normal' }) + vim.cmd('syntax on') + return buf, vim.inspect_pos(0, 0, 10), vim.inspect_pos(buf1, 0, 10).syntax + end) eq('', eval('v:errmsg')) eq({ @@ -95,14 +94,14 @@ describe('vim.show_pos', function() end) it('it does not error', function() - exec_lua([[ + exec_lua(function() local buf = vim.api.nvim_create_buf(true, false) vim.api.nvim_set_current_buf(buf) - vim.api.nvim_buf_set_lines(0, 0, -1, false, {"local a = 123"}) + vim.api.nvim_buf_set_lines(0, 0, -1, false, { 'local a = 123' }) vim.bo[buf].filetype = 'lua' - vim.cmd("syntax on") - return {buf, vim.show_pos(0, 0, 10)} - ]]) + vim.cmd('syntax on') + return { buf, vim.show_pos(0, 0, 10) } + end) eq('', eval('v:errmsg')) end) end) diff --git a/test/functional/lua/loader_spec.lua b/test/functional/lua/loader_spec.lua index f13e6664c5..8508f2aa14 100644 --- a/test/functional/lua/loader_spec.lua +++ b/test/functional/lua/loader_spec.lua @@ -12,14 +12,14 @@ describe('vim.loader', function() it('can work in compatibility with --luamod-dev #27413', function() clear({ args = { '--luamod-dev' } }) - exec_lua [[ + exec_lua(function() vim.loader.enable() - require("vim.fs") + require('vim.fs') -- try to load other vim submodules as well (Nvim Lua stdlib) for key, _ in pairs(vim._submodules) do - local modname = 'vim.' .. key -- e.g. "vim.fs" + local modname = 'vim.' .. key -- e.g. "vim.fs" local lhs = vim[key] local rhs = require(modname) @@ -28,28 +28,25 @@ describe('vim.loader', function() ('%s != require("%s"), %s != %s'):format(modname, modname, tostring(lhs), tostring(rhs)) ) end - ]] + end) end) it('handles changing files (#23027)', function() - exec_lua [[ + exec_lua(function() vim.loader.enable() - ]] + end) local tmp = t.tmpname() command('edit ' .. tmp) eq( 1, - exec_lua( - [[ - vim.api.nvim_buf_set_lines(0, 0, -1, true, {'_G.TEST=1'}) - vim.cmd.write() - loadfile(...)() - return _G.TEST - ]], - tmp - ) + exec_lua(function() + vim.api.nvim_buf_set_lines(0, 0, -1, true, { '_G.TEST=1' }) + vim.cmd.write() + loadfile(tmp)() + return _G.TEST + end) ) -- fs latency @@ -57,15 +54,12 @@ describe('vim.loader', function() eq( 2, - exec_lua( - [[ - vim.api.nvim_buf_set_lines(0, 0, -1, true, {'_G.TEST=2'}) - vim.cmd.write() - loadfile(...)() - return _G.TEST - ]], - tmp - ) + exec_lua(function() + vim.api.nvim_buf_set_lines(0, 0, -1, true, { '_G.TEST=2' }) + vim.cmd.write() + loadfile(tmp)() + return _G.TEST + end) ) end) @@ -74,8 +68,7 @@ describe('vim.loader', function() vim.loader.enable() ]] - local tmp = t.tmpname() - assert(os.remove(tmp)) + local tmp = t.tmpname(false) assert(t.mkdir(tmp)) assert(t.mkdir(tmp .. '/%')) local tmp1 = tmp .. '/%/x' @@ -88,4 +81,15 @@ describe('vim.loader', function() eq(1, exec_lua('return loadfile(...)()', tmp1)) eq(2, exec_lua('return loadfile(...)()', tmp2)) end) + + it('correct indent on error message (#29809)', function() + local errmsg = exec_lua [[ + vim.loader.enable() + local _, errmsg = pcall(require, 'non_existent_module') + return errmsg + ]] + local errors = vim.split(errmsg, '\n') + eq("\tcache_loader: module 'non_existent_module' not found", errors[3]) + eq("\tcache_loader_lib: module 'non_existent_module' not found", errors[4]) + end) end) diff --git a/test/functional/lua/loop_spec.lua b/test/functional/lua/loop_spec.lua index 566a171a84..de8200a5f1 100644 --- a/test/functional/lua/loop_spec.lua +++ b/test/functional/lua/loop_spec.lua @@ -25,30 +25,34 @@ describe('vim.uv', function() it('timer', function() exec_lua('vim.api.nvim_set_var("coroutine_cnt", 0)', {}) - local code = [[ + local code = function() local touch = 0 local function wait(ms) local this = coroutine.running() assert(this) - local timer = vim.uv.new_timer() - timer:start(ms, 0, vim.schedule_wrap(function () - timer:close() - touch = touch + 1 - coroutine.resume(this) - touch = touch + 1 - assert(touch==3) - vim.api.nvim_set_var("coroutine_cnt_1", touch) - end)) + local timer = assert(vim.uv.new_timer()) + timer:start( + ms, + 0, + vim.schedule_wrap(function() + timer:close() + touch = touch + 1 + coroutine.resume(this) + touch = touch + 1 + assert(touch == 3) + vim.api.nvim_set_var('coroutine_cnt_1', touch) + end) + ) coroutine.yield() touch = touch + 1 return touch end coroutine.wrap(function() local touched = wait(10) - assert(touched==touch) - vim.api.nvim_set_var("coroutine_cnt", touched) + assert(touched == touch) + vim.api.nvim_set_var('coroutine_cnt', touched) end)() - ]] + end eq(0, api.nvim_get_var('coroutine_cnt')) exec_lua(code) @@ -99,15 +103,19 @@ describe('vim.uv', function() -- callbacks can be scheduled to be executed in the main event loop -- where the entire API is available - exec_lua([[ - local timer = vim.uv.new_timer() - timer:start(20, 0, vim.schedule_wrap(function () - _G.is_fast = vim.in_fast_event() - timer:close() - vim.api.nvim_set_var("valid", true) - vim.api.nvim_command("echomsg 'howdy'") - end)) - ]]) + exec_lua(function() + local timer = assert(vim.uv.new_timer()) + timer:start( + 20, + 0, + vim.schedule_wrap(function() + _G.is_fast = vim.in_fast_event() + timer:close() + vim.api.nvim_set_var('valid', true) + vim.api.nvim_command("echomsg 'howdy'") + end) + ) + end) screen:expect([[ ^ | @@ -118,15 +126,15 @@ describe('vim.uv', function() eq(false, exec_lua('return _G.is_fast')) -- fast (not deferred) API functions are allowed to be called directly - exec_lua([[ - local timer = vim.uv.new_timer() - timer:start(20, 0, function () + exec_lua(function() + local timer = assert(vim.uv.new_timer()) + timer:start(20, 0, function() timer:close() -- input is queued for processing after the callback returns - vim.api.nvim_input("isneaky") + vim.api.nvim_input('isneaky') _G.mode = vim.api.nvim_get_mode() end) - ]]) + end) screen:expect([[ sneaky^ | {1:~ }|*8 @@ -134,15 +142,15 @@ describe('vim.uv', function() ]]) eq({ blocking = false, mode = 'n' }, exec_lua('return _G.mode')) - exec_lua([[ - local timer = vim.uv.new_timer() - timer:start(20, 0, function () + exec_lua(function() + local timer = assert(vim.uv.new_timer()) + timer:start(20, 0, function() _G.is_fast = vim.in_fast_event() timer:close() - _G.value = vim.fn.has("nvim-0.5") - _G.unvalue = vim.fn.has("python3") + _G.value = vim.fn.has('nvim-0.5') + _G.unvalue = vim.fn.has('python3') end) - ]]) + end) screen:expect({ any = [[{3:Vim:E5560: Vimscript function must not be called i}]] }) feed('<cr>') diff --git a/test/functional/lua/luaeval_spec.lua b/test/functional/lua/luaeval_spec.lua index 3f62cd8325..2b23f72c7d 100644 --- a/test/functional/lua/luaeval_spec.lua +++ b/test/functional/lua/luaeval_spec.lua @@ -13,6 +13,7 @@ local fn = n.fn local clear = n.clear local eval = n.eval local feed = n.feed +local assert_alive = n.assert_alive local NIL = vim.NIL local eq = t.eq @@ -72,9 +73,9 @@ describe('luaeval()', function() end) it('are successfully converted to special dictionaries in table keys', function() command([[let d = luaeval('{["\0"]=1}')]]) - eq({_TYPE={}, _VAL={{{_TYPE={}, _VAL={'\n'}}, 1}}}, api.nvim_get_var('d')) + eq({_TYPE={}, _VAL={{'\000', 1}}}, api.nvim_get_var('d')) eq(1, fn.eval('d._TYPE is v:msgpack_types.map')) - eq(1, fn.eval('d._VAL[0][0]._TYPE is v:msgpack_types.string')) + eq(eval('v:t_blob'), fn.eval('type(d._VAL[0][0])')) end) it('are successfully converted to blobs from a list', function() command([[let l = luaeval('{"abc", "a\0b", "c\0d", "def"}')]]) @@ -125,11 +126,11 @@ describe('luaeval()', function() local level = 30 eq(nested_by_level[level].o, fn.luaeval(nested_by_level[level].s)) - eq({_TYPE={}, _VAL={{{_TYPE={}, _VAL={'\n', '\n'}}, '\000\n\000\000'}}}, + eq({_TYPE={}, _VAL={{'\000\n\000', '\000\n\000\000'}}}, fn.luaeval([[{['\0\n\0']='\0\n\0\0'}]])) eq(1, eval([[luaeval('{["\0\n\0"]="\0\n\0\0"}')._TYPE is v:msgpack_types.map]])) - eq(1, eval([[luaeval('{["\0\n\0"]="\0\n\0\0"}')._VAL[0][0]._TYPE is v:msgpack_types.string]])) - eq({nested={{_TYPE={}, _VAL={{{_TYPE={}, _VAL={'\n', '\n'}}, '\000\n\000\000'}}}}}, + eq(eval("v:t_blob"), eval([[type(luaeval('{["\0\n\0"]="\0\n\0\0"}')._VAL[0][0])]])) + eq({nested={{_TYPE={}, _VAL={{'\000\n\000', '\000\n\000\000'}}}}}, fn.luaeval([[{nested={{['\0\n\0']='\0\n\0\0'}}}]])) end) @@ -177,17 +178,15 @@ describe('luaeval()', function() end it('correctly passes special dictionaries', function() - eq({0, '\000\n\000'}, luaevalarg(sp('binary', '["\\n", "\\n"]'))) eq({0, '\000\n\000'}, luaevalarg(sp('string', '["\\n", "\\n"]'))) eq({0, true}, luaevalarg(sp('boolean', 1))) eq({0, false}, luaevalarg(sp('boolean', 0))) eq({0, NIL}, luaevalarg(sp('nil', 0))) - eq({0, {[""]=""}}, luaevalarg(mapsp(sp('binary', '[""]'), '""'))) eq({0, {[""]=""}}, luaevalarg(mapsp(sp('string', '[""]'), '""'))) end) it('issues an error in some cases', function() - eq("Vim(call):E5100: Cannot convert given lua table: table should contain either only integer keys or only string keys", + eq("Vim(call):E5100: Cannot convert given Lua table: table should contain either only integer keys or only string keys", exc_exec('call luaeval("{1, foo=2}")')) startswith("Vim(call):E5107: Error loading lua [string \"luaeval()\"]:", @@ -511,23 +510,16 @@ describe('v:lua', function() it('works in func options', function() local screen = Screen.new(60, 8) - screen:set_default_attr_ids({ - [1] = {bold = true, foreground = Screen.colors.Blue1}, - [2] = {background = Screen.colors.WebGray}, - [3] = {background = Screen.colors.LightMagenta}, - [4] = {bold = true}, - [5] = {bold = true, foreground = Screen.colors.SeaGreen4}, - }) screen:attach() api.nvim_set_option_value('omnifunc', 'v:lua.mymod.omni', {}) feed('isome st<c-x><c-o>') screen:expect{grid=[[ some stuff^ | - {1:~ }{2: stuff }{1: }| - {1:~ }{3: steam }{1: }| - {1:~ }{3: strange things }{1: }| + {1:~ }{12: stuff }{1: }| + {1:~ }{4: steam }{1: }| + {1:~ }{4: strange things }{1: }| {1:~ }|*3 - {4:-- Omni completion (^O^N^P) }{5:match 1 of 3} | + {5:-- Omni completion (^O^N^P) }{6:match 1 of 3} | ]]} api.nvim_set_option_value('operatorfunc', 'v:lua.mymod.noisy', {}) feed('<Esc>g@g@') @@ -560,5 +552,41 @@ describe('v:lua', function() eq("Vim:E107: Missing parentheses: v:lua", pcall_err(eval, "'bad'->v:lua")) eq("Vim:E1085: Not a callable type: v:lua", pcall_err(eval, "'bad'->v:lua()")) eq([[Vim:E15: Invalid expression: "v:lua.()"]], pcall_err(eval, "'bad'->v:lua.()")) + + eq("Vim:E1085: Not a callable type: v:lua", pcall_err(eval, "v:lua()")) + eq([[Vim:E15: Invalid expression: "v:lua.()"]], pcall_err(eval, "v:lua.()")) + end) + + describe('invalid use in fold text', function() + before_each(function() + feed('ifoo<CR>bar<Esc>') + command('1,2fold') + end) + + it('with missing function name when used as simple function', function() + api.nvim_set_option_value('debug', 'throw', {}) + eq( + [[Vim(eval):E15: Invalid expression: "v:lua.()"]], + pcall_err(command, 'set foldtext=v:lua.() | eval foldtextresult(1)') + ) + end) + + it('with missing function name when used in expression', function() + api.nvim_set_option_value('debug', 'throw', {}) + eq( + [[Vim(eval):E15: Invalid expression: "+v:lua.()"]], + pcall_err(command, 'set foldtext=+v:lua.() | eval foldtextresult(1)') + ) + end) + + it('with non-existent function when used as simple function', function() + command('set foldtext=v:lua.NoSuchFunc() | eval foldtextresult(1)') + assert_alive() + end) + + it('with non-existent function when used in expression', function() + command('set foldtext=+v:lua.NoSuchFunc() | eval foldtextresult(1)') + assert_alive() + end) end) end) diff --git a/test/functional/lua/mpack_spec.lua b/test/functional/lua/mpack_spec.lua index efd69d4607..ebede26936 100644 --- a/test/functional/lua/mpack_spec.lua +++ b/test/functional/lua/mpack_spec.lua @@ -11,20 +11,20 @@ describe('lua vim.mpack', function() it('encodes vim.NIL', function() eq( { true, true, true, true }, - exec_lua [[ - local var = vim.mpack.decode(vim.mpack.encode({33, vim.NIL, 77})) - return {var[1]==33, var[2]==vim.NIL, var[3]==77, var[4]==nil} - ]] + exec_lua(function() + local var = vim.mpack.decode(vim.mpack.encode({ 33, vim.NIL, 77 })) + return { var[1] == 33, var[2] == vim.NIL, var[3] == 77, var[4] == nil } + end) ) end) it('encodes vim.empty_dict()', function() eq( { { {}, 'foo', {} }, true, false }, - exec_lua [[ - local var = vim.mpack.decode(vim.mpack.encode({{}, "foo", vim.empty_dict()})) - return {var, vim.islist(var[1]), vim.islist(var[3])} - ]] + exec_lua(function() + local var = vim.mpack.decode(vim.mpack.encode({ {}, 'foo', vim.empty_dict() })) + return { var, vim.islist(var[1]), vim.islist(var[3]) } + end) ) end) end) diff --git a/test/functional/lua/overrides_spec.lua b/test/functional/lua/overrides_spec.lua index 849978f080..33a2813200 100644 --- a/test/functional/lua/overrides_spec.lua +++ b/test/functional/lua/overrides_spec.lua @@ -134,14 +134,12 @@ describe('print', function() eq('abc def', exec_capture('lua print("abc", "", "def")')) end) it('defers printing in luv event handlers', function() - exec_lua( - [[ - local cmd = ... + exec_lua(function(cmd) function test() local timer = vim.uv.new_timer() local done = false timer:start(10, 0, function() - print("very fast") + print('very fast') timer:close() done = true end) @@ -149,14 +147,12 @@ describe('print', function() -- loop until we know for sure the callback has been executed while not done do os.execute(cmd) - vim.uv.run("nowait") -- fake os_breakcheck() + vim.uv.run('nowait') -- fake os_breakcheck() end - print("very slow") - vim.api.nvim_command("sleep 1m") -- force deferred event processing + print('very slow') + vim.api.nvim_command('sleep 1m') -- force deferred event processing end - ]], - (is_os('win') and 'timeout 1') or 'sleep 0.1' - ) + end, (is_os('win') and 'timeout 1') or 'sleep 0.1') eq('very slow\nvery fast', exec_capture('lua test()')) end) diff --git a/test/functional/lua/runtime_spec.lua b/test/functional/lua/runtime_spec.lua index 4adce42c3e..f63363d6d9 100644 --- a/test/functional/lua/runtime_spec.lua +++ b/test/functional/lua/runtime_spec.lua @@ -401,4 +401,12 @@ describe('runtime:', function() eq('ABab', eval('g:seq')) end) end) + + it('cpp ftplugin loads c ftplugin #29053', function() + eq('', eval('&commentstring')) + eq('', eval('&omnifunc')) + exec('edit file.cpp') + eq('// %s', eval('&commentstring')) + eq('ccomplete#Complete', eval('&omnifunc')) + end) end) diff --git a/test/functional/lua/snippet_spec.lua b/test/functional/lua/snippet_spec.lua index 413aa93994..eb2f17216c 100644 --- a/test/functional/lua/snippet_spec.lua +++ b/test/functional/lua/snippet_spec.lua @@ -1,3 +1,5 @@ +---@diagnostic disable: no-unknown + local t = require('test.testutil') local n = require('test.functional.testnvim')() @@ -16,11 +18,6 @@ local retry = t.retry describe('vim.snippet', function() before_each(function() clear() - - exec_lua([[ - vim.keymap.set({ 'i', 's' }, '<Tab>', function() vim.snippet.jump(1) end, { buffer = true }) - vim.keymap.set({ 'i', 's' }, '<S-Tab>', function() vim.snippet.jump(-1) end, { buffer = true }) - ]]) end) after_each(clear) @@ -61,7 +58,13 @@ describe('vim.snippet', function() end) it('adds indentation based on the start of snippet lines', function() + local curbuf = api.nvim_get_current_buf() + test_expand_success({ 'if $1 then', ' $0', 'end' }, { 'if then', ' ', 'end' }) + + -- Regression test: #29658 + api.nvim_buf_set_lines(curbuf, 0, -1, false, {}) + test_expand_success({ '${1:foo^bar}\n' }, { 'foo^bar', '' }) end) it('replaces tabs with spaces when expandtab is set', function() @@ -286,4 +289,24 @@ describe('vim.snippet', function() ]] ) end) + + it('restores snippet navigation keymaps', function() + -- Create a buffer keymap in insert mode that deletes all lines. + local curbuf = api.nvim_get_current_buf() + exec_lua('vim.api.nvim_buf_set_keymap(..., "i", "<Tab>", "<cmd>normal ggdG<cr>", {})', curbuf) + + test_expand_success({ 'var $1 = $2' }, { 'var = ' }) + + -- While the snippet is active, <Tab> should navigate between tabstops. + feed('x') + poke_eventloop() + feed('<Tab>0') + eq({ 'var x = 0' }, buf_lines(0)) + + exec_lua('vim.snippet.stop()') + + -- After exiting the snippet, the buffer keymap should be restored. + feed('<Esc>O<cr><Tab>') + eq({ '' }, buf_lines(0)) + end) end) diff --git a/test/functional/lua/system_spec.lua b/test/functional/lua/system_spec.lua index e72a009d2e..482bfcf1a9 100644 --- a/test/functional/lua/system_spec.lua +++ b/test/functional/lua/system_spec.lua @@ -6,10 +6,8 @@ local exec_lua = n.exec_lua local eq = t.eq local function system_sync(cmd, opts) - return exec_lua( - [[ - local cmd, opts = ... - local obj = vim.system(...) + return exec_lua(function() + local obj = vim.system(cmd, opts) if opts.timeout then -- Minor delay before calling wait() so the timeout uv timer can have a headstart over the @@ -24,16 +22,11 @@ local function system_sync(cmd, opts) assert(not proc, 'process still exists') return res - ]], - cmd, - opts - ) + end) end local function system_async(cmd, opts) - return exec_lua( - [[ - local cmd, opts = ... + return exec_lua(function() _G.done = false local obj = vim.system(cmd, opts, function(obj) _G.done = true @@ -51,10 +44,7 @@ local function system_async(cmd, opts) assert(not proc, 'process still exists') return _G.ret - ]], - cmd, - opts - ) + end) end describe('vim.system', function() @@ -84,7 +74,7 @@ describe('vim.system', function() end it('kill processes', function() - exec_lua([[ + exec_lua(function() local signal local cmd = vim.system({ 'cat', '-' }, { stdin = true }, function(r) signal = r.signal @@ -104,19 +94,21 @@ describe('vim.system', function() assert(not proc, 'process still exists') assert(signal == 2) - ]]) + end) end) it('SystemObj:wait() does not process non-fast events #27292', function() eq( false, - exec_lua([[ + exec_lua(function() _G.processed = false local cmd = vim.system({ 'sleep', '1' }) - vim.schedule(function() _G.processed = true end) + vim.schedule(function() + _G.processed = true + end) cmd:wait() return _G.processed - ]]) + end) ) eq(true, exec_lua([[return _G.processed]])) end) diff --git a/test/functional/lua/text_spec.lua b/test/functional/lua/text_spec.lua index 9e77953c8c..be471bfd62 100644 --- a/test/functional/lua/text_spec.lua +++ b/test/functional/lua/text_spec.lua @@ -20,5 +20,11 @@ describe('vim.text', function() eq(input, vim.text.hexdecode(output)) end end) + + it('works with very large strings', function() + local input, output = string.rep('😂', 2 ^ 16), string.rep('F09F9882', 2 ^ 16) + eq(output, vim.text.hexencode(input)) + eq(input, vim.text.hexdecode(output)) + end) end) end) diff --git a/test/functional/lua/thread_spec.lua b/test/functional/lua/thread_spec.lua index 780057b580..cbf23517dc 100644 --- a/test/functional/lua/thread_spec.lua +++ b/test/functional/lua/thread_spec.lua @@ -18,13 +18,6 @@ describe('thread', function() clear() screen = Screen.new(50, 10) screen:attach() - screen:set_default_attr_ids({ - [1] = { bold = true, foreground = Screen.colors.Blue1 }, - [2] = { bold = true, reverse = true }, - [3] = { foreground = Screen.colors.Grey100, background = Screen.colors.Red }, - [4] = { bold = true, foreground = Screen.colors.SeaGreen4 }, - [5] = { bold = true }, - }) end) it('entry func is executed in protected mode', function() @@ -38,10 +31,10 @@ describe('thread', function() screen:expect([[ | {1:~ }|*5 - {2: }| - {3:Error in luv thread:} | - {3:[string "<nvim>"]:2: Error in thread entry func} | - {4:Press ENTER or type command to continue}^ | + {3: }| + {9:Error in luv thread:} | + {9:[string "<nvim>"]:2: Error in thread entry func} | + {6:Press ENTER or type command to continue}^ | ]]) feed('<cr>') assert_alive() @@ -65,10 +58,10 @@ describe('thread', function() screen:expect([[ | {1:~ }|*5 - {2: }| - {3:Error in luv callback, thread:} | - {3:[string "<nvim>"]:6: Error in thread callback} | - {4:Press ENTER or type command to continue}^ | + {3: }| + {9:Error in luv callback, thread:} | + {9:[string "<nvim>"]:6: Error in thread callback} | + {6:Press ENTER or type command to continue}^ | ]]) feed('<cr>') assert_alive() @@ -265,13 +258,6 @@ describe('threadpool', function() it('with invalid return value', function() local screen = Screen.new(50, 10) screen:attach() - screen:set_default_attr_ids({ - [1] = { bold = true, foreground = Screen.colors.Blue1 }, - [2] = { bold = true, reverse = true }, - [3] = { foreground = Screen.colors.Grey100, background = Screen.colors.Red }, - [4] = { bold = true, foreground = Screen.colors.SeaGreen4 }, - [5] = { bold = true }, - }) exec_lua [[ local work = vim.uv.new_work(function() return {} end, function() end) @@ -281,10 +267,10 @@ describe('threadpool', function() screen:expect([[ | {1:~ }|*5 - {2: }| - {3:Error in luv thread:} | - {3:Error: thread arg not support type 'table' at 1} | - {4:Press ENTER or type command to continue}^ | + {3: }| + {9:Error in luv thread:} | + {9:Error: thread arg not support type 'table' at 1} | + {6:Press ENTER or type command to continue}^ | ]]) end) diff --git a/test/functional/lua/ui_event_spec.lua b/test/functional/lua/ui_event_spec.lua index 1e80c88403..0a6deaa41c 100644 --- a/test/functional/lua/ui_event_spec.lua +++ b/test/functional/lua/ui_event_spec.lua @@ -37,6 +37,9 @@ describe('vim.ui_attach', function() [2] = { bold = true }, [3] = { background = Screen.colors.Grey }, [4] = { background = Screen.colors.LightMagenta }, + [5] = { reverse = true }, + [6] = { reverse = true, bold = true }, + [7] = { background = Screen.colors.Yellow1 }, }) screen:attach() end) @@ -188,6 +191,56 @@ describe('vim.ui_attach', function() feed('version<CR><CR>v<Esc>') n.assert_alive() end) + + it("preserved 'incsearch/command' screen state after :redraw from ext_cmdline", function() + exec_lua([[ + vim.cmd.norm('ifoobar') + vim.cmd('1split cmdline') + local buf = vim.api.nvim_get_current_buf() + vim.cmd.wincmd('p') + vim.ui_attach(ns, { ext_cmdline = true }, function(event, ...) + if event == 'cmdline_show' then + local content = select(1, ...) + vim.api.nvim_buf_set_lines(buf, -2, -1, false, {content[1][2]}) + vim.cmd('redraw') + end + return true + end) + ]]) + -- Updates a cmdline window + feed(':cmdline') + screen:expect({ + grid = [[ + cmdline | + {5:cmdline [+] }| + fooba^r | + {6:[No Name] [+] }| + | + ]], + }) + -- Does not clear 'incsearch' highlighting + feed('<Esc>/foo') + screen:expect({ + grid = [[ + foo | + {5:cmdline [+] }| + {5:foo}ba^r | + {6:[No Name] [+] }| + | + ]], + }) + -- Shows new cmdline state during 'inccommand' + feed('<Esc>:%s/bar/baz') + screen:expect({ + grid = [[ + %s/bar/baz | + {5:cmdline [+] }| + foo{7:ba^z} | + {6:[No Name] [+] }| + | + ]], + }) + end) end) describe('vim.ui_attach', function() diff --git a/test/functional/lua/ui_spec.lua b/test/functional/lua/ui_spec.lua index d69e893c96..d5eede2885 100644 --- a/test/functional/lua/ui_spec.lua +++ b/test/functional/lua/ui_spec.lua @@ -157,5 +157,28 @@ describe('vim.ui', function() exec_lua [[local _, err = vim.ui.open('foo') ; return err]] ) end) + + it('opt.cmd #29490', function() + t.matches( + 'ENOENT: no such file or directory', + t.pcall_err(exec_lua, function() + vim.ui.open('foo', { cmd = { 'non-existent-tool' } }) + end) + ) + + eq( + { + code = 0, + signal = 0, + stderr = '', + stdout = 'arg1=arg1;arg2=https://example.com;', + }, + exec_lua(function(cmd_) + local cmd, err = vim.ui.open('https://example.com', { cmd = cmd_ }) + assert(cmd and not err) + return cmd:wait() + end, { n.testprg('printargs-test'), 'arg1' }) + ) + end) end) end) diff --git a/test/functional/lua/uri_spec.lua b/test/functional/lua/uri_spec.lua index 553afb35d3..258b96bc43 100644 --- a/test/functional/lua/uri_spec.lua +++ b/test/functional/lua/uri_spec.lua @@ -217,7 +217,7 @@ describe('URI methods', function() ]], file ) - local expected_uri = 'file:///' .. file:gsub('\\', '/') + local expected_uri = 'file:///' .. t.fix_slashes(file) eq(expected_uri, exec_lua(test_case)) os.remove(file) end) diff --git a/test/functional/lua/version_spec.lua b/test/functional/lua/version_spec.lua index 4ce8fb8dfe..c172555091 100644 --- a/test/functional/lua/version_spec.lua +++ b/test/functional/lua/version_spec.lua @@ -112,6 +112,10 @@ describe('version', function() assert(vim.version.range('1.2.3-alpha'):has('1.2.3-alpha')) assert(not vim.version.range('1.2.3-alpha'):has('1.2.3-beta')) end) + + it('returns nil with empty version', function() + eq(vim.version.parse(''), nil) + end) end) describe('cmp()', function() diff --git a/test/functional/lua/vim_spec.lua b/test/functional/lua/vim_spec.lua index c8f94c6ffa..3c65ec664e 100644 --- a/test/functional/lua/vim_spec.lua +++ b/test/functional/lua/vim_spec.lua @@ -135,19 +135,19 @@ describe('lua stdlib', function() -- See MAINTAIN.md for the soft/hard deprecation policy describe(('vim.deprecate prerel=%s,'):format(prerel or 'nil'), function() - local curver = exec_lua('return vim.version()') --[[@as {major:number, minor:number}]] - -- "0.10" or "0.10-dev+xxx" - local curstr = ('%s.%s%s'):format(curver.major, curver.minor, prerel or '') - -- "0.10" or "0.11" - local nextver = ('%s.%s'):format(curver.major, curver.minor + (prerel and 0 or 1)) - local was_removed = prerel and 'was removed' or 'will be removed' + local curver --- @type {major:number, minor:number} + + before_each(function() + curver = exec_lua('return vim.version()') + end) it('plugin=nil, same message skipped', function() + -- "0.10" or "0.10-dev+xxx" + local curstr = ('%s.%s%s'):format(curver.major, curver.minor, prerel or '') eq( - dedent( - [[ - foo.bar() is deprecated. Run ":checkhealth vim.deprecated" for more information]] - ):format(curstr), + ([[foo.bar() is deprecated. Run ":checkhealth vim.deprecated" for more information]]):format( + curstr + ), exec_lua('return vim.deprecate(...)', 'foo.bar()', 'zub.wooo{ok=yay}', curstr) ) -- Same message as above; skipped this time. @@ -162,6 +162,10 @@ describe('lua stdlib', function() end) it('plugin=nil, show error if hard-deprecated', function() + -- "0.10" or "0.11" + local nextver = ('%s.%s'):format(curver.major, curver.minor + (prerel and 0 or 1)) + + local was_removed = prerel and 'was removed' or 'will be removed' eq( dedent( [[ @@ -173,8 +177,7 @@ describe('lua stdlib', function() it('plugin=nil, to be deleted in the next major version (1.0)', function() eq( - dedent [[ - foo.baz() is deprecated. Run ":checkhealth vim.deprecated" for more information]], + [[foo.baz() is deprecated. Run ":checkhealth vim.deprecated" for more information]], exec_lua [[ return vim.deprecate('foo.baz()', nil, '1.0') ]] ) end) @@ -535,12 +538,6 @@ describe('lua stdlib', function() matches('big failure\nvery async', remove_trace(eval('v:errmsg'))) local screen = Screen.new(60, 5) - screen:set_default_attr_ids({ - [1] = { bold = true, foreground = Screen.colors.Blue1 }, - [2] = { bold = true, reverse = true }, - [3] = { foreground = Screen.colors.Grey100, background = Screen.colors.Red }, - [4] = { bold = true, foreground = Screen.colors.SeaGreen4 }, - }) screen:attach() screen:expect { grid = [[ @@ -559,11 +556,11 @@ describe('lua stdlib', function() ]]) screen:expect { grid = [[ - {3:stack traceback:} | - {3: [C]: in function 'nvim_command'} | - {3: [string "<nvim>"]:2: in function <[string "<nvim>"]:}| - {3:1>} | - {4:Press ENTER or type command to continue}^ | + {9:stack traceback:} | + {9: [C]: in function 'nvim_command'} | + {9: [string "<nvim>"]:2: in function <[string "<nvim>"]:}| + {9:1>} | + {6:Press ENTER or type command to continue}^ | ]], } end) @@ -1062,7 +1059,7 @@ describe('lua stdlib', function() local a = { a = {[2] = 3} } local b = { a = {[3] = 3} } local c = vim.tbl_deep_extend("force", a, b) - return vim.deep_equal(c, {a = {[3] = 3}}) + return vim.deep_equal(c, {a = {[2] = 3, [3] = 3}}) ]])) eq( @@ -1074,34 +1071,28 @@ describe('lua stdlib', function() ]]) ) - matches( - 'invalid "behavior": nil', - pcall_err( - exec_lua, - [[ - return vim.tbl_deep_extend() - ]] - ) - ) + ok(exec_lua([[ + local a = { sub = { 'a', 'b' } } + local b = { sub = { 'b', 'c' } } + local c = vim.tbl_deep_extend('force', a, b) + return vim.deep_equal(c, { sub = { 'b', 'c' } }) + ]])) + + matches('invalid "behavior": nil', pcall_err(exec_lua, [[return vim.tbl_deep_extend()]])) matches( 'wrong number of arguments %(given 1, expected at least 3%)', - pcall_err( - exec_lua, - [[ - return vim.tbl_deep_extend("keep") - ]] - ) + pcall_err(exec_lua, [[return vim.tbl_deep_extend("keep")]]) ) matches( 'wrong number of arguments %(given 2, expected at least 3%)', - pcall_err( - exec_lua, - [[ - return vim.tbl_deep_extend("keep", {}) - ]] - ) + pcall_err(exec_lua, [[return vim.tbl_deep_extend("keep", {})]]) + ) + + matches( + 'after the second argument%: expected table, got number', + pcall_err(exec_lua, [[return vim.tbl_deep_extend("keep", {}, 42)]]) ) end) @@ -1200,8 +1191,7 @@ describe('lua stdlib', function() ]]) eq(true, exec_lua([[return next(vim.fn.FooFunc(3)) == nil ]])) eq(3, eval('g:test')) - -- compat: nvim_call_function uses "special" value for empty dict - eq(true, exec_lua([[return next(vim.api.nvim_call_function("FooFunc", {5})) == true ]])) + eq(true, exec_lua([[return vim.tbl_isempty(vim.api.nvim_call_function("FooFunc", {5}))]])) eq(5, eval('g:test')) eq({ 2, 'foo', true }, exec_lua([[return vim.fn.VarArg(2, "foo", true)]])) @@ -1239,7 +1229,7 @@ describe('lua stdlib', function() ) end) - it('vim.fn should error when calling API function', function() + it('vim.fn errors when calling API function', function() matches( 'Tried to call API function with vim.fn: use vim.api.nvim_get_current_line instead', pcall_err(exec_lua, 'vim.fn.nvim_get_current_line()') @@ -1314,12 +1304,6 @@ describe('lua stdlib', function() end) local screen = Screen.new(50, 7) - screen:set_default_attr_ids({ - [1] = { bold = true, foreground = Screen.colors.Blue1 }, - [2] = { bold = true, reverse = true }, - [3] = { foreground = Screen.colors.Grey100, background = Screen.colors.Red }, - [4] = { bold = true, foreground = Screen.colors.SeaGreen4 }, - }) screen:attach() exec_lua([[ timer = vim.uv.new_timer() @@ -1332,13 +1316,13 @@ describe('lua stdlib', function() ]]) screen:expect { grid = [[ - {3:[string "<nvim>"]:6: E5560: rpcrequest must not be}| - {3: called in a lua loop callback} | - {3:stack traceback:} | - {3: [C]: in function 'rpcrequest'} | - {3: [string "<nvim>"]:6: in function <[string }| - {3:"<nvim>"]:2>} | - {4:Press ENTER or type command to continue}^ | + {9:[string "<nvim>"]:6: E5560: rpcrequest must not be}| + {9: called in a lua loop callback} | + {9:stack traceback:} | + {9: [C]: in function 'rpcrequest'} | + {9: [string "<nvim>"]:6: in function <[string }| + {9:"<nvim>"]:2>} | + {6:Press ENTER or type command to continue}^ | ]], } feed('<cr>') @@ -1408,7 +1392,25 @@ describe('lua stdlib', function() exec_lua("vim.validate{arg1={{}, 't' }, arg2={ 'foo', 's' }}") exec_lua("vim.validate{arg1={2, function(a) return (a % 2) == 0 end, 'even number' }}") exec_lua("vim.validate{arg1={5, {'n', 's'} }, arg2={ 'foo', {'n', 's'} }}") - + vim.validate('arg1', 5, 'number') + vim.validate('arg1', '5', 'string') + vim.validate('arg1', { 5 }, 'table') + vim.validate('arg1', function() + return 5 + end, 'function') + vim.validate('arg1', nil, 'number', true) + vim.validate('arg1', nil, 'string', true) + vim.validate('arg1', nil, 'table', true) + vim.validate('arg1', nil, 'function', true) + + matches('arg1: expected number, got nil', pcall_err(vim.validate, 'arg1', nil, 'number')) + matches('arg1: expected string, got nil', pcall_err(vim.validate, 'arg1', nil, 'string')) + matches('arg1: expected table, got nil', pcall_err(vim.validate, 'arg1', nil, 'table')) + matches('arg1: expected function, got nil', pcall_err(vim.validate, 'arg1', nil, 'function')) + matches('arg1: expected string, got number', pcall_err(vim.validate, 'arg1', 5, 'string')) + matches('arg1: expected table, got number', pcall_err(vim.validate, 'arg1', 5, 'table')) + matches('arg1: expected function, got number', pcall_err(vim.validate, 'arg1', 5, 'function')) + matches('arg1: expected number, got string', pcall_err(vim.validate, 'arg1', '5', 'number')) matches('expected table, got number', pcall_err(exec_lua, "vim.validate{ 1, 'x' }")) matches('invalid type name: x', pcall_err(exec_lua, "vim.validate{ arg1={ 1, 'x' }}")) matches('invalid type name: 1', pcall_err(exec_lua, 'vim.validate{ arg1={ 1, 1 }}')) @@ -1472,6 +1474,60 @@ describe('lua stdlib', function() ]]) ) + eq( + { false, false }, + exec_lua([[ + local meta = { __call = {} } + assert(meta.__call) + local function new() + return setmetatable({}, meta) + end + local not_callable = new() + return { pcall(function() not_callable() end), vim.is_callable(not_callable) } + ]]) + ) + eq( + { false, false }, + exec_lua([[ + local function new() + return { __call = function()end } + end + local not_callable = new() + assert(not_callable.__call) + return { pcall(function() not_callable() end), vim.is_callable(not_callable) } + ]]) + ) + eq( + { false, false }, + exec_lua([[ + local meta = setmetatable( + { __index = { __call = function() end } }, + { __index = { __call = function() end } } + ) + assert(meta.__call) + local not_callable = setmetatable({}, meta) + assert(not_callable.__call) + return { pcall(function() not_callable() end), vim.is_callable(not_callable) } + ]]) + ) + eq( + { false, false }, + exec_lua([[ + local meta = setmetatable({ + __index = function() + return function() end + end, + }, { + __index = function() + return function() end + end, + }) + assert(meta.__call) + local not_callable = setmetatable({}, meta) + assert(not_callable.__call) + return { pcall(function() not_callable() end), vim.is_callable(not_callable) } + ]]) + ) eq(false, exec_lua('return vim.is_callable(1)')) eq(false, exec_lua("return vim.is_callable('foo')")) eq(false, exec_lua('return vim.is_callable({})')) @@ -1891,7 +1947,7 @@ describe('lua stdlib', function() eq(NIL, fn.luaeval 'vim.v.null') matches([[attempt to index .* nil value]], pcall_err(exec_lua, 'return vim.v[0].progpath')) eq('Key is read-only: count', pcall_err(exec_lua, [[vim.v.count = 42]])) - eq('Dictionary is locked', pcall_err(exec_lua, [[vim.v.nosuchvar = 42]])) + eq('Dict is locked', pcall_err(exec_lua, [[vim.v.nosuchvar = 42]])) eq('Key is fixed: errmsg', pcall_err(exec_lua, [[vim.v.errmsg = nil]])) exec_lua([[vim.v.errmsg = 'set by Lua']]) eq('set by Lua', eval('v:errmsg')) @@ -1919,16 +1975,12 @@ describe('lua stdlib', function() eq({ 1, 5 }, api.nvim_win_get_cursor(0)) local screen = Screen.new(60, 3) - screen:set_default_attr_ids({ - [0] = { bold = true, foreground = Screen.colors.Blue }, - [1] = { background = Screen.colors.Yellow }, - }) screen:attach() eq(1, eval('v:hlsearch')) screen:expect { grid = [[ - {1:foo} {1:^foo} {1:foo} | - {0:~ }| + {10:foo} {10:^foo} {10:foo} | + {1:~ }| | ]], } @@ -1937,7 +1989,7 @@ describe('lua stdlib', function() screen:expect { grid = [[ foo ^foo foo | - {0:~ }| + {1:~ }| | ]], } @@ -1945,8 +1997,8 @@ describe('lua stdlib', function() eq(1, eval('v:hlsearch')) screen:expect { grid = [[ - {1:foo} {1:^foo} {1:foo} | - {0:~ }| + {10:foo} {10:^foo} {10:foo} | + {1:~ }| | ]], } @@ -2005,13 +2057,17 @@ describe('lua stdlib', function() vim.cmd "enew" ]] eq(100, fn.luaeval 'vim.wo.scrolloff') + + matches('only bufnr=0 is supported', pcall_err(exec_lua, 'vim.wo[0][10].signcolumn = "no"')) + + matches('only bufnr=0 is supported', pcall_err(exec_lua, 'local a = vim.wo[0][10].signcolumn')) end) describe('vim.opt', function() -- TODO: We still need to write some tests for optlocal, opt and then getting the options -- Probably could also do some stuff with getting things from viml side as well to confirm behavior is the same. - it('should allow setting number values', function() + it('allows setting number values', function() local scrolloff = exec_lua [[ vim.opt.scrolloff = 10 return vim.o.scrolloff @@ -2019,7 +2075,7 @@ describe('lua stdlib', function() eq(10, scrolloff) end) - pending('should handle STUPID window things', function() + pending('handles STUPID window things', function() local result = exec_lua [[ local result = {} @@ -2032,7 +2088,7 @@ describe('lua stdlib', function() eq({}, result) end) - it('should allow setting tables', function() + it('allows setting tables', function() local wildignore = exec_lua [[ vim.opt.wildignore = { 'hello', 'world' } return vim.o.wildignore @@ -2040,7 +2096,7 @@ describe('lua stdlib', function() eq('hello,world', wildignore) end) - it('should allow setting tables with shortnames', function() + it('allows setting tables with shortnames', function() local wildignore = exec_lua [[ vim.opt.wig = { 'hello', 'world' } return vim.o.wildignore @@ -2048,7 +2104,7 @@ describe('lua stdlib', function() eq('hello,world', wildignore) end) - it('should error when you attempt to set string values to numeric options', function() + it('errors when you attempt to set string values to numeric options', function() local result = exec_lua [[ return { pcall(function() vim.opt.textwidth = 'hello world' end) @@ -2058,7 +2114,7 @@ describe('lua stdlib', function() eq(false, result[1]) end) - it('should error when you attempt to setlocal a global value', function() + it('errors when you attempt to setlocal a global value', function() local result = exec_lua [[ return pcall(function() vim.opt_local.clipboard = "hello" end) ]] @@ -2066,7 +2122,7 @@ describe('lua stdlib', function() eq(false, result) end) - it('should allow you to set boolean values', function() + it('allows you to set boolean values', function() eq( { true, false, true }, exec_lua [[ @@ -2086,7 +2142,7 @@ describe('lua stdlib', function() ) end) - it('should change current buffer values and defaults for global local values', function() + it('changes current buffer values and defaults for global local values', function() local result = exec_lua [[ local result = {} @@ -2125,7 +2181,7 @@ describe('lua stdlib', function() eq('', result[8]) end) - it('should allow you to retrieve window opts even if they have not been set', function() + it('allows you to retrieve window opts even if they have not been set', function() local result = exec_lua [[ local result = {} table.insert(result, vim.opt.number:get()) @@ -2140,7 +2196,7 @@ describe('lua stdlib', function() eq({ false, false, true, true }, result) end) - it('should allow all sorts of string manipulation', function() + it('allows all sorts of string manipulation', function() eq( { 'hello', 'hello world', 'start hello world' }, exec_lua [[ @@ -2161,7 +2217,7 @@ describe('lua stdlib', function() end) describe('option:get()', function() - it('should work for boolean values', function() + it('works for boolean values', function() eq( false, exec_lua [[ @@ -2171,7 +2227,7 @@ describe('lua stdlib', function() ) end) - it('should work for number values', function() + it('works for number values', function() local tabstop = exec_lua [[ vim.opt.tabstop = 10 return vim.opt.tabstop:get() @@ -2180,7 +2236,7 @@ describe('lua stdlib', function() eq(10, tabstop) end) - it('should work for string values', function() + it('works for string values', function() eq( 'hello world', exec_lua [[ @@ -2190,7 +2246,7 @@ describe('lua stdlib', function() ) end) - it('should work for set type flaglists', function() + it('works for set type flaglists', function() local formatoptions = exec_lua [[ vim.opt.formatoptions = 'tcro' return vim.opt.formatoptions:get() @@ -2200,7 +2256,7 @@ describe('lua stdlib', function() eq(true, not formatoptions.q) end) - it('should work for set type flaglists', function() + it('works for set type flaglists', function() local formatoptions = exec_lua [[ vim.opt.formatoptions = { t = true, c = true, r = true, o = true } return vim.opt.formatoptions:get() @@ -2210,7 +2266,7 @@ describe('lua stdlib', function() eq(true, not formatoptions.q) end) - it('should work for array list type options', function() + it('works for array list type options', function() local wildignore = exec_lua [[ vim.opt.wildignore = "*.c,*.o,__pycache__" return vim.opt.wildignore:get() @@ -2220,7 +2276,7 @@ describe('lua stdlib', function() eq('*.c', wildignore[1]) end) - it('should work for options that are both commalist and flaglist', function() + it('works for options that are both commalist and flaglist', function() local result = exec_lua [[ vim.opt.whichwrap = "b,s" return vim.opt.whichwrap:get() @@ -2236,7 +2292,7 @@ describe('lua stdlib', function() eq({ b = true, h = true }, result) end) - it('should work for key-value pair options', function() + it('works for key-value pair options', function() local listchars = exec_lua [[ vim.opt.listchars = "tab:> ,space:_" return vim.opt.listchars:get() @@ -2248,7 +2304,7 @@ describe('lua stdlib', function() }, listchars) end) - it('should allow you to add numeric options', function() + it('allows you to add numeric options', function() eq( 16, exec_lua [[ @@ -2259,7 +2315,7 @@ describe('lua stdlib', function() ) end) - it('should allow you to subtract numeric options', function() + it('allows you to subtract numeric options', function() eq( 2, exec_lua [[ @@ -2272,7 +2328,7 @@ describe('lua stdlib', function() end) describe('key:value style options', function() - it('should handle dictionary style', function() + it('handles dict style', function() local listchars = exec_lua [[ vim.opt.listchars = { eol = "~", @@ -2284,7 +2340,7 @@ describe('lua stdlib', function() eq('eol:~,space:.', listchars) end) - it('should allow adding dictionary style', function() + it('allows adding dict style', function() local listchars = exec_lua [[ vim.opt.listchars = { eol = "~", @@ -2299,7 +2355,7 @@ describe('lua stdlib', function() eq('eol:~,space:-', listchars) end) - it('should allow adding dictionary style', function() + it('allows adding dict style', function() local listchars = exec_lua [[ vim.opt.listchars = { eol = "~", @@ -2313,7 +2369,7 @@ describe('lua stdlib', function() eq('eol:~,space:_', listchars) end) - it('should allow completely new keys', function() + it('allows completely new keys', function() local listchars = exec_lua [[ vim.opt.listchars = { eol = "~", @@ -2327,7 +2383,7 @@ describe('lua stdlib', function() eq('eol:~,space:.,tab:>>>', listchars) end) - it('should allow subtracting dictionary style', function() + it('allows subtracting dict style', function() local listchars = exec_lua [[ vim.opt.listchars = { eol = "~", @@ -2341,7 +2397,7 @@ describe('lua stdlib', function() eq('eol:~', listchars) end) - it('should allow subtracting dictionary style', function() + it('allows subtracting dict style', function() local listchars = exec_lua [[ vim.opt.listchars = { eol = "~", @@ -2355,7 +2411,7 @@ describe('lua stdlib', function() eq('', listchars) end) - it('should allow subtracting dictionary style multiple times', function() + it('allows subtracting dict style multiple times', function() local listchars = exec_lua [[ vim.opt.listchars = { eol = "~", @@ -2369,7 +2425,7 @@ describe('lua stdlib', function() eq('eol:~', listchars) end) - it('should allow adding a key:value string to a listchars', function() + it('allows adding a key:value string to a listchars', function() local listchars = exec_lua [[ vim.opt.listchars = { eol = "~", @@ -2383,7 +2439,7 @@ describe('lua stdlib', function() eq('eol:~,space:.,tab:>~', listchars) end) - it('should allow prepending a key:value string to a listchars', function() + it('allows prepending a key:value string to a listchars', function() local listchars = exec_lua [[ vim.opt.listchars = { eol = "~", @@ -2398,7 +2454,7 @@ describe('lua stdlib', function() end) end) - it('should automatically set when calling remove', function() + it('automatically sets when calling remove', function() eq( 'foo,baz', exec_lua [[ @@ -2410,7 +2466,7 @@ describe('lua stdlib', function() ) end) - it('should automatically set when calling remove with a table', function() + it('automatically sets when calling remove with a table', function() eq( 'foo', exec_lua [[ @@ -2422,7 +2478,7 @@ describe('lua stdlib', function() ) end) - it('should automatically set when calling append', function() + it('automatically sets when calling append', function() eq( 'foo,bar,baz,bing', exec_lua [[ @@ -2434,7 +2490,7 @@ describe('lua stdlib', function() ) end) - it('should automatically set when calling append with a table', function() + it('automatically sets when calling append with a table', function() eq( 'foo,bar,baz,bing,zap', exec_lua [[ @@ -2446,7 +2502,7 @@ describe('lua stdlib', function() ) end) - it('should allow adding tables', function() + it('allows adding tables', function() local wildignore = exec_lua [[ vim.opt.wildignore = 'foo' return vim.o.wildignore @@ -2460,7 +2516,7 @@ describe('lua stdlib', function() eq('foo,bar,baz', wildignore) end) - it('should handle adding duplicates', function() + it('handles adding duplicates', function() local wildignore = exec_lua [[ vim.opt.wildignore = 'foo' return vim.o.wildignore @@ -2480,7 +2536,7 @@ describe('lua stdlib', function() eq('foo,bar,baz', wildignore) end) - it('should allow adding multiple times', function() + it('allows adding multiple times', function() local wildignore = exec_lua [[ vim.opt.wildignore = 'foo' vim.opt.wildignore = vim.opt.wildignore + 'bar' + 'baz' @@ -2489,7 +2545,7 @@ describe('lua stdlib', function() eq('foo,bar,baz', wildignore) end) - it('should remove values when you use minus', function() + it('removes values when you use minus', function() local wildignore = exec_lua [[ vim.opt.wildignore = 'foo' return vim.o.wildignore @@ -2509,7 +2565,7 @@ describe('lua stdlib', function() eq('foo,baz', wildignore) end) - it('should prepend values when using ^', function() + it('prepends values when using ^', function() local wildignore = exec_lua [[ vim.opt.wildignore = 'foo' vim.opt.wildignore = vim.opt.wildignore ^ 'first' @@ -2524,7 +2580,7 @@ describe('lua stdlib', function() eq('super_first,first,foo', wildignore) end) - it('should not remove duplicates from wildmode: #14708', function() + it('does not remove duplicates from wildmode: #14708', function() local wildmode = exec_lua [[ vim.opt.wildmode = {"full", "list", "full"} return vim.o.wildmode @@ -2534,7 +2590,7 @@ describe('lua stdlib', function() end) describe('option types', function() - it('should allow to set option with numeric value', function() + it('allows to set option with numeric value', function() eq( 4, exec_lua [[ @@ -2583,7 +2639,7 @@ describe('lua stdlib', function() ) end) - it('should allow to set option with boolean value', function() + it('allows to set option with boolean value', function() eq( true, exec_lua [[ @@ -2632,7 +2688,7 @@ describe('lua stdlib', function() ) end) - it('should allow to set option with array or string value', function() + it('allows to set option with array or string value', function() eq( 'indent,eol,start', exec_lua [[ @@ -2679,7 +2735,7 @@ describe('lua stdlib', function() ) end) - it('should allow set option with map or string value', function() + it('allows set option with map or string value', function() eq( 'eol:~,space:.', exec_lua [[ @@ -2729,7 +2785,7 @@ describe('lua stdlib', function() ) end) - it('should allow set option with set or string value', function() + it('allows set option with set or string value', function() local ww = exec_lua [[ vim.opt.whichwrap = { b = true, @@ -3179,11 +3235,11 @@ describe('lua stdlib', function() ]] end) - it('should run from lua', function() + it('runs from lua', function() exec_lua [[vim.wait(100, function() return true end)]] end) - it('should wait the expected time if false', function() + it('waits the expected time if false', function() eq( { time = true, wait_result = { false, -1 } }, exec_lua [[ @@ -3199,7 +3255,7 @@ describe('lua stdlib', function() ) end) - it('should not block other events', function() + it('does not block other events', function() eq( { time = true, wait_result = true }, exec_lua [[ @@ -3224,7 +3280,7 @@ describe('lua stdlib', function() ) end) - it('should not process non-fast events when commanded', function() + it('does not process non-fast events when commanded', function() eq( { wait_result = false }, exec_lua [[ @@ -3247,7 +3303,7 @@ describe('lua stdlib', function() ) end) - it('should work with vim.defer_fn', function() + it('works with vim.defer_fn', function() eq( { time = true, wait_result = true }, exec_lua [[ @@ -3264,7 +3320,7 @@ describe('lua stdlib', function() ) end) - it('should not crash when callback errors', function() + it('does not crash when callback errors', function() local result = exec_lua [[ return {pcall(function() vim.wait(1000, function() error("As Expected") end) end)} ]] @@ -3280,7 +3336,7 @@ describe('lua stdlib', function() ) end) - it('should allow waiting with no callback, explicit', function() + it('allows waiting with no callback, explicit', function() eq( true, exec_lua [[ @@ -3291,7 +3347,7 @@ describe('lua stdlib', function() ) end) - it('should allow waiting with no callback, implicit', function() + it('allows waiting with no callback, implicit', function() eq( true, exec_lua [[ @@ -3302,7 +3358,7 @@ describe('lua stdlib', function() ) end) - it('should call callbacks exactly once if they return true immediately', function() + it('calls callbacks exactly once if they return true immediately', function() eq( true, exec_lua [[ @@ -3316,7 +3372,7 @@ describe('lua stdlib', function() ) end) - it('should call callbacks few times with large `interval`', function() + it('calls callbacks few times with large `interval`', function() eq( true, exec_lua [[ @@ -3327,7 +3383,7 @@ describe('lua stdlib', function() ) end) - it('should play nice with `not` when fails', function() + it('plays nice with `not` when fails', function() eq( true, exec_lua [[ @@ -3340,7 +3396,7 @@ describe('lua stdlib', function() ) end) - it('should play nice with `if` when success', function() + it('plays nice with `if` when success', function() eq( true, exec_lua [[ @@ -3353,7 +3409,7 @@ describe('lua stdlib', function() ) end) - it('should return immediately with false if timeout is 0', function() + it('returns immediately with false if timeout is 0', function() eq( { false, -1 }, exec_lua [[ @@ -3364,7 +3420,7 @@ describe('lua stdlib', function() ) end) - it('should work with tables with __call', function() + it('works with tables with __call', function() eq( true, exec_lua [[ @@ -3374,7 +3430,7 @@ describe('lua stdlib', function() ) end) - it('should work with tables with __call that change', function() + it('works with tables with __call that change', function() eq( true, exec_lua [[ @@ -3391,7 +3447,7 @@ describe('lua stdlib', function() ) end) - it('should not work with negative intervals', function() + it('fails with negative intervals', function() local pcall_result = exec_lua [[ return pcall(function() vim.wait(1000, function() return false end, -1) end) ]] @@ -3399,7 +3455,7 @@ describe('lua stdlib', function() eq(false, pcall_result) end) - it('should not work with weird intervals', function() + it('fails with weird intervals', function() local pcall_result = exec_lua [[ return pcall(function() vim.wait(1000, function() return false end, 'a string value') end) ]] @@ -3442,7 +3498,7 @@ describe('lua stdlib', function() end) end) - it('should not run in fast callbacks #26122', function() + it('fails in fast callbacks #26122', function() local screen = Screen.new(80, 10) screen:attach() exec_lua([[ @@ -3462,15 +3518,11 @@ describe('lua stdlib', function() it('vim.notify_once', function() local screen = Screen.new(60, 5) - screen:set_default_attr_ids({ - [0] = { bold = true, foreground = Screen.colors.Blue }, - [1] = { foreground = Screen.colors.Red }, - }) screen:attach() screen:expect { grid = [[ ^ | - {0:~ }|*3 + {1:~ }|*3 | ]], } @@ -3478,15 +3530,15 @@ describe('lua stdlib', function() screen:expect { grid = [[ ^ | - {0:~ }|*3 - {1:I'll only tell you this once...} | + {1:~ }|*3 + {19:I'll only tell you this once...} | ]], } feed('<C-l>') screen:expect { grid = [[ ^ | - {0:~ }|*3 + {1:~ }|*3 | ]], } @@ -3663,10 +3715,6 @@ describe('lua stdlib', function() it('updates ruler if cursor moved', function() -- Fixed for win_execute in vim-patch:8.1.2124, but should've applied to nvim_win_call too! local screen = Screen.new(30, 5) - screen:set_default_attr_ids { - [1] = { reverse = true }, - [2] = { bold = true, reverse = true }, - } screen:attach() exec_lua [[ _G.api = vim.api @@ -3681,9 +3729,9 @@ describe('lua stdlib', function() ]] screen:expect [[ 19 | - {1:[No Name] [+] 20,1 3%}| - ^19 | {2:[No Name] [+] 20,1 3%}| + ^19 | + {3:[No Name] [+] 20,1 3%}| | ]] exec_lua [[ @@ -3692,9 +3740,9 @@ describe('lua stdlib', function() ]] screen:expect [[ 99 | - {1:[No Name] [+] 100,1 19%}| + {2:[No Name] [+] 100,1 19%}| ^19 | - {2:[No Name] [+] 20,1 3%}| + {3:[No Name] [+] 20,1 3%}| | ]] end) @@ -3810,13 +3858,6 @@ describe('lua stdlib', function() it('vim.lua_omnifunc', function() local screen = Screen.new(60, 5) - screen:set_default_attr_ids { - [1] = { foreground = Screen.colors.Blue1, bold = true }, - [2] = { background = Screen.colors.WebGray }, - [3] = { background = Screen.colors.LightMagenta }, - [4] = { bold = true }, - [5] = { foreground = Screen.colors.SeaGreen, bold = true }, - } screen:attach() command [[ set omnifunc=v:lua.vim.lua_omnifunc ]] @@ -3826,10 +3867,10 @@ describe('lua stdlib', function() screen:expect { grid = [[ vim.inspect^ | - {1:~ }{2: inspect }{1: }| - {1:~ }{3: inspect_pos }{1: }| + {1:~ }{12: inspect }{1: }| + {1:~ }{4: inspect_pos }{1: }| {1:~ }| - {4:-- Omni completion (^O^N^P) }{5:match 1 of 2} | + {5:-- Omni completion (^O^N^P) }{6:match 1 of 2} | ]], } end) @@ -4002,7 +4043,36 @@ end) describe('vim.keymap', function() before_each(clear) - it('can make a mapping', function() + it('validates', function() + matches( + 'mode: expected string|table, got number', + pcall_err(exec_lua, [[vim.keymap.set(42, 'x', print)]]) + ) + + matches( + 'rhs: expected string|function, got nil', + pcall_err(exec_lua, [[vim.keymap.set('n', 'x')]]) + ) + + matches( + 'lhs: expected string, got table', + pcall_err(exec_lua, [[vim.keymap.set('n', {}, print)]]) + ) + + matches( + 'opts: expected table, got function', + pcall_err(exec_lua, [[vim.keymap.set({}, 'x', 42, function() end)]]) + ) + + matches( + 'rhs: expected string|function, got number', + pcall_err(exec_lua, [[vim.keymap.set('z', 'x', 42)]]) + ) + + matches('Invalid mode shortname: "z"', pcall_err(exec_lua, [[vim.keymap.set('z', 'x', 'y')]])) + end) + + it('mapping', function() eq( 0, exec_lua [[ @@ -4017,7 +4087,7 @@ describe('vim.keymap', function() eq(1, exec_lua [[return GlobalCount]]) end) - it('can make an expr mapping', function() + it('expr mapping', function() exec_lua [[ vim.keymap.set('n', 'aa', function() return '<Insert>π<C-V><M-π>foo<lt><Esc>' end, {expr = true}) ]] @@ -4027,7 +4097,7 @@ describe('vim.keymap', function() eq({ 'π<M-π>foo<' }, api.nvim_buf_get_lines(0, 0, -1, false)) end) - it('can overwrite a mapping', function() + it('overwrite a mapping', function() eq( 0, exec_lua [[ @@ -4050,7 +4120,7 @@ describe('vim.keymap', function() eq(0, exec_lua [[return GlobalCount]]) end) - it('can unmap a mapping', function() + it('unmap', function() eq( 0, exec_lua [[ @@ -4074,7 +4144,7 @@ describe('vim.keymap', function() eq('\nNo mapping found', n.exec_capture('nmap asdf')) end) - it('works with buffer-local mappings', function() + it('buffer-local mappings', function() eq( 0, exec_lua [[ @@ -4116,7 +4186,7 @@ describe('vim.keymap', function() ) end) - it('can do <Plug> mappings', function() + it('<Plug> mappings', function() eq( 0, exec_lua [[ diff --git a/test/functional/lua/watch_spec.lua b/test/functional/lua/watch_spec.lua index bd8faadf5b..ad16df8a7c 100644 --- a/test/functional/lua/watch_spec.lua +++ b/test/functional/lua/watch_spec.lua @@ -12,7 +12,6 @@ local skip = t.skip -- events which can happen with some backends on some platforms local function touch(path) local tmp = t.tmpname() - io.open(tmp, 'w'):close() assert(vim.uv.fs_rename(tmp, path)) end @@ -22,16 +21,78 @@ describe('vim._watch', function() end) local function run(watchfunc) - it('detects file changes (watchfunc=' .. watchfunc .. '())', function() - if watchfunc == 'fswatch' then + -- Monkey-patches vim.notify_once so we can "spy" on it. + local function spy_notify_once() + exec_lua [[ + _G.__notify_once_msgs = {} + vim.notify_once = (function(overridden) + return function(msg, level, opts) + table.insert(_G.__notify_once_msgs, msg) + return overridden(msg, level, opts) + end + end)(vim.notify_once) + ]] + end + + local function last_notify_once_msg() + return exec_lua 'return _G.__notify_once_msgs[#_G.__notify_once_msgs]' + end + + local function do_watch(root_dir, watchfunc_) + exec_lua( + [[ + local root_dir, watchfunc = ... + + _G.events = {} + + _G.stop_watch = vim._watch[watchfunc](root_dir, { + debounce = 100, + include_pattern = vim.lpeg.P(root_dir) * vim.lpeg.P("/file") ^ -1, + exclude_pattern = vim.lpeg.P(root_dir .. '/file.unwatched'), + }, function(path, change_type) + table.insert(_G.events, { path = path, change_type = change_type }) + end) + ]], + root_dir, + watchfunc_ + ) + end + + it(watchfunc .. '() ignores nonexistent paths', function() + if watchfunc == 'inotify' then + skip(n.fn.executable('inotifywait') == 0, 'inotifywait not found') + skip(is_os('bsd'), 'inotifywait on bsd CI seems to expect path to exist?') + end + + local msg = ('watch.%s: ENOENT: no such file or directory'):format(watchfunc) + + spy_notify_once() + do_watch('/i am /very/funny.go', watchfunc) + + if watchfunc ~= 'inotify' then -- watch.inotify() doesn't (currently) call vim.notify_once. + t.retry(nil, 2000, function() + t.eq(msg, last_notify_once_msg()) + end) + end + eq(0, exec_lua [[return #_G.events]]) + + exec_lua [[_G.stop_watch()]] + end) + + it(watchfunc .. '() detects file changes', function() + if watchfunc == 'inotify' then skip(is_os('win'), 'not supported on windows') skip(is_os('mac'), 'flaky test on mac') - skip(not is_ci() and n.fn.executable('fswatch') == 0, 'fswatch not installed and not on CI') + skip(not is_ci() and n.fn.executable('inotifywait') == 0, 'inotifywait not found') end + -- Note: because this is not `elseif`, BSD is skipped for *all* cases...? if watchfunc == 'watch' then 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') else skip( is_os('bsd'), @@ -39,10 +100,8 @@ describe('vim._watch', function() ) end - local root_dir = vim.uv.fs_mkdtemp(vim.fs.dirname(t.tmpname()) .. '/nvim_XXXXXXXXXX') - local expected_events = 0 - + --- Waits for a new event, or fails if no events are triggered. local function wait_for_event() expected_events = expected_events + 1 exec_lua( @@ -63,26 +122,11 @@ describe('vim._watch', function() ) end + local root_dir = vim.uv.fs_mkdtemp(vim.fs.dirname(t.tmpname(false)) .. '/nvim_XXXXXXXXXX') local unwatched_path = root_dir .. '/file.unwatched' local watched_path = root_dir .. '/file' - exec_lua( - [[ - local root_dir, watchfunc = ... - - _G.events = {} - - _G.stop_watch = vim._watch[watchfunc](root_dir, { - debounce = 100, - include_pattern = vim.lpeg.P(root_dir) * vim.lpeg.P("/file") ^ -1, - exclude_pattern = vim.lpeg.P(root_dir .. '/file.unwatched'), - }, function(path, change_type) - table.insert(_G.events, { path = path, change_type = change_type }) - end) - ]], - root_dir, - watchfunc - ) + do_watch(root_dir, watchfunc) if watchfunc ~= 'watch' then vim.uv.sleep(200) @@ -90,16 +134,13 @@ describe('vim._watch', function() touch(watched_path) touch(unwatched_path) - wait_for_event() os.remove(watched_path) os.remove(unwatched_path) - wait_for_event() exec_lua [[_G.stop_watch()]] - -- No events should come through anymore vim.uv.sleep(100) @@ -123,5 +164,5 @@ describe('vim._watch', function() run('watch') run('watchdirs') - run('fswatch') + run('inotify') end) diff --git a/test/functional/lua/with_spec.lua b/test/functional/lua/with_spec.lua new file mode 100644 index 0000000000..99b80ef749 --- /dev/null +++ b/test/functional/lua/with_spec.lua @@ -0,0 +1,1626 @@ +local t = require('test.testutil') +local n = require('test.functional.testnvim')() +local Screen = require('test.functional.ui.screen') + +local fn = n.fn +local api = n.api +local command = n.command +local eq = t.eq +local exec_lua = n.exec_lua +local exec_capture = n.exec_capture +local matches = t.matches +local pcall_err = t.pcall_err + +describe('vim._with', function() + before_each(function() + n.clear() + exec_lua([[ + _G.fn = vim.fn + _G.api = vim.api + + _G.setup_buffers = function() + return api.nvim_create_buf(false, true), api.nvim_get_current_buf() + end + + _G.setup_windows = function() + local other_win = api.nvim_get_current_win() + vim.cmd.new() + return other_win, api.nvim_get_current_win() + end + ]]) + end) + + local assert_events_trigger = function() + local out = exec_lua [[ + -- Needs three global values defined: + -- - `test_events` - array of events which are tested. + -- - `test_context` - context to be tested. + -- - `test_trig_event` - callable triggering at least one tested event. + _G.n_events = 0 + local opts = { callback = function() _G.n_events = _G.n_events + 1 end } + api.nvim_create_autocmd(_G.test_events, opts) + + local context = { bo = { commentstring = '-- %s' } } + + -- Should not trigger events on its own + vim._with(_G.test_context, function() end) + local is_no_events = _G.n_events == 0 + + -- Should trigger events if specifically asked inside callback + local is_events = vim._with(_G.test_context, function() + _G.test_trig_event() + return _G.n_events > 0 + end) + return { is_no_events, is_events } + ]] + eq({ true, true }, out) + end + + describe('`bo` context', function() + before_each(function() + exec_lua [[ + _G.other_buf, _G.cur_buf = setup_buffers() + + -- 'commentstring' is local to buffer and string + vim.bo[other_buf].commentstring = '## %s' + vim.bo[cur_buf].commentstring = '// %s' + vim.go.commentstring = '$$ %s' + + -- 'undolevels' is global or local to buffer (global-local) and number + vim.bo[other_buf].undolevels = 100 + vim.bo[cur_buf].undolevels = 250 + vim.go.undolevels = 500 + + _G.get_state = function() + return { + bo = { + cms_cur = vim.bo[cur_buf].commentstring, + cms_other = vim.bo[other_buf].commentstring, + ul_cur = vim.bo[cur_buf].undolevels, + ul_other = vim.bo[other_buf].undolevels, + }, + go = { + cms = vim.go.commentstring, + ul = vim.go.undolevels, + }, + } + end + ]] + end) + + it('works', function() + local out = exec_lua [[ + local context = { bo = { commentstring = '-- %s', undolevels = 0 } } + + local before = get_state() + local inner = vim._with(context, function() + assert(api.nvim_get_current_buf() == cur_buf) + return get_state() + end) + + return { before = before, inner = inner, after = get_state() } + ]] + + eq({ + bo = { cms_cur = '-- %s', cms_other = '## %s', ul_cur = 0, ul_other = 100 }, + go = { cms = '$$ %s', ul = 500 }, + }, out.inner) + eq(out.before, out.after) + end) + + it('sets options in `buf` context', function() + local out = exec_lua [[ + local context = { buf = other_buf, bo = { commentstring = '-- %s', undolevels = 0 } } + + local before = get_state() + local inner = vim._with(context, function() + assert(api.nvim_get_current_buf() == other_buf) + return get_state() + end) + + return { before = before, inner = inner, after = get_state() } + ]] + + eq({ + bo = { cms_cur = '// %s', cms_other = '-- %s', ul_cur = 250, ul_other = 0 }, + go = { cms = '$$ %s', ul = 500 }, + }, out.inner) + eq(out.before, out.after) + end) + + it('restores only options from context', function() + local out = exec_lua [[ + local context = { bo = { commentstring = '-- %s' } } + + local inner = vim._with(context, function() + assert(api.nvim_get_current_buf() == cur_buf) + vim.bo[cur_buf].undolevels = 750 + vim.bo[cur_buf].commentstring = '!! %s' + return get_state() + end) + + return { inner = inner, after = get_state() } + ]] + + eq({ + bo = { cms_cur = '!! %s', cms_other = '## %s', ul_cur = 750, ul_other = 100 }, + go = { cms = '$$ %s', ul = 500 }, + }, out.inner) + eq({ + bo = { cms_cur = '// %s', cms_other = '## %s', ul_cur = 750, ul_other = 100 }, + go = { cms = '$$ %s', ul = 500 }, + }, out.after) + end) + + it('does not trigger events', function() + exec_lua [[ + _G.test_events = { 'BufEnter', 'BufLeave', 'BufWinEnter', 'BufWinLeave' } + _G.test_context = { bo = { commentstring = '-- %s' } } + _G.test_trig_event = function() vim.cmd.new() end + ]] + assert_events_trigger() + end) + + it('can be nested', function() + local out = exec_lua [[ + local before, before_inner, after_inner = get_state(), nil, nil + vim._with({ bo = { commentstring = '-- %s', undolevels = 0 } }, function() + before_inner = get_state() + inner = vim._with({ bo = { commentstring = '!! %s' } }, get_state) + after_inner = get_state() + end) + return { + before = before, before_inner = before_inner, + inner = inner, + after_inner = after_inner, after = get_state(), + } + ]] + eq('!! %s', out.inner.bo.cms_cur) + eq(0, out.inner.bo.ul_cur) + eq(out.before_inner, out.after_inner) + eq(out.before, out.after) + end) + end) + + describe('`buf` context', function() + it('works', function() + local out = exec_lua [[ + local other_buf, cur_buf = setup_buffers() + local inner = vim._with({ buf = other_buf }, function() + return api.nvim_get_current_buf() + end) + return { inner == other_buf, api.nvim_get_current_buf() == cur_buf } + ]] + eq({ true, true }, out) + end) + + it('does not trigger events', function() + exec_lua [[ + _G.test_events = { 'BufEnter', 'BufLeave', 'BufWinEnter', 'BufWinLeave' } + _G.test_context = { buf = other_buf } + _G.test_trig_event = function() vim.cmd.new() end + ]] + assert_events_trigger() + end) + + it('can access buffer options', function() + local out = exec_lua [[ + other_buf, cur_buf = setup_buffers() + vim.bo[other_buf].commentstring = '## %s' + vim.bo[cur_buf].commentstring = '// %s' + + vim._with({ buf = other_buf }, function() + vim.cmd.set('commentstring=--\\ %s') + end) + + return vim.bo[other_buf].commentstring == '-- %s' and + vim.bo[cur_buf].commentstring == '// %s' + ]] + eq(true, out) + end) + + it('works with different kinds of buffers', function() + exec_lua [[ + local assert_buf = function(buf) + vim._with({ buf = buf }, function() + assert(api.nvim_get_current_buf() == buf) + end) + end + + -- Current + assert_buf(api.nvim_get_current_buf()) + + -- Hidden listed + local listed = api.nvim_create_buf(true, true) + assert_buf(listed) + + -- Visible + local other_win, cur_win = setup_windows() + api.nvim_win_set_buf(other_win, listed) + assert_buf(listed) + + -- Shown but not visible + vim.cmd.tabnew() + assert_buf(listed) + + -- Shown in several windows + api.nvim_win_set_buf(0, listed) + assert_buf(listed) + + -- Shown in floating window + local float_buf = api.nvim_create_buf(false, true) + local config = { relative = 'editor', row = 1, col = 1, width = 5, height = 5 } + api.nvim_open_win(float_buf, false, config) + assert_buf(float_buf) + ]] + end) + + it('does not cause ml_get errors with invalid visual selection', function() + exec_lua [[ + api.nvim_buf_set_lines(0, 0, -1, true, { 'a', 'b', 'c' }) + api.nvim_feedkeys(vim.keycode('G<C-V>'), 'txn', false) + local other_buf, _ = setup_buffers() + vim._with({ buf = buf }, function() vim.cmd.redraw() end) + ]] + end) + + it('can be nested', function() + exec_lua [[ + local other_buf, cur_buf = setup_buffers() + vim._with({ buf = other_buf }, function() + assert(api.nvim_get_current_buf() == other_buf) + inner = vim._with({ buf = cur_buf }, function() + assert(api.nvim_get_current_buf() == cur_buf) + end) + assert(api.nvim_get_current_buf() == other_buf) + end) + assert(api.nvim_get_current_buf() == cur_buf) + ]] + end) + + it('can be nested crazily with hidden buffers', function() + local out = exec_lua([[ + local n = 0 + local function with_recursive_nested_bufs() + n = n + 1 + if n > 20 then return true end + + local other_buf, _ = setup_buffers() + vim.bo[other_buf].commentstring = '## %s' + local callback = function() + return api.nvim_get_current_buf() == other_buf + and vim.bo[other_buf].commentstring == '## %s' + and with_recursive_nested_bufs() + end + return vim._with({ buf = other_buf }, callback) and + api.nvim_buf_delete(other_buf, {}) == nil + end + + return with_recursive_nested_bufs() + ]]) + eq(true, out) + end) + end) + + describe('`emsg_silent` context', function() + pending('works', function() + local ok = pcall( + exec_lua, + [[ + _G.f = function() + error('This error should not interfer with execution', 0) + end + -- Should not produce error same as `vim.cmd('silent! lua _G.f()')` + vim._with({ emsg_silent = true }, f) + ]] + ) + eq(true, ok) + + -- Should properly report errors afterwards + ok = pcall(exec_lua, 'lua _G.f()') + eq(false, ok) + end) + + it('can be nested', function() + local ok = pcall( + exec_lua, + [[ + _G.f = function() + error('This error should not interfer with execution', 0) + end + -- Should produce error same as `_G.f()` + vim._with({ emsg_silent = true }, function() + vim._with( { emsg_silent = false }, f) + end) + ]] + ) + eq(false, ok) + end) + end) + + describe('`env` context', function() + before_each(function() + exec_lua [[ + vim.fn.setenv('aaa', 'hello') + _G.get_state = function() + return { aaa = vim.fn.getenv('aaa'), bbb = vim.fn.getenv('bbb') } + end + ]] + end) + + it('works', function() + local out = exec_lua [[ + local context = { env = { aaa = 'inside', bbb = 'wow' } } + local before = get_state() + local inner = vim._with(context, get_state) + return { before = before, inner = inner, after = get_state() } + ]] + + eq({ aaa = 'inside', bbb = 'wow' }, out.inner) + eq(out.before, out.after) + end) + + it('restores only variables from context', function() + local out = exec_lua [[ + local context = { env = { bbb = 'wow' } } + local before = get_state() + local inner = vim._with(context, function() + vim.env.aaa = 'inside' + return get_state() + end) + return { before = before, inner = inner, after = get_state() } + ]] + + eq({ aaa = 'inside', bbb = 'wow' }, out.inner) + eq({ aaa = 'inside', bbb = vim.NIL }, out.after) + end) + + it('can be nested', function() + local out = exec_lua [[ + local before, before_inner, after_inner = get_state(), nil, nil + vim._with({ env = { aaa = 'inside', bbb = 'wow' } }, function() + before_inner = get_state() + inner = vim._with({ env = { aaa = 'more inside' } }, get_state) + after_inner = get_state() + end) + return { + before = before, before_inner = before_inner, + inner = inner, + after_inner = after_inner, after = get_state(), + } + ]] + eq('more inside', out.inner.aaa) + eq('wow', out.inner.bbb) + eq(out.before_inner, out.after_inner) + eq(out.before, out.after) + end) + end) + + describe('`go` context', function() + before_each(function() + exec_lua [[ + vim.bo.commentstring = '## %s' + vim.go.commentstring = '$$ %s' + vim.wo.winblend = 25 + vim.go.winblend = 50 + vim.go.langmap = 'xy,yx' + + _G.get_state = function() + return { + bo = { cms = vim.bo.commentstring }, + wo = { winbl = vim.wo.winblend }, + go = { + cms = vim.go.commentstring, + winbl = vim.go.winblend, + lmap = vim.go.langmap, + }, + } + end + ]] + end) + + it('works', function() + local out = exec_lua [[ + local context = { + go = { commentstring = '-- %s', winblend = 75, langmap = 'ab,ba' }, + } + local before = get_state() + local inner = vim._with(context, get_state) + return { before = before, inner = inner, after = get_state() } + ]] + + eq({ + bo = { cms = '## %s' }, + wo = { winbl = 25 }, + go = { cms = '-- %s', winbl = 75, lmap = 'ab,ba' }, + }, out.inner) + eq(out.before, out.after) + end) + + it('works with `eventignore`', function() + -- This might be an issue if saving and restoring option context is done + -- to account for triggering `OptionSet`, but in not a good way + local out = exec_lua [[ + vim.go.eventignore = 'ModeChanged' + local inner = vim._with({ go = { eventignore = 'CursorMoved' } }, function() + return vim.go.eventignore + end) + return { inner = inner, after = vim.go.eventignore } + ]] + eq({ inner = 'CursorMoved', after = 'ModeChanged' }, out) + end) + + it('restores only options from context', function() + local out = exec_lua [[ + local context = { go = { langmap = 'ab,ba' } } + + local inner = vim._with(context, function() + vim.go.commentstring = '!! %s' + vim.go.winblend = 75 + vim.go.langmap = 'uv,vu' + return get_state() + end) + + return { inner = inner, after = get_state() } + ]] + + eq({ + bo = { cms = '## %s' }, + wo = { winbl = 25 }, + go = { cms = '!! %s', winbl = 75, lmap = 'uv,vu' }, + }, out.inner) + eq({ + bo = { cms = '## %s' }, + wo = { winbl = 25 }, + go = { cms = '!! %s', winbl = 75, lmap = 'xy,yx' }, + }, out.after) + end) + + it('does not trigger events', function() + exec_lua [[ + _G.test_events = { + 'BufEnter', 'BufLeave', 'BufWinEnter', 'BufWinLeave', 'WinEnter', 'WinLeave' + } + _G.test_context = { go = { commentstring = '-- %s', winblend = 75, langmap = 'ab,ba' } } + _G.test_trig_event = function() vim.cmd.new() end + ]] + assert_events_trigger() + end) + + it('can be nested', function() + local out = exec_lua [[ + local before, before_inner, after_inner = get_state(), nil, nil + vim._with({ go = { langmap = 'ab,ba', commentstring = '-- %s' } }, function() + before_inner = get_state() + inner = vim._with({ go = { langmap = 'uv,vu' } }, get_state) + after_inner = get_state() + end) + return { + before = before, before_inner = before_inner, + inner = inner, + after_inner = after_inner, after = get_state(), + } + ]] + eq('uv,vu', out.inner.go.lmap) + eq('-- %s', out.inner.go.cms) + eq(out.before_inner, out.after_inner) + eq(out.before, out.after) + end) + end) + + describe('`hide` context', function() + pending('works', function() + local ok = pcall( + exec_lua, + [[ + vim.o.hidden = false + vim.bo.modified = true + local init_buf = api.nvim_get_current_buf() + -- Should not produce error same as `vim.cmd('hide enew')` + vim._with({ hide = true }, function() + vim.cmd.enew() + end) + assert(api.nvim_get_current_buf() ~= init_buf) + ]] + ) + eq(true, ok) + end) + + it('can be nested', function() + local ok = pcall( + exec_lua, + [[ + vim.o.hidden = false + vim.bo.modified = true + -- Should produce error same as `vim.cmd.enew()` + vim._with({ hide = true }, function() + vim._with({ hide = false }, function() + vim.cmd.enew() + end) + end) + ]] + ) + eq(false, ok) + end) + end) + + describe('`horizontal` context', function() + local is_approx_eq = function(dim, id_1, id_2) + local f = dim == 'height' and api.nvim_win_get_height or api.nvim_win_get_width + return math.abs(f(id_1) - f(id_2)) <= 1 + end + + local win_id_1, win_id_2, win_id_3 + before_each(function() + win_id_1 = api.nvim_get_current_win() + command('wincmd v | wincmd 5>') + win_id_2 = api.nvim_get_current_win() + command('wincmd s | wincmd 5+') + win_id_3 = api.nvim_get_current_win() + + eq(is_approx_eq('width', win_id_1, win_id_2), false) + eq(is_approx_eq('height', win_id_3, win_id_2), false) + end) + + pending('works', function() + exec_lua [[ + -- Should be same as `vim.cmd('horizontal wincmd =')` + vim._with({ horizontal = true }, function() + vim.cmd.wincmd('=') + end) + ]] + eq(is_approx_eq('width', win_id_1, win_id_2), true) + eq(is_approx_eq('height', win_id_3, win_id_2), false) + end) + + pending('can be nested', function() + exec_lua [[ + -- Should be same as `vim.cmd.wincmd('=')` + vim._with({ horizontal = true }, function() + vim._with({ horizontal = false }, function() + vim.cmd.wincmd('=') + end) + end) + ]] + eq(is_approx_eq('width', win_id_1, win_id_2), true) + eq(is_approx_eq('height', win_id_3, win_id_2), true) + end) + end) + + describe('`keepalt` context', function() + pending('works', function() + local out = exec_lua [[ + vim.cmd('edit alt') + vim.cmd('edit new') + assert(fn.bufname('#') == 'alt') + + -- Should work as `vim.cmd('keepalt edit very-new')` + vim._with({ keepalt = true }, function() + vim.cmd.edit('very-new') + end) + return fn.bufname('#') == 'alt' + ]] + eq(true, out) + end) + + it('can be nested', function() + local out = exec_lua [[ + vim.cmd('edit alt') + vim.cmd('edit new') + assert(fn.bufname('#') == 'alt') + + -- Should work as `vim.cmd.edit('very-new')` + vim._with({ keepalt = true }, function() + vim._with({ keepalt = false }, function() + vim.cmd.edit('very-new') + end) + end) + return fn.bufname('#') == 'alt' + ]] + eq(false, out) + end) + end) + + describe('`keepjumps` context', function() + pending('works', function() + local out = exec_lua [[ + api.nvim_buf_set_lines(0, 0, -1, false, { 'aaa', 'bbb', 'ccc' }) + local jumplist_before = fn.getjumplist() + -- Should work as `vim.cmd('keepjumps normal! Ggg')` + vim._with({ keepjumps = true }, function() + vim.cmd('normal! Ggg') + end) + return vim.deep_equal(jumplist_before, fn.getjumplist()) + ]] + eq(true, out) + end) + + it('can be nested', function() + local out = exec_lua [[ + api.nvim_buf_set_lines(0, 0, -1, false, { 'aaa', 'bbb', 'ccc' }) + local jumplist_before = fn.getjumplist() + vim._with({ keepjumps = true }, function() + vim._with({ keepjumps = false }, function() + vim.cmd('normal! Ggg') + end) + end) + return vim.deep_equal(jumplist_before, fn.getjumplist()) + ]] + eq(false, out) + end) + end) + + describe('`keepmarks` context', function() + pending('works', function() + local out = exec_lua [[ + vim.cmd('set cpoptions+=R') + api.nvim_buf_set_lines(0, 0, -1, false, { 'bbb', 'ccc', 'aaa' }) + api.nvim_buf_set_mark(0, 'm', 2, 2, {}) + + -- Should be the same as `vim.cmd('keepmarks %!sort')` + vim._with({ keepmarks = true }, function() + vim.cmd('%!sort') + end) + return api.nvim_buf_get_mark(0, 'm') + ]] + eq({ 2, 2 }, out) + end) + + it('can be nested', function() + local out = exec_lua [[ + vim.cmd('set cpoptions+=R') + api.nvim_buf_set_lines(0, 0, -1, false, { 'bbb', 'ccc', 'aaa' }) + api.nvim_buf_set_mark(0, 'm', 2, 2, {}) + + vim._with({ keepmarks = true }, function() + vim._with({ keepmarks = false }, function() + vim.cmd('%!sort') + end) + end) + return api.nvim_buf_get_mark(0, 'm') + ]] + eq({ 0, 2 }, out) + end) + end) + + describe('`keepatterns` context', function() + pending('works', function() + local out = exec_lua [[ + api.nvim_buf_set_lines(0, 0, -1, false, { 'aaa', 'bbb' }) + vim.cmd('/aaa') + -- Should be the same as `vim.cmd('keeppatterns /bbb')` + vim._with({ keeppatterns = true }, function() + vim.cmd('/bbb') + end) + return fn.getreg('/') + ]] + eq('aaa', out) + end) + + it('can be nested', function() + local out = exec_lua [[ + api.nvim_buf_set_lines(0, 0, -1, false, { 'aaa', 'bbb' }) + vim.cmd('/aaa') + vim._with({ keeppatterns = true }, function() + vim._with({ keeppatterns = false }, function() + vim.cmd('/bbb') + end) + end) + return fn.getreg('/') + ]] + eq('bbb', out) + end) + end) + + describe('`lockmarks` context', function() + it('works', function() + local mark = exec_lua [[ + api.nvim_buf_set_lines(0, 0, 0, false, { 'aaa', 'bbb', 'ccc' }) + api.nvim_buf_set_mark(0, 'm', 2, 2, {}) + -- Should be same as `:lockmarks lua api.nvim_buf_set_lines(...)` + vim._with({ lockmarks = true }, function() + api.nvim_buf_set_lines(0, 0, 2, false, { 'uuu', 'vvv', 'www' }) + end) + return api.nvim_buf_get_mark(0, 'm') + ]] + eq({ 2, 2 }, mark) + end) + + it('can be nested', function() + local mark = exec_lua [[ + api.nvim_buf_set_lines(0, 0, 0, false, { 'aaa', 'bbb', 'ccc' }) + api.nvim_buf_set_mark(0, 'm', 2, 2, {}) + vim._with({ lockmarks = true }, function() + vim._with({ lockmarks = false }, function() + api.nvim_buf_set_lines(0, 0, 2, false, { 'uuu', 'vvv', 'www' }) + end) + end) + return api.nvim_buf_get_mark(0, 'm') + ]] + eq({ 0, 2 }, mark) + end) + end) + + describe('`noautocmd` context', function() + it('works', function() + local out = exec_lua [[ + _G.n_events = 0 + vim.cmd('au ModeChanged * lua _G.n_events = _G.n_events + 1') + -- Should be the same as `vim.cmd('noautocmd normal! vv')` + vim._with({ noautocmd = true }, function() + vim.cmd('normal! vv') + end) + return _G.n_events + ]] + eq(0, out) + end) + + it('works with User events', function() + local out = exec_lua [[ + _G.n_events = 0 + vim.cmd('au User MyEvent lua _G.n_events = _G.n_events + 1') + -- Should be the same as `vim.cmd('noautocmd doautocmd User MyEvent')` + vim._with({ noautocmd = true }, function() + api.nvim_exec_autocmds('User', { pattern = 'MyEvent' }) + end) + return _G.n_events + ]] + eq(0, out) + end) + + pending('can be nested', function() + local out = exec_lua [[ + _G.n_events = 0 + vim.cmd('au ModeChanged * lua _G.n_events = _G.n_events + 1') + vim._with({ noautocmd = true }, function() + vim._with({ noautocmd = false }, function() + vim.cmd('normal! vv') + end) + end) + return _G.n_events + ]] + eq(2, out) + end) + end) + + describe('`o` context', function() + before_each(function() + exec_lua [[ + _G.other_win, _G.cur_win = setup_windows() + _G.other_buf, _G.cur_buf = setup_buffers() + + vim.bo[other_buf].commentstring = '## %s' + vim.bo[cur_buf].commentstring = '// %s' + vim.go.commentstring = '$$ %s' + + vim.bo[other_buf].undolevels = 100 + vim.bo[cur_buf].undolevels = 250 + vim.go.undolevels = 500 + + vim.wo[other_win].virtualedit = 'block' + vim.wo[cur_win].virtualedit = 'insert' + vim.go.virtualedit = 'none' + + vim.wo[other_win].winblend = 10 + vim.wo[cur_win].winblend = 25 + vim.go.winblend = 50 + + vim.go.langmap = 'xy,yx' + + _G.get_state = function() + return { + bo = { + cms_cur = vim.bo[cur_buf].commentstring, + cms_other = vim.bo[other_buf].commentstring, + ul_cur = vim.bo[cur_buf].undolevels, + ul_other = vim.bo[other_buf].undolevels, + }, + wo = { + ve_cur = vim.wo[cur_win].virtualedit, + ve_other = vim.wo[other_win].virtualedit, + winbl_cur = vim.wo[cur_win].winblend, + winbl_other = vim.wo[other_win].winblend, + }, + go = { + cms = vim.go.commentstring, + ul = vim.go.undolevels, + ve = vim.go.virtualedit, + winbl = vim.go.winblend, + lmap = vim.go.langmap, + }, + } + end + ]] + end) + + it('works', function() + local out = exec_lua [[ + local context = { + o = { + commentstring = '-- %s', + undolevels = 0, + virtualedit = 'all', + winblend = 75, + langmap = 'ab,ba', + }, + } + + local before = get_state() + local inner = vim._with(context, function() + assert(api.nvim_get_current_buf() == cur_buf) + assert(api.nvim_get_current_win() == cur_win) + return get_state() + end) + + return { before = before, inner = inner, after = get_state() } + ]] + + -- Options in context are set with `vim.o`, so usually both local + -- and global values are affected. Yet all of them should be later + -- restored to pre-context values. + eq({ + bo = { cms_cur = '-- %s', cms_other = '## %s', ul_cur = -123456, ul_other = 100 }, + wo = { ve_cur = 'all', ve_other = 'block', winbl_cur = 75, winbl_other = 10 }, + go = { cms = '-- %s', ul = 0, ve = 'all', winbl = 75, lmap = 'ab,ba' }, + }, out.inner) + eq(out.before, out.after) + end) + + it('sets options in `buf` context', function() + local out = exec_lua [[ + local context = { buf = other_buf, o = { commentstring = '-- %s', undolevels = 0 } } + + local before = get_state() + local inner = vim._with(context, function() + assert(api.nvim_get_current_buf() == other_buf) + return get_state() + end) + + return { before = before, inner = inner, after = get_state() } + ]] + + eq({ + bo = { cms_cur = '// %s', cms_other = '-- %s', ul_cur = 250, ul_other = -123456 }, + wo = { ve_cur = 'insert', ve_other = 'block', winbl_cur = 25, winbl_other = 10 }, + -- Global `winbl` inside context ideally should be untouched and equal + -- to 50. It seems to be equal to 0 because `context.buf` uses + -- `aucmd_prepbuf` C approach which has no guarantees about window or + -- window option values inside context. + go = { cms = '-- %s', ul = 0, ve = 'none', winbl = 0, lmap = 'xy,yx' }, + }, out.inner) + eq(out.before, out.after) + end) + + it('sets options in `win` context', function() + local out = exec_lua [[ + local context = { win = other_win, o = { winblend = 75, virtualedit = 'all' } } + + local before = get_state() + local inner = vim._with(context, function() + assert(api.nvim_get_current_win() == other_win) + return get_state() + end) + + return { before = before, inner = inner, after = get_state() } + ]] + + eq({ + bo = { cms_cur = '// %s', cms_other = '## %s', ul_cur = 250, ul_other = 100 }, + wo = { winbl_cur = 25, winbl_other = 75, ve_cur = 'insert', ve_other = 'all' }, + go = { cms = '$$ %s', ul = 500, winbl = 75, ve = 'all', lmap = 'xy,yx' }, + }, out.inner) + eq(out.before, out.after) + end) + + it('restores only options from context', function() + local out = exec_lua [[ + local context = { o = { undolevels = 0, winblend = 75, langmap = 'ab,ba' } } + + local inner = vim._with(context, function() + assert(api.nvim_get_current_buf() == cur_buf) + assert(api.nvim_get_current_win() == cur_win) + + vim.o.commentstring = '!! %s' + vim.o.undolevels = 750 + vim.o.virtualedit = 'onemore' + vim.o.winblend = 99 + vim.o.langmap = 'uv,vu' + return get_state() + end) + + return { inner = inner, after = get_state() } + ]] + + eq({ + bo = { cms_cur = '!! %s', cms_other = '## %s', ul_cur = -123456, ul_other = 100 }, + wo = { ve_cur = 'onemore', ve_other = 'block', winbl_cur = 99, winbl_other = 10 }, + go = { cms = '!! %s', ul = 750, ve = 'onemore', winbl = 99, lmap = 'uv,vu' }, + }, out.inner) + eq({ + bo = { cms_cur = '!! %s', cms_other = '## %s', ul_cur = 250, ul_other = 100 }, + wo = { ve_cur = 'onemore', ve_other = 'block', winbl_cur = 25, winbl_other = 10 }, + go = { cms = '!! %s', ul = 500, ve = 'onemore', winbl = 50, lmap = 'xy,yx' }, + }, out.after) + end) + + it('does not trigger events', function() + exec_lua [[ + _G.test_events = { + 'BufEnter', 'BufLeave', 'WinEnter', 'WinLeave', 'BufWinEnter', 'BufWinLeave' + } + _G.test_context = { o = { undolevels = 0, winblend = 75, langmap = 'ab,ba' } } + _G.test_trig_event = function() vim.cmd.new() end + ]] + assert_events_trigger() + end) + + it('can be nested', function() + local out = exec_lua [[ + local before, before_inner, after_inner = get_state(), nil, nil + local cxt_o = { commentstring = '-- %s', winblend = 75, langmap = 'ab,ba', undolevels = 0 } + vim._with({ o = cxt_o }, function() + before_inner = get_state() + local inner_cxt_o = { commentstring = '!! %s', winblend = 99, langmap = 'uv,vu' } + inner = vim._with({ o = inner_cxt_o }, get_state) + after_inner = get_state() + end) + return { + before = before, before_inner = before_inner, + inner = inner, + after_inner = after_inner, after = get_state(), + } + ]] + eq('!! %s', out.inner.bo.cms_cur) + eq(99, out.inner.wo.winbl_cur) + eq('uv,vu', out.inner.go.lmap) + eq(0, out.inner.go.ul) + eq(out.before_inner, out.after_inner) + eq(out.before, out.after) + end) + end) + + describe('`sandbox` context', function() + it('works', function() + local ok, err = pcall( + exec_lua, + [[ + -- Should work as `vim.cmd('sandbox call append(0, "aaa")')` + vim._with({ sandbox = true }, function() + fn.append(0, 'aaa') + end) + ]] + ) + eq(false, ok) + matches('Not allowed in sandbox', err) + end) + + it('can NOT be nested', function() + -- This behavior is intentionally different from other flags as allowing + -- disabling `sandbox` from nested function seems to be against the point + -- of using `sandbox` context in the first place + local ok, err = pcall( + exec_lua, + [[ + vim._with({ sandbox = true }, function() + vim._with({ sandbox = false }, function() + fn.append(0, 'aaa') + end) + end) + ]] + ) + eq(false, ok) + matches('Not allowed in sandbox', err) + end) + end) + + describe('`silent` context', function() + it('works', function() + exec_lua [[ + -- Should be same as `vim.cmd('silent lua print("aaa")')` + vim._with({ silent = true }, function() print('aaa') end) + ]] + eq('', exec_capture('messages')) + + exec_lua [[ vim._with({ silent = true }, function() vim.cmd.echomsg('"bbb"') end) ]] + eq('', exec_capture('messages')) + + local screen = Screen.new(20, 5) + screen:set_default_attr_ids { + [1] = { bold = true, reverse = true }, + [2] = { bold = true, foreground = Screen.colors.Blue }, + } + screen:attach() + exec_lua [[ vim._with({ silent = true }, function() vim.cmd.echo('"ccc"') end) ]] + screen:expect [[ + ^ | + {2:~ }|*3 + | + ]] + end) + + pending('can be nested', function() + exec_lua [[ vim._with({ silent = true }, function() + vim._with({ silent = false }, function() + print('aaa') + end) + end)]] + eq('aaa', exec_capture('messages')) + end) + end) + + describe('`unsilent` context', function() + it('works', function() + exec_lua [[ + _G.f = function() + -- Should be same as `vim.cmd('unsilent lua print("aaa")')` + vim._with({ unsilent = true }, function() print('aaa') end) + end + ]] + command('silent lua f()') + eq('aaa', exec_capture('messages')) + end) + + pending('can be nested', function() + exec_lua [[ + _G.f = function() + vim._with({ unsilent = true }, function() + vim._with({ unsilent = false }, function() print('aaa') end) + end) + end + ]] + command('silent lua f()') + eq('', exec_capture('messages')) + end) + end) + + describe('`win` context', function() + it('works', function() + local out = exec_lua [[ + local other_win, cur_win = setup_windows() + local inner = vim._with({ win = other_win }, function() + return api.nvim_get_current_win() + end) + return { inner == other_win, api.nvim_get_current_win() == cur_win } + ]] + eq({ true, true }, out) + end) + + it('does not trigger events', function() + exec_lua [[ + _G.test_events = { 'WinEnter', 'WinLeave', 'BufWinEnter', 'BufWinLeave' } + _G.test_context = { win = other_win } + _G.test_trig_event = function() vim.cmd.new() end + ]] + assert_events_trigger() + end) + + it('can access window options', function() + local out = exec_lua [[ + local other_win, cur_win = setup_windows() + vim.wo[other_win].winblend = 10 + vim.wo[cur_win].winblend = 25 + + vim._with({ win = other_win }, function() + vim.cmd.setlocal('winblend=0') + end) + + return vim.wo[other_win].winblend == 0 and vim.wo[cur_win].winblend == 25 + ]] + eq(true, out) + end) + + it('works with different kinds of windows', function() + exec_lua [[ + local assert_win = function(win) + vim._with({ win = win }, function() + assert(api.nvim_get_current_win() == win) + end) + end + + -- Current + assert_win(api.nvim_get_current_win()) + + -- Not visible + local other_win, cur_win = setup_windows() + vim.cmd.tabnew() + assert_win(other_win) + + -- Floating + local float_win = api.nvim_open_win( + api.nvim_create_buf(false, true), + false, + { relative = 'editor', row = 1, col = 1, height = 5, width = 5} + ) + assert_win(float_win) + ]] + end) + + it('does not cause ml_get errors with invalid visual selection', function() + exec_lua [[ + local feedkeys = function(keys) api.nvim_feedkeys(vim.keycode(keys), 'txn', false) end + + -- Add lines to the current buffer and make another window looking into an empty buffer. + local win_empty, win_lines = setup_windows() + api.nvim_buf_set_lines(0, 0, -1, true, { 'a', 'b', 'c' }) + + -- Start Visual in current window, redraw in other window with fewer lines. + -- Should be fixed by vim-patch:8.2.4018. + feedkeys('G<C-V>') + vim._with({ win = win_empty }, function() vim.cmd.redraw() end) + + -- Start Visual in current window, extend it in other window with more lines. + -- Fixed for win_execute by vim-patch:8.2.4026, but nvim_win_call should also not be affected. + feedkeys('<Esc>gg') + api.nvim_set_current_win(win_empty) + feedkeys('gg<C-V>') + vim._with({ win = win_lines }, function() feedkeys('G<C-V>') end) + vim.cmd.redraw() + ]] + end) + + it('can be nested', function() + exec_lua [[ + local other_win, cur_win = setup_windows() + vim._with({ win = other_win }, function() + assert(api.nvim_get_current_win() == other_win) + inner = vim._with({ win = cur_win }, function() + assert(api.nvim_get_current_win() == cur_win) + end) + assert(api.nvim_get_current_win() == other_win) + end) + assert(api.nvim_get_current_win() == cur_win) + ]] + end) + + it('updates ruler if cursor moved', function() + local screen = Screen.new(30, 5) + screen:set_default_attr_ids { + [1] = { reverse = true }, + [2] = { bold = true, reverse = true }, + } + screen:attach() + exec_lua [[ + vim.opt.ruler = true + local lines = {} + for i = 0, 499 do lines[#lines + 1] = tostring(i) end + api.nvim_buf_set_lines(0, 0, -1, true, lines) + api.nvim_win_set_cursor(0, { 20, 0 }) + vim.cmd 'split' + _G.win = api.nvim_get_current_win() + vim.cmd "wincmd w | redraw" + ]] + screen:expect [[ + 19 | + {1:[No Name] [+] 20,1 3%}| + ^19 | + {2:[No Name] [+] 20,1 3%}| + | + ]] + exec_lua [[ + vim._with({ win = win }, function() api.nvim_win_set_cursor(0, { 100, 0 }) end) + vim.cmd "redraw" + ]] + screen:expect [[ + 99 | + {1:[No Name] [+] 100,1 19%}| + ^19 | + {2:[No Name] [+] 20,1 3%}| + | + ]] + end) + + it('layout in current tabpage does not affect windows in others', function() + command('tab split') + local t2_move_win = api.nvim_get_current_win() + command('vsplit') + local t2_other_win = api.nvim_get_current_win() + command('tabprevious') + matches('E36: Not enough room$', pcall_err(command, 'execute "split|"->repeat(&lines)')) + command('vsplit') + + exec_lua('vim._with({ win = ... }, function() vim.cmd.wincmd "J" end)', t2_move_win) + eq({ 'col', { { 'leaf', t2_other_win }, { 'leaf', t2_move_win } } }, fn.winlayout(2)) + end) + end) + + describe('`wo` context', function() + before_each(function() + exec_lua [[ + _G.other_win, _G.cur_win = setup_windows() + + -- 'virtualedit' is global or local to window (global-local) and string + vim.wo[other_win].virtualedit = 'block' + vim.wo[cur_win].virtualedit = 'insert' + vim.go.virtualedit = 'none' + + -- 'winblend' is local to window and number + vim.wo[other_win].winblend = 10 + vim.wo[cur_win].winblend = 25 + vim.go.winblend = 50 + + _G.get_state = function() + return { + wo = { + ve_cur = vim.wo[cur_win].virtualedit, + ve_other = vim.wo[other_win].virtualedit, + winbl_cur = vim.wo[cur_win].winblend, + winbl_other = vim.wo[other_win].winblend, + }, + go = { + ve = vim.go.virtualedit, + winbl = vim.go.winblend, + }, + } + end + ]] + end) + + it('works', function() + local out = exec_lua [[ + local context = { wo = { virtualedit = 'all', winblend = 75 } } + + local before = get_state() + local inner = vim._with(context, function() + assert(api.nvim_get_current_win() == cur_win) + return get_state() + end) + + return { before = before, inner = inner, after = get_state() } + ]] + + eq({ + wo = { ve_cur = 'all', ve_other = 'block', winbl_cur = 75, winbl_other = 10 }, + go = { ve = 'none', winbl = 75 }, + }, out.inner) + eq(out.before, out.after) + end) + + it('sets options in `win` context', function() + local out = exec_lua [[ + local context = { win = other_win, wo = { virtualedit = 'all', winblend = 75 } } + + local before = get_state() + local inner = vim._with(context, function() + assert(api.nvim_get_current_win() == other_win) + return get_state() + end) + + return { before = before, inner = inner, after = get_state() } + ]] + + eq({ + wo = { ve_cur = 'insert', ve_other = 'all', winbl_cur = 25, winbl_other = 75 }, + go = { ve = 'none', winbl = 75 }, + }, out.inner) + eq(out.before, out.after) + end) + + it('restores only options from context', function() + local out = exec_lua [[ + local context = { wo = { winblend = 75 } } + + local inner = vim._with(context, function() + assert(api.nvim_get_current_win() == cur_win) + vim.wo[cur_win].virtualedit = 'onemore' + vim.wo[cur_win].winblend = 99 + return get_state() + end) + + return { inner = inner, after = get_state() } + ]] + + eq({ + wo = { ve_cur = 'onemore', ve_other = 'block', winbl_cur = 99, winbl_other = 10 }, + go = { ve = 'none', winbl = 99 }, + }, out.inner) + eq({ + wo = { ve_cur = 'onemore', ve_other = 'block', winbl_cur = 25, winbl_other = 10 }, + go = { ve = 'none', winbl = 50 }, + }, out.after) + end) + + it('does not trigger events', function() + exec_lua [[ + _G.test_events = { 'WinEnter', 'WinLeave', 'BufWinEnter', 'BufWinLeave' } + _G.test_context = { wo = { winblend = 75 } } + _G.test_trig_event = function() vim.cmd.new() end + ]] + assert_events_trigger() + end) + + it('can be nested', function() + local out = exec_lua [[ + local before, before_inner, after_inner = get_state(), nil, nil + vim._with({ wo = { winblend = 75, virtualedit = 'all' } }, function() + before_inner = get_state() + inner = vim._with({ wo = { winblend = 99 } }, get_state) + after_inner = get_state() + end) + return { + before = before, before_inner = before_inner, + inner = inner, + after_inner = after_inner, after = get_state(), + } + ]] + eq(99, out.inner.wo.winbl_cur) + eq('all', out.inner.wo.ve_cur) + eq(out.before_inner, out.after_inner) + eq(out.before, out.after) + end) + end) + + it('returns what callback returns', function() + local out_verify = exec_lua [[ + out = { vim._with({}, function() + return 'a', 2, nil, { 4 }, function() end + end) } + return { + out[1] == 'a', out[2] == 2, out[3] == nil, + vim.deep_equal(out[4], { 4 }), + type(out[5]) == 'function', + vim.tbl_count(out), + } + ]] + eq({ true, true, true, true, true, 4 }, out_verify) + end) + + it('can return values by reference', function() + local out = exec_lua [[ + local val = { 4, 10 } + local ref = vim._with({}, function() return val end) + ref[1] = 7 + return val + ]] + eq({ 7, 10 }, out) + end) + + it('can not work with conflicting `buf` and `win`', function() + local out = exec_lua [[ + local other_buf, cur_buf = setup_buffers() + local other_win, cur_win = setup_windows() + assert(api.nvim_win_get_buf(other_win) ~= other_buf) + local _, err = pcall(vim._with, { buf = other_buf, win = other_win }, function() end) + return err + ]] + matches('Can not set both `buf` and `win`', out) + end) + + it('works with several contexts at once', function() + local out = exec_lua [[ + local other_buf, cur_buf = setup_buffers() + vim.bo[other_buf].commentstring = '## %s' + api.nvim_buf_set_lines(other_buf, 0, -1, false, { 'aaa', 'bbb', 'ccc' }) + api.nvim_buf_set_mark(other_buf, 'm', 2, 2, {}) + + vim.go.commentstring = '// %s' + vim.go.langmap = 'xy,yx' + + local context = { + buf = other_buf, + bo = { commentstring = '-- %s' }, + go = { langmap = 'ab,ba' }, + lockmarks = true, + } + + local inner = vim._with(context, function() + api.nvim_buf_set_lines(0, 0, -1, false, { 'uuu', 'vvv', 'www' }) + return { + buf = api.nvim_get_current_buf(), + bo = { cms = vim.bo.commentstring }, + go = { cms = vim.go.commentstring, lmap = vim.go.langmap }, + mark = api.nvim_buf_get_mark(0, 'm') + } + end) + + local after = { + buf = api.nvim_get_current_buf(), + bo = { cms = vim.bo[other_buf].commentstring }, + go = { cms = vim.go.commentstring, lmap = vim.go.langmap }, + mark = api.nvim_buf_get_mark(other_buf, 'm') + } + + return { + context_buf = other_buf, cur_buf = cur_buf, + inner = inner, after = after + } + ]] + + eq({ + buf = out.context_buf, + bo = { cms = '-- %s' }, + go = { cms = '// %s', lmap = 'ab,ba' }, + mark = { 2, 2 }, + }, out.inner) + eq({ + buf = out.cur_buf, + bo = { cms = '## %s' }, + go = { cms = '// %s', lmap = 'xy,yx' }, + mark = { 2, 2 }, + }, out.after) + end) + + it('works with same option set in different contexts', function() + local out = exec_lua [[ + local get_state = function() + return { + bo = { cms = vim.bo.commentstring }, + wo = { ve = vim.wo.virtualedit }, + go = { cms = vim.go.commentstring, ve = vim.go.virtualedit }, + } + end + + vim.bo.commentstring = '// %s' + vim.go.commentstring = '$$ %s' + vim.wo.virtualedit = 'insert' + vim.go.virtualedit = 'none' + + local before = get_state() + local context_no_go = { + o = { commentstring = '-- %s', virtualedit = 'all' }, + bo = { commentstring = '!! %s' }, + wo = { virtualedit = 'onemore' }, + } + local inner_no_go = vim._with(context_no_go, get_state) + local middle = get_state() + local context_with_go = { + o = { commentstring = '-- %s', virtualedit = 'all' }, + bo = { commentstring = '!! %s' }, + wo = { virtualedit = 'onemore' }, + go = { commentstring = '@@ %s', virtualedit = 'block' }, + } + local inner_with_go = vim._with(context_with_go, get_state) + return { + before = before, + inner_no_go = inner_no_go, + middle = middle, + inner_with_go = inner_with_go, + after = get_state(), + } + ]] + + -- Should prefer explicit local scopes instead of `o` + eq({ + bo = { cms = '!! %s' }, + wo = { ve = 'onemore' }, + go = { cms = '-- %s', ve = 'all' }, + }, out.inner_no_go) + eq(out.before, out.middle) + + -- Should prefer explicit global scopes instead of `o` + eq({ + bo = { cms = '!! %s' }, + wo = { ve = 'onemore' }, + go = { cms = '@@ %s', ve = 'block' }, + }, out.inner_with_go) + eq(out.middle, out.after) + end) + + pending('can forward command modifiers to user command', function() + local out = exec_lua [[ + local test_flags = { + 'emsg_silent', + 'hide', + 'keepalt', + 'keepjumps', + 'keepmarks', + 'keeppatterns', + 'lockmarks', + 'noautocmd', + 'silent', + 'unsilent', + } + + local used_smods + local command = function(data) + used_smods = data.smods + end + api.nvim_create_user_command('DummyLog', command, {}) + + local res = {} + for _, flag in ipairs(test_flags) do + used_smods = nil + vim._with({ [flag] = true }, function() vim.cmd('DummyLog') end) + res[flag] = used_smods[flag] + end + return res + ]] + for k, v in pairs(out) do + eq({ k, true }, { k, v }) + end + end) + + it('handles error in callback', function() + -- Should still restore initial context + local out_buf = exec_lua [[ + local other_buf, cur_buf = setup_buffers() + vim.bo[other_buf].commentstring = '## %s' + + local context = { buf = other_buf, bo = { commentstring = '-- %s' } } + local ok, err = pcall(vim._with, context, function() error('Oops buf', 0) end) + + return { + ok, + err, + api.nvim_get_current_buf() == cur_buf, + vim.bo[other_buf].commentstring, + } + ]] + eq({ false, 'Oops buf', true, '## %s' }, out_buf) + + local out_win = exec_lua [[ + local other_win, cur_win = setup_windows() + vim.wo[other_win].winblend = 25 + + local context = { win = other_win, wo = { winblend = 50 } } + local ok, err = pcall(vim._with, context, function() error('Oops win', 0) end) + + return { + ok, + err, + api.nvim_get_current_win() == cur_win, + vim.wo[other_win].winblend, + } + ]] + eq({ false, 'Oops win', true, 25 }, out_win) + end) + + it('handles not supported option', function() + local out = exec_lua [[ + -- Should still restore initial state + vim.bo.commentstring = '## %s' + + local context = { o = { commentstring = '-- %s' }, bo = { winblend = 10 } } + local ok, err = pcall(vim._with, context, function() end) + + return { ok = ok, err = err, cms = vim.bo.commentstring } + ]] + eq(false, out.ok) + matches('window.*option.*winblend', out.err) + eq('## %s', out.cms) + end) + + it('validates arguments', function() + exec_lua [[ + _G.get_error = function(...) + local _, err = pcall(vim._with, ...) + return err or '' + end + ]] + local get_error = function(string_args) + return exec_lua('return get_error(' .. string_args .. ')') + end + + matches('context.*table', get_error("'a', function() end")) + matches('f.*function', get_error('{}, 1')) + + local assert_context = function(bad_context, expected_type) + local bad_field = vim.tbl_keys(bad_context)[1] + matches( + 'context%.' .. bad_field .. '.*' .. expected_type, + get_error(vim.inspect(bad_context) .. ', function() end') + ) + end + + assert_context({ bo = 1 }, 'table') + assert_context({ buf = 'a' }, 'number') + assert_context({ emsg_silent = 1 }, 'boolean') + assert_context({ env = 1 }, 'table') + assert_context({ go = 1 }, 'table') + assert_context({ hide = 1 }, 'boolean') + assert_context({ keepalt = 1 }, 'boolean') + assert_context({ keepjumps = 1 }, 'boolean') + assert_context({ keepmarks = 1 }, 'boolean') + assert_context({ keeppatterns = 1 }, 'boolean') + assert_context({ lockmarks = 1 }, 'boolean') + assert_context({ noautocmd = 1 }, 'boolean') + assert_context({ o = 1 }, 'table') + assert_context({ sandbox = 1 }, 'boolean') + assert_context({ silent = 1 }, 'boolean') + assert_context({ unsilent = 1 }, 'boolean') + assert_context({ win = 'a' }, 'number') + assert_context({ wo = 1 }, 'table') + + matches('Invalid buffer', get_error('{ buf = -1 }, function() end')) + matches('Invalid window', get_error('{ win = -1 }, function() end')) + end) +end) diff --git a/test/functional/lua/xdiff_spec.lua b/test/functional/lua/xdiff_spec.lua index d5589c1f13..269dbde10a 100644 --- a/test/functional/lua/xdiff_spec.lua +++ b/test/functional/lua/xdiff_spec.lua @@ -12,15 +12,11 @@ describe('xdiff bindings', function() end) describe('can diff text', function() - before_each(function() - exec_lua [[ - a1 = 'Hello\n' - b1 = 'Helli\n' - - a2 = 'Hello\nbye\nfoo\n' - b2 = 'Helli\nbye\nbar\nbaz\n' - ]] - end) + local a1 = 'Hello\n' + local b1 = 'Helli\n' + + local a2 = 'Hello\nbye\nfoo\n' + local b2 = 'Helli\nbye\nbar\nbaz\n' it('with no callback', function() eq( @@ -30,7 +26,9 @@ describe('xdiff bindings', function() '+Helli', '', }, '\n'), - exec_lua('return vim.diff(a1, b1)') + exec_lua(function() + return vim.diff(a1, b1) + end) ) eq( @@ -44,63 +42,81 @@ describe('xdiff bindings', function() '+baz', '', }, '\n'), - exec_lua('return vim.diff(a2, b2)') + exec_lua(function() + return vim.diff(a2, b2) + end) ) end) it('with callback', function() - exec_lua([[on_hunk = function(sa, ca, sb, cb) - exp[#exp+1] = {sa, ca, sb, cb} - end]]) - eq( { { 1, 1, 1, 1 } }, - exec_lua [[ - exp = {} - assert(vim.diff(a1, b1, {on_hunk = on_hunk}) == nil) + exec_lua(function() + local exp = {} --- @type table[] + assert(vim.diff(a1, b1, { + on_hunk = function(...) + exp[#exp + 1] = { ... } + end, + }) == nil) return exp - ]] + end) ) eq( { { 1, 1, 1, 1 }, { 3, 1, 3, 2 } }, - exec_lua [[ - exp = {} - assert(vim.diff(a2, b2, {on_hunk = on_hunk}) == nil) + exec_lua(function() + local exp = {} --- @type table[] + assert(vim.diff(a2, b2, { + on_hunk = function(...) + exp[#exp + 1] = { ... } + end, + }) == nil) return exp - ]] + end) ) -- gives higher precedence to on_hunk over result_type eq( { { 1, 1, 1, 1 }, { 3, 1, 3, 2 } }, - exec_lua [[ - exp = {} - assert(vim.diff(a2, b2, {on_hunk = on_hunk, result_type='indices'}) == nil) + exec_lua(function() + local exp = {} --- @type table[] + assert(vim.diff(a2, b2, { + on_hunk = function(...) + exp[#exp + 1] = { ... } + end, + result_type = 'indices', + }) == nil) return exp - ]] + end) ) end) it('with error callback', function() - exec_lua [[ - on_hunk = function(sa, ca, sb, cb) - error('ERROR1') - end - ]] - eq( - [[error running function on_hunk: [string "<nvim>"]:0: ERROR1]], - pcall_err(exec_lua, [[vim.diff(a1, b1, {on_hunk = on_hunk})]]) + [[.../xdiff_spec.lua:0: error running function on_hunk: .../xdiff_spec.lua:0: ERROR1]], + pcall_err(exec_lua, function() + vim.diff(a1, b1, { + on_hunk = function() + error('ERROR1') + end, + }) + end) ) end) it('with hunk_lines', function() - eq({ { 1, 1, 1, 1 } }, exec_lua([[return vim.diff(a1, b1, {result_type = 'indices'})]])) + eq( + { { 1, 1, 1, 1 } }, + exec_lua(function() + return vim.diff(a1, b1, { result_type = 'indices' }) + end) + ) eq( { { 1, 1, 1, 1 }, { 3, 1, 3, 2 } }, - exec_lua([[return vim.diff(a2, b2, {result_type = 'indices'})]]) + exec_lua(function() + return vim.diff(a2, b2, { result_type = 'indices' }) + end) ) end) @@ -143,16 +159,11 @@ describe('xdiff bindings', function() '+}', '', }, '\n'), - exec_lua( - [[ - local args = {...} - return vim.diff(args[1], args[2], { - algorithm = 'patience' + exec_lua(function() + return vim.diff(a, b, { + algorithm = 'patience', }) - ]], - a, - b - ) + end) ) end) end) @@ -174,4 +185,13 @@ describe('xdiff bindings', function() pcall_err(exec_lua, [[vim.diff('a', 'b', { on_hunk = true })]]) ) end) + + it('can handle strings with embedded NUL characters (GitHub #30305)', function() + eq( + { { 0, 0, 1, 1 }, { 1, 0, 3, 2 } }, + exec_lua(function() + return vim.diff('\n', '\0\n\n\nb', { linematch = true, result_type = 'indices' }) + end) + ) + end) end) diff --git a/test/functional/options/autochdir_spec.lua b/test/functional/options/autochdir_spec.lua index c490ab67a9..a409262d84 100644 --- a/test/functional/options/autochdir_spec.lua +++ b/test/functional/options/autochdir_spec.lua @@ -22,7 +22,7 @@ describe("'autochdir'", function() end) it('is not overwritten by getwinvar() call #17609', function() - local curdir = vim.uv.cwd():gsub('\\', '/') + local curdir = t.fix_slashes(vim.uv.cwd()) local dir_a = curdir .. '/Xtest-functional-options-autochdir.dir_a' local dir_b = curdir .. '/Xtest-functional-options-autochdir.dir_b' mkdir(dir_a) diff --git a/test/functional/options/defaults_spec.lua b/test/functional/options/defaults_spec.lua index f61139d92d..e3d15fa30f 100644 --- a/test/functional/options/defaults_spec.lua +++ b/test/functional/options/defaults_spec.lua @@ -1,3 +1,9 @@ +-- +-- Tests for default options and environment decisions. +-- +-- See editor/defaults_spec.lua for default autocmds, mappings, commands, and menus. +-- + local t = require('test.testutil') local n = require('test.functional.testnvim')() local Screen = require('test.functional.ui.screen') @@ -17,7 +23,6 @@ local insert = n.insert local neq = t.neq local mkdir = t.mkdir local rmdir = n.rmdir -local alter_slashes = n.alter_slashes local tbl_contains = vim.tbl_contains local expect_exit = n.expect_exit local check_close = n.check_close @@ -247,6 +252,7 @@ describe('startup defaults', function() } }) eq('Xtest-logpath', eval('$NVIM_LOG_FILE')) end) + it('defaults to stdpath("log")/log if empty', function() eq(true, mkdir(xdgdir) and mkdir(xdgstatedir)) clear({ @@ -255,8 +261,9 @@ describe('startup defaults', function() NVIM_LOG_FILE = '', -- Empty is invalid. }, }) - eq(xdgstatedir .. '/log', string.gsub(eval('$NVIM_LOG_FILE'), '\\', '/')) + eq(xdgstatedir .. '/log', t.fix_slashes(eval('$NVIM_LOG_FILE'))) end) + it('defaults to stdpath("log")/log if invalid', function() eq(true, mkdir(xdgdir) and mkdir(xdgstatedir)) clear({ @@ -265,7 +272,9 @@ describe('startup defaults', function() NVIM_LOG_FILE = '.', -- Any directory is invalid. }, }) - eq(xdgstatedir .. '/log', string.gsub(eval('$NVIM_LOG_FILE'), '\\', '/')) + eq(xdgstatedir .. '/log', t.fix_slashes(eval('$NVIM_LOG_FILE'))) + -- Avoid "failed to open $NVIM_LOG_FILE" noise in test output. + expect_exit(command, 'qall!') end) end) end) @@ -339,9 +348,11 @@ describe('XDG defaults', function() local state_dir = is_os('win') and 'nvim-data' or 'nvim' local root_path = is_os('win') and 'C:' or '' - describe('with too long XDG variables', function() + describe('with too long XDG vars', function() before_each(function() clear({ + -- Ensure valid --listen address despite broken XDG vars (else Nvim won't start). + args = { '--listen', is_os('win') and '' or t.tmpname(false) }, args_rm = { 'runtimepath' }, env = { NVIM_LOG_FILE = testlog, @@ -361,6 +372,9 @@ describe('XDG defaults', function() it('are correctly set', function() if not is_os('win') then + -- Broken XDG vars cause serverstart() to fail (except on Windows, where servernames are not + -- informed by $XDG_STATE_HOME). + t.matches('Failed to start server: no such file or directory', t.pcall_err(fn.serverstart)) assert_log('Failed to start server: no such file or directory: /X/X/X', testlog, 10) end @@ -368,69 +382,69 @@ describe('XDG defaults', function() eq( ( - ( + t.fix_slashes( root_path - .. ('/x'):rep(4096) - .. '/nvim' - .. ',' - .. root_path - .. ('/a'):rep(2048) - .. '/nvim' - .. ',' - .. root_path - .. ('/b'):rep(2048) - .. '/nvim' - .. (',' .. root_path .. '/c/nvim') - .. ',' - .. root_path - .. ('/X'):rep(4096) - .. '/' - .. data_dir - .. '/site' - .. ',' - .. root_path - .. ('/A'):rep(2048) - .. '/nvim/site' - .. ',' - .. root_path - .. ('/B'):rep(2048) - .. '/nvim/site' - .. (',' .. root_path .. '/C/nvim/site') - .. ',' - .. vimruntime - .. ',' - .. libdir - .. (',' .. root_path .. '/C/nvim/site/after') - .. ',' - .. root_path - .. ('/B'):rep(2048) - .. '/nvim/site/after' - .. ',' - .. root_path - .. ('/A'):rep(2048) - .. '/nvim/site/after' - .. ',' - .. root_path - .. ('/X'):rep(4096) - .. '/' - .. data_dir - .. '/site/after' - .. (',' .. root_path .. '/c/nvim/after') - .. ',' - .. root_path - .. ('/b'):rep(2048) - .. '/nvim/after' - .. ',' - .. root_path - .. ('/a'):rep(2048) - .. '/nvim/after' - .. ',' - .. root_path - .. ('/x'):rep(4096) - .. '/nvim/after' - ):gsub('\\', '/') + .. ('/x'):rep(4096) + .. '/nvim' + .. ',' + .. root_path + .. ('/a'):rep(2048) + .. '/nvim' + .. ',' + .. root_path + .. ('/b'):rep(2048) + .. '/nvim' + .. (',' .. root_path .. '/c/nvim') + .. ',' + .. root_path + .. ('/X'):rep(4096) + .. '/' + .. data_dir + .. '/site' + .. ',' + .. root_path + .. ('/A'):rep(2048) + .. '/nvim/site' + .. ',' + .. root_path + .. ('/B'):rep(2048) + .. '/nvim/site' + .. (',' .. root_path .. '/C/nvim/site') + .. ',' + .. vimruntime + .. ',' + .. libdir + .. (',' .. root_path .. '/C/nvim/site/after') + .. ',' + .. root_path + .. ('/B'):rep(2048) + .. '/nvim/site/after' + .. ',' + .. root_path + .. ('/A'):rep(2048) + .. '/nvim/site/after' + .. ',' + .. root_path + .. ('/X'):rep(4096) + .. '/' + .. data_dir + .. '/site/after' + .. (',' .. root_path .. '/c/nvim/after') + .. ',' + .. root_path + .. ('/b'):rep(2048) + .. '/nvim/after' + .. ',' + .. root_path + .. ('/a'):rep(2048) + .. '/nvim/after' + .. ',' + .. root_path + .. ('/x'):rep(4096) + .. '/nvim/after' + ) ), - (api.nvim_get_option_value('runtimepath', {})):gsub('\\', '/') + t.fix_slashes(api.nvim_get_option_value('runtimepath', {})) ) command('set runtimepath&') command('set backupdir&') @@ -439,92 +453,94 @@ describe('XDG defaults', function() command('set viewdir&') eq( ( - ( + t.fix_slashes( root_path - .. ('/x'):rep(4096) - .. '/nvim' - .. ',' - .. root_path - .. ('/a'):rep(2048) - .. '/nvim' - .. ',' - .. root_path - .. ('/b'):rep(2048) - .. '/nvim' - .. (',' .. root_path .. '/c/nvim') - .. ',' - .. root_path - .. ('/X'):rep(4096) - .. '/' - .. data_dir - .. '/site' - .. ',' - .. root_path - .. ('/A'):rep(2048) - .. '/nvim/site' - .. ',' - .. root_path - .. ('/B'):rep(2048) - .. '/nvim/site' - .. (',' .. root_path .. '/C/nvim/site') - .. ',' - .. vimruntime - .. ',' - .. libdir - .. (',' .. root_path .. '/C/nvim/site/after') - .. ',' - .. root_path - .. ('/B'):rep(2048) - .. '/nvim/site/after' - .. ',' - .. root_path - .. ('/A'):rep(2048) - .. '/nvim/site/after' - .. ',' - .. root_path - .. ('/X'):rep(4096) - .. '/' - .. data_dir - .. '/site/after' - .. (',' .. root_path .. '/c/nvim/after') - .. ',' - .. root_path - .. ('/b'):rep(2048) - .. '/nvim/after' - .. ',' - .. root_path - .. ('/a'):rep(2048) - .. '/nvim/after' - .. ',' - .. root_path - .. ('/x'):rep(4096) - .. '/nvim/after' - ):gsub('\\', '/') + .. ('/x'):rep(4096) + .. '/nvim' + .. ',' + .. root_path + .. ('/a'):rep(2048) + .. '/nvim' + .. ',' + .. root_path + .. ('/b'):rep(2048) + .. '/nvim' + .. (',' .. root_path .. '/c/nvim') + .. ',' + .. root_path + .. ('/X'):rep(4096) + .. '/' + .. data_dir + .. '/site' + .. ',' + .. root_path + .. ('/A'):rep(2048) + .. '/nvim/site' + .. ',' + .. root_path + .. ('/B'):rep(2048) + .. '/nvim/site' + .. (',' .. root_path .. '/C/nvim/site') + .. ',' + .. vimruntime + .. ',' + .. libdir + .. (',' .. root_path .. '/C/nvim/site/after') + .. ',' + .. root_path + .. ('/B'):rep(2048) + .. '/nvim/site/after' + .. ',' + .. root_path + .. ('/A'):rep(2048) + .. '/nvim/site/after' + .. ',' + .. root_path + .. ('/X'):rep(4096) + .. '/' + .. data_dir + .. '/site/after' + .. (',' .. root_path .. '/c/nvim/after') + .. ',' + .. root_path + .. ('/b'):rep(2048) + .. '/nvim/after' + .. ',' + .. root_path + .. ('/a'):rep(2048) + .. '/nvim/after' + .. ',' + .. root_path + .. ('/x'):rep(4096) + .. '/nvim/after' + ) ), - (api.nvim_get_option_value('runtimepath', {})):gsub('\\', '/') + t.fix_slashes(api.nvim_get_option_value('runtimepath', {})) ) eq( '.,' .. root_path .. ('/X'):rep(4096) .. '/' .. state_dir .. '/backup//', - (api.nvim_get_option_value('backupdir', {}):gsub('\\', '/')) + t.fix_slashes(api.nvim_get_option_value('backupdir', {})) ) eq( root_path .. ('/X'):rep(4096) .. '/' .. state_dir .. '/swap//', - (api.nvim_get_option_value('directory', {})):gsub('\\', '/') + t.fix_slashes(api.nvim_get_option_value('directory', {})) ) eq( root_path .. ('/X'):rep(4096) .. '/' .. state_dir .. '/undo//', - (api.nvim_get_option_value('undodir', {})):gsub('\\', '/') + t.fix_slashes(api.nvim_get_option_value('undodir', {})) ) eq( root_path .. ('/X'):rep(4096) .. '/' .. state_dir .. '/view//', - (api.nvim_get_option_value('viewdir', {})):gsub('\\', '/') + t.fix_slashes(api.nvim_get_option_value('viewdir', {})) ) end) end) - describe('with XDG variables that can be expanded', function() + describe('with expandable XDG vars', function() before_each(function() clear({ + -- Ensure valid --listen address despite broken XDG vars (else Nvim won't start). + args = { '--listen', is_os('win') and '' or t.tmpname(false) }, args_rm = { 'runtimepath' }, env = { NVIM_LOG_FILE = testlog, @@ -544,6 +560,9 @@ describe('XDG defaults', function() it('are not expanded', function() if not is_os('win') then + -- Broken XDG vars cause serverstart() to fail (except on Windows, where servernames are not + -- informed by $XDG_STATE_HOME). + t.matches('Failed to start server: no such file or directory', t.pcall_err(fn.serverstart)) assert_log( 'Failed to start server: no such file or directory: %$XDG_RUNTIME_DIR%/', testlog, @@ -554,26 +573,26 @@ describe('XDG defaults', function() local vimruntime, libdir = vimruntime_and_libdir() eq( ( - ( + t.fix_slashes( '$XDG_DATA_HOME/nvim' - .. ',$XDG_DATA_DIRS/nvim' - .. ',$XDG_CONFIG_HOME/' - .. data_dir - .. '/site' - .. ',$XDG_CONFIG_DIRS/nvim/site' - .. ',' - .. vimruntime - .. ',' - .. libdir - .. ',$XDG_CONFIG_DIRS/nvim/site/after' - .. ',$XDG_CONFIG_HOME/' - .. data_dir - .. '/site/after' - .. ',$XDG_DATA_DIRS/nvim/after' - .. ',$XDG_DATA_HOME/nvim/after' - ):gsub('\\', '/') + .. ',$XDG_DATA_DIRS/nvim' + .. ',$XDG_CONFIG_HOME/' + .. data_dir + .. '/site' + .. ',$XDG_CONFIG_DIRS/nvim/site' + .. ',' + .. vimruntime + .. ',' + .. libdir + .. ',$XDG_CONFIG_DIRS/nvim/site/after' + .. ',$XDG_CONFIG_HOME/' + .. data_dir + .. '/site/after' + .. ',$XDG_DATA_DIRS/nvim/after' + .. ',$XDG_DATA_HOME/nvim/after' + ) ), - (api.nvim_get_option_value('runtimepath', {})):gsub('\\', '/') + t.fix_slashes(api.nvim_get_option_value('runtimepath', {})) ) command('set runtimepath&') command('set backupdir&') @@ -582,80 +601,80 @@ describe('XDG defaults', function() command('set viewdir&') eq( ( - ( + t.fix_slashes( '$XDG_DATA_HOME/nvim' - .. ',$XDG_DATA_DIRS/nvim' - .. ',$XDG_CONFIG_HOME/' - .. data_dir - .. '/site' - .. ',$XDG_CONFIG_DIRS/nvim/site' - .. ',' - .. vimruntime - .. ',' - .. libdir - .. ',$XDG_CONFIG_DIRS/nvim/site/after' - .. ',$XDG_CONFIG_HOME/' - .. data_dir - .. '/site/after' - .. ',$XDG_DATA_DIRS/nvim/after' - .. ',$XDG_DATA_HOME/nvim/after' - ):gsub('\\', '/') + .. ',$XDG_DATA_DIRS/nvim' + .. ',$XDG_CONFIG_HOME/' + .. data_dir + .. '/site' + .. ',$XDG_CONFIG_DIRS/nvim/site' + .. ',' + .. vimruntime + .. ',' + .. libdir + .. ',$XDG_CONFIG_DIRS/nvim/site/after' + .. ',$XDG_CONFIG_HOME/' + .. data_dir + .. '/site/after' + .. ',$XDG_DATA_DIRS/nvim/after' + .. ',$XDG_DATA_HOME/nvim/after' + ) ), - (api.nvim_get_option_value('runtimepath', {})):gsub('\\', '/') + t.fix_slashes(api.nvim_get_option_value('runtimepath', {})) ) eq( ('.,$XDG_CONFIG_HOME/' .. state_dir .. '/backup//'), - api.nvim_get_option_value('backupdir', {}):gsub('\\', '/') + t.fix_slashes(api.nvim_get_option_value('backupdir', {})) ) eq( ('$XDG_CONFIG_HOME/' .. state_dir .. '/swap//'), - api.nvim_get_option_value('directory', {}):gsub('\\', '/') + t.fix_slashes(api.nvim_get_option_value('directory', {})) ) eq( ('$XDG_CONFIG_HOME/' .. state_dir .. '/undo//'), - api.nvim_get_option_value('undodir', {}):gsub('\\', '/') + t.fix_slashes(api.nvim_get_option_value('undodir', {})) ) eq( ('$XDG_CONFIG_HOME/' .. state_dir .. '/view//'), - api.nvim_get_option_value('viewdir', {}):gsub('\\', '/') + t.fix_slashes(api.nvim_get_option_value('viewdir', {})) ) command('set all&') eq( - ( + t.fix_slashes( '$XDG_DATA_HOME/nvim' - .. ',$XDG_DATA_DIRS/nvim' - .. ',$XDG_CONFIG_HOME/' - .. data_dir - .. '/site' - .. ',$XDG_CONFIG_DIRS/nvim/site' - .. ',' - .. vimruntime - .. ',' - .. libdir - .. ',$XDG_CONFIG_DIRS/nvim/site/after' - .. ',$XDG_CONFIG_HOME/' - .. data_dir - .. '/site/after' - .. ',$XDG_DATA_DIRS/nvim/after' - .. ',$XDG_DATA_HOME/nvim/after' - ):gsub('\\', '/'), - (api.nvim_get_option_value('runtimepath', {})):gsub('\\', '/') + .. ',$XDG_DATA_DIRS/nvim' + .. ',$XDG_CONFIG_HOME/' + .. data_dir + .. '/site' + .. ',$XDG_CONFIG_DIRS/nvim/site' + .. ',' + .. vimruntime + .. ',' + .. libdir + .. ',$XDG_CONFIG_DIRS/nvim/site/after' + .. ',$XDG_CONFIG_HOME/' + .. data_dir + .. '/site/after' + .. ',$XDG_DATA_DIRS/nvim/after' + .. ',$XDG_DATA_HOME/nvim/after' + ), + t.fix_slashes(api.nvim_get_option_value('runtimepath', {})) ) eq( ('.,$XDG_CONFIG_HOME/' .. state_dir .. '/backup//'), - api.nvim_get_option_value('backupdir', {}):gsub('\\', '/') + t.fix_slashes(api.nvim_get_option_value('backupdir', {})) ) eq( ('$XDG_CONFIG_HOME/' .. state_dir .. '/swap//'), - api.nvim_get_option_value('directory', {}):gsub('\\', '/') + t.fix_slashes(api.nvim_get_option_value('directory', {})) ) eq( ('$XDG_CONFIG_HOME/' .. state_dir .. '/undo//'), - api.nvim_get_option_value('undodir', {}):gsub('\\', '/') + t.fix_slashes(api.nvim_get_option_value('undodir', {})) ) eq( ('$XDG_CONFIG_HOME/' .. state_dir .. '/view//'), - api.nvim_get_option_value('viewdir', {}):gsub('\\', '/') + t.fix_slashes(api.nvim_get_option_value('viewdir', {})) ) eq(nil, (fn.tempname()):match('XDG_RUNTIME_DIR')) end) @@ -895,7 +914,7 @@ describe('stdpath()', function() assert_alive() -- Check for crash. #8393 end) - it('reacts to $NVIM_APPNAME', function() + it('$NVIM_APPNAME', function() local appname = 'NVIM_APPNAME_TEST' .. ('_'):rep(106) clear({ env = { NVIM_APPNAME = appname, NVIM_LOG_FILE = testlog } }) eq(appname, fn.fnamemodify(fn.stdpath('config'), ':t')) @@ -916,10 +935,10 @@ describe('stdpath()', function() local function test_appname(testAppname, expected_exitcode) local lua_code = string.format( [[ - local child = vim.fn.jobstart({ vim.v.progpath, '--clean', '--headless', '+qall!' }, { env = { NVIM_APPNAME = %q } }) + local child = vim.fn.jobstart({ vim.v.progpath, '--clean', '--headless', '--listen', 'x', '+qall!' }, { env = { NVIM_APPNAME = %q } }) return vim.fn.jobwait({ child }, %d)[1] ]], - alter_slashes(testAppname), + testAppname, 3000 ) eq(expected_exitcode, exec_lua(lua_code)) @@ -935,9 +954,25 @@ describe('stdpath()', function() -- Valid appnames: test_appname('a/b', 0) test_appname('a/b\\c', 0) - if not is_os('win') then - assert_log('Failed to start server: no such file or directory:', testlog) - end + end) + + it('$NVIM_APPNAME relative path', function() + local tmpdir = t.tmpname(false) + t.mkdir(tmpdir) + + clear({ + args_rm = { '--listen' }, + env = { + NVIM_APPNAME = 'relative/appname', + NVIM_LOG_FILE = testlog, + TMPDIR = tmpdir, + }, + }) + + t.matches(vim.pesc(tmpdir), t.fix_slashes(fn.tempname())) + t.assert_nolog('tempdir', testlog, 100) + t.assert_nolog('TMPDIR', testlog, 100) + t.matches([=[[/\\]relative%-appname.[^/\\]+]=], api.nvim_get_vvar('servername')) end) describe('returns a String', function() @@ -945,19 +980,19 @@ describe('stdpath()', function() it('knows XDG_CONFIG_HOME', function() clear({ env = { - XDG_CONFIG_HOME = alter_slashes('/home/docwhat/.config'), + XDG_CONFIG_HOME = '/home/docwhat/.config', }, }) - eq(alter_slashes('/home/docwhat/.config/nvim'), fn.stdpath('config')) + eq('/home/docwhat/.config/nvim', t.fix_slashes(fn.stdpath('config'))) end) it('handles changes during runtime', function() clear({ env = { - XDG_CONFIG_HOME = alter_slashes('/home/original'), + XDG_CONFIG_HOME = '/home/original', } }) - eq(alter_slashes('/home/original/nvim'), fn.stdpath('config')) - command("let $XDG_CONFIG_HOME='" .. alter_slashes('/home/new') .. "'") - eq(alter_slashes('/home/new/nvim'), fn.stdpath('config')) + eq('/home/original/nvim', t.fix_slashes(fn.stdpath('config'))) + command("let $XDG_CONFIG_HOME='/home/new'") + eq('/home/new/nvim', t.fix_slashes(fn.stdpath('config'))) end) it("doesn't expand $VARIABLES", function() @@ -967,32 +1002,32 @@ describe('stdpath()', function() VARIABLES = 'this-should-not-happen', }, }) - eq(alter_slashes('$VARIABLES/nvim'), fn.stdpath('config')) + eq('$VARIABLES/nvim', t.fix_slashes(fn.stdpath('config'))) end) it("doesn't expand ~/", function() clear({ env = { - XDG_CONFIG_HOME = alter_slashes('~/frobnitz'), + XDG_CONFIG_HOME = '~/frobnitz', } }) - eq(alter_slashes('~/frobnitz/nvim'), fn.stdpath('config')) + eq('~/frobnitz/nvim', t.fix_slashes(fn.stdpath('config'))) end) end) describe('with "data"', function() it('knows XDG_DATA_HOME', function() clear({ env = { - XDG_DATA_HOME = alter_slashes('/home/docwhat/.local'), + XDG_DATA_HOME = '/home/docwhat/.local', } }) - eq(alter_slashes('/home/docwhat/.local/' .. datadir), fn.stdpath('data')) + eq('/home/docwhat/.local/' .. datadir, t.fix_slashes(fn.stdpath('data'))) end) it('handles changes during runtime', function() clear({ env = { - XDG_DATA_HOME = alter_slashes('/home/original'), + XDG_DATA_HOME = '/home/original', } }) - eq(alter_slashes('/home/original/' .. datadir), fn.stdpath('data')) - command("let $XDG_DATA_HOME='" .. alter_slashes('/home/new') .. "'") - eq(alter_slashes('/home/new/' .. datadir), fn.stdpath('data')) + eq('/home/original/' .. datadir, t.fix_slashes(fn.stdpath('data'))) + command("let $XDG_DATA_HOME='/home/new'") + eq('/home/new/' .. datadir, t.fix_slashes(fn.stdpath('data'))) end) it("doesn't expand $VARIABLES", function() @@ -1002,14 +1037,14 @@ describe('stdpath()', function() VARIABLES = 'this-should-not-happen', }, }) - eq(alter_slashes('$VARIABLES/' .. datadir), fn.stdpath('data')) + eq('$VARIABLES/' .. datadir, t.fix_slashes(fn.stdpath('data'))) end) it("doesn't expand ~/", function() clear({ env = { - XDG_DATA_HOME = alter_slashes('~/frobnitz'), + XDG_DATA_HOME = '~/frobnitz', } }) - eq(alter_slashes('~/frobnitz/' .. datadir), fn.stdpath('data')) + eq('~/frobnitz/' .. datadir, t.fix_slashes(fn.stdpath('data'))) end) end) @@ -1017,19 +1052,19 @@ describe('stdpath()', function() it('knows XDG_STATE_HOME', function() clear({ env = { - XDG_STATE_HOME = alter_slashes('/home/docwhat/.local'), + XDG_STATE_HOME = '/home/docwhat/.local', }, }) - eq(alter_slashes('/home/docwhat/.local/' .. statedir), fn.stdpath('state')) + eq('/home/docwhat/.local/' .. statedir, t.fix_slashes(fn.stdpath('state'))) end) it('handles changes during runtime', function() clear({ env = { - XDG_STATE_HOME = alter_slashes('/home/original'), + XDG_STATE_HOME = '/home/original', } }) - eq(alter_slashes('/home/original/' .. statedir), fn.stdpath('state')) - command("let $XDG_STATE_HOME='" .. alter_slashes('/home/new') .. "'") - eq(alter_slashes('/home/new/' .. statedir), fn.stdpath('state')) + eq('/home/original/' .. statedir, t.fix_slashes(fn.stdpath('state'))) + command("let $XDG_STATE_HOME='" .. '/home/new' .. "'") + eq('/home/new/' .. statedir, t.fix_slashes(fn.stdpath('state'))) end) it("doesn't expand $VARIABLES", function() @@ -1039,14 +1074,14 @@ describe('stdpath()', function() VARIABLES = 'this-should-not-happen', }, }) - eq(alter_slashes('$VARIABLES/' .. statedir), fn.stdpath('state')) + eq('$VARIABLES/' .. statedir, t.fix_slashes(fn.stdpath('state'))) end) it("doesn't expand ~/", function() clear({ env = { - XDG_STATE_HOME = alter_slashes('~/frobnitz'), + XDG_STATE_HOME = '~/frobnitz', } }) - eq(alter_slashes('~/frobnitz/' .. statedir), fn.stdpath('state')) + eq('~/frobnitz/' .. statedir, t.fix_slashes(fn.stdpath('state'))) end) end) @@ -1054,19 +1089,19 @@ describe('stdpath()', function() it('knows XDG_CACHE_HOME', function() clear({ env = { - XDG_CACHE_HOME = alter_slashes('/home/docwhat/.cache'), + XDG_CACHE_HOME = '/home/docwhat/.cache', }, }) - eq(alter_slashes('/home/docwhat/.cache/nvim'), fn.stdpath('cache')) + eq('/home/docwhat/.cache/nvim', t.fix_slashes(fn.stdpath('cache'))) end) it('handles changes during runtime', function() clear({ env = { - XDG_CACHE_HOME = alter_slashes('/home/original'), + XDG_CACHE_HOME = '/home/original', } }) - eq(alter_slashes('/home/original/nvim'), fn.stdpath('cache')) - command("let $XDG_CACHE_HOME='" .. alter_slashes('/home/new') .. "'") - eq(alter_slashes('/home/new/nvim'), fn.stdpath('cache')) + eq('/home/original/nvim', t.fix_slashes(fn.stdpath('cache'))) + command("let $XDG_CACHE_HOME='" .. '/home/new' .. "'") + eq('/home/new/nvim', t.fix_slashes(fn.stdpath('cache'))) end) it("doesn't expand $VARIABLES", function() @@ -1076,14 +1111,14 @@ describe('stdpath()', function() VARIABLES = 'this-should-not-happen', }, }) - eq(alter_slashes('$VARIABLES/nvim'), fn.stdpath('cache')) + eq('$VARIABLES/nvim', t.fix_slashes(fn.stdpath('cache'))) end) it("doesn't expand ~/", function() clear({ env = { - XDG_CACHE_HOME = alter_slashes('~/frobnitz'), + XDG_CACHE_HOME = '~/frobnitz', } }) - eq(alter_slashes('~/frobnitz/nvim'), fn.stdpath('cache')) + eq('~/frobnitz/nvim', t.fix_slashes(fn.stdpath('cache'))) end) end) end) @@ -1097,6 +1132,7 @@ describe('stdpath()', function() HOMEDRIVE = 'C:', HOMEPATH = '\\Users\\docwhat', LOCALAPPDATA = 'C:\\Users\\docwhat\\AppData\\Local', + NVIM_LOG_FILE = testlog, TEMP = 'C:\\Users\\docwhat\\AppData\\Local\\Temp', TMPDIR = 'C:\\Users\\docwhat\\AppData\\Local\\Temp', TMP = 'C:\\Users\\docwhat\\AppData\\Local\\Temp', @@ -1107,6 +1143,7 @@ describe('stdpath()', function() HOMEDRIVE = 'HOMEDRIVE-should-be-ignored', HOMEPATH = 'HOMEPATH-should-be-ignored', LOCALAPPDATA = 'LOCALAPPDATA-should-be-ignored', + NVIM_LOG_FILE = testlog, TEMP = 'TEMP-should-be-ignored', TMPDIR = 'TMPDIR-should-be-ignored', TMP = 'TMP-should-be-ignored', @@ -1130,12 +1167,18 @@ describe('stdpath()', function() describe(msg, function() it('set via system', function() set_paths_via_system(env_var_name, paths) - eq(expected_paths, fn.stdpath(stdpath_arg)) + eq(expected_paths, t.fix_slashes(fn.stdpath(stdpath_arg))) + if not is_os('win') then + assert_log('$TMPDIR tempdir not a directory.*TMPDIR%-should%-be%-ignored', testlog, 100) + end end) it('set at runtime', function() set_paths_at_runtime(env_var_name, paths) - eq(expected_paths, fn.stdpath(stdpath_arg)) + eq(expected_paths, t.fix_slashes(fn.stdpath(stdpath_arg))) + if not is_os('win') then + assert_log('$TMPDIR tempdir not a directory.*TMPDIR%-should%-be%-ignored', testlog, 100) + end end) end) end @@ -1146,10 +1189,10 @@ describe('stdpath()', function() 'config_dirs', 'XDG_CONFIG_DIRS', { - alter_slashes('/home/docwhat/.config'), + t.fix_slashes('/home/docwhat/.config'), }, { - alter_slashes('/home/docwhat/.config/nvim'), + t.fix_slashes('/home/docwhat/.config/nvim'), } ) @@ -1158,12 +1201,12 @@ describe('stdpath()', function() 'config_dirs', 'XDG_CONFIG_DIRS', { - alter_slashes('/home/docwhat/.config'), - alter_slashes('/etc/config'), + t.fix_slashes('/home/docwhat/.config'), + t.fix_slashes('/etc/config'), }, { - alter_slashes('/home/docwhat/.config/nvim'), - alter_slashes('/etc/config/nvim'), + t.fix_slashes('/home/docwhat/.config/nvim'), + t.fix_slashes('/etc/config/nvim'), } ) @@ -1173,25 +1216,25 @@ describe('stdpath()', function() 'XDG_CONFIG_DIRS', { '$HOME', '$TMP' }, { - alter_slashes('$HOME/nvim'), - alter_slashes('$TMP/nvim'), + t.fix_slashes('$HOME/nvim'), + t.fix_slashes('$TMP/nvim'), } ) behaves_like_dir_list_env("doesn't expand ~/", 'config_dirs', 'XDG_CONFIG_DIRS', { - alter_slashes('~/.oldconfig'), - alter_slashes('~/.olderconfig'), + t.fix_slashes('~/.oldconfig'), + t.fix_slashes('~/.olderconfig'), }, { - alter_slashes('~/.oldconfig/nvim'), - alter_slashes('~/.olderconfig/nvim'), + t.fix_slashes('~/.oldconfig/nvim'), + t.fix_slashes('~/.olderconfig/nvim'), }) end) describe('with "data_dirs"', function() behaves_like_dir_list_env('knows XDG_DATA_DIRS with one path', 'data_dirs', 'XDG_DATA_DIRS', { - alter_slashes('/home/docwhat/.data'), + t.fix_slashes('/home/docwhat/.data'), }, { - alter_slashes('/home/docwhat/.data/nvim'), + t.fix_slashes('/home/docwhat/.data/nvim'), }) behaves_like_dir_list_env( @@ -1199,12 +1242,12 @@ describe('stdpath()', function() 'data_dirs', 'XDG_DATA_DIRS', { - alter_slashes('/home/docwhat/.data'), - alter_slashes('/etc/local'), + t.fix_slashes('/home/docwhat/.data'), + t.fix_slashes('/etc/local'), }, { - alter_slashes('/home/docwhat/.data/nvim'), - alter_slashes('/etc/local/nvim'), + t.fix_slashes('/home/docwhat/.data/nvim'), + t.fix_slashes('/etc/local/nvim'), } ) @@ -1214,17 +1257,17 @@ describe('stdpath()', function() 'XDG_DATA_DIRS', { '$HOME', '$TMP' }, { - alter_slashes('$HOME/nvim'), - alter_slashes('$TMP/nvim'), + t.fix_slashes('$HOME/nvim'), + t.fix_slashes('$TMP/nvim'), } ) behaves_like_dir_list_env("doesn't expand ~/", 'data_dirs', 'XDG_DATA_DIRS', { - alter_slashes('~/.oldconfig'), - alter_slashes('~/.olderconfig'), + t.fix_slashes('~/.oldconfig'), + t.fix_slashes('~/.olderconfig'), }, { - alter_slashes('~/.oldconfig/nvim'), - alter_slashes('~/.olderconfig/nvim'), + t.fix_slashes('~/.oldconfig/nvim'), + t.fix_slashes('~/.olderconfig/nvim'), }) end) end) @@ -1244,23 +1287,3 @@ describe('stdpath()', function() end) end) end) - -describe('autocommands', function() - it('closes terminal with default shell on success', function() - clear() - api.nvim_set_option_value('shell', n.testprg('shell-test'), {}) - command('set shellcmdflag=EXIT shellredir= shellpipe= shellquote= shellxquote=') - - -- Should not block other events - command('let g:n=0') - command('au BufEnter * let g:n = g:n + 1') - - command('terminal') - eq(1, eval('get(g:, "n", 0)')) - - t.retry(nil, 1000, function() - neq('terminal', api.nvim_get_option_value('buftype', { buf = 0 })) - eq(2, eval('get(g:, "n", 0)')) - end) - end) -end) diff --git a/test/functional/plugin/editorconfig_spec.lua b/test/functional/plugin/editorconfig_spec.lua index 839a723405..5f69b8938a 100644 --- a/test/functional/plugin/editorconfig_spec.lua +++ b/test/functional/plugin/editorconfig_spec.lua @@ -7,7 +7,6 @@ local eq = t.eq local pathsep = n.get_pathsep() local fn = n.fn local api = n.api -local exec_lua = n.exec_lua local testdir = 'Xtest-editorconfig' @@ -16,8 +15,16 @@ local testdir = 'Xtest-editorconfig' local function test_case(name, expected) local filename = testdir .. pathsep .. name command('edit ' .. filename) + for opt, val in pairs(expected) do - eq(val, api.nvim_get_option_value(opt, { buf = 0 }), name) + local opt_info = api.nvim_get_option_info2(opt, {}) + if opt_info.scope == 'win' then + eq(val, api.nvim_get_option_value(opt, { win = 0 }), name) + elseif opt_info.scope == 'buf' then + eq(val, api.nvim_get_option_value(opt, { buf = 0 }), name) + else + eq(val, api.nvim_get_option_value(opt, {}), name) + end end end @@ -93,6 +100,12 @@ setup(function() [max_line_length.txt] max_line_length = 42 + + [short_spelling_language.txt] + spelling_language = de + + [long_spelling_language.txt] + spelling_language = en-NZ ]] ) end) @@ -213,13 +226,18 @@ But not this one end) it('does not operate on invalid buffers', function() - local ok, err = unpack(exec_lua([[ + local ok, err = unpack(n.exec_lua(function() vim.cmd.edit('test.txt') local bufnr = vim.api.nvim_get_current_buf() vim.cmd.bwipeout(bufnr) - return {pcall(require('editorconfig').config, bufnr)} - ]])) + return { pcall(require('editorconfig').config, bufnr) } + end)) eq(true, ok, err) end) + + it('sets spelllang', function() + test_case('short_spelling_language.txt', { spelllang = 'de' }) + test_case('long_spelling_language.txt', { spelllang = 'en_nz' }) + end) end) diff --git a/test/functional/plugin/health_spec.lua b/test/functional/plugin/health_spec.lua index 9c7c953fb0..7089313303 100644 --- a/test/functional/plugin/health_spec.lua +++ b/test/functional/plugin/health_spec.lua @@ -40,11 +40,22 @@ describe(':checkhealth', function() matches('ERROR $VIM .* zub', curbuf_contents()) end) - it('completions can be listed via getcompletion()', function() - clear() + it('getcompletion()', function() + clear { args = { '-u', 'NORC', '+set runtimepath+=test/functional/fixtures' } } + eq('vim.deprecated', getcompletion('vim', 'checkhealth')[1]) eq('vim.provider', getcompletion('vim.prov', 'checkhealth')[1]) eq('vim.lsp', getcompletion('vim.ls', 'checkhealth')[1]) + + -- "test_plug/health/init.lua" should complete as "test_plug", not "test_plug.health". #30342 + eq({ + 'test_plug', + 'test_plug.full_render', + 'test_plug.submodule', + 'test_plug.submodule_empty', + 'test_plug.success1', + 'test_plug.success2', + }, getcompletion('test_plug', 'checkhealth')) end) it('completion checks for vim.health._complete() return type #28456', function() @@ -57,11 +68,9 @@ describe(':checkhealth', function() end) end) -describe('health.vim', function() +describe('vim.health', function() before_each(function() - clear { args = { '-u', 'NORC' } } - -- Provides healthcheck functions - command('set runtimepath+=test/functional/fixtures') + clear { args = { '-u', 'NORC', '+set runtimepath+=test/functional/fixtures' } } end) describe(':checkhealth', function() @@ -70,7 +79,7 @@ describe('health.vim', function() n.expect([[ ============================================================================== - test_plug.full_render: require("test_plug.full_render.health").check() + test_plug.full_render: require("test_plug.full_render.health").check() report 1 ~ - OK life is fine @@ -93,7 +102,7 @@ describe('health.vim', function() n.expect([[ ============================================================================== - test_plug: require("test_plug.health").check() + test_plug: require("test_plug.health").check() report 1 ~ - OK everything is fine @@ -102,7 +111,7 @@ describe('health.vim', function() - OK nothing to see here ============================================================================== - test_plug.success1: require("test_plug.success1.health").check() + test_plug.success1: require("test_plug.success1.health").check() report 1 ~ - OK everything is fine @@ -111,7 +120,7 @@ describe('health.vim', function() - OK nothing to see here ============================================================================== - test_plug.success2: require("test_plug.success2.health").check() + test_plug.success2: require("test_plug.success2.health").check() another 1 ~ - OK ok @@ -123,7 +132,7 @@ describe('health.vim', function() n.expect([[ ============================================================================== - test_plug.submodule: require("test_plug.submodule.health").check() + test_plug.submodule: require("test_plug.submodule.health").check() report 1 ~ - OK everything is fine @@ -148,9 +157,10 @@ describe('health.vim', function() local screen = Screen.new(50, 12) screen:attach() screen:set_default_attr_ids({ + h1 = { reverse = true }, + h2 = { foreground = tonumber('0x6a0dad') }, Ok = { foreground = Screen.colors.LightGreen }, Error = { foreground = Screen.colors.Red }, - Heading = { foreground = tonumber('0x6a0dad') }, Bar = { foreground = Screen.colors.LightGrey, background = Screen.colors.DarkGrey }, }) command('checkhealth foo success1') @@ -158,15 +168,15 @@ describe('health.vim', function() screen:expect { grid = [[ ^ | - {Bar:──────────────────────────────────────────────────}| - {Heading:foo: } | + {Bar: }| + {h1:foo: }| | - {Error:ERROR} No healthcheck found for "foo" plugin. | | - {Bar:──────────────────────────────────────────────────}| - {Heading:test_plug.success1: require("test_plug.success1.he}| + {Bar: }| + {h1:test_plug.success1: require("test_pl}| | - {Heading:report 1} | + {h2:report 1} | - {Ok:OK} everything is fine | | ]], @@ -179,7 +189,7 @@ describe('health.vim', function() n.expect([[ ============================================================================== - non_existent_healthcheck: + non_existent_healthcheck: - ERROR No healthcheck found for "non_existent_healthcheck" plugin. ]]) @@ -207,18 +217,17 @@ end) describe(':checkhealth window', function() before_each(function() - clear { args = { '-u', 'NORC' } } - -- Provides healthcheck functions - command('set runtimepath+=test/functional/fixtures') + clear { args = { '-u', 'NORC', '+set runtimepath+=test/functional/fixtures' } } command('set nofoldenable nowrap laststatus=0') end) it('opens directly if no buffer created', function() local screen = Screen.new(50, 12) screen:set_default_attr_ids { + h1 = { reverse = true }, + h2 = { foreground = tonumber('0x6a0dad') }, [1] = { foreground = Screen.colors.Blue, bold = true }, [14] = { foreground = Screen.colors.LightGrey, background = Screen.colors.DarkGray }, - [31] = { foreground = tonumber('0x6a0dad') }, [32] = { foreground = Screen.colors.PaleGreen2 }, } screen:attach({ ext_multigrid = true }) @@ -230,15 +239,15 @@ describe(':checkhealth window', function() [3:--------------------------------------------------]| ## grid 2 ^ | - {14:──────────────────────────────────────────────────}| - {14:────────────────────────────} | - {31:test_plug.success1: require("test_plug.success1. }| - {31:health").check()} | + {14: }| + {14: } | + {h1:test_plug.success1: }| + {h1:require("test_plug.success1.health").check()} | | - {31:report 1} | + {h2:report 1} | - {32:OK} everything is fine | | - {31:report 2} | + {h2:report 2} | - {32:OK} nothing to see here | ## grid 3 | @@ -249,9 +258,10 @@ describe(':checkhealth window', function() local function test_health_vsplit(left, emptybuf, mods) local screen = Screen.new(50, 20) screen:set_default_attr_ids { + h1 = { reverse = true }, + h2 = { foreground = tonumber('0x6a0dad') }, [1] = { foreground = Screen.colors.Blue, bold = true }, [14] = { foreground = Screen.colors.LightGrey, background = Screen.colors.DarkGray }, - [31] = { foreground = tonumber('0x6a0dad') }, [32] = { foreground = Screen.colors.PaleGreen2 }, } screen:attach({ ext_multigrid = true }) @@ -271,19 +281,20 @@ describe(':checkhealth window', function() | ## grid 4 ^ | - {14:─────────────────────────}|*3 - {14:───} | - {31:test_plug.success1: }| - {31:require("test_plug. }| - {31:success1.health").check()}| + {14: }|*3 + {14: } | + {h1:test_plug. }| + {h1:success1: }| + {h1:require("test_plug. }| + {h1:success1.health").check()}| | - {31:report 1} | + {h2:report 1} | - {32:OK} everything is fine | | - {31:report 2} | + {h2:report 2} | - {32:OK} nothing to see here | | - {1:~ }|*4 + {1:~ }|*3 ]]):format( left and '[4:-------------------------]│[2:------------------------]|*19' or '[2:------------------------]│[4:-------------------------]|*19', @@ -330,10 +341,10 @@ describe(':checkhealth window', function() | ## grid 4 ^ | - ──────────────────────────────────────────────────| - ──────────────────────────── | - test_plug.success1: require("test_plug.success1. | - health").check() | + | + | + test_plug.success1: | + require("test_plug.success1.health").check() | | report 1 | - OK everything is fine | @@ -382,7 +393,7 @@ describe(':checkhealth window', function() command('file my_buff') command('checkhealth success1') -- define a function that collects all buffers in each tab - -- returns a dictionary like {tab1 = ["buf1", "buf2"], tab2 = ["buf3"]} + -- returns a dict like {tab1 = ["buf1", "buf2"], tab2 = ["buf3"]} source([[ function CollectBuffersPerTab() let buffs = {} diff --git a/test/functional/plugin/lsp/codelens_spec.lua b/test/functional/plugin/lsp/codelens_spec.lua index cd20e95dd1..20ef1cb49e 100644 --- a/test/functional/plugin/lsp/codelens_spec.lua +++ b/test/functional/plugin/lsp/codelens_spec.lua @@ -13,36 +13,34 @@ describe('vim.lsp.codelens', function() it('on_codelens_stores_and_displays_lenses', function() local fake_uri = 'file:///fake/uri' - local bufnr = exec_lua( - [[ - fake_uri = ... + local bufnr = exec_lua(function() local bufnr = vim.uri_to_bufnr(fake_uri) - local lines = {'So', 'many', 'lines'} + local lines = { 'So', 'many', 'lines' } vim.fn.bufload(bufnr) vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines) return bufnr - ]], - fake_uri - ) + end) - exec_lua( - [[ - local bufnr = ... + exec_lua(function() local lenses = { { range = { - start = { line = 0, character = 0, }, - ['end'] = { line = 0, character = 0 } + start = { line = 0, character = 0 }, + ['end'] = { line = 0, character = 0 }, }, - command = { title = 'Lens1', command = 'Dummy' } + command = { title = 'Lens1', command = 'Dummy' }, }, } - vim.lsp.codelens.on_codelens(nil, lenses, {method='textDocument/codeLens', client_id=1, bufnr=bufnr}) - ]], - bufnr - ) + vim.lsp.codelens.on_codelens( + nil, + lenses, + { method = 'textDocument/codeLens', client_id = 1, bufnr = bufnr } + ) + end) - local stored_lenses = exec_lua('return vim.lsp.codelens.get(...)', bufnr) + local stored_lenses = exec_lua(function() + return vim.lsp.codelens.get(bufnr) + end) local expected = { { range = { @@ -57,58 +55,54 @@ describe('vim.lsp.codelens', function() } eq(expected, stored_lenses) - local virtual_text_chunks = exec_lua( - [[ - local bufnr = ... + local virtual_text_chunks = exec_lua(function() local ns = vim.lsp.codelens.__namespaces[1] local extmarks = vim.api.nvim_buf_get_extmarks(bufnr, ns, 0, -1, {}) return vim.api.nvim_buf_get_extmark_by_id(bufnr, ns, extmarks[1][1], { details = true })[3].virt_text - ]], - bufnr - ) + end) eq({ [1] = { 'Lens1', 'LspCodeLens' } }, virtual_text_chunks) end) it('can clear all lens', function() local fake_uri = 'file:///fake/uri' - local bufnr = exec_lua( - [[ - fake_uri = ... + local bufnr = exec_lua(function() local bufnr = vim.uri_to_bufnr(fake_uri) - local lines = {'So', 'many', 'lines'} + local lines = { 'So', 'many', 'lines' } vim.fn.bufload(bufnr) vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines) return bufnr - ]], - fake_uri - ) + end) - exec_lua( - [[ - local bufnr = ... + exec_lua(function() local lenses = { { range = { - start = { line = 0, character = 0, }, - ['end'] = { line = 0, character = 0 } + start = { line = 0, character = 0 }, + ['end'] = { line = 0, character = 0 }, }, - command = { title = 'Lens1', command = 'Dummy' } + command = { title = 'Lens1', command = 'Dummy' }, }, } - vim.lsp.codelens.on_codelens(nil, lenses, {method='textDocument/codeLens', client_id=1, bufnr=bufnr}) - ]], - bufnr - ) + vim.lsp.codelens.on_codelens( + nil, + lenses, + { method = 'textDocument/codeLens', client_id = 1, bufnr = bufnr } + ) + end) - local stored_lenses = exec_lua('return vim.lsp.codelens.get(...)', bufnr) + local stored_lenses = exec_lua(function() + return vim.lsp.codelens.get(bufnr) + end) eq(1, #stored_lenses) - exec_lua([[ + exec_lua(function() vim.lsp.codelens.clear() - ]]) + end) - stored_lenses = exec_lua('return vim.lsp.codelens.get(...)', bufnr) + stored_lenses = exec_lua(function() + return vim.lsp.codelens.get(bufnr) + end) eq(0, #stored_lenses) end) end) diff --git a/test/functional/plugin/lsp/completion_spec.lua b/test/functional/plugin/lsp/completion_spec.lua index 2798d57381..4df8d77d44 100644 --- a/test/functional/plugin/lsp/completion_spec.lua +++ b/test/functional/plugin/lsp/completion_spec.lua @@ -1,9 +1,16 @@ ---@diagnostic disable: no-unknown local t = require('test.testutil') +local t_lsp = require('test.functional.plugin.lsp.testutil') local n = require('test.functional.testnvim')() +local clear = n.clear local eq = t.eq +local neq = t.neq local exec_lua = n.exec_lua +local feed = n.feed +local retry = t.retry + +local create_server_definition = t_lsp.create_server_definition --- Convert completion results. --- @@ -11,38 +18,32 @@ local exec_lua = n.exec_lua ---@param candidates lsp.CompletionList|lsp.CompletionItem[] ---@param lnum? integer 0-based, defaults to 0 ---@return {items: table[], server_start_boundary: integer?} -local function complete(line, candidates, lnum) +local function complete(line, candidates, lnum, server_boundary) lnum = lnum or 0 -- nvim_win_get_cursor returns 0 based column, line:find returns 1 based local cursor_col = line:find('|') - 1 line = line:gsub('|', '') - return exec_lua( - [[ - local line, cursor_col, lnum, result = ... + return exec_lua(function(result) local line_to_cursor = line:sub(1, cursor_col) local client_start_boundary = vim.fn.match(line_to_cursor, '\\k*$') - local items, server_start_boundary = require("vim.lsp._completion")._convert_results( + local items, new_server_boundary = require('vim.lsp.completion')._convert_results( line, lnum, cursor_col, + 1, client_start_boundary, - nil, + server_boundary, result, - "utf-16" + 'utf-16' ) return { items = items, - server_start_boundary = server_start_boundary + server_start_boundary = new_server_boundary, } - ]], - line, - cursor_col, - lnum, - candidates - ) + end, candidates) end -describe('vim.lsp._completion', function() +describe('vim.lsp.completion: item conversion', function() before_each(n.clear) -- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion @@ -70,39 +71,24 @@ describe('vim.lsp._completion', function() textEdit = { newText = 'foobar', range = range0 }, }, { label = 'foocar', sortText = 'f', textEdit = { newText = 'foobar', range = range0 } }, - -- real-world snippet text + -- plain text { label = 'foocar', sortText = 'g', - insertText = 'foodar', + insertText = 'foodar(${1:var1})', + insertTextFormat = 1, + }, + { + label = '•INT16_C(c)', + insertText = 'INT16_C(${1:c})', insertTextFormat = 2, + filterText = 'INT16_C', + sortText = 'h', textEdit = { - newText = 'foobar(${1:place holder}, ${2:more ...holder{\\}})', + newText = 'INT16_C(${1:c})', range = range0, }, }, - { - label = 'foocar', - sortText = 'h', - insertText = 'foodar(${1:var1} typ1, ${2:var2} *typ2) {$0\\}', - insertTextFormat = 2, - }, - -- nested snippet tokens - { - label = 'foocar', - sortText = 'i', - insertText = 'foodar(${1:${2|typ1,typ2|}}) {$0\\}', - insertTextFormat = 2, - }, - -- braced tabstop - { label = 'foocar', sortText = 'j', insertText = 'foodar()${0}', insertTextFormat = 2 }, - -- plain text - { - label = 'foocar', - sortText = 'k', - insertText = 'foodar(${1:var1})', - insertTextFormat = 1, - }, } local expected = { { @@ -131,23 +117,167 @@ describe('vim.lsp._completion', function() }, { abbr = 'foocar', - word = 'foobar(place holder, more ...holder{})', + word = 'foodar(${1:var1})', -- marked as PlainText, text is used as is }, { - abbr = 'foocar', - word = 'foodar(var1 typ1, var2 *typ2) {}', + abbr = '•INT16_C(c)', + word = 'INT16_C', }, + } + local result = complete('|', completion_list) + result = vim.tbl_map(function(x) + return { + abbr = x.abbr, + word = x.word, + } + end, result.items) + eq(expected, result) + end) + + it('filters on label if filterText is missing', function() + local completion_list = { + { label = 'foo' }, + { label = 'bar' }, + } + local result = complete('fo|', completion_list) + local expected = { { - abbr = 'foocar', - word = 'foodar(typ1) {}', + abbr = 'foo', + word = 'foo', }, + } + result = vim.tbl_map(function(x) + return { + abbr = x.abbr, + word = x.word, + } + end, result.items) + eq(expected, result) + end) + + it('works on non word prefix', function() + local completion_list = { + { label = ' foo', insertText = '->foo' }, + } + local result = complete('wp.|', completion_list, 0, 2) + local expected = { { - abbr = 'foocar', - word = 'foodar()', + abbr = ' foo', + word = '->foo', }, + } + result = vim.tbl_map(function(x) + return { + abbr = x.abbr, + word = x.word, + } + end, result.items) + eq(expected, result) + end) + + it('trims trailing newline or tab from textEdit', function() + local range0 = { + start = { line = 0, character = 0 }, + ['end'] = { line = 0, character = 0 }, + } + local items = { { - abbr = 'foocar', - word = 'foodar(${1:var1})', + detail = 'ansible.builtin', + filterText = 'lineinfile ansible.builtin.lineinfile builtin ansible', + kind = 7, + label = 'ansible.builtin.lineinfile', + sortText = '2_ansible.builtin.lineinfile', + textEdit = { + newText = 'ansible.builtin.lineinfile:\n ', + range = range0, + }, + }, + } + local result = complete('|', items) + result = vim.tbl_map(function(x) + return { + abbr = x.abbr, + word = x.word, + } + end, result.items) + + local expected = { + { + abbr = 'ansible.builtin.lineinfile', + word = 'ansible.builtin.lineinfile:', + }, + } + eq(expected, result) + end) + + it('prefers wordlike components for snippets', function() + -- There are two goals here: + -- + -- 1. The `word` should match what the user started typing, so that vim.fn.complete() doesn't + -- filter it away, preventing snippet expansion + -- + -- For example, if they type `items@ins`, luals returns `table.insert(items, $0)` as + -- textEdit.newText and `insert` as label. + -- There would be no prefix match if textEdit.newText is used as `word` + -- + -- 2. If users do not expand a snippet, but continue typing, they should see a somewhat reasonable + -- `word` getting inserted. + -- + -- For example in: + -- + -- insertText: "testSuites ${1:Env}" + -- label: "testSuites" + -- + -- "testSuites" should have priority as `word`, as long as the full snippet gets expanded on accept (<c-y>) + local range0 = { + start = { line = 0, character = 0 }, + ['end'] = { line = 0, character = 0 }, + } + local completion_list = { + -- luals postfix snippet (typed text: items@ins|) + { + label = 'insert', + insertTextFormat = 2, + textEdit = { + newText = 'table.insert(items, $0)', + range = range0, + }, + }, + + -- eclipse.jdt.ls `new` snippet + { + label = 'new', + insertTextFormat = 2, + textEdit = { + newText = '${1:Object} ${2:foo} = new ${1}(${3});\n${0}', + range = range0, + }, + textEditText = '${1:Object} ${2:foo} = new ${1}(${3});\n${0}', + }, + + -- eclipse.jdt.ls `List.copyO` function call completion + { + label = 'copyOf(Collection<? extends E> coll) : List<E>', + insertTextFormat = 2, + insertText = 'copyOf', + textEdit = { + newText = 'copyOf(${1:coll})', + range = range0, + }, + }, + } + local expected = { + { + abbr = 'copyOf(Collection<? extends E> coll) : List<E>', + word = 'copyOf', + }, + { + abbr = 'insert', + word = 'insert', + }, + { + abbr = 'new', + word = 'new', }, } local result = complete('|', completion_list) @@ -159,6 +289,7 @@ describe('vim.lsp._completion', function() end, result.items) eq(expected, result) end) + it('uses correct start boundary', function() local completion_list = { isIncomplete = false, @@ -186,8 +317,10 @@ describe('vim.lsp._completion', function() dup = 1, empty = 1, icase = 1, + info = '', kind = 'Module', menu = '', + hl_group = '', word = 'this_thread', } local result = complete(' std::this|', completion_list) @@ -218,7 +351,7 @@ describe('vim.lsp._completion', function() }, }, { - filterText = 'notthis_thread', + filterText = 'no_match', insertText = 'notthis_thread', insertTextFormat = 1, kind = 9, @@ -240,8 +373,10 @@ describe('vim.lsp._completion', function() dup = 1, empty = 1, icase = 1, + info = '', kind = 'Module', menu = '', + hl_group = '', word = 'this_thread', } local result = complete(' std::this|is', completion_list) @@ -278,4 +413,316 @@ describe('vim.lsp._completion', function() eq('item-property-has-priority', item.data) eq({ line = 1, character = 1 }, item.textEdit.range.start) end) + + it( + 'uses insertText as textEdit.newText if there are editRange defaults but no textEditText', + function() + --- @type lsp.CompletionList + local completion_list = { + isIncomplete = false, + itemDefaults = { + editRange = { + start = { line = 1, character = 1 }, + ['end'] = { line = 1, character = 4 }, + }, + insertTextFormat = 2, + data = 'foobar', + }, + items = { + { + insertText = 'the-insertText', + label = 'hello', + data = 'item-property-has-priority', + }, + }, + } + local result = complete('|', completion_list) + eq(1, #result.items) + local text = result.items[1].user_data.nvim.lsp.completion_item.textEdit.newText + eq('the-insertText', text) + end + ) + + it( + 'defaults to label as textEdit.newText if insertText or textEditText are not present', + function() + local completion_list = { + isIncomplete = false, + itemDefaults = { + editRange = { + start = { line = 1, character = 1 }, + ['end'] = { line = 1, character = 4 }, + }, + insertTextFormat = 2, + data = 'foobar', + }, + items = { + { + label = 'hello', + data = 'item-property-has-priority', + }, + }, + } + local result = complete('|', completion_list) + eq(1, #result.items) + local text = result.items[1].user_data.nvim.lsp.completion_item.textEdit.newText + eq('hello', text) + end + ) +end) + +describe('vim.lsp.completion: protocol', function() + before_each(function() + clear() + exec_lua(create_server_definition) + exec_lua(function() + _G.capture = {} + --- @diagnostic disable-next-line:duplicate-set-field + vim.fn.complete = function(col, matches) + _G.capture.col = col + _G.capture.matches = matches + end + end) + end) + + after_each(clear) + + --- @param completion_result lsp.CompletionList + --- @return integer + local function create_server(completion_result) + return exec_lua(function() + local server = _G._create_server({ + capabilities = { + completionProvider = { + triggerCharacters = { '.' }, + }, + }, + handlers = { + ['textDocument/completion'] = function(_, _, callback) + callback(nil, completion_result) + end, + }, + }) + + local bufnr = vim.api.nvim_get_current_buf() + vim.api.nvim_win_set_buf(0, bufnr) + return vim.lsp.start({ + name = 'dummy', + cmd = server.cmd, + on_attach = function(client, bufnr0) + vim.lsp.completion.enable(true, client.id, bufnr0, { + convert = function(item) + return { abbr = item.label:gsub('%b()', '') } + end, + }) + end, + }) + end) + end + + local function assert_matches(fn) + retry(nil, nil, function() + fn(exec_lua('return _G.capture.matches')) + end) + end + + --- @param pos [integer, integer] + local function trigger_at_pos(pos) + exec_lua(function() + local win = vim.api.nvim_get_current_win() + vim.api.nvim_win_set_cursor(win, pos) + vim.lsp.completion.trigger() + end) + + retry(nil, nil, function() + neq(nil, exec_lua('return _G.capture.col')) + end) + end + + it('fetches completions and shows them using complete on trigger', function() + create_server({ + isIncomplete = false, + items = { + { + label = 'hello', + }, + { + label = 'hercules', + tags = { 1 }, -- 1 represents Deprecated tag + }, + { + label = 'hero', + deprecated = true, + }, + }, + }) + + feed('ih') + trigger_at_pos({ 1, 1 }) + + assert_matches(function(matches) + eq({ + { + abbr = 'hello', + dup = 1, + empty = 1, + icase = 1, + info = '', + kind = 'Unknown', + menu = '', + hl_group = '', + user_data = { + nvim = { + lsp = { + client_id = 1, + completion_item = { + label = 'hello', + }, + }, + }, + }, + word = 'hello', + }, + { + abbr = 'hercules', + dup = 1, + empty = 1, + icase = 1, + info = '', + kind = 'Unknown', + menu = '', + hl_group = 'DiagnosticDeprecated', + user_data = { + nvim = { + lsp = { + client_id = 1, + completion_item = { + label = 'hercules', + tags = { 1 }, + }, + }, + }, + }, + word = 'hercules', + }, + { + abbr = 'hero', + dup = 1, + empty = 1, + icase = 1, + info = '', + kind = 'Unknown', + menu = '', + hl_group = 'DiagnosticDeprecated', + user_data = { + nvim = { + lsp = { + client_id = 1, + completion_item = { + label = 'hero', + deprecated = true, + }, + }, + }, + }, + word = 'hero', + }, + }, matches) + end) + end) + + it('merges results from multiple clients', function() + create_server({ + isIncomplete = false, + items = { + { + label = 'hello', + }, + }, + }) + create_server({ + isIncomplete = false, + items = { + { + label = 'hallo', + }, + }, + }) + + feed('ih') + trigger_at_pos({ 1, 1 }) + + assert_matches(function(matches) + eq(2, #matches) + eq('hello', matches[1].word) + eq('hallo', matches[2].word) + end) + end) + + it('executes commands', function() + local completion_list = { + isIncomplete = false, + items = { + { + label = 'hello', + command = { + arguments = { '1', '0' }, + command = 'dummy', + title = '', + }, + }, + }, + } + local client_id = create_server(completion_list) + + exec_lua(function() + _G.called = false + local client = assert(vim.lsp.get_client_by_id(client_id)) + client.commands.dummy = function() + _G.called = true + end + end) + + feed('ih') + trigger_at_pos({ 1, 1 }) + + local item = completion_list.items[1] + exec_lua(function() + vim.v.completed_item = { + user_data = { + nvim = { + lsp = { + client_id = client_id, + completion_item = item, + }, + }, + }, + } + end) + + feed('<C-x><C-o><C-y>') + + assert_matches(function(matches) + eq(1, #matches) + eq('hello', matches[1].word) + eq(true, exec_lua('return _G.called')) + end) + end) + + it('enable(…,{convert=fn}) custom word/abbr format', function() + create_server({ + isIncomplete = false, + items = { + { + label = 'foo(bar)', + }, + }, + }) + + feed('ifo') + trigger_at_pos({ 1, 1 }) + assert_matches(function(matches) + eq('foo', matches[1].abbr) + end) + end) end) diff --git a/test/functional/plugin/lsp/diagnostic_spec.lua b/test/functional/plugin/lsp/diagnostic_spec.lua index c5e14ffdc2..78c684083b 100644 --- a/test/functional/plugin/lsp/diagnostic_spec.lua +++ b/test/functional/plugin/lsp/diagnostic_spec.lua @@ -11,7 +11,9 @@ local neq = t.neq local create_server_definition = t_lsp.create_server_definition describe('vim.lsp.diagnostic', function() - local fake_uri + local fake_uri --- @type string + local client_id --- @type integer + local diagnostic_bufnr --- @type integer before_each(function() clear { env = { @@ -19,198 +21,174 @@ describe('vim.lsp.diagnostic', function() VIMRUNTIME = os.getenv 'VIMRUNTIME', } } - exec_lua [[ + exec_lua(function() require('vim.lsp') - make_range = function(x1, y1, x2, y2) + _G.make_range = function(x1, y1, x2, y2) return { start = { line = x1, character = y1 }, ['end'] = { line = x2, character = y2 } } end - make_error = function(msg, x1, y1, x2, y2) + _G.make_error = function(msg, x1, y1, x2, y2) return { - range = make_range(x1, y1, x2, y2), + range = _G.make_range(x1, y1, x2, y2), message = msg, severity = 1, } end - make_warning = function(msg, x1, y1, x2, y2) + _G.make_warning = function(msg, x1, y1, x2, y2) return { - range = make_range(x1, y1, x2, y2), + range = _G.make_range(x1, y1, x2, y2), message = msg, severity = 2, } end - make_information = function(msg, x1, y1, x2, y2) + _G.make_information = function(msg, x1, y1, x2, y2) return { - range = make_range(x1, y1, x2, y2), + range = _G.make_range(x1, y1, x2, y2), message = msg, severity = 3, } end - function get_extmarks(bufnr, client_id) - local namespace = vim.lsp.diagnostic.get_namespace(client_id) + function _G.get_extmarks(bufnr, client_id0) + local namespace = vim.lsp.diagnostic.get_namespace(client_id0) local ns = vim.diagnostic.get_namespace(namespace) local extmarks = {} if ns.user_data.virt_text_ns then - for _, e in pairs(vim.api.nvim_buf_get_extmarks(bufnr, ns.user_data.virt_text_ns, 0, -1, {details=true})) do + for _, e in + pairs( + vim.api.nvim_buf_get_extmarks( + bufnr, + ns.user_data.virt_text_ns, + 0, + -1, + { details = true } + ) + ) + do table.insert(extmarks, e) end end if ns.user_data.underline_ns then - for _, e in pairs(vim.api.nvim_buf_get_extmarks(bufnr, ns.user_data.underline_ns, 0, -1, {details=true})) do + for _, e in + pairs( + vim.api.nvim_buf_get_extmarks( + bufnr, + ns.user_data.underline_ns, + 0, + -1, + { details = true } + ) + ) + do table.insert(extmarks, e) end end return extmarks end - client_id = vim.lsp.start_client { + client_id = assert(vim.lsp.start_client { cmd_env = { - NVIM_LUA_NOTRACK = "1"; - }; + NVIM_LUA_NOTRACK = '1', + }, cmd = { - vim.v.progpath, '-es', '-u', 'NONE', '--headless' - }; - offset_encoding = "utf-16"; - } - ]] + vim.v.progpath, + '-es', + '-u', + 'NONE', + '--headless', + }, + offset_encoding = 'utf-16', + }) + end) fake_uri = 'file:///fake/uri' - exec_lua( - [[ - fake_uri = ... + exec_lua(function() diagnostic_bufnr = vim.uri_to_bufnr(fake_uri) - local lines = {"1st line of text", "2nd line of text", "wow", "cool", "more", "lines"} + local lines = { '1st line of text', '2nd line of text', 'wow', 'cool', 'more', 'lines' } vim.fn.bufload(diagnostic_bufnr) vim.api.nvim_buf_set_lines(diagnostic_bufnr, 0, 1, false, lines) vim.api.nvim_win_set_buf(0, diagnostic_bufnr) - return diagnostic_bufnr - ]], - fake_uri - ) + end) end) after_each(function() clear() end) - describe('vim.lsp.diagnostic', function() - it('maintains LSP information when translating diagnostics', function() - local result = exec_lua [[ - local diagnostics = { - make_error("Error 1", 1, 1, 1, 5), - } - - diagnostics[1].code = 42 - diagnostics[1].data = "Hello world" - - vim.lsp.diagnostic.on_publish_diagnostics(nil, { - uri = fake_uri, - diagnostics = diagnostics, - }, {client_id=client_id}) - - return { - vim.diagnostic.get(diagnostic_bufnr, {lnum=1})[1], - vim.lsp.diagnostic.get_line_diagnostics(diagnostic_bufnr, 1)[1], - } - ]] - eq({ code = 42, data = 'Hello world' }, result[1].user_data.lsp) - eq(42, result[1].code) - eq(42, result[2].code) - eq('Hello world', result[2].data) - end) - end) - describe('vim.lsp.diagnostic.on_publish_diagnostics', function() it('allows configuring the virtual text via vim.lsp.with', function() local expected_spacing = 10 - local extmarks = exec_lua( - [[ - PublishDiagnostics = vim.lsp.with(vim.lsp.diagnostic.on_publish_diagnostics, { + local extmarks = exec_lua(function() + _G.PublishDiagnostics = vim.lsp.with(vim.lsp.diagnostic.on_publish_diagnostics, { virtual_text = { - spacing = ..., + spacing = expected_spacing, }, }) - PublishDiagnostics(nil, { - uri = fake_uri, - diagnostics = { - make_error('Delayed Diagnostic', 4, 4, 4, 4), - } - }, {client_id=client_id} - ) + _G.PublishDiagnostics(nil, { + uri = fake_uri, + diagnostics = { + _G.make_error('Delayed Diagnostic', 4, 4, 4, 4), + }, + }, { client_id = client_id }) - return get_extmarks(diagnostic_bufnr, client_id) - ]], - expected_spacing - ) + return _G.get_extmarks(diagnostic_bufnr, client_id) + end) - local virt_text = extmarks[1][4].virt_text - local spacing = virt_text[1][1] + local spacing = extmarks[1][4].virt_text[1][1] eq(expected_spacing, #spacing) end) it('allows configuring the virtual text via vim.lsp.with using a function', function() local expected_spacing = 10 - local extmarks = exec_lua( - [[ - spacing = ... - - PublishDiagnostics = vim.lsp.with(vim.lsp.diagnostic.on_publish_diagnostics, { + local extmarks = exec_lua(function() + _G.PublishDiagnostics = vim.lsp.with(vim.lsp.diagnostic.on_publish_diagnostics, { virtual_text = function() return { - spacing = spacing, + spacing = expected_spacing, } end, }) - PublishDiagnostics(nil, { - uri = fake_uri, - diagnostics = { - make_error('Delayed Diagnostic', 4, 4, 4, 4), - } - }, {client_id=client_id} - ) + _G.PublishDiagnostics(nil, { + uri = fake_uri, + diagnostics = { + _G.make_error('Delayed Diagnostic', 4, 4, 4, 4), + }, + }, { client_id = client_id }) - return get_extmarks(diagnostic_bufnr, client_id) - ]], - expected_spacing - ) + return _G.get_extmarks(diagnostic_bufnr, client_id) + end) - local virt_text = extmarks[1][4].virt_text - local spacing = virt_text[1][1] + local spacing = extmarks[1][4].virt_text[1][1] eq(expected_spacing, #spacing) end) it('allows filtering via severity limit', function() local get_extmark_count_with_severity = function(severity_limit) - return exec_lua( - [[ - PublishDiagnostics = vim.lsp.with(vim.lsp.diagnostic.on_publish_diagnostics, { + return exec_lua(function() + _G.PublishDiagnostics = vim.lsp.with(vim.lsp.diagnostic.on_publish_diagnostics, { underline = false, virtual_text = { - severity = { min = ... } + severity = { min = severity_limit }, }, }) - PublishDiagnostics(nil, { - uri = fake_uri, - diagnostics = { - make_warning('Delayed Diagnostic', 4, 4, 4, 4), - } - }, {client_id=client_id} - ) - - return #get_extmarks(diagnostic_bufnr, client_id) - ]], - severity_limit - ) + _G.PublishDiagnostics(nil, { + uri = fake_uri, + diagnostics = { + _G.make_warning('Delayed Diagnostic', 4, 4, 4, 4), + }, + }, { client_id = client_id }) + + return #_G.get_extmarks(diagnostic_bufnr, client_id) + end, client_id, fake_uri, severity_limit) end -- No messages with Error or higher @@ -223,218 +201,284 @@ describe('vim.lsp.diagnostic', function() it('correctly handles UTF-16 offsets', function() local line = 'All 💼 and no 🎉 makes Jack a dull 👦' - local result = exec_lua( - [[ - local line = ... - vim.api.nvim_buf_set_lines(diagnostic_bufnr, 0, -1, false, {line}) + local result = exec_lua(function() + vim.api.nvim_buf_set_lines(diagnostic_bufnr, 0, -1, false, { line }) vim.lsp.diagnostic.on_publish_diagnostics(nil, { - uri = fake_uri, - diagnostics = { - make_error('UTF-16 Diagnostic', 0, 7, 0, 8), - } - }, {client_id=client_id} - ) + uri = fake_uri, + diagnostics = { + _G.make_error('UTF-16 Diagnostic', 0, 7, 0, 8), + }, + }, { client_id = client_id }) local diags = vim.diagnostic.get(diagnostic_bufnr) vim.lsp.stop_client(client_id) vim.api.nvim_exec_autocmds('VimLeavePre', { modeline = false }) return diags - ]], - line - ) + end) eq(1, #result) - eq(exec_lua([[return vim.str_byteindex(..., 7, true)]], line), result[1].col) - eq(exec_lua([[return vim.str_byteindex(..., 8, true)]], line), result[1].end_col) + eq( + exec_lua(function() + return vim.str_byteindex(line, 7, true) + end), + result[1].col + ) + eq( + exec_lua(function() + return vim.str_byteindex(line, 8, true) + end), + result[1].end_col + ) end) it('does not create buffer on empty diagnostics', function() - local bufnr - -- No buffer is created without diagnostics - bufnr = exec_lua [[ - vim.lsp.diagnostic.on_publish_diagnostics(nil, { - uri = "file:///fake/uri2", - diagnostics = {}, - }, {client_id=client_id}) - return vim.fn.bufnr(vim.uri_to_fname("file:///fake/uri2")) - ]] - eq(-1, bufnr) + eq( + -1, + exec_lua(function() + vim.lsp.diagnostic.on_publish_diagnostics(nil, { + uri = 'file:///fake/uri2', + diagnostics = {}, + }, { client_id = client_id }) + return vim.fn.bufnr(vim.uri_to_fname('file:///fake/uri2')) + end) + ) -- Create buffer on diagnostics - bufnr = exec_lua [[ - vim.lsp.diagnostic.on_publish_diagnostics(nil, { - uri = "file:///fake/uri2", - diagnostics = { - make_error('Diagnostic', 0, 0, 0, 0), - }, - }, {client_id=client_id}) - return vim.fn.bufnr(vim.uri_to_fname("file:///fake/uri2")) - ]] - neq(-1, bufnr) - eq(1, exec_lua([[return #vim.diagnostic.get(...)]], bufnr)) + neq( + -1, + exec_lua(function() + vim.lsp.diagnostic.on_publish_diagnostics(nil, { + uri = 'file:///fake/uri2', + diagnostics = { + _G.make_error('Diagnostic', 0, 0, 0, 0), + }, + }, { client_id = client_id }) + return vim.fn.bufnr(vim.uri_to_fname('file:///fake/uri2')) + end) + ) + eq( + 1, + exec_lua(function() + return #vim.diagnostic.get(_G.bufnr) + end) + ) -- Clear diagnostics after buffer was created - bufnr = exec_lua [[ - vim.lsp.diagnostic.on_publish_diagnostics(nil, { - uri = "file:///fake/uri2", - diagnostics = {}, - }, {client_id=client_id}) - return vim.fn.bufnr(vim.uri_to_fname("file:///fake/uri2")) - ]] - neq(-1, bufnr) - eq(0, exec_lua([[return #vim.diagnostic.get(...)]], bufnr)) + neq( + -1, + exec_lua(function() + vim.lsp.diagnostic.on_publish_diagnostics(nil, { + uri = 'file:///fake/uri2', + diagnostics = {}, + }, { client_id = client_id }) + return vim.fn.bufnr(vim.uri_to_fname('file:///fake/uri2')) + end) + ) + eq( + 0, + exec_lua(function() + return #vim.diagnostic.get(_G.bufnr) + end) + ) end) end) describe('vim.lsp.diagnostic.on_diagnostic', function() before_each(function() exec_lua(create_server_definition) - exec_lua([[ - server = _create_server({ + exec_lua(function() + _G.server = _G._create_server({ capabilities = { - diagnosticProvider = { - } - } + diagnosticProvider = {}, + }, }) - function get_extmarks(bufnr, client_id) - local namespace = vim.lsp.diagnostic.get_namespace(client_id, true) + function _G.get_extmarks(bufnr, client_id0) + local namespace = vim.lsp.diagnostic.get_namespace(client_id0, true) local ns = vim.diagnostic.get_namespace(namespace) local extmarks = {} if ns.user_data.virt_text_ns then - for _, e in pairs(vim.api.nvim_buf_get_extmarks(bufnr, ns.user_data.virt_text_ns, 0, -1, {details=true})) do + for _, e in + pairs( + vim.api.nvim_buf_get_extmarks( + bufnr, + ns.user_data.virt_text_ns, + 0, + -1, + { details = true } + ) + ) + do table.insert(extmarks, e) end end if ns.user_data.underline_ns then - for _, e in pairs(vim.api.nvim_buf_get_extmarks(bufnr, ns.user_data.underline_ns, 0, -1, {details=true})) do + for _, e in + pairs( + vim.api.nvim_buf_get_extmarks( + bufnr, + ns.user_data.underline_ns, + 0, + -1, + { details = true } + ) + ) + do table.insert(extmarks, e) end end return extmarks end - client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd }) - ]]) + client_id = vim.lsp.start({ name = 'dummy', cmd = _G.server.cmd }) + end) end) it('adds diagnostics to vim.diagnostics', function() - local diags = exec_lua([[ - vim.lsp.diagnostic.on_diagnostic(nil, - { - kind = 'full', - items = { - make_error('Pull Diagnostic', 4, 4, 4, 4), - } + local diags = exec_lua(function() + vim.lsp.diagnostic.on_diagnostic(nil, { + kind = 'full', + items = { + _G.make_error('Pull Diagnostic', 4, 4, 4, 4), }, - { - params = { - textDocument = { uri = fake_uri }, - }, - uri = fake_uri, - client_id = client_id, + }, { + params = { + textDocument = { uri = fake_uri }, }, - {} - ) + uri = fake_uri, + client_id = client_id, + }, {}) return vim.diagnostic.get(diagnostic_bufnr) - ]]) + end) eq(1, #diags) eq('Pull Diagnostic', diags[1].message) end) + it('severity defaults to error if missing', function() + ---@type vim.Diagnostic[] + local diagnostics = exec_lua(function() + vim.lsp.diagnostic.on_diagnostic(nil, { + kind = 'full', + items = { + { + range = _G.make_range(4, 4, 4, 4), + message = 'bad!', + }, + }, + }, { + params = { + textDocument = { uri = fake_uri }, + }, + uri = fake_uri, + client_id = client_id, + }, {}) + return vim.diagnostic.get(diagnostic_bufnr) + end) + eq(1, #diagnostics) + eq(1, diagnostics[1].severity) + end) + it('allows configuring the virtual text via vim.lsp.with', function() local expected_spacing = 10 - local extmarks = exec_lua( - [[ - Diagnostic = vim.lsp.with(vim.lsp.diagnostic.on_diagnostic, { + local extmarks = exec_lua(function() + _G.Diagnostic = vim.lsp.with(vim.lsp.diagnostic.on_diagnostic, { virtual_text = { - spacing = ..., + spacing = expected_spacing, }, }) - Diagnostic(nil, - { - kind = 'full', - items = { - make_error('Pull Diagnostic', 4, 4, 4, 4), - } + _G.Diagnostic(nil, { + kind = 'full', + items = { + _G.make_error('Pull Diagnostic', 4, 4, 4, 4), }, - { - params = { - textDocument = { uri = fake_uri }, - }, - uri = fake_uri, - client_id = client_id, + }, { + params = { + textDocument = { uri = fake_uri }, }, - {} - ) + uri = fake_uri, + client_id = client_id, + }, {}) - return get_extmarks(diagnostic_bufnr, client_id) - ]], - expected_spacing - ) + return _G.get_extmarks(diagnostic_bufnr, client_id) + end) eq(2, #extmarks) eq(expected_spacing, #extmarks[1][4].virt_text[1][1]) end) it('clears diagnostics when client detaches', function() - exec_lua([[ - vim.lsp.diagnostic.on_diagnostic(nil, - { - kind = 'full', - items = { - make_error('Pull Diagnostic', 4, 4, 4, 4), - } + exec_lua(function() + vim.lsp.diagnostic.on_diagnostic(nil, { + kind = 'full', + items = { + _G.make_error('Pull Diagnostic', 4, 4, 4, 4), }, - { - params = { - textDocument = { uri = fake_uri }, - }, - uri = fake_uri, - client_id = client_id, + }, { + params = { + textDocument = { uri = fake_uri }, }, - {} - ) - ]]) - local diags = exec_lua([[return vim.diagnostic.get(diagnostic_bufnr)]]) - eq(1, #diags) + uri = fake_uri, + client_id = client_id, + }, {}) + end) + + eq( + 1, + exec_lua(function() + return #vim.diagnostic.get(diagnostic_bufnr) + end) + ) - exec_lua([[ vim.lsp.stop_client(client_id) ]]) + exec_lua(function() + vim.lsp.stop_client(client_id) + end) - diags = exec_lua([[return vim.diagnostic.get(diagnostic_bufnr)]]) - eq(0, #diags) + eq( + 0, + exec_lua(function() + return #vim.diagnostic.get(diagnostic_bufnr) + end) + ) end) it('keeps diagnostics when one client detaches and others still are attached', function() - exec_lua([[ - client_id2 = vim.lsp.start({ name = 'dummy2', cmd = server.cmd }) - - vim.lsp.diagnostic.on_diagnostic(nil, - { - kind = 'full', - items = { - make_error('Pull Diagnostic', 4, 4, 4, 4), - } + local client_id2 + exec_lua(function() + client_id2 = vim.lsp.start({ name = 'dummy2', cmd = _G.server.cmd }) + + vim.lsp.diagnostic.on_diagnostic(nil, { + kind = 'full', + items = { + _G.make_error('Pull Diagnostic', 4, 4, 4, 4), }, - { - params = { - textDocument = { uri = fake_uri }, - }, - uri = fake_uri, - client_id = client_id, + }, { + params = { + textDocument = { uri = fake_uri }, }, - {} - ) - ]]) - local diags = exec_lua([[return vim.diagnostic.get(diagnostic_bufnr)]]) - eq(1, #diags) + uri = fake_uri, + client_id = client_id, + }, {}) + end) + + eq( + 1, + exec_lua(function() + return #vim.diagnostic.get(diagnostic_bufnr) + end) + ) - exec_lua([[ vim.lsp.stop_client(client_id2) ]]) + exec_lua(function() + vim.lsp.stop_client(client_id2) + end) - diags = exec_lua([[return vim.diagnostic.get(diagnostic_bufnr)]]) - eq(1, #diags) + eq( + 1, + exec_lua(function() + return #vim.diagnostic.get(diagnostic_bufnr) + end) + ) end) end) end) diff --git a/test/functional/plugin/lsp/handler_spec.lua b/test/functional/plugin/lsp/handler_spec.lua index 013a5fb5e7..4b05b676a8 100644 --- a/test/functional/plugin/lsp/handler_spec.lua +++ b/test/functional/plugin/lsp/handler_spec.lua @@ -11,28 +11,31 @@ describe('lsp-handlers', function() it('should return a table with the default keys', function() eq( { hello = 'world' }, - exec_lua [[ - return vim.lsp._with_extend('test', { 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 [[ - return vim.lsp._with_extend('test', { other = true, hello = 'world' }, { hello = 'universe' }) - ]] + 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, - "return vim.lsp._with_extend('test', { hello = 'world' }, { invalid = true })" - ) + pcall_err(exec_lua, function() + return vim.lsp._with_extend('test', { hello = 'world' }, { invalid = true }) + end) ) end) end) diff --git a/test/functional/plugin/lsp/incremental_sync_spec.lua b/test/functional/plugin/lsp/incremental_sync_spec.lua index 238b90b57d..f60e159d64 100644 --- a/test/functional/plugin/lsp/incremental_sync_spec.lua +++ b/test/functional/plugin/lsp/incremental_sync_spec.lua @@ -10,11 +10,9 @@ local feed = n.feed before_each(function() clear() - exec_lua [[ - local evname = ... + exec_lua(function() local sync = require('vim.lsp.sync') local events = {} - local buffer_cache = {} -- local format_line_ending = { -- ["unix"] = '\n', @@ -24,35 +22,43 @@ before_each(function() -- local line_ending = format_line_ending[vim.api.nvim_get_option_value('fileformat', {})] - - function test_register(bufnr, id, offset_encoding, line_ending) - local curr_lines + --- @diagnostic disable-next-line:duplicate-set-field + function _G.test_register(bufnr, id, offset_encoding, line_ending) local prev_lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, true) - local function callback(_, bufnr, changedtick, firstline, lastline, new_lastline) - if test_unreg == id then + local function callback(_, bufnr0, _changedtick, firstline, lastline, new_lastline) + if _G.test_unreg == id then return true end - local curr_lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, true) + local curr_lines = vim.api.nvim_buf_get_lines(bufnr0, 0, -1, true) local incremental_change = sync.compute_diff( - prev_lines, curr_lines, firstline, lastline, new_lastline, offset_encoding, line_ending) + prev_lines, + curr_lines, + firstline, + lastline, + new_lastline, + offset_encoding, + line_ending + ) table.insert(events, incremental_change) prev_lines = curr_lines end - local opts = {on_lines=callback, on_detach=callback, on_reload=callback} + local opts = { on_lines = callback, on_detach = callback, on_reload = callback } vim.api.nvim_buf_attach(bufnr, false, opts) end - function get_events() + --- @diagnostic disable-next-line:duplicate-set-field + function _G.get_events() local ret_events = events events = {} return ret_events end - ]] + end) end) +--- @param edit_operations string[] local function test_edit( prev_buffer, edit_operations, @@ -64,13 +70,22 @@ local function test_edit( line_ending = line_ending or '\n' api.nvim_buf_set_lines(0, 0, -1, true, prev_buffer) - exec_lua('return test_register(...)', 0, 'test1', offset_encoding, line_ending) + exec_lua(function() + return _G.test_register(0, 'test1', offset_encoding, line_ending) + end) for _, edit in ipairs(edit_operations) do feed(edit) end - eq(expected_text_changes, exec_lua('return get_events(...)')) - exec_lua("test_unreg = 'test1'") + eq( + expected_text_changes, + exec_lua(function() + return _G.get_events() + end) + ) + exec_lua(function() + _G.test_unreg = 'test1' + end) end describe('incremental synchronization', function() @@ -170,7 +185,7 @@ describe('incremental synchronization', function() } test_edit({ 'a' }, { 'rb' }, expected_text_changes, 'utf-16', '\n') end) - it('deleting a line', function() + it('deleting the first line', function() local expected_text_changes = { { range = { @@ -183,11 +198,49 @@ describe('incremental synchronization', function() line = 1, }, }, - rangeLength = 12, + rangeLength = 6, + text = '', + }, + } + test_edit({ 'hello', 'world' }, { 'ggdd' }, expected_text_changes, 'utf-16', '\n') + end) + it('deleting the last line', function() + local expected_text_changes = { + { + range = { + ['start'] = { + character = 0, + line = 1, + }, + ['end'] = { + character = 0, + line = 2, + }, + }, + rangeLength = 6, + text = '', + }, + } + test_edit({ 'hello', 'world' }, { '2ggdd' }, expected_text_changes, 'utf-16', '\n') + end) + it('deleting all lines', function() + local expected_text_changes = { + { + range = { + ['start'] = { + character = 0, + line = 0, + }, + ['end'] = { + character = 5, + line = 1, + }, + }, + rangeLength = 11, text = '', }, } - test_edit({ 'hello world' }, { 'dd' }, expected_text_changes, 'utf-16', '\n') + test_edit({ 'hello', 'world' }, { 'ggdG' }, expected_text_changes, 'utf-16', '\n') end) it('deleting an empty line', function() local expected_text_changes = { diff --git a/test/functional/plugin/lsp/inlay_hint_spec.lua b/test/functional/plugin/lsp/inlay_hint_spec.lua index d3b5ae0e4e..471f2cc3e8 100644 --- a/test/functional/plugin/lsp/inlay_hint_spec.lua +++ b/test/functional/plugin/lsp/inlay_hint_spec.lua @@ -12,7 +12,8 @@ local api = n.api local clear_notrace = t_lsp.clear_notrace local create_server_definition = t_lsp.create_server_definition -local text = dedent([[ +describe('vim.lsp.inlay_hint', function() + local text = dedent([[ auto add(int a, int b) { return a + b; } int main() { @@ -22,7 +23,7 @@ int main() { } }]]) -local response = [==[ + local response = [==[ [ {"kind":1,"paddingLeft":false,"label":"-> int","position":{"character":22,"line":0},"paddingRight":false}, {"kind":2,"paddingLeft":false,"label":"a:","position":{"character":15,"line":5},"paddingRight":true}, @@ -30,7 +31,7 @@ local response = [==[ ] ]==] -local grid_without_inlay_hints = [[ + local grid_without_inlay_hints = [[ auto add(int a, int b) { return a + b; } | | int main() { | @@ -42,7 +43,7 @@ local grid_without_inlay_hints = [[ | ]] -local grid_with_inlay_hints = [[ + local grid_with_inlay_hints = [[ auto add(int a, int b){1:-> int} { return a + b; } | | int main() { | @@ -54,54 +55,58 @@ local grid_with_inlay_hints = [[ | ]] ---- @type test.functional.ui.screen -local screen -before_each(function() - clear_notrace() - screen = Screen.new(50, 9) - screen:attach() - - exec_lua(create_server_definition) - exec_lua( - [[ - local response = ... - server = _create_server({ - capabilities = { - inlayHintProvider = true, - }, - handlers = { - ['textDocument/inlayHint'] = function(_, _, callback) - callback(nil, vim.json.decode(response)) - end, - } - }) + --- @type test.functional.ui.screen + local screen - bufnr = vim.api.nvim_get_current_buf() - vim.api.nvim_win_set_buf(0, bufnr) + --- @type integer + local client_id - client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd }) - ]], - response - ) + --- @type integer + local bufnr - insert(text) - exec_lua([[vim.lsp.inlay_hint.enable(true, { bufnr = bufnr })]]) - screen:expect({ grid = grid_with_inlay_hints }) -end) + before_each(function() + clear_notrace() + screen = Screen.new(50, 9) + screen:attach() -after_each(function() - api.nvim_exec_autocmds('VimLeavePre', { modeline = false }) -end) + bufnr = n.api.nvim_get_current_buf() + exec_lua(create_server_definition) + client_id = exec_lua(function() + _G.server = _G._create_server({ + capabilities = { + inlayHintProvider = true, + }, + handlers = { + ['textDocument/inlayHint'] = function(_, _, callback) + callback(nil, vim.json.decode(response)) + end, + }, + }) + + return vim.lsp.start({ name = 'dummy', cmd = _G.server.cmd }) + end) + + insert(text) + exec_lua(function() + vim.lsp.inlay_hint.enable(true, { bufnr = bufnr }) + end) + screen:expect({ grid = grid_with_inlay_hints }) + end) + + after_each(function() + api.nvim_exec_autocmds('VimLeavePre', { modeline = false }) + end) -describe('vim.lsp.inlay_hint', function() it('clears inlay hints when sole client detaches', function() - exec_lua([[vim.lsp.stop_client(client_id)]]) + exec_lua(function() + vim.lsp.stop_client(client_id) + end) screen:expect({ grid = grid_without_inlay_hints, unchanged = true }) end) it('does not clear inlay hints when one of several clients detaches', function() - exec_lua([[ - server2 = _create_server({ + local client_id2 = exec_lua(function() + _G.server2 = _G._create_server({ capabilities = { inlayHintProvider = true, }, @@ -109,13 +114,16 @@ describe('vim.lsp.inlay_hint', function() ['textDocument/inlayHint'] = function(_, _, callback) callback(nil, {}) end, - } + }, }) - client2 = vim.lsp.start({ name = 'dummy2', cmd = server2.cmd }) + local client_id2 = vim.lsp.start({ name = 'dummy2', cmd = _G.server2.cmd }) vim.lsp.inlay_hint.enable(true, { bufnr = bufnr }) - ]]) + return client_id2 + end) - exec_lua([[ vim.lsp.stop_client(client2) ]]) + exec_lua(function() + vim.lsp.stop_client(client_id2) + end) screen:expect({ grid = grid_with_inlay_hints, unchanged = true }) end) @@ -123,61 +131,85 @@ describe('vim.lsp.inlay_hint', function() it('validation', function() t.matches( 'enable: expected boolean, got table', - t.pcall_err(exec_lua, [[vim.lsp.inlay_hint.enable({}, { bufnr = bufnr })]]) + t.pcall_err(exec_lua, function() + --- @diagnostic disable-next-line:param-type-mismatch + vim.lsp.inlay_hint.enable({}, { bufnr = bufnr }) + end) ) t.matches( 'enable: expected boolean, got number', - t.pcall_err(exec_lua, [[vim.lsp.inlay_hint.enable(42)]]) + t.pcall_err(exec_lua, function() + --- @diagnostic disable-next-line:param-type-mismatch + vim.lsp.inlay_hint.enable(42) + end) ) t.matches( 'filter: expected table, got number', - t.pcall_err(exec_lua, [[vim.lsp.inlay_hint.enable(true, 42)]]) + t.pcall_err(exec_lua, function() + --- @diagnostic disable-next-line:param-type-mismatch + vim.lsp.inlay_hint.enable(true, 42) + end) ) end) describe('clears/applies inlay hints when passed false/true/nil', function() + local bufnr2 --- @type integer before_each(function() - exec_lua([[ - bufnr2 = vim.api.nvim_create_buf(true, false) - vim.lsp.buf_attach_client(bufnr2, client_id) - vim.api.nvim_win_set_buf(0, bufnr2) - ]]) + bufnr2 = exec_lua(function() + local bufnr2_0 = vim.api.nvim_create_buf(true, false) + vim.lsp.buf_attach_client(bufnr2_0, client_id) + vim.api.nvim_win_set_buf(0, bufnr2_0) + return bufnr2_0 + end) insert(text) - exec_lua([[vim.lsp.inlay_hint.enable(true, { bufnr = bufnr2 })]]) - exec_lua([[vim.api.nvim_win_set_buf(0, bufnr)]]) + exec_lua(function() + vim.lsp.inlay_hint.enable(true, { bufnr = bufnr2 }) + end) + n.api.nvim_win_set_buf(0, bufnr) screen:expect({ grid = grid_with_inlay_hints }) end) it('for one single buffer', function() - exec_lua([[ + exec_lua(function() vim.lsp.inlay_hint.enable(false, { bufnr = bufnr }) vim.api.nvim_win_set_buf(0, bufnr2) - ]]) + end) screen:expect({ grid = grid_with_inlay_hints, unchanged = true }) - exec_lua([[vim.api.nvim_win_set_buf(0, bufnr)]]) + n.api.nvim_win_set_buf(0, bufnr) screen:expect({ grid = grid_without_inlay_hints, unchanged = true }) - exec_lua([[vim.lsp.inlay_hint.enable(true, { bufnr = bufnr })]]) + exec_lua(function() + vim.lsp.inlay_hint.enable(true, { bufnr = bufnr }) + end) screen:expect({ grid = grid_with_inlay_hints, unchanged = true }) - exec_lua( - [[vim.lsp.inlay_hint.enable(not vim.lsp.inlay_hint.is_enabled({ bufnr = bufnr }), { bufnr = bufnr })]] - ) + exec_lua(function() + vim.lsp.inlay_hint.enable( + not vim.lsp.inlay_hint.is_enabled({ bufnr = bufnr }), + { bufnr = bufnr } + ) + end) screen:expect({ grid = grid_without_inlay_hints, unchanged = true }) - exec_lua([[vim.lsp.inlay_hint.enable(true, { bufnr = bufnr })]]) + exec_lua(function() + vim.lsp.inlay_hint.enable(true, { bufnr = bufnr }) + end) screen:expect({ grid = grid_with_inlay_hints, unchanged = true }) end) it('for all buffers', function() - exec_lua([[vim.lsp.inlay_hint.enable(false)]]) + exec_lua(function() + vim.lsp.inlay_hint.enable(false) + end) screen:expect({ grid = grid_without_inlay_hints, unchanged = true }) - exec_lua([[vim.api.nvim_win_set_buf(0, bufnr2)]]) + n.api.nvim_win_set_buf(0, bufnr2) screen:expect({ grid = grid_without_inlay_hints, unchanged = true }) - exec_lua([[vim.lsp.inlay_hint.enable(true)]]) + exec_lua(function() + vim.lsp.inlay_hint.enable(true) + end) screen:expect({ grid = grid_with_inlay_hints, unchanged = true }) - exec_lua([[vim.api.nvim_win_set_buf(0, bufnr)]]) + n.api.nvim_win_set_buf(0, bufnr) screen:expect({ grid = grid_with_inlay_hints, unchanged = true }) end) end) @@ -198,10 +230,8 @@ describe('vim.lsp.inlay_hint', function() paddingRight = false, } - exec_lua( - [[ - local expected2 = ... - server2 = _create_server({ + exec_lua(function() + _G.server2 = _G._create_server({ capabilities = { inlayHintProvider = true, }, @@ -209,52 +239,139 @@ describe('vim.lsp.inlay_hint', function() ['textDocument/inlayHint'] = function(_, _, callback) callback(nil, { expected2 }) end, - } + }, }) - client2 = vim.lsp.start({ name = 'dummy2', cmd = server2.cmd }) + _G.client2 = vim.lsp.start({ name = 'dummy2', cmd = _G.server2.cmd }) vim.lsp.inlay_hint.enable(true, { bufnr = bufnr }) - ]], - expected2 - ) + end) --- @type vim.lsp.inlay_hint.get.ret - local res = exec_lua([[return vim.lsp.inlay_hint.get()]]) - eq({ - { bufnr = 1, client_id = 1, inlay_hint = expected[1] }, - { bufnr = 1, client_id = 1, inlay_hint = expected[2] }, - { bufnr = 1, client_id = 1, inlay_hint = expected[3] }, - { bufnr = 1, client_id = 2, inlay_hint = expected2 }, - }, res) + eq( + { + { bufnr = 1, client_id = 1, inlay_hint = expected[1] }, + { bufnr = 1, client_id = 1, inlay_hint = expected[2] }, + { bufnr = 1, client_id = 1, inlay_hint = expected[3] }, + { bufnr = 1, client_id = 2, inlay_hint = expected2 }, + }, + exec_lua(function() + return vim.lsp.inlay_hint.get() + end) + ) - --- @type vim.lsp.inlay_hint.get.ret - res = exec_lua([[return vim.lsp.inlay_hint.get({ - range = { - start = { line = 2, character = 10 }, - ["end"] = { line = 2, character = 10 }, + eq( + { + { bufnr = 1, client_id = 2, inlay_hint = expected2 }, }, - })]]) - eq({ - { bufnr = 1, client_id = 2, inlay_hint = expected2 }, - }, res) + exec_lua(function() + return vim.lsp.inlay_hint.get({ + range = { + start = { line = 2, character = 10 }, + ['end'] = { line = 2, character = 10 }, + }, + }) + end) + ) - --- @type vim.lsp.inlay_hint.get.ret - res = exec_lua([[return vim.lsp.inlay_hint.get({ - bufnr = vim.api.nvim_get_current_buf(), - range = { - start = { line = 4, character = 18 }, - ["end"] = { line = 5, character = 17 }, + eq( + { + { bufnr = 1, client_id = 1, inlay_hint = expected[2] }, + { bufnr = 1, client_id = 1, inlay_hint = expected[3] }, }, - })]]) - eq({ - { bufnr = 1, client_id = 1, inlay_hint = expected[2] }, - { bufnr = 1, client_id = 1, inlay_hint = expected[3] }, - }, res) + exec_lua(function() + return vim.lsp.inlay_hint.get({ + bufnr = vim.api.nvim_get_current_buf(), + range = { + start = { line = 4, character = 18 }, + ['end'] = { line = 5, character = 17 }, + }, + }) + end) + ) - --- @type vim.lsp.inlay_hint.get.ret - res = exec_lua([[return vim.lsp.inlay_hint.get({ - bufnr = vim.api.nvim_get_current_buf() + 1, - })]]) - eq({}, res) + eq( + {}, + exec_lua(function() + return vim.lsp.inlay_hint.get({ + bufnr = vim.api.nvim_get_current_buf() + 1, + }) + end) + ) + end) + end) +end) + +describe('Inlay hints handler', function() + local text = dedent([[ +test text + ]]) + + local response = [==[ + [ + { "position": { "line": 0, "character": 0 }, "label": "0" }, + { "position": { "line": 0, "character": 0 }, "label": "1" }, + { "position": { "line": 0, "character": 0 }, "label": "2" }, + { "position": { "line": 0, "character": 0 }, "label": "3" }, + { "position": { "line": 0, "character": 0 }, "label": "4" } + ] + ]==] + + local grid_without_inlay_hints = [[ + test text | + ^ | + | +]] + + local grid_with_inlay_hints = [[ + {1:01234}test text | + ^ | + | +]] + + --- @type test.functional.ui.screen + local screen + + --- @type integer + local client_id + + --- @type integer + local bufnr + + before_each(function() + clear_notrace() + screen = Screen.new(50, 3) + screen:attach() + + exec_lua(create_server_definition) + bufnr = n.api.nvim_get_current_buf() + client_id = exec_lua(function() + _G.server = _G._create_server({ + capabilities = { + inlayHintProvider = true, + }, + handlers = { + ['textDocument/inlayHint'] = function(_, _, callback) + callback(nil, vim.json.decode(response)) + end, + }, + }) + + vim.api.nvim_win_set_buf(0, bufnr) + + return vim.lsp.start({ name = 'dummy', cmd = _G.server.cmd }) + end) + insert(text) + end) + + it('renders hints with same position in received order', function() + exec_lua([[vim.lsp.inlay_hint.enable(true, { bufnr = bufnr })]]) + screen:expect({ grid = grid_with_inlay_hints }) + exec_lua(function() + vim.lsp.stop_client(client_id) end) + screen:expect({ grid = grid_without_inlay_hints, unchanged = true }) + end) + + after_each(function() + api.nvim_exec_autocmds('VimLeavePre', { modeline = false }) end) end) diff --git a/test/functional/plugin/lsp/semantic_tokens_spec.lua b/test/functional/plugin/lsp/semantic_tokens_spec.lua index 7908c5d2e7..f72aab7e0b 100644 --- a/test/functional/plugin/lsp/semantic_tokens_spec.lua +++ b/test/functional/plugin/lsp/semantic_tokens_spec.lua @@ -25,7 +25,7 @@ after_each(function() end) describe('semantic token highlighting', function() - local screen + local screen --- @type test.functional.ui.screen before_each(function() screen = Screen.new(40, 16) screen:attach() @@ -84,10 +84,8 @@ describe('semantic token highlighting', function() before_each(function() exec_lua(create_server_definition) - exec_lua( - [[ - local legend, response, edit_response = ... - server = _create_server({ + exec_lua(function() + _G.server = _G._create_server({ capabilities = { semanticTokensProvider = { full = { delta = true }, @@ -101,24 +99,19 @@ describe('semantic token highlighting', function() ['textDocument/semanticTokens/full/delta'] = function(_, _, callback) callback(nil, vim.fn.json_decode(edit_response)) end, - } + }, }) - ]], - legend, - response, - edit_response - ) + end, legend, response, edit_response) end) it('buffer is highlighted when attached', function() - exec_lua([[ - bufnr = vim.api.nvim_get_current_buf() + insert(text) + exec_lua(function() + local bufnr = vim.api.nvim_get_current_buf() vim.api.nvim_win_set_buf(0, bufnr) vim.bo[bufnr].filetype = 'some-filetype' - client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd }) - ]]) - - insert(text) + vim.lsp.start({ name = 'dummy', cmd = _G.server.cmd }) + end) screen:expect { grid = [[ @@ -141,23 +134,20 @@ describe('semantic token highlighting', function() end) it('use LspTokenUpdate and highlight_token', function() - exec_lua([[ - vim.api.nvim_create_autocmd("LspTokenUpdate", { + insert(text) + exec_lua(function() + vim.api.nvim_create_autocmd('LspTokenUpdate', { callback = function(args) - local token = args.data.token - if token.type == "function" and token.modifiers.declaration then - vim.lsp.semantic_tokens.highlight_token( - token, args.buf, args.data.client_id, "Macro" - ) + local token = args.data.token --- @type STTokenRange + if token.type == 'function' and token.modifiers.declaration then + vim.lsp.semantic_tokens.highlight_token(token, args.buf, args.data.client_id, 'Macro') end end, }) - bufnr = vim.api.nvim_get_current_buf() + local bufnr = vim.api.nvim_get_current_buf() vim.api.nvim_win_set_buf(0, bufnr) - client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd }) - ]]) - - insert(text) + vim.lsp.start({ name = 'dummy', cmd = _G.server.cmd }) + end) screen:expect { grid = [[ @@ -180,18 +170,23 @@ describe('semantic token highlighting', function() end) it('buffer is unhighlighted when client is detached', function() - exec_lua([[ - bufnr = vim.api.nvim_get_current_buf() - vim.api.nvim_win_set_buf(0, bufnr) - client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd }) - ]]) - insert(text) - exec_lua([[ + local bufnr = n.api.nvim_get_current_buf() + local client_id = exec_lua(function() + vim.api.nvim_win_set_buf(0, bufnr) + local client_id = vim.lsp.start({ name = 'dummy', cmd = _G.server.cmd }) + vim.wait(1000, function() + return #_G.server.messages > 1 + end) + return client_id + end) + + exec_lua(function() + --- @diagnostic disable-next-line:duplicate-set-field vim.notify = function() end vim.lsp.buf_detach_client(bufnr, client_id) - ]]) + end) screen:expect { grid = [[ @@ -216,18 +211,19 @@ describe('semantic token highlighting', function() it( 'buffer is highlighted and unhighlighted when semantic token highlighting is started and stopped', function() - exec_lua([[ - bufnr = vim.api.nvim_get_current_buf() - vim.api.nvim_win_set_buf(0, bufnr) - client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd }) - ]]) + local bufnr = n.api.nvim_get_current_buf() + local client_id = exec_lua(function() + vim.api.nvim_win_set_buf(0, bufnr) + return vim.lsp.start({ name = 'dummy', cmd = _G.server.cmd }) + end) insert(text) - exec_lua([[ - vim.notify = function() end - vim.lsp.semantic_tokens.stop(bufnr, client_id) - ]]) + exec_lua(function() + --- @diagnostic disable-next-line:duplicate-set-field + vim.notify = function() end + vim.lsp.semantic_tokens.stop(bufnr, client_id) + end) screen:expect { grid = [[ @@ -248,9 +244,9 @@ describe('semantic token highlighting', function() ]], } - exec_lua([[ - vim.lsp.semantic_tokens.start(bufnr, client_id) - ]]) + exec_lua(function() + vim.lsp.semantic_tokens.start(bufnr, client_id) + end) screen:expect { grid = [[ @@ -274,18 +270,17 @@ describe('semantic token highlighting', function() ) it('highlights start and stop when using "0" for current buffer', function() - exec_lua([[ - bufnr = vim.api.nvim_get_current_buf() - vim.api.nvim_win_set_buf(0, bufnr) - client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd }) - ]]) + local client_id = exec_lua(function() + return vim.lsp.start({ name = 'dummy', cmd = _G.server.cmd }) + end) insert(text) - exec_lua([[ + exec_lua(function() + --- @diagnostic disable-next-line:duplicate-set-field vim.notify = function() end vim.lsp.semantic_tokens.stop(0, client_id) - ]]) + end) screen:expect { grid = [[ @@ -306,9 +301,9 @@ describe('semantic token highlighting', function() ]], } - exec_lua([[ + exec_lua(function() vim.lsp.semantic_tokens.start(0, client_id) - ]]) + end) screen:expect { grid = [[ @@ -331,13 +326,10 @@ describe('semantic token highlighting', function() end) it('buffer is re-highlighted when force refreshed', function() - exec_lua([[ - bufnr = vim.api.nvim_get_current_buf() - vim.api.nvim_win_set_buf(0, bufnr) - client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd }) - ]]) - insert(text) + exec_lua(function() + vim.lsp.start({ name = 'dummy', cmd = _G.server.cmd }) + end) screen:expect { grid = [[ @@ -358,9 +350,9 @@ describe('semantic token highlighting', function() ]], } - exec_lua([[ - vim.lsp.semantic_tokens.force_refresh(bufnr) - ]]) + exec_lua(function() + vim.lsp.semantic_tokens.force_refresh() + end) screen:expect { grid = [[ @@ -384,7 +376,9 @@ describe('semantic token highlighting', function() local messages = exec_lua('return server.messages') local token_request_count = 0 - for _, message in ipairs(messages) do + for _, message in + ipairs(messages --[[@as {method:string,params:table}[] ]]) + do assert(message.method ~= 'textDocument/semanticTokens/full/delta', 'delta request received') if message.method == 'textDocument/semanticTokens/full' then token_request_count = token_request_count + 1 @@ -394,31 +388,29 @@ describe('semantic token highlighting', function() end) it('destroys the highlighter if the buffer is deleted', function() - exec_lua([[ - bufnr = vim.api.nvim_get_current_buf() - vim.api.nvim_win_set_buf(0, bufnr) - client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd }) - ]]) + exec_lua(function() + vim.lsp.start({ name = 'dummy', cmd = _G.server.cmd }) + end) insert(text) - local highlighters = exec_lua([[ - vim.api.nvim_buf_delete(bufnr, { force = true }) - local semantic_tokens = vim.lsp.semantic_tokens - return semantic_tokens.__STHighlighter.active - ]]) - - eq({}, highlighters) + eq( + {}, + exec_lua(function() + local bufnr = vim.api.nvim_get_current_buf() + vim.api.nvim_buf_delete(bufnr, { force = true }) + return vim.lsp.semantic_tokens.__STHighlighter.active + end) + ) end) it('updates highlights with delta request on buffer change', function() - exec_lua([[ - bufnr = vim.api.nvim_get_current_buf() - vim.api.nvim_win_set_buf(0, bufnr) - client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd }) - ]]) - insert(text) + + exec_lua(function() + vim.lsp.start({ name = 'dummy', cmd = _G.server.cmd }) + end) + screen:expect { grid = [[ #include <iostream> | @@ -459,45 +451,49 @@ describe('semantic token highlighting', function() end) it('prevents starting semantic token highlighting with invalid conditions', function() - exec_lua([[ - bufnr = vim.api.nvim_get_current_buf() - vim.api.nvim_win_set_buf(0, bufnr) - client_id = vim.lsp.start_client({ name = 'dummy', cmd = server.cmd }) - notifications = {} - vim.notify = function(...) table.insert(notifications, 1, {...}) end - ]]) - eq(false, exec_lua('return vim.lsp.buf_is_attached(bufnr, client_id)')) + local client_id = exec_lua(function() + _G.notifications = {} + --- @diagnostic disable-next-line:duplicate-set-field + vim.notify = function(...) + table.insert(_G.notifications, 1, { ... }) + end + return vim.lsp.start_client({ name = 'dummy', cmd = _G.server.cmd }) + end) + eq(false, exec_lua('return vim.lsp.buf_is_attached(0, ...)', client_id)) insert(text) - local notifications = exec_lua([[ - vim.lsp.semantic_tokens.start(bufnr, client_id) - return notifications - ]]) - matches('%[LSP%] Client with id %d not attached to buffer %d', notifications[1][1]) - - notifications = exec_lua([[ - vim.lsp.semantic_tokens.start(bufnr, client_id + 1) - return notifications - ]]) - matches('%[LSP%] No client with id %d', notifications[1][1]) + matches( + '%[LSP%] Client with id %d not attached to buffer %d', + exec_lua(function() + vim.lsp.semantic_tokens.start(0, client_id) + return _G.notifications[1][1] + end) + ) + + matches( + '%[LSP%] No client with id %d', + exec_lua(function() + vim.lsp.semantic_tokens.start(0, client_id + 1) + return _G.notifications[1][1] + end) + ) end) it( 'opt-out: does not activate semantic token highlighting if disabled in client attach', function() - exec_lua([[ - bufnr = vim.api.nvim_get_current_buf() - vim.api.nvim_win_set_buf(0, bufnr) - client_id = vim.lsp.start({ + local client_id = exec_lua(function() + return vim.lsp.start({ name = 'dummy', - cmd = server.cmd, - on_attach = vim.schedule_wrap(function(client, bufnr) + cmd = _G.server.cmd, + --- @param client vim.lsp.Client + on_attach = vim.schedule_wrap(function(client, _bufnr) client.server_capabilities.semanticTokensProvider = nil end), }) - ]]) - eq(true, exec_lua('return vim.lsp.buf_is_attached(bufnr, client_id)')) + end) + eq(true, exec_lua('return vim.lsp.buf_is_attached(0, ...)', client_id)) insert(text) @@ -520,13 +516,18 @@ describe('semantic token highlighting', function() ]], } - local notifications = exec_lua([[ - local notifications = {} - vim.notify = function(...) table.insert(notifications, 1, {...}) end - vim.lsp.semantic_tokens.start(bufnr, client_id) - return notifications - ]]) - eq('[LSP] Server does not support semantic tokens', notifications[1][1]) + eq( + '[LSP] Server does not support semantic tokens', + exec_lua(function() + local notifications = {} + --- @diagnostic disable-next-line:duplicate-set-field + vim.notify = function(...) + table.insert(notifications, 1, { ... }) + end + vim.lsp.semantic_tokens.start(0, client_id) + return notifications[1][1] + end) + ) screen:expect { grid = [[ @@ -551,28 +552,32 @@ describe('semantic token highlighting', function() ) it('ignores null responses from the server', function() - exec_lua([[ - local legend, response, edit_response = ... - server2 = _create_server({ + local client_id = exec_lua(function() + _G.server2 = _G._create_server({ capabilities = { semanticTokensProvider = { full = { delta = false }, }, }, handlers = { + --- @param callback function ['textDocument/semanticTokens/full'] = function(_, _, callback) callback(nil, nil) end, - ['textDocument/semanticTokens/full/delta'] = function() + --- @param callback function + ['textDocument/semanticTokens/full/delta'] = function(_, _, callback) callback(nil, nil) end, - } + }, }) - bufnr = vim.api.nvim_get_current_buf() - vim.api.nvim_win_set_buf(0, bufnr) - client_id = vim.lsp.start({ name = 'dummy', cmd = server2.cmd }) - ]]) - eq(true, exec_lua('return vim.lsp.buf_is_attached(bufnr, client_id)')) + return vim.lsp.start({ name = 'dummy', cmd = _G.server2.cmd }) + end) + eq( + true, + exec_lua(function() + return vim.lsp.buf_is_attached(0, client_id) + end) + ) insert(text) @@ -597,10 +602,9 @@ describe('semantic token highlighting', function() end) it('does not send delta requests if not supported by server', function() - exec_lua( - [[ - local legend, response, edit_response = ... - server2 = _create_server({ + insert(text) + exec_lua(function() + _G.server2 = _G._create_server({ capabilities = { semanticTokensProvider = { full = { delta = false }, @@ -614,18 +618,11 @@ describe('semantic token highlighting', function() ['textDocument/semanticTokens/full/delta'] = function(_, _, callback) callback(nil, vim.fn.json_decode(edit_response)) end, - } + }, }) - bufnr = vim.api.nvim_get_current_buf() - vim.api.nvim_win_set_buf(0, bufnr) - client_id = vim.lsp.start({ name = 'dummy', cmd = server2.cmd }) - ]], - legend, - response, - edit_response - ) + return vim.lsp.start({ name = 'dummy', cmd = _G.server2.cmd }) + end) - insert(text) screen:expect { grid = [[ #include <iostream> | @@ -669,7 +666,9 @@ describe('semantic token highlighting', function() } local messages = exec_lua('return server2.messages') local token_request_count = 0 - for _, message in ipairs(messages) do + for _, message in + ipairs(messages --[[@as {method:string,params:table}[] ]]) + do assert(message.method ~= 'textDocument/semanticTokens/full/delta', 'delta request received') if message.method == 'textDocument/semanticTokens/full' then token_request_count = token_request_count + 1 @@ -1064,10 +1063,8 @@ b = "as"]], }) do it(test.it, function() exec_lua(create_server_definition) - exec_lua( - [[ - local legend, resp = ... - server = _create_server({ + local client_id = exec_lua(function(legend, resp) + _G.server = _G._create_server({ capabilities = { semanticTokensProvider = { full = { delta = false }, @@ -1078,25 +1075,22 @@ b = "as"]], ['textDocument/semanticTokens/full'] = function(_, _, callback) callback(nil, vim.fn.json_decode(resp)) end, - } + }, }) - bufnr = vim.api.nvim_get_current_buf() - vim.api.nvim_win_set_buf(0, bufnr) - client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd }) - ]], - test.legend, - test.response - ) + return vim.lsp.start({ name = 'dummy', cmd = _G.server.cmd }) + end, test.legend, test.response) insert(test.text) test.expected_screen() - local highlights = exec_lua([[ - local semantic_tokens = vim.lsp.semantic_tokens - return semantic_tokens.__STHighlighter.active[bufnr].client_state[client_id].current_result.highlights - ]]) - eq(test.expected, highlights) + eq( + test.expected, + exec_lua(function() + local bufnr = vim.api.nvim_get_current_buf() + return vim.lsp.semantic_tokens.__STHighlighter.active[bufnr].client_state[client_id].current_result.highlights + end) + ) end) end end) @@ -1449,11 +1443,11 @@ int main() }, }) do it(test.it, function() + local bufnr = n.api.nvim_get_current_buf() + insert(test.text1) exec_lua(create_server_definition) - exec_lua( - [[ - local legend, resp1, resp2 = ... - server = _create_server({ + local client_id = exec_lua(function(legend, resp1, resp2) + _G.server = _G._create_server({ capabilities = { semanticTokensProvider = { full = { delta = true }, @@ -1467,54 +1461,44 @@ int main() ['textDocument/semanticTokens/full/delta'] = function(_, _, callback) callback(nil, vim.fn.json_decode(resp2)) end, - } + }, }) - bufnr = vim.api.nvim_get_current_buf() - vim.api.nvim_win_set_buf(0, bufnr) - client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd }) + local client_id = assert(vim.lsp.start({ name = 'dummy', cmd = _G.server.cmd })) -- speed up vim.api.nvim_buf_set_lines calls by changing debounce to 10 for these tests - semantic_tokens = vim.lsp.semantic_tokens vim.schedule(function() - semantic_tokens.stop(bufnr, client_id) - semantic_tokens.start(bufnr, client_id, { debounce = 10 }) + vim.lsp.semantic_tokens.stop(bufnr, client_id) + vim.lsp.semantic_tokens.start(bufnr, client_id, { debounce = 10 }) end) - ]], - test.legend, - test.response1, - test.response2 - ) - - insert(test.text1) + return client_id + end, test.legend, test.response1, test.response2) test.expected_screen1() - local highlights = exec_lua([[ - return semantic_tokens.__STHighlighter.active[bufnr].client_state[client_id].current_result.highlights - ]]) - - eq(test.expected1, highlights) + eq( + test.expected1, + exec_lua(function() + return vim.lsp.semantic_tokens.__STHighlighter.active[bufnr].client_state[client_id].current_result.highlights + end) + ) if test.edit then feed(test.edit) else - exec_lua( - [[ - local text = ... - vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, vim.fn.split(text, "\n")) + exec_lua(function(text) + vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, vim.fn.split(text, '\n')) vim.wait(15) -- wait for debounce - ]], - test.text2 - ) + end, test.text2) end test.expected_screen2() - highlights = exec_lua([[ - return semantic_tokens.__STHighlighter.active[bufnr].client_state[client_id].current_result.highlights - ]]) - - eq(test.expected2, highlights) + eq( + test.expected2, + exec_lua(function() + return vim.lsp.semantic_tokens.__STHighlighter.active[bufnr].client_state[client_id].current_result.highlights + end) + ) end) end end) diff --git a/test/functional/plugin/lsp/testutil.lua b/test/functional/plugin/lsp/testutil.lua index 3430a1e1a3..a36cbac568 100644 --- a/test/functional/plugin/lsp/testutil.lua +++ b/test/functional/plugin/lsp/testutil.lua @@ -21,8 +21,35 @@ function M.clear_notrace() } end -M.create_server_definition = [[ - function _create_server(opts) +M.create_tcp_echo_server = function() + --- Create a TCP server that echos the first message it receives. + --- @param host string + ---@return uv.uv_tcp_t + ---@return integer + ---@return fun():string|nil + function _G._create_tcp_server(host) + local uv = vim.uv + local server = assert(uv.new_tcp()) + local init = nil + server:bind(host, 0) + server:listen(127, function(err) + assert(not err, err) + local socket = assert(uv.new_tcp()) + server:accept(socket) + socket:read_start(require('vim.lsp.rpc').create_read_loop(function(body) + init = body + socket:close() + end)) + end) + local port = server:getsockname().port + return server, port, function() + return init + end + end +end + +M.create_server_definition = function() + function _G._create_server(opts) opts = opts or {} local server = {} server.messages = {} @@ -42,7 +69,7 @@ M.create_server_definition = [[ handler(method, params, callback) elseif method == 'initialize' then callback(nil, { - capabilities = opts.capabilities or {} + capabilities = opts.capabilities or {}, }) elseif method == 'shutdown' then callback(nil, nil) @@ -54,7 +81,7 @@ M.create_server_definition = [[ function srv.notify(method, params) table.insert(server.messages, { method = method, - params = params + params = params, }) if method == 'exit' then dispatchers.on_exit(0, 15) @@ -74,63 +101,62 @@ M.create_server_definition = [[ return server end -]] +end -- Fake LSP server. M.fake_lsp_code = 'test/functional/fixtures/fake-lsp-server.lua' M.fake_lsp_logfile = 'Xtest-fake-lsp.log' local function fake_lsp_server_setup(test_name, timeout_ms, options, settings) - exec_lua( - [=[ - lsp = require('vim.lsp') - local test_name, fake_lsp_code, fake_lsp_logfile, timeout, options, settings = ... - TEST_RPC_CLIENT_ID = lsp.start_client { + exec_lua(function(fake_lsp_code, fake_lsp_logfile, timeout) + options = options or {} + settings = settings or {} + _G.lsp = require('vim.lsp') + _G.TEST_RPC_CLIENT_ID = _G.lsp.start_client { cmd_env = { - NVIM_LOG_FILE = fake_lsp_logfile; - NVIM_LUA_NOTRACK = "1"; - NVIM_APPNAME = "nvim_lsp_test"; - }; + NVIM_LOG_FILE = fake_lsp_logfile, + NVIM_LUA_NOTRACK = '1', + NVIM_APPNAME = 'nvim_lsp_test', + }, cmd = { - vim.v.progpath, '-l', fake_lsp_code, test_name, tostring(timeout), - }; + vim.v.progpath, + '-l', + fake_lsp_code, + test_name, + tostring(timeout), + }, handlers = setmetatable({}, { - __index = function(t, method) + __index = function(_t, _method) return function(...) return vim.rpcrequest(1, 'handler', ...) end - end; - }); - workspace_folders = {{ + end, + }), + workspace_folders = { + { uri = 'file://' .. vim.uv.cwd(), name = 'test_folder', - }}; - before_init = function(params, config) + }, + }, + before_init = function(_params, _config) vim.schedule(function() - vim.rpcrequest(1, "setup") + vim.rpcrequest(1, 'setup') end) end, on_init = function(client, result) - TEST_RPC_CLIENT = client - vim.rpcrequest(1, "init", result) - end; + _G.TEST_RPC_CLIENT = client + vim.rpcrequest(1, 'init', result) + end, flags = { - allow_incremental_sync = options.allow_incremental_sync or false; - debounce_text_changes = options.debounce_text_changes or 0; - }; - settings = settings; + allow_incremental_sync = options.allow_incremental_sync or false, + debounce_text_changes = options.debounce_text_changes or 0, + }, + settings = settings, on_exit = function(...) - vim.rpcnotify(1, "exit", ...) - end; + vim.rpcnotify(1, 'exit', ...) + end, } - ]=], - test_name, - M.fake_lsp_code, - M.fake_lsp_logfile, - timeout_ms or 1e3, - options or {}, - settings or {} - ) + end, M.fake_lsp_code, M.fake_lsp_logfile, timeout_ms or 1e3) end --- @class test.lsp.Config @@ -160,18 +186,13 @@ function M.test_rpc_server(config) -- Workaround for not being able to yield() inside __index for Lua 5.1 :( -- Otherwise I would just return the value here. return function(...) - return exec_lua( - [=[ - local name = ... - if type(TEST_RPC_CLIENT[name]) == 'function' then - return TEST_RPC_CLIENT[name](select(2, ...)) - else - return TEST_RPC_CLIENT[name] - end - ]=], - name, - ... - ) + 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] + end + end, ...) end end, }) diff --git a/test/functional/plugin/lsp/utils_spec.lua b/test/functional/plugin/lsp/utils_spec.lua index 6c6dec0667..64d58eeffd 100644 --- a/test/functional/plugin/lsp/utils_spec.lua +++ b/test/functional/plugin/lsp/utils_spec.lua @@ -11,21 +11,11 @@ describe('vim.lsp.util', function() describe('stylize_markdown', function() local stylize_markdown = function(content, opts) - return exec_lua( - [[ - local bufnr = vim.uri_to_bufnr("file:///fake/uri") + return exec_lua(function() + local bufnr = vim.uri_to_bufnr('file:///fake/uri') vim.fn.bufload(bufnr) - - local args = { ... } - local content = args[1] - local opts = args[2] - local stripped_content = vim.lsp.util.stylize_markdown(bufnr, content, opts) - - return stripped_content - ]], - content, - opts - ) + return vim.lsp.util.stylize_markdown(bufnr, content, opts) + end) end it('code fences', function() @@ -93,9 +83,64 @@ describe('vim.lsp.util', function() end) end) - describe('normalize_markdown', function() + it('convert_input_to_markdown_lines', function() + local r = exec_lua(function() + local hover_data = { + kind = 'markdown', + value = '```lua\nfunction vim.api.nvim_buf_attach(buffer: integer, send_buffer: boolean, opts: vim.api.keyset.buf_attach)\n -> boolean\n```\n\n---\n\n Activates buffer-update events. Example:\n\n\n\n ```lua\n events = {}\n vim.api.nvim_buf_attach(0, false, {\n on_lines = function(...)\n table.insert(events, {...})\n end,\n })\n ```\n\n\n @see `nvim_buf_detach()`\n @see `api-buffer-updates-lua`\n@*param* `buffer` — Buffer handle, or 0 for current buffer\n\n\n\n@*param* `send_buffer` — True if whole buffer.\n Else the first notification will be `nvim_buf_changedtick_event`.\n\n\n@*param* `opts` — Optional parameters.\n\n - on_lines: Lua callback. Args:\n - the string "lines"\n - buffer handle\n - b:changedtick\n@*return* — False if foo;\n\n otherwise True.\n\n@see foo\n@see bar\n\n', + } + return vim.lsp.util.convert_input_to_markdown_lines(hover_data) + end) + local expected = { + '```lua', + 'function vim.api.nvim_buf_attach(buffer: integer, send_buffer: boolean, opts: vim.api.keyset.buf_attach)', + ' -> boolean', + '```', + '', + '---', + '', + ' Activates buffer-update events. Example:', + '', + '', + '', + ' ```lua', + ' events = {}', + ' vim.api.nvim_buf_attach(0, false, {', + ' on_lines = function(...)', + ' table.insert(events, {...})', + ' end,', + ' })', + ' ```', + '', + '', + ' @see `nvim_buf_detach()`', + ' @see `api-buffer-updates-lua`', + '', + -- For each @param/@return: #30695 + -- - Separate each by one empty line. + -- - Remove all other blank lines. + '@*param* `buffer` — Buffer handle, or 0 for current buffer', + '', + '@*param* `send_buffer` — True if whole buffer.', + ' Else the first notification will be `nvim_buf_changedtick_event`.', + '', + '@*param* `opts` — Optional parameters.', + ' - on_lines: Lua callback. Args:', + ' - the string "lines"', + ' - buffer handle', + ' - b:changedtick', + '', + '@*return* — False if foo;', + ' otherwise True.', + '@see foo', + '@see bar', + } + eq(expected, r) + end) + + describe('_normalize_markdown', function() it('collapses consecutive blank lines', function() - local result = exec_lua [[ + local result = exec_lua(function() local lines = { 'foo', '', @@ -103,25 +148,25 @@ describe('vim.lsp.util', function() '', 'bar', '', - 'baz' + 'baz', } return vim.lsp.util._normalize_markdown(lines) - ]] + end) local expected = { 'foo', '', 'bar', '', 'baz' } eq(expected, result) end) it('removes preceding and trailing empty lines', function() - local result = exec_lua [[ + local result = exec_lua(function() local lines = { '', 'foo', 'bar', '', - '' + '', } return vim.lsp.util._normalize_markdown(lines) - ]] + end) local expected = { 'foo', 'bar' } eq(expected, result) end) @@ -129,19 +174,14 @@ describe('vim.lsp.util', function() describe('make_floating_popup_options', function() local function assert_anchor(anchor_bias, expected_anchor) - local opts = exec_lua( - [[ - local args = { ... } - local anchor_bias = args[1] - return vim.lsp.util.make_floating_popup_options(30, 10, { anchor_bias = anchor_bias }) - ]], - anchor_bias - ) + local opts = exec_lua(function() + return vim.lsp.util.make_floating_popup_options(30, 10, { anchor_bias = anchor_bias }) + end) eq(expected_anchor, string.sub(opts.anchor, 1, 1)) end - local screen + local screen --- @type test.functional.ui.screen before_each(function() n.clear() screen = Screen.new(80, 80) @@ -221,9 +261,9 @@ describe('vim.lsp.util', function() end) it('bordered window truncates dimensions correctly', function() - local opts = exec_lua([[ + local opts = exec_lua(function() return vim.lsp.util.make_floating_popup_options(100, 100, { border = 'single' }) - ]]) + end) eq(56, opts.height) end) diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index c95a96baca..9956fdf628 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -33,12 +33,38 @@ local create_server_definition = t_lsp.create_server_definition local fake_lsp_code = t_lsp.fake_lsp_code local fake_lsp_logfile = t_lsp.fake_lsp_logfile local test_rpc_server = t_lsp.test_rpc_server +local create_tcp_echo_server = t_lsp.create_tcp_echo_server local function get_buf_option(name, bufnr) - bufnr = bufnr or 'BUFFER' - return exec_lua( - string.format("return vim.api.nvim_get_option_value('%s', { buf = %s })", name, bufnr) + return exec_lua(function() + bufnr = bufnr or _G.BUFFER + return vim.api.nvim_get_option_value(name, { buf = bufnr }) + end) +end + +local function make_edit(y_0, x_0, y_1, x_1, text) + return { + range = { + start = { line = y_0, character = x_0 }, + ['end'] = { line = y_1, character = x_1 }, + }, + newText = type(text) == 'table' and table.concat(text, '\n') or (text or ''), + } +end + +--- @param edits [integer, integer, integer, integer, string|string[]][] +--- @param encoding? string +local function apply_text_edits(edits, encoding) + local edits1 = vim.tbl_map( + --- @param edit [integer, integer, integer, integer, string|string[]] + function(edit) + return make_edit(unpack(edit)) + end, + edits ) + exec_lua(function() + vim.lsp.util.apply_text_edits(edits1, 1, encoding or 'utf-16') + end) end -- TODO(justinmk): hangs on Windows https://github.com/neovim/neovim/pull/11837 @@ -46,111 +72,156 @@ if skip(is_os('win')) then return end -teardown(function() - os.remove(fake_lsp_logfile) -end) - describe('LSP', function() before_each(function() clear_notrace() - - -- Run an instance of nvim on the file which contains our "scripts". - -- Pass TEST_NAME to pick the script. - local test_name = 'basic_init' - exec_lua( - [=[ - lsp = require('vim.lsp') - local test_name, fake_lsp_code, fake_lsp_logfile = ... - function test__start_client() - return lsp.start_client { - cmd_env = { - NVIM_LOG_FILE = fake_lsp_logfile; - NVIM_APPNAME = "nvim_lsp_test"; - }; - cmd = { - vim.v.progpath, '-l', fake_lsp_code, test_name; - }; - workspace_folders = {{ - uri = 'file://' .. vim.uv.cwd(), - name = 'test_folder', - }}; - } - end - TEST_CLIENT1 = test__start_client() - ]=], - test_name, - fake_lsp_code, - fake_lsp_logfile - ) end) after_each(function() + stop() + exec_lua('lsp.stop_client(lsp.get_clients(), true)') api.nvim_exec_autocmds('VimLeavePre', { modeline = false }) - -- exec_lua("lsp.stop_all_clients(true)") + end) + + teardown(function() + os.remove(fake_lsp_logfile) end) describe('server_name specified', function() + before_each(function() + -- Run an instance of nvim on the file which contains our "scripts". + -- Pass TEST_NAME to pick the script. + local test_name = 'basic_init' + exec_lua(function() + _G.lsp = require('vim.lsp') + function _G.test__start_client() + return vim.lsp.start_client { + cmd_env = { + NVIM_LOG_FILE = fake_lsp_logfile, + NVIM_APPNAME = 'nvim_lsp_test', + }, + cmd = { + vim.v.progpath, + '-l', + fake_lsp_code, + test_name, + }, + workspace_folders = { + { + uri = 'file://' .. vim.uv.cwd(), + name = 'test_folder', + }, + }, + } + end + _G.TEST_CLIENT1 = _G.test__start_client() + end) + end) + it('start_client(), stop_client()', function() retry(nil, 4000, function() - eq(1, exec_lua('return #lsp.get_clients()')) + eq( + 1, + exec_lua(function() + return #vim.lsp.get_clients() + end) + ) end) eq( 2, - exec_lua([[ - TEST_CLIENT2 = test__start_client() - return TEST_CLIENT2 - ]]) + exec_lua(function() + _G.TEST_CLIENT2 = _G.test__start_client() + return _G.TEST_CLIENT2 + end) ) eq( 3, - exec_lua([[ - TEST_CLIENT3 = test__start_client() - return TEST_CLIENT3 - ]]) + exec_lua(function() + _G.TEST_CLIENT3 = _G.test__start_client() + return _G.TEST_CLIENT3 + end) ) retry(nil, 4000, function() - eq(3, exec_lua('return #lsp.get_clients()')) + eq( + 3, + exec_lua(function() + return #vim.lsp.get_clients() + end) + ) end) - eq(false, exec_lua('return lsp.get_client_by_id(TEST_CLIENT1) == nil')) - eq(false, exec_lua('return lsp.get_client_by_id(TEST_CLIENT1).is_stopped()')) - exec_lua('return lsp.get_client_by_id(TEST_CLIENT1).stop()') + eq( + false, + exec_lua(function() + return vim.lsp.get_client_by_id(_G.TEST_CLIENT1) == nil + end) + ) + eq( + false, + exec_lua(function() + return vim.lsp.get_client_by_id(_G.TEST_CLIENT1).is_stopped() + end) + ) + exec_lua(function() + return vim.lsp.get_client_by_id(_G.TEST_CLIENT1).stop() + end) retry(nil, 4000, function() - eq(2, exec_lua('return #lsp.get_clients()')) + eq( + 2, + exec_lua(function() + return #vim.lsp.get_clients() + end) + ) end) - eq(true, exec_lua('return lsp.get_client_by_id(TEST_CLIENT1) == nil')) + eq( + true, + exec_lua(function() + return vim.lsp.get_client_by_id(_G.TEST_CLIENT1) == nil + end) + ) - exec_lua('lsp.stop_client({TEST_CLIENT2, TEST_CLIENT3})') + exec_lua(function() + vim.lsp.stop_client({ _G.TEST_CLIENT2, _G.TEST_CLIENT3 }) + end) retry(nil, 4000, function() - eq(0, exec_lua('return #lsp.get_clients()')) + eq( + 0, + exec_lua(function() + return #vim.lsp.get_clients() + end) + ) end) end) it('stop_client() also works on client objects', function() - exec_lua([[ - TEST_CLIENT2 = test__start_client() - TEST_CLIENT3 = test__start_client() - ]]) + exec_lua(function() + _G.TEST_CLIENT2 = _G.test__start_client() + _G.TEST_CLIENT3 = _G.test__start_client() + end) retry(nil, 4000, function() - eq(3, exec_lua('return #lsp.get_clients()')) + eq( + 3, + exec_lua(function() + return #vim.lsp.get_clients() + end) + ) end) -- Stop all clients. - exec_lua('lsp.stop_client(lsp.get_clients())') + exec_lua(function() + vim.lsp.stop_client(vim.lsp.get_clients()) + end) retry(nil, 4000, function() - eq(0, exec_lua('return #lsp.get_clients()')) + eq( + 0, + exec_lua(function() + return #vim.lsp.get_clients() + end) + ) end) end) end) -end) -describe('LSP', function() describe('basic_init test', function() - after_each(function() - stop() - exec_lua('lsp.stop_client(lsp.get_clients(), true)') - api.nvim_exec_autocmds('VimLeavePre', { modeline = false }) - end) - it('should run correctly', function() local expected_handlers = { { NIL, {}, { method = 'test', client_id = 1 } }, @@ -221,28 +292,28 @@ describe('LSP', function() function() clear() exec_lua(create_server_definition) - local result = exec_lua([[ - local server = _create_server({ - capabilities = { - positionEncoding = "utf-8" - }, - }) + local result = exec_lua(function() + local server = _G._create_server({ + capabilities = { + positionEncoding = 'utf-8', + }, + }) - local client_id = vim.lsp.start({ - name = 'dummy', - cmd = server.cmd, - }) + local client_id = vim.lsp.start({ + name = 'dummy', + cmd = server.cmd, + }) - if not client_id then - return 'vim.lsp.start did not return client_id' - end + if not client_id then + return 'vim.lsp.start did not return client_id' + end - local client = vim.lsp.get_client_by_id(client_id) - if not client then - return 'No client found with id ' .. client_id - end - return client.offset_encoding - ]]) + local client = vim.lsp.get_client_by_id(client_id) + if not client then + return 'No client found with id ' .. client_id + end + return client.offset_encoding + end) eq('utf-8', result) end ) @@ -255,7 +326,7 @@ describe('LSP', function() return end local expected_handlers = { - { NIL, {}, { method = 'shutdown', bufnr = 1, client_id = 1 } }, + { NIL, {}, { method = 'shutdown', bufnr = 1, client_id = 1, version = 0 } }, { NIL, {}, { method = 'test', client_id = 1 } }, } test_rpc_server { @@ -285,14 +356,24 @@ describe('LSP', function() test_rpc_server { test_name = 'basic_finish', on_setup = function() - exec_lua [[ - BUFFER = vim.api.nvim_create_buf(false, true) - ]] - eq(true, exec_lua('return lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID)')) - eq(true, exec_lua('return lsp.buf_is_attached(BUFFER, TEST_RPC_CLIENT_ID)')) - exec_lua [[ - vim.api.nvim_command(BUFFER.."bwipeout") - ]] + exec_lua(function() + _G.BUFFER = vim.api.nvim_create_buf(false, true) + end) + eq( + true, + exec_lua(function() + return vim.lsp.buf_attach_client(_G.BUFFER, _G.TEST_RPC_CLIENT_ID) + end) + ) + eq( + true, + exec_lua(function() + return vim.lsp.buf_is_attached(_G.BUFFER, _G.TEST_RPC_CLIENT_ID) + end) + ) + exec_lua(function() + vim.cmd(_G.BUFFER .. 'bwipeout') + end) end, on_init = function(_client) client = _client @@ -305,8 +386,15 @@ describe('LSP', function() on_handler = function(err, result, ctx) eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') if ctx.method == 'finish' then - exec_lua('return lsp.buf_detach_client(BUFFER, TEST_RPC_CLIENT_ID)') - eq(false, exec_lua('return lsp.buf_is_attached(BUFFER, TEST_RPC_CLIENT_ID)')) + exec_lua(function() + return vim.lsp.buf_detach_client(_G.BUFFER, _G.TEST_RPC_CLIENT_ID) + end) + eq( + false, + exec_lua(function() + return vim.lsp.buf_is_attached(_G.BUFFER, _G.TEST_RPC_CLIENT_ID) + end) + ) client.stop() end end, @@ -318,31 +406,38 @@ describe('LSP', function() test_rpc_server { test_name = 'basic_init', on_setup = function() - exec_lua [[ - BUFFER = vim.api.nvim_create_buf(false, true) + exec_lua(function() + _G.BUFFER = vim.api.nvim_create_buf(false, true) vim.api.nvim_create_autocmd('LspAttach', { callback = function(args) - local client = vim.lsp.get_client_by_id(args.data.client_id) - vim.g.lsp_attached = client.name + local client0 = assert(vim.lsp.get_client_by_id(args.data.client_id)) + vim.g.lsp_attached = client0.name end, }) vim.api.nvim_create_autocmd('LspDetach', { callback = function(args) - local client = vim.lsp.get_client_by_id(args.data.client_id) - vim.g.lsp_detached = client.name + local client0 = assert(vim.lsp.get_client_by_id(args.data.client_id)) + vim.g.lsp_detached = client0.name end, }) - ]] + end) end, on_init = function(_client) client = _client - eq(true, exec_lua('return lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID)')) + eq( + true, + exec_lua(function() + return vim.lsp.buf_attach_client(_G.BUFFER, _G.TEST_RPC_CLIENT_ID) + end) + ) client.notify('finish') end, on_handler = function(_, _, ctx) if ctx.method == 'finish' then eq('basic_init', api.nvim_get_var('lsp_attached')) - exec_lua('return lsp.buf_detach_client(BUFFER, TEST_RPC_CLIENT_ID)') + exec_lua(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() end @@ -356,10 +451,10 @@ describe('LSP', function() test_name = 'set_defaults_all_capabilities', on_init = function(_client) client = _client - exec_lua [[ - BUFFER = vim.api.nvim_create_buf(false, true) - lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID) - ]] + exec_lua(function() + _G.BUFFER = vim.api.nvim_create_buf(false, true) + vim.lsp.buf_attach_client(_G.BUFFER, _G.TEST_RPC_CLIENT_ID) + end) end, on_handler = function(_, _, ctx) if ctx.method == 'test' then @@ -369,13 +464,13 @@ describe('LSP', function() eq('', get_buf_option('keywordprg')) eq( true, - exec_lua [[ - local keymap - vim.api.nvim_buf_call(BUFFER, function() - keymap = vim.fn.maparg("K", "n", false, true) + exec_lua(function() + local keymap --- @type table<string,any> + vim._with({ buf = _G.BUFFER }, function() + keymap = vim.fn.maparg('K', 'n', false, true) + end) + return keymap.callback == vim.lsp.buf.hover end) - return keymap.callback == vim.lsp.buf.hover - ]] ) client.stop() end @@ -386,13 +481,13 @@ describe('LSP', function() eq('', get_buf_option('formatexpr')) eq( '', - exec_lua [[ - local keymap - vim.api.nvim_buf_call(BUFFER, function() - keymap = vim.fn.maparg("K", "n", false, false) + exec_lua(function() + local keymap --- @type string + vim._with({ buf = _G.BUFFER }, function() + keymap = vim.fn.maparg('K', 'n', false, false) + end) + return keymap end) - return keymap - ]] ) end, } @@ -400,40 +495,42 @@ describe('LSP', function() it('should overwrite options set by ftplugins', function() local client --- @type vim.lsp.Client + local BUFFER_1 --- @type integer + local BUFFER_2 --- @type integer test_rpc_server { test_name = 'set_defaults_all_capabilities', on_init = function(_client) client = _client - exec_lua [[ + exec_lua(function() vim.api.nvim_command('filetype plugin on') BUFFER_1 = vim.api.nvim_create_buf(false, true) BUFFER_2 = vim.api.nvim_create_buf(false, true) vim.api.nvim_set_option_value('filetype', 'man', { buf = BUFFER_1 }) vim.api.nvim_set_option_value('filetype', 'xml', { buf = BUFFER_2 }) - ]] + end) -- Sanity check to ensure that some values are set after setting filetype. - eq("v:lua.require'man'.goto_tag", get_buf_option('tagfunc', 'BUFFER_1')) - eq('xmlcomplete#CompleteTags', get_buf_option('omnifunc', 'BUFFER_2')) - eq('xmlformat#Format()', get_buf_option('formatexpr', 'BUFFER_2')) + eq("v:lua.require'man'.goto_tag", get_buf_option('tagfunc', BUFFER_1)) + eq('xmlcomplete#CompleteTags', get_buf_option('omnifunc', BUFFER_2)) + eq('xmlformat#Format()', get_buf_option('formatexpr', BUFFER_2)) - exec_lua [[ - lsp.buf_attach_client(BUFFER_1, TEST_RPC_CLIENT_ID) - lsp.buf_attach_client(BUFFER_2, TEST_RPC_CLIENT_ID) - ]] + exec_lua(function() + vim.lsp.buf_attach_client(BUFFER_1, _G.TEST_RPC_CLIENT_ID) + vim.lsp.buf_attach_client(BUFFER_2, _G.TEST_RPC_CLIENT_ID) + end) end, on_handler = function(_, _, ctx) if ctx.method == 'test' then - 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')) + 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() end end, on_exit = function(_, _) - eq('', get_buf_option('tagfunc', 'BUFFER_1')) - eq('', get_buf_option('omnifunc', 'BUFFER_2')) - eq('', get_buf_option('formatexpr', 'BUFFER_2')) + eq('', get_buf_option('tagfunc', BUFFER_1)) + eq('', get_buf_option('omnifunc', BUFFER_2)) + eq('', get_buf_option('formatexpr', BUFFER_2)) end, } end) @@ -444,13 +541,13 @@ describe('LSP', function() test_name = 'set_defaults_all_capabilities', on_init = function(_client) client = _client - exec_lua [[ - BUFFER = vim.api.nvim_create_buf(false, true) - vim.api.nvim_set_option_value('tagfunc', 'tfu', { buf = BUFFER }) - vim.api.nvim_set_option_value('omnifunc', 'ofu', { buf = BUFFER }) - vim.api.nvim_set_option_value('formatexpr', 'fex', { buf = BUFFER }) - lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID) - ]] + exec_lua(function() + _G.BUFFER = vim.api.nvim_create_buf(false, true) + vim.api.nvim_set_option_value('tagfunc', 'tfu', { buf = _G.BUFFER }) + vim.api.nvim_set_option_value('omnifunc', 'ofu', { buf = _G.BUFFER }) + vim.api.nvim_set_option_value('formatexpr', 'fex', { buf = _G.BUFFER }) + vim.lsp.buf_attach_client(_G.BUFFER, _G.TEST_RPC_CLIENT_ID) + end) end, on_handler = function(_, _, ctx) if ctx.method == 'test' then @@ -471,19 +568,19 @@ describe('LSP', function() it('should detach buffer on bufwipe', function() clear() exec_lua(create_server_definition) - local result = exec_lua([[ - local server = _create_server() + local result = exec_lua(function() + local server = _G._create_server() local bufnr = vim.api.nvim_create_buf(false, true) vim.api.nvim_set_current_buf(bufnr) local detach_called = false - vim.api.nvim_create_autocmd("LspDetach", { + vim.api.nvim_create_autocmd('LspDetach', { callback = function() detach_called = true - end + end, }) local client_id = vim.lsp.start({ name = 'detach-dummy', cmd = server.cmd }) - assert(client_id, "lsp.start must return client_id") - local client = vim.lsp.get_client_by_id(client_id) + assert(client_id, 'lsp.start must return client_id') + local client = assert(vim.lsp.get_client_by_id(client_id)) local num_attached_before = vim.tbl_count(client.attached_buffers) vim.api.nvim_buf_delete(bufnr, { force = true }) local num_attached_after = vim.tbl_count(client.attached_buffers) @@ -494,7 +591,7 @@ describe('LSP', function() num_attached_after = num_attached_after, detach_called = detach_called, } - ]]) + end) eq(true, result ~= nil, 'exec_lua must return result') eq(1, result.num_attached_before) eq(0, result.num_attached_after) @@ -504,30 +601,62 @@ describe('LSP', function() it('should not re-attach buffer if it was deleted in on_init #28575', function() clear() exec_lua(create_server_definition) - exec_lua([[ - local server = _create_server({ + exec_lua(function() + local server = _G._create_server({ handlers = { - initialize = function(method, params, callback) + initialize = function(_, _, callback) vim.schedule(function() callback(nil, { capabilities = {} }) end) - end - } + end, + }, }) local bufnr = vim.api.nvim_create_buf(false, true) local on_init_called = false - local client_id = vim.lsp.start({ + local client_id = assert(vim.lsp.start({ name = 'detach-dummy', cmd = server.cmd, on_init = function() vim.api.nvim_buf_delete(bufnr, {}) on_init_called = true - end - }) + end, + })) vim.lsp.buf_attach_client(bufnr, client_id) - local ok = vim.wait(1000, function() return on_init_called end) - assert(ok, "on_init was not called") - ]]) + local ok = vim.wait(1000, function() + return on_init_called + end) + assert(ok, 'on_init was not called') + end) + end) + + it('should allow on_lines + nvim_buf_delete during LSP initialization #28575', function() + clear() + exec_lua(create_server_definition) + exec_lua(function() + local initialized = false + local server = _G._create_server({ + handlers = { + initialize = function(_, _, callback) + vim.schedule(function() + callback(nil, { capabilities = {} }) + initialized = true + end) + end, + }, + }) + local bufnr = vim.api.nvim_create_buf(false, true) + vim.api.nvim_set_current_buf(bufnr) + vim.lsp.start({ + name = 'detach-dummy', + cmd = server.cmd, + }) + vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { 'hello' }) + vim.api.nvim_buf_delete(bufnr, {}) + local ok = vim.wait(1000, function() + return initialized + end) + assert(ok, 'lsp did not initialize') + end) end) it('client should return settings via workspace/configuration handler', function() @@ -560,19 +689,25 @@ describe('LSP', function() on_handler = function(err, result, ctx) eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') if ctx.method == 'start' then - exec_lua([=[ - local client = vim.lsp.get_client_by_id(TEST_RPC_CLIENT_ID) - client.settings = { - testSetting1 = true; - testSetting2 = false; - test = {Setting3 = 'nested' }; - }]=]) + exec_lua(function() + local client0 = vim.lsp.get_client_by_id(_G.TEST_RPC_CLIENT_ID) + client0.settings = { + testSetting1 = true, + testSetting2 = false, + test = { Setting3 = 'nested' }, + } + end) end if ctx.method == 'workspace/configuration' then local server_result = exec_lua( - [=[ + [[ local method, params = ... - return require'vim.lsp.handlers'['workspace/configuration'](err, params, {method=method, client_id=TEST_RPC_CLIENT_ID})]=], + return require 'vim.lsp.handlers'['workspace/configuration']( + err, + params, + { method = method, client_id = _G.TEST_RPC_CLIENT_ID } + ) + ]], ctx.method, result ) @@ -584,6 +719,7 @@ describe('LSP', function() end, } end) + it( 'workspace/configuration returns NIL per section if client was started without config.settings', function() @@ -594,15 +730,19 @@ describe('LSP', function() c.stop() end, on_setup = function() - result = exec_lua [[ - local result = { - items = { - {section = 'foo'}, - {section = 'bar'}, + result = exec_lua(function() + local result0 = { + items = { + { section = 'foo' }, + { section = 'bar' }, + }, } - } - return vim.lsp.handlers['workspace/configuration'](nil, result, {client_id=TEST_RPC_CLIENT_ID}) - ]] + return vim.lsp.handlers['workspace/configuration']( + nil, + result0, + { client_id = _G.TEST_RPC_CLIENT_ID } + ) + end) end, } eq({ NIL, NIL }, result) @@ -617,7 +757,9 @@ describe('LSP', function() test_name = 'basic_check_capabilities', on_init = function(client) client.stop() - local full_kind = exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Full") + local full_kind = exec_lua(function() + return require 'vim.lsp.protocol'.TextDocumentSyncKind.Full + end) eq(full_kind, client.server_capabilities().textDocumentSync.change) eq({ includeText = false }, client.server_capabilities().textDocumentSync.save) eq(false, client.server_capabilities().codeLensProvider) @@ -650,11 +792,11 @@ describe('LSP', function() on_handler = function(err, result, ctx) eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') if ctx.method == 'start' then - exec_lua([=[ - BUFFER = vim.api.nvim_get_current_buf() - lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID) - vim.api.nvim_exec_autocmds('BufWritePost', { buffer = BUFFER, modeline = false }) - ]=]) + exec_lua(function() + _G.BUFFER = vim.api.nvim_get_current_buf() + vim.lsp.buf_attach_client(_G.BUFFER, _G.TEST_RPC_CLIENT_ID) + vim.api.nvim_exec_autocmds('BufWritePost', { buffer = _G.BUFFER, modeline = false }) + end) else client.stop() end @@ -665,21 +807,21 @@ describe('LSP', function() it('BufWritePre does not send notifications if server lacks willSave capabilities', function() clear() exec_lua(create_server_definition) - local messages = exec_lua([[ - local server = _create_server({ + local messages = exec_lua(function() + local server = _G._create_server({ capabilities = { textDocumentSync = { willSave = false, willSaveWaitUntil = false, - } + }, }, }) - local client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd }) + local client_id = assert(vim.lsp.start({ name = 'dummy', cmd = server.cmd })) local buf = vim.api.nvim_get_current_buf() vim.api.nvim_exec_autocmds('BufWritePre', { buffer = buf, modeline = false }) vim.lsp.stop_client(client_id) return server.messages - ]]) + end) eq(4, #messages) eq('initialize', messages[1].method) eq('initialized', messages[2].method) @@ -690,13 +832,13 @@ describe('LSP', function() it('BufWritePre sends willSave / willSaveWaitUntil, applies textEdits', function() clear() exec_lua(create_server_definition) - local result = exec_lua([[ - local server = _create_server({ + local result = exec_lua(function() + local server = _G._create_server({ capabilities = { textDocumentSync = { willSave = true, willSaveWaitUntil = true, - } + }, }, handlers = { ['textDocument/willSaveWaitUntil'] = function(_, _, callback) @@ -705,21 +847,21 @@ describe('LSP', function() start = { line = 0, character = 0 }, ['end'] = { line = 0, character = 0 }, }, - newText = 'Hello' + newText = 'Hello', } - callback(nil, { text_edit, }) - end + callback(nil, { text_edit }) + end, }, }) local buf = vim.api.nvim_get_current_buf() - local client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd }) + local client_id = assert(vim.lsp.start({ name = 'dummy', cmd = server.cmd })) vim.api.nvim_exec_autocmds('BufWritePre', { buffer = buf, modeline = false }) vim.lsp.stop_client(client_id) return { messages = server.messages, - lines = vim.api.nvim_buf_get_lines(buf, 0, -1, true) + lines = vim.api.nvim_buf_get_lines(buf, 0, -1, true), } - ]]) + end) local messages = result.messages eq('textDocument/willSave', messages[3].method) eq('textDocument/willSaveWaitUntil', messages[4].method) @@ -745,20 +887,16 @@ describe('LSP', function() eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') if ctx.method == 'start' then local tmpfile_old = tmpname() - local tmpfile_new = tmpname() - os.remove(tmpfile_new) - exec_lua( - [=[ - local oldname, newname = ... - BUFFER = vim.api.nvim_get_current_buf() - vim.api.nvim_buf_set_name(BUFFER, oldname) - vim.api.nvim_buf_set_lines(BUFFER, 0, -1, true, {"help me"}) - lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID) - vim.api.nvim_buf_call(BUFFER, function() vim.cmd('saveas ' .. newname) end) - ]=], - tmpfile_old, - tmpfile_new - ) + local tmpfile_new = tmpname(false) + exec_lua(function() + _G.BUFFER = vim.api.nvim_get_current_buf() + vim.api.nvim_buf_set_name(_G.BUFFER, tmpfile_old) + vim.api.nvim_buf_set_lines(_G.BUFFER, 0, -1, true, { 'help me' }) + vim.lsp.buf_attach_client(_G.BUFFER, _G.TEST_RPC_CLIENT_ID) + vim._with({ buf = _G.BUFFER }, function() + vim.cmd('saveas ' .. tmpfile_new) + end) + end) else client.stop() end @@ -784,12 +922,12 @@ describe('LSP', function() on_handler = function(err, result, ctx) eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') if ctx.method == 'start' then - exec_lua([=[ - BUFFER = vim.api.nvim_get_current_buf() - vim.api.nvim_buf_set_lines(BUFFER, 0, -1, true, {"help me"}) - lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID) - vim.api.nvim_exec_autocmds('BufWritePost', { buffer = BUFFER, modeline = false }) - ]=]) + exec_lua(function() + _G.BUFFER = vim.api.nvim_get_current_buf() + vim.api.nvim_buf_set_lines(_G.BUFFER, 0, -1, true, { 'help me' }) + vim.lsp.buf_attach_client(_G.BUFFER, _G.TEST_RPC_CLIENT_ID) + vim.api.nvim_exec_autocmds('BufWritePost', { buffer = _G.BUFFER, modeline = false }) + end) else client.stop() end @@ -843,16 +981,18 @@ describe('LSP', function() test_rpc_server { test_name = 'capabilities_for_client_supports_method', on_setup = function() - exec_lua([=[ - BUFFER = vim.api.nvim_get_current_buf() - lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID) - vim.lsp.handlers['textDocument/typeDefinition'] = function() end - vim.cmd(BUFFER.."bwipeout") - ]=]) + exec_lua(function() + _G.BUFFER = vim.api.nvim_get_current_buf() + vim.lsp.buf_attach_client(_G.BUFFER, _G.TEST_RPC_CLIENT_ID) + vim.lsp.handlers['textDocument/typeDefinition'] = function() end + vim.cmd(_G.BUFFER .. 'bwipeout') + end) end, on_init = function(client) client.stop() - exec_lua('vim.lsp.buf.type_definition()') + exec_lua(function() + vim.lsp.buf.type_definition() + end) end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -873,13 +1013,15 @@ describe('LSP', function() test_rpc_server { test_name = 'capabilities_for_client_supports_method', on_setup = function() - exec_lua([=[ + exec_lua(function() vim.lsp.handlers['textDocument/typeDefinition'] = function() end - ]=]) + end) end, on_init = function(client) client.stop() - exec_lua('vim.lsp.buf.type_definition()') + exec_lua(function() + vim.lsp.buf.type_definition() + end) end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -920,7 +1062,11 @@ describe('LSP', function() it('should forward ContentModified to callback', function() local expected_handlers = { { NIL, {}, { method = 'finish', client_id = 1 } }, - { { code = -32801 }, NIL, { method = 'error_code_test', bufnr = 1, client_id = 1 } }, + { + { code = -32801 }, + NIL, + { method = 'error_code_test', bufnr = 1, client_id = 1, version = 0 }, + }, } local client --- @type vim.lsp.Client test_rpc_server { @@ -950,7 +1096,7 @@ describe('LSP', function() it('should track pending requests to the language server', function() local expected_handlers = { { NIL, {}, { method = 'finish', client_id = 1 } }, - { NIL, {}, { method = 'slow_request', bufnr = 1, client_id = 1 } }, + { NIL, {}, { method = 'slow_request', bufnr = 1, client_id = 1, version = 0 } }, } local client --- @type vim.lsp.Client test_rpc_server { @@ -958,7 +1104,9 @@ describe('LSP', function() on_init = function(_client) client = _client client.request('slow_request') - local request = exec_lua([=[ return TEST_RPC_CLIENT.requests[2] ]=]) + 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') @@ -971,8 +1119,10 @@ describe('LSP', function() on_handler = function(err, _, ctx) eq(table.remove(expected_handlers), { err, {}, ctx }, 'expected handler') if ctx.method == 'slow_request' then - local request = exec_lua([=[ return TEST_RPC_CLIENT.requests[2] ]=]) - eq(NIL, request) + local request = exec_lua(function() + return _G.TEST_RPC_CLIENT.requests[2] + end) + eq(nil, request) client.notify('finish') end if ctx.method == 'finish' then @@ -993,7 +1143,9 @@ describe('LSP', function() client = _client client.request('slow_request') client.cancel_request(2) - local request = exec_lua([=[ return TEST_RPC_CLIENT.requests[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') @@ -1005,8 +1157,10 @@ describe('LSP', function() end, on_handler = function(err, _, ctx) eq(table.remove(expected_handlers), { err, {}, ctx }, 'expected handler') - local request = exec_lua([=[ return TEST_RPC_CLIENT.requests[2] ]=]) - eq(NIL, request) + local request = exec_lua(function() + return _G.TEST_RPC_CLIENT.requests[2] + end) + eq(nil, request) if ctx.method == 'finish' then client.stop() end @@ -1017,7 +1171,7 @@ describe('LSP', function() it('should clear pending and cancel requests on reply', function() local expected_handlers = { { NIL, {}, { method = 'finish', client_id = 1 } }, - { NIL, {}, { method = 'slow_request', bufnr = 1, client_id = 1 } }, + { NIL, {}, { method = 'slow_request', bufnr = 1, client_id = 1, version = 0 } }, } local client --- @type vim.lsp.Client test_rpc_server { @@ -1025,11 +1179,15 @@ describe('LSP', function() on_init = function(_client) client = _client client.request('slow_request') - local request = exec_lua([=[ return TEST_RPC_CLIENT.requests[2] ]=]) + 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) - request = exec_lua([=[ return TEST_RPC_CLIENT.requests[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') @@ -1042,8 +1200,10 @@ describe('LSP', function() on_handler = function(err, _, ctx) eq(table.remove(expected_handlers), { err, {}, ctx }, 'expected handler') if ctx.method == 'slow_request' then - local request = exec_lua([=[ return TEST_RPC_CLIENT.requests[2] ]=]) - eq(NIL, request) + local request = exec_lua(function() + return _G.TEST_RPC_CLIENT.requests[2] + end) + eq(nil, request) client.notify('finish') end if ctx.method == 'finish' then @@ -1056,7 +1216,7 @@ describe('LSP', function() it('should trigger LspRequest autocmd when requests table changes', function() local expected_handlers = { { NIL, {}, { method = 'finish', client_id = 1 } }, - { NIL, {}, { method = 'slow_request', bufnr = 1, client_id = 1 } }, + { NIL, {}, { method = 'slow_request', bufnr = 1, client_id = 1, version = 0 } }, } local client --- @type vim.lsp.Client test_rpc_server { @@ -1098,21 +1258,23 @@ describe('LSP', function() test_rpc_server { test_name = 'basic_finish', on_setup = function() - exec_lua [[ - BUFFER = vim.api.nvim_create_buf(false, true) - vim.api.nvim_buf_set_lines(BUFFER, 0, -1, false, { - "testing"; - "123"; + exec_lua(function() + _G.BUFFER = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_lines(_G.BUFFER, 0, -1, false, { + 'testing', + '123', }) - assert(TEST_RPC_CLIENT_ID == 1) - assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID)) - assert(lsp.buf_is_attached(BUFFER, TEST_RPC_CLIENT_ID)) - vim.cmd(BUFFER.."bwipeout") - ]] + assert(_G.TEST_RPC_CLIENT_ID == 1) + assert(vim.lsp.buf_attach_client(_G.BUFFER, _G.TEST_RPC_CLIENT_ID)) + assert(vim.lsp.buf_is_attached(_G.BUFFER, _G.TEST_RPC_CLIENT_ID)) + vim.cmd(_G.BUFFER .. 'bwipeout') + end) end, on_init = function(_client) client = _client - local full_kind = exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Full") + local full_kind = exec_lua(function() + return require 'vim.lsp.protocol'.TextDocumentSyncKind.Full + end) eq(full_kind, client.server_capabilities().textDocumentSync.change) eq(true, client.server_capabilities().textDocumentSync.openClose) client.notify('finish') @@ -1140,25 +1302,30 @@ describe('LSP', function() test_rpc_server { test_name = 'basic_check_buffer_open', on_setup = function() - exec_lua [[ - BUFFER = vim.api.nvim_create_buf(false, true) - vim.api.nvim_buf_set_lines(BUFFER, 0, -1, false, { - "testing"; - "123"; + exec_lua(function() + _G.BUFFER = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_lines(_G.BUFFER, 0, -1, false, { + 'testing', + '123', }) - ]] - exec_lua [[ - assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID)) - ]] + end) + exec_lua(function() + assert(vim.lsp.buf_attach_client(_G.BUFFER, _G.TEST_RPC_CLIENT_ID)) + end) end, on_init = function(_client) client = _client - local full_kind = exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Full") + local full_kind = exec_lua(function() + return require 'vim.lsp.protocol'.TextDocumentSyncKind.Full + end) eq(full_kind, client.server_capabilities().textDocumentSync.change) eq(true, client.server_capabilities().textDocumentSync.openClose) - exec_lua [[ - assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID), "Already attached, returns true") - ]] + exec_lua(function() + assert( + vim.lsp.buf_attach_client(_G.BUFFER, _G.TEST_RPC_CLIENT_ID), + 'Already attached, returns true' + ) + end) end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -1186,22 +1353,24 @@ describe('LSP', function() test_rpc_server { test_name = 'basic_check_buffer_open', on_setup = function() - exec_lua [[ - BUFFER = vim.api.nvim_create_buf(false, true) - vim.api.nvim_buf_set_lines(BUFFER, 0, -1, false, { - "testing"; - "123"; + exec_lua(function() + _G.BUFFER = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_lines(_G.BUFFER, 0, -1, false, { + 'testing', + '123', }) - ]] + end) end, on_init = function(_client) client = _client - local full_kind = exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Full") + local full_kind = exec_lua(function() + return require 'vim.lsp.protocol'.TextDocumentSyncKind.Full + end) eq(full_kind, client.server_capabilities().textDocumentSync.change) eq(true, client.server_capabilities().textDocumentSync.openClose) - exec_lua [[ - assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID)) - ]] + exec_lua(function() + assert(vim.lsp.buf_attach_client(_G.BUFFER, _G.TEST_RPC_CLIENT_ID)) + end) end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -1229,22 +1398,24 @@ describe('LSP', function() test_rpc_server { test_name = 'basic_check_buffer_open_and_change', on_setup = function() - exec_lua [[ - BUFFER = vim.api.nvim_create_buf(false, true) - vim.api.nvim_buf_set_lines(BUFFER, 0, -1, false, { - "testing"; - "123"; + exec_lua(function() + _G.BUFFER = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_lines(_G.BUFFER, 0, -1, false, { + 'testing', + '123', }) - ]] + end) end, on_init = function(_client) client = _client - local full_kind = exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Full") + local full_kind = exec_lua(function() + return require 'vim.lsp.protocol'.TextDocumentSyncKind.Full + end) eq(full_kind, client.server_capabilities().textDocumentSync.change) eq(true, client.server_capabilities().textDocumentSync.openClose) - exec_lua [[ - assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID)) - ]] + exec_lua(function() + assert(vim.lsp.buf_attach_client(_G.BUFFER, _G.TEST_RPC_CLIENT_ID)) + end) end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -1252,11 +1423,11 @@ describe('LSP', function() end, on_handler = function(err, result, ctx) if ctx.method == 'start' then - exec_lua [[ - vim.api.nvim_buf_set_lines(BUFFER, 1, 2, false, { - "boop"; + exec_lua(function() + vim.api.nvim_buf_set_lines(_G.BUFFER, 1, 2, false, { + 'boop', }) - ]] + end) client.notify('finish') end eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') @@ -1277,23 +1448,25 @@ describe('LSP', function() test_rpc_server { test_name = 'basic_check_buffer_open_and_change_noeol', on_setup = function() - exec_lua [[ - BUFFER = vim.api.nvim_create_buf(false, true) - vim.api.nvim_buf_set_lines(BUFFER, 0, -1, false, { - "testing"; - "123"; + exec_lua(function() + _G.BUFFER = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_lines(_G.BUFFER, 0, -1, false, { + 'testing', + '123', }) - vim.bo[BUFFER].eol = false - ]] + vim.bo[_G.BUFFER].eol = false + end) end, on_init = function(_client) client = _client - local full_kind = exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Full") + local full_kind = exec_lua(function() + return require 'vim.lsp.protocol'.TextDocumentSyncKind.Full + end) eq(full_kind, client.server_capabilities().textDocumentSync.change) eq(true, client.server_capabilities().textDocumentSync.openClose) - exec_lua [[ - assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID)) - ]] + exec_lua(function() + assert(vim.lsp.buf_attach_client(_G.BUFFER, _G.TEST_RPC_CLIENT_ID)) + end) end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -1301,11 +1474,11 @@ describe('LSP', function() end, on_handler = function(err, result, ctx) if ctx.method == 'start' then - exec_lua [[ - vim.api.nvim_buf_set_lines(BUFFER, 1, 2, false, { - "boop"; + exec_lua(function() + vim.api.nvim_buf_set_lines(_G.BUFFER, 1, 2, false, { + 'boop', }) - ]] + end) client.notify('finish') end eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') @@ -1336,6 +1509,7 @@ describe('LSP', function() }, bufnr = 2, client_id = 1, + version = 0, }, }, { NIL, {}, { method = 'start', client_id = 1 } }, @@ -1344,21 +1518,21 @@ describe('LSP', function() test_rpc_server { test_name = 'inlay_hint', on_setup = function() - exec_lua [[ - BUFFER = vim.api.nvim_create_buf(false, true) - vim.api.nvim_buf_set_lines(BUFFER, 0, -1, false, { - "testing"; - "123"; + exec_lua(function() + _G.BUFFER = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_lines(_G.BUFFER, 0, -1, false, { + 'testing', + '123', }) - vim.bo[BUFFER].eol = false - ]] + vim.bo[_G.BUFFER].eol = false + end) end, on_init = function(_client) client = _client eq(true, client.supports_method('textDocument/inlayHint')) - exec_lua [[ - assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID)) - ]] + exec_lua(function() + assert(vim.lsp.buf_attach_client(_G.BUFFER, _G.TEST_RPC_CLIENT_ID)) + end) end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -1366,9 +1540,9 @@ describe('LSP', function() end, on_handler = function(err, result, ctx) if ctx.method == 'start' then - exec_lua [[ - vim.lsp.inlay_hint.enable(true, { bufnr = BUFFER }) - ]] + exec_lua(function() + vim.lsp.inlay_hint.enable(true, { bufnr = _G.BUFFER }) + end) end if ctx.method == 'textDocument/inlayHint' then client.notify('finish') @@ -1394,23 +1568,24 @@ describe('LSP', function() allow_incremental_sync = true, }, on_setup = function() - exec_lua [[ - BUFFER = vim.api.nvim_create_buf(false, true) - vim.api.nvim_buf_set_lines(BUFFER, 0, -1, false, { - "testing"; - "123"; + exec_lua(function() + _G.BUFFER = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_lines(_G.BUFFER, 0, -1, false, { + 'testing', + '123', }) - ]] + end) end, on_init = function(_client) client = _client - local sync_kind = - exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Incremental") + local sync_kind = exec_lua(function() + return require 'vim.lsp.protocol'.TextDocumentSyncKind.Incremental + end) eq(sync_kind, client.server_capabilities().textDocumentSync.change) eq(true, client.server_capabilities().textDocumentSync.openClose) - exec_lua [[ - assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID)) - ]] + exec_lua(function() + assert(vim.lsp.buf_attach_client(_G.BUFFER, _G.TEST_RPC_CLIENT_ID)) + end) end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -1418,11 +1593,11 @@ describe('LSP', function() end, on_handler = function(err, result, ctx) if ctx.method == 'start' then - exec_lua [[ - vim.api.nvim_buf_set_lines(BUFFER, 1, 2, false, { - "123boop"; + exec_lua(function() + vim.api.nvim_buf_set_lines(_G.BUFFER, 1, 2, false, { + '123boop', }) - ]] + end) client.notify('finish') end eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') @@ -1432,6 +1607,7 @@ describe('LSP', function() end, } end) + it('should check the body and didChange incremental with debounce', function() local expected_handlers = { { NIL, {}, { method = 'shutdown', client_id = 1 } }, @@ -1446,23 +1622,24 @@ describe('LSP', function() debounce_text_changes = 5, }, on_setup = function() - exec_lua [[ - BUFFER = vim.api.nvim_create_buf(false, true) - vim.api.nvim_buf_set_lines(BUFFER, 0, -1, false, { - "testing"; - "123"; + exec_lua(function() + _G.BUFFER = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_lines(_G.BUFFER, 0, -1, false, { + 'testing', + '123', }) - ]] + end) end, on_init = function(_client) client = _client - local sync_kind = - exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Incremental") + local sync_kind = exec_lua(function() + return require 'vim.lsp.protocol'.TextDocumentSyncKind.Incremental + end) eq(sync_kind, client.server_capabilities().textDocumentSync.change) eq(true, client.server_capabilities().textDocumentSync.openClose) - exec_lua [[ - assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID)) - ]] + exec_lua(function() + assert(vim.lsp.buf_attach_client(_G.BUFFER, _G.TEST_RPC_CLIENT_ID)) + end) end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -1470,11 +1647,11 @@ describe('LSP', function() end, on_handler = function(err, result, ctx) if ctx.method == 'start' then - exec_lua [[ - vim.api.nvim_buf_set_lines(BUFFER, 1, 2, false, { - "123boop"; + exec_lua(function() + vim.api.nvim_buf_set_lines(_G.BUFFER, 1, 2, false, { + '123boop', }) - ]] + end) client.notify('finish') end eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') @@ -1496,23 +1673,24 @@ describe('LSP', function() test_rpc_server { test_name = 'basic_check_buffer_open_and_change_incremental_editing', on_setup = function() - exec_lua [[ - BUFFER = vim.api.nvim_create_buf(false, true) - vim.api.nvim_buf_set_lines(BUFFER, 0, -1, false, { - "testing"; - "123"; + exec_lua(function() + _G.BUFFER = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_lines(_G.BUFFER, 0, -1, false, { + 'testing', + '123', }) - ]] + end) end, on_init = function(_client) client = _client - local sync_kind = - exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Incremental") + local sync_kind = exec_lua(function() + return require 'vim.lsp.protocol'.TextDocumentSyncKind.Incremental + end) eq(sync_kind, client.server_capabilities().textDocumentSync.change) eq(true, client.server_capabilities().textDocumentSync.openClose) - exec_lua [[ - assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID)) - ]] + exec_lua(function() + assert(vim.lsp.buf_attach_client(_G.BUFFER, _G.TEST_RPC_CLIENT_ID)) + end) end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -1541,22 +1719,24 @@ describe('LSP', function() test_rpc_server { test_name = 'basic_check_buffer_open_and_change_multi', on_setup = function() - exec_lua [[ - BUFFER = vim.api.nvim_create_buf(false, true) - vim.api.nvim_buf_set_lines(BUFFER, 0, -1, false, { - "testing"; - "123"; + exec_lua(function() + _G.BUFFER = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_lines(_G.BUFFER, 0, -1, false, { + 'testing', + '123', }) - ]] + end) end, on_init = function(_client) client = _client - local sync_kind = exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Full") + local sync_kind = exec_lua(function() + return require 'vim.lsp.protocol'.TextDocumentSyncKind.Full + end) eq(sync_kind, client.server_capabilities().textDocumentSync.change) eq(true, client.server_capabilities().textDocumentSync.openClose) - exec_lua [[ - assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID)) - ]] + exec_lua(function() + assert(vim.lsp.buf_attach_client(_G.BUFFER, _G.TEST_RPC_CLIENT_ID)) + end) end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -1564,14 +1744,14 @@ describe('LSP', function() end, on_handler = function(err, result, ctx) if ctx.method == 'start' then - exec_lua [[ - vim.api.nvim_buf_set_lines(BUFFER, 1, 2, false, { - "321"; + exec_lua(function() + vim.api.nvim_buf_set_lines(_G.BUFFER, 1, 2, false, { + '321', }) - vim.api.nvim_buf_set_lines(BUFFER, 1, 2, false, { - "boop"; + vim.api.nvim_buf_set_lines(_G.BUFFER, 1, 2, false, { + 'boop', }) - ]] + end) client.notify('finish') end eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') @@ -1592,22 +1772,24 @@ describe('LSP', function() test_rpc_server { test_name = 'basic_check_buffer_open_and_change_multi_and_close', on_setup = function() - exec_lua [[ - BUFFER = vim.api.nvim_create_buf(false, true) - vim.api.nvim_buf_set_lines(BUFFER, 0, -1, false, { - "testing"; - "123"; + exec_lua(function() + _G.BUFFER = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_lines(_G.BUFFER, 0, -1, false, { + 'testing', + '123', }) - ]] + end) end, on_init = function(_client) client = _client - local sync_kind = exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Full") + local sync_kind = exec_lua(function() + return require 'vim.lsp.protocol'.TextDocumentSyncKind.Full + end) eq(sync_kind, client.server_capabilities().textDocumentSync.change) eq(true, client.server_capabilities().textDocumentSync.openClose) - exec_lua [[ - assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID)) - ]] + exec_lua(function() + assert(vim.lsp.buf_attach_client(_G.BUFFER, _G.TEST_RPC_CLIENT_ID)) + end) end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -1615,15 +1797,15 @@ describe('LSP', function() end, on_handler = function(err, result, ctx) if ctx.method == 'start' then - exec_lua [[ - vim.api.nvim_buf_set_lines(BUFFER, 1, 2, false, { - "321"; + exec_lua(function() + vim.api.nvim_buf_set_lines(_G.BUFFER, 1, 2, false, { + '321', }) - vim.api.nvim_buf_set_lines(BUFFER, 1, 2, false, { - "boop"; + vim.api.nvim_buf_set_lines(_G.BUFFER, 1, 2, false, { + 'boop', }) - vim.api.nvim_command(BUFFER.."bwipeout") - ]] + vim.api.nvim_command(_G.BUFFER .. 'bwipeout') + end) client.notify('finish') end eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') @@ -1679,19 +1861,19 @@ describe('LSP', function() test_rpc_server { test_name = 'decode_nil', on_setup = function() - exec_lua [[ - BUFFER = vim.api.nvim_create_buf(false, true) - vim.api.nvim_buf_set_lines(BUFFER, 0, -1, false, { - "testing"; - "123"; + exec_lua(function() + _G.BUFFER = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_lines(_G.BUFFER, 0, -1, false, { + 'testing', + '123', }) - ]] + end) end, on_init = function(_client) client = _client - exec_lua [[ - assert(lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID)) - ]] + exec_lua(function() + assert(vim.lsp.buf_attach_client(_G.BUFFER, _G.TEST_RPC_CLIENT_ID)) + end) end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -1706,22 +1888,58 @@ describe('LSP', function() } end) end) -end) -describe('LSP', function() - before_each(function() - clear_notrace() - end) + describe('apply vscode text_edits', function() + it('single replace', function() + insert('012345678901234567890123456789') + apply_text_edits({ + { 0, 3, 0, 6, { 'Hello' } }, + }) + eq({ '012Hello678901234567890123456789' }, buf_lines(1)) + end) - local function make_edit(y_0, x_0, y_1, x_1, text) - return { - range = { - start = { line = y_0, character = x_0 }, - ['end'] = { line = y_1, character = x_1 }, - }, - newText = type(text) == 'table' and table.concat(text, '\n') or (text or ''), - } - end + it('two replaces', function() + insert('012345678901234567890123456789') + apply_text_edits({ + { 0, 3, 0, 6, { 'Hello' } }, + { 0, 6, 0, 9, { 'World' } }, + }) + eq({ '012HelloWorld901234567890123456789' }, buf_lines(1)) + end) + + it('same start pos insert are kept in order', function() + insert('012345678901234567890123456789') + apply_text_edits({ + { 0, 3, 0, 3, { 'World' } }, + { 0, 3, 0, 3, { 'Hello' } }, + }) + eq({ '012WorldHello345678901234567890123456789' }, buf_lines(1)) + end) + + it('same start pos insert and replace are kept in order', function() + insert('012345678901234567890123456789') + apply_text_edits({ + { 0, 3, 0, 3, { 'World' } }, + { 0, 3, 0, 3, { 'Hello' } }, + { 0, 3, 0, 8, { 'No' } }, + }) + eq({ '012WorldHelloNo8901234567890123456789' }, buf_lines(1)) + end) + + it('multiline', function() + exec_lua(function() + vim.api.nvim_buf_set_lines(1, 0, 0, true, { ' {', ' "foo": "bar"', ' }' }) + end) + eq({ ' {', ' "foo": "bar"', ' }', '' }, buf_lines(1)) + apply_text_edits({ + { 0, 0, 3, 0, { '' } }, + { 3, 0, 3, 0, { '{\n' } }, + { 3, 0, 3, 0, { ' "foo": "bar"\n' } }, + { 3, 0, 3, 0, { '}\n' } }, + }) + eq({ '{', ' "foo": "bar"', '}', '' }, buf_lines(1)) + end) + end) describe('apply_text_edits', function() before_each(function() @@ -1732,14 +1950,14 @@ describe('LSP', function() Fourth line of text å å ɧ 汉语 ↥ 🤦 🦄]])) end) + it('applies simple edits', function() - local edits = { - make_edit(0, 0, 0, 0, { '123' }), - make_edit(1, 0, 1, 1, { '2' }), - make_edit(2, 0, 2, 2, { '3' }), - make_edit(3, 2, 3, 4, { '' }), - } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, 'utf-16') + apply_text_edits({ + { 0, 0, 0, 0, { '123' } }, + { 1, 0, 1, 1, { '2' } }, + { 2, 0, 2, 2, { '3' } }, + { 3, 2, 3, 4, { '' } }, + }) eq({ '123First line of text', '2econd line of text', @@ -1748,18 +1966,18 @@ describe('LSP', function() 'å å ɧ 汉语 ↥ 🤦 🦄', }, buf_lines(1)) end) + it('applies complex edits', function() - local edits = { - make_edit(0, 0, 0, 0, { '', '12' }), - make_edit(0, 0, 0, 0, { '3', 'foo' }), - make_edit(0, 1, 0, 1, { 'bar', '123' }), - make_edit(0, #'First ', 0, #'First line of text', { 'guy' }), - make_edit(1, 0, 1, #'Second', { 'baz' }), - make_edit(2, #'Th', 2, #'Third', { 'e next' }), - make_edit(3, #'', 3, #'Fourth', { 'another line of text', 'before this' }), - make_edit(3, #'Fourth', 3, #'Fourth line of text', { '!' }), - } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, 'utf-16') + apply_text_edits({ + { 0, 0, 0, 0, { '', '12' } }, + { 0, 0, 0, 0, { '3', 'foo' } }, + { 0, 1, 0, 1, { 'bar', '123' } }, + { 0, #'First ', 0, #'First line of text', { 'guy' } }, + { 1, 0, 1, #'Second', { 'baz' } }, + { 2, #'Th', 2, #'Third', { 'e next' } }, + { 3, #'', 3, #'Fourth', { 'another line of text', 'before this' } }, + { 3, #'Fourth', 3, #'Fourth line of text', { '!' } }, + }) eq({ '', '123', @@ -1772,18 +1990,18 @@ describe('LSP', function() 'å å ɧ 汉语 ↥ 🤦 🦄', }, buf_lines(1)) end) + it('applies complex edits (reversed range)', function() - local edits = { - make_edit(0, 0, 0, 0, { '', '12' }), - make_edit(0, 0, 0, 0, { '3', 'foo' }), - make_edit(0, 1, 0, 1, { 'bar', '123' }), - make_edit(0, #'First line of text', 0, #'First ', { 'guy' }), - make_edit(1, #'Second', 1, 0, { 'baz' }), - make_edit(2, #'Third', 2, #'Th', { 'e next' }), - make_edit(3, #'Fourth', 3, #'', { 'another line of text', 'before this' }), - make_edit(3, #'Fourth line of text', 3, #'Fourth', { '!' }), - } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, 'utf-16') + apply_text_edits({ + { 0, 0, 0, 0, { '', '12' } }, + { 0, 0, 0, 0, { '3', 'foo' } }, + { 0, 1, 0, 1, { 'bar', '123' } }, + { 0, #'First line of text', 0, #'First ', { 'guy' } }, + { 1, #'Second', 1, 0, { 'baz' } }, + { 2, #'Third', 2, #'Th', { 'e next' } }, + { 3, #'Fourth', 3, #'', { 'another line of text', 'before this' } }, + { 3, #'Fourth line of text', 3, #'Fourth', { '!' } }, + }) eq({ '', '123', @@ -1796,11 +2014,11 @@ describe('LSP', function() 'å å ɧ 汉语 ↥ 🤦 🦄', }, buf_lines(1)) end) + it('applies non-ASCII characters edits', function() - local edits = { - make_edit(4, 3, 4, 4, { 'ä' }), - } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, 'utf-16') + apply_text_edits({ + { 4, 3, 4, 4, { 'ä' } }, + }) eq({ 'First line of text', 'Second line of text', @@ -1809,11 +2027,11 @@ describe('LSP', function() 'å ä ɧ 汉语 ↥ 🤦 🦄', }, buf_lines(1)) end) + it('applies text edits at the end of the document', function() - local edits = { - make_edit(5, 0, 5, 0, 'foobar'), - } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, 'utf-16') + apply_text_edits({ + { 5, 0, 5, 0, 'foobar' }, + }) eq({ 'First line of text', 'Second line of text', @@ -1823,12 +2041,12 @@ describe('LSP', function() 'foobar', }, buf_lines(1)) end) + it('applies multiple text edits at the end of the document', function() - local edits = { - make_edit(4, 0, 5, 0, ''), - make_edit(5, 0, 5, 0, 'foobar'), - } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, 'utf-16') + apply_text_edits({ + { 4, 0, 5, 0, '' }, + { 5, 0, 5, 0, 'foobar' }, + }) eq({ 'First line of text', 'Second line of text', @@ -1837,62 +2055,56 @@ describe('LSP', function() 'foobar', }, buf_lines(1)) end) + it('it restores marks', function() - local edits = { - make_edit(1, 0, 2, 5, 'foobar'), - make_edit(4, 0, 5, 0, 'barfoo'), - } eq(true, api.nvim_buf_set_mark(1, 'a', 2, 1, {})) - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, 'utf-16') + apply_text_edits({ + { 1, 0, 2, 5, 'foobar' }, + { 4, 0, 5, 0, 'barfoo' }, + }) eq({ 'First line of text', 'foobar line of text', 'Fourth line of text', 'barfoo', }, buf_lines(1)) - local mark = api.nvim_buf_get_mark(1, 'a') - eq({ 2, 1 }, mark) + eq({ 2, 1 }, api.nvim_buf_get_mark(1, 'a')) end) it('it restores marks to last valid col', function() - local edits = { - make_edit(1, 0, 2, 15, 'foobar'), - make_edit(4, 0, 5, 0, 'barfoo'), - } eq(true, api.nvim_buf_set_mark(1, 'a', 2, 10, {})) - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, 'utf-16') + apply_text_edits({ + { 1, 0, 2, 15, 'foobar' }, + { 4, 0, 5, 0, 'barfoo' }, + }) eq({ 'First line of text', 'foobarext', 'Fourth line of text', 'barfoo', }, buf_lines(1)) - local mark = api.nvim_buf_get_mark(1, 'a') - eq({ 2, 9 }, mark) + eq({ 2, 9 }, api.nvim_buf_get_mark(1, 'a')) end) it('it restores marks to last valid line', function() - local edits = { - make_edit(1, 0, 4, 5, 'foobar'), - make_edit(4, 0, 5, 0, 'barfoo'), - } eq(true, api.nvim_buf_set_mark(1, 'a', 4, 1, {})) - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, 'utf-16') + apply_text_edits({ + { 1, 0, 4, 5, 'foobar' }, + { 4, 0, 5, 0, 'barfoo' }, + }) eq({ 'First line of text', 'foobaro', }, buf_lines(1)) - local mark = api.nvim_buf_get_mark(1, 'a') - eq({ 2, 1 }, mark) + eq({ 2, 1 }, api.nvim_buf_get_mark(1, 'a')) end) describe('cursor position', function() it("don't fix the cursor if the range contains the cursor", function() api.nvim_win_set_cursor(0, { 2, 6 }) - local edits = { - make_edit(1, 0, 1, 19, 'Second line of text'), - } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, 'utf-16') + apply_text_edits({ + { 1, 0, 1, 19, 'Second line of text' }, + }) eq({ 'First line of text', 'Second line of text', @@ -1905,11 +2117,10 @@ describe('LSP', function() it('fix the cursor to the valid col if the content was removed', function() api.nvim_win_set_cursor(0, { 2, 6 }) - local edits = { - make_edit(1, 0, 1, 6, ''), - make_edit(1, 6, 1, 19, ''), - } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, 'utf-16') + apply_text_edits({ + { 1, 0, 1, 6, '' }, + { 1, 6, 1, 19, '' }, + }) eq({ 'First line of text', '', @@ -1922,11 +2133,10 @@ describe('LSP', function() it('fix the cursor to the valid row if the content was removed', function() api.nvim_win_set_cursor(0, { 2, 6 }) - local edits = { - make_edit(1, 0, 1, 6, ''), - make_edit(0, 18, 5, 0, ''), - } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, 'utf-16') + apply_text_edits({ + { 1, 0, 1, 6, '' }, + { 0, 18, 5, 0, '' }, + }) eq({ 'First line of text', }, buf_lines(1)) @@ -1935,10 +2145,9 @@ describe('LSP', function() it('fix the cursor row', function() api.nvim_win_set_cursor(0, { 3, 0 }) - local edits = { - make_edit(1, 0, 2, 0, ''), - } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, 'utf-16') + apply_text_edits({ + { 1, 0, 2, 0, '' }, + }) eq({ 'First line of text', 'Third line of text', @@ -1953,10 +2162,9 @@ describe('LSP', function() api.nvim_buf_set_lines(1, -1, -1, true, { '' }) api.nvim_win_set_cursor(0, { 2, 11 }) - local edits = { - make_edit(1, 7, 1, 11, ''), - } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, 'utf-16') + apply_text_edits({ + { 1, 7, 1, 11, '' }, + }) eq({ 'First line of text', 'Second of text', @@ -1970,10 +2178,9 @@ describe('LSP', function() it('fix the cursor row and col', function() api.nvim_win_set_cursor(0, { 2, 12 }) - local edits = { - make_edit(0, 11, 1, 12, ''), - } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, 'utf-16') + apply_text_edits({ + { 0, 11, 1, 12, '' }, + }) eq({ 'First line of text', 'Third line of text', @@ -1986,24 +2193,23 @@ describe('LSP', function() describe('with LSP end line after what Vim considers to be the end line', function() it('applies edits when the last linebreak is considered a new line', function() - local edits = { - make_edit(0, 0, 5, 0, { 'All replaced' }), - } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, 'utf-16') + apply_text_edits({ + { 0, 0, 5, 0, { 'All replaced' } }, + }) eq({ 'All replaced' }, buf_lines(1)) end) + it("applies edits when the end line is 2 larger than vim's", function() - local edits = { - make_edit(0, 0, 6, 0, { 'All replaced' }), - } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, 'utf-16') + apply_text_edits({ + { 0, 0, 6, 0, { 'All replaced' } }, + }) eq({ 'All replaced' }, buf_lines(1)) end) + it('applies edits with a column offset', function() - local edits = { - make_edit(0, 0, 5, 2, { 'All replaced' }), - } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, 'utf-16') + apply_text_edits({ + { 0, 0, 5, 2, { 'All replaced' } }, + }) eq({ 'All replaced' }, buf_lines(1)) end) end) @@ -2015,38 +2221,38 @@ describe('LSP', function() Test line one Test line two 21 char]])) end) + describe('with LSP end column out of bounds and start column at 0', function() it('applies edits at the end of the buffer', function() - local edits = { - make_edit(0, 0, 1, 22, { '#include "whatever.h"\r\n#include <algorithm>\r' }), - } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, 'utf-8') + apply_text_edits({ + { 0, 0, 1, 22, { '#include "whatever.h"\r\n#include <algorithm>\r' } }, + }, 'utf-8') eq({ '#include "whatever.h"', '#include <algorithm>' }, buf_lines(1)) end) + it('applies edits in the middle of the buffer', function() - local edits = { - make_edit(0, 0, 0, 22, { '#include "whatever.h"\r\n#include <algorithm>\r' }), - } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, 'utf-8') + apply_text_edits({ + { 0, 0, 0, 22, { '#include "whatever.h"\r\n#include <algorithm>\r' } }, + }, 'utf-8') eq( { '#include "whatever.h"', '#include <algorithm>', 'Test line two 21 char' }, buf_lines(1) ) end) end) + describe('with LSP end column out of bounds and start column NOT at 0', function() it('applies edits at the end of the buffer', function() - local edits = { - make_edit(0, 2, 1, 22, { '#include "whatever.h"\r\n#include <algorithm>\r' }), - } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, 'utf-8') + apply_text_edits({ + { 0, 2, 1, 22, { '#include "whatever.h"\r\n#include <algorithm>\r' } }, + }, 'utf-8') eq({ 'Te#include "whatever.h"', '#include <algorithm>' }, buf_lines(1)) end) + it('applies edits in the middle of the buffer', function() - local edits = { - make_edit(0, 2, 0, 22, { '#include "whatever.h"\r\n#include <algorithm>\r' }), - } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, 'utf-8') + apply_text_edits({ + { 0, 2, 0, 22, { '#include "whatever.h"\r\n#include <algorithm>\r' } }, + }, 'utf-8') eq( { 'Te#include "whatever.h"', '#include <algorithm>', 'Test line two 21 char' }, buf_lines(1) @@ -2057,6 +2263,7 @@ describe('LSP', function() describe('apply_text_document_edit', function() local target_bufnr --- @type integer + local text_document_edit = function(editVersion) return { edits = { @@ -2068,50 +2275,43 @@ describe('LSP', function() }, } end + before_each(function() - target_bufnr = exec_lua [[ - local bufnr = vim.uri_to_bufnr("file:///fake/uri") - local lines = {"1st line of text", "2nd line of 语text"} + target_bufnr = exec_lua(function() + local bufnr = vim.uri_to_bufnr('file:///fake/uri') + local lines = { '1st line of text', '2nd line of 语text' } vim.api.nvim_buf_set_lines(bufnr, 0, 1, false, lines) return bufnr - ]] + end) end) + it('correctly goes ahead with the edit if all is normal', function() - exec_lua("vim.lsp.util.apply_text_document_edit(..., nil, 'utf-16')", text_document_edit(5)) + exec_lua(function(text_edit) + vim.lsp.util.apply_text_document_edit(text_edit, nil, 'utf-16') + end, text_document_edit(5)) eq({ 'First ↥ 🤦 🦄 line of text', '2nd line of 语text', }, buf_lines(target_bufnr)) end) + it('always accepts edit with version = 0', function() - exec_lua( - [[ - local args = {...} - local bufnr = select(1, ...) - local text_edit = select(2, ...) - vim.lsp.util.buf_versions[bufnr] = 10 + exec_lua(function(text_edit) + vim.lsp.util.buf_versions[target_bufnr] = 10 vim.lsp.util.apply_text_document_edit(text_edit, nil, 'utf-16') - ]], - target_bufnr, - text_document_edit(0) - ) + end, text_document_edit(0)) eq({ 'First ↥ 🤦 🦄 line of text', '2nd line of 语text', }, buf_lines(target_bufnr)) end) + it('skips the edit if the version of the edit is behind the local buffer ', function() local apply_edit_mocking_current_version = function(edit, versionedBuf) - exec_lua( - [[ - local args = {...} - local versionedBuf = args[2] + exec_lua(function() vim.lsp.util.buf_versions[versionedBuf.bufnr] = versionedBuf.currentVersion - vim.lsp.util.apply_text_document_edit(args[1], nil, 'utf-16') - ]], - edit, - versionedBuf - ) + vim.lsp.util.apply_text_document_edit(edit, nil, 'utf-16') + end) end local baseText = { @@ -2164,13 +2364,17 @@ describe('LSP', function() } eq( expected, - exec_lua [[ - local apply_edit = { - label = nil; - edit = {}; - } - return vim.lsp.handlers['workspace/applyEdit'](nil, apply_edit, {client_id = TEST_RPC_CLIENT_ID}) - ]] + exec_lua(function() + local apply_edit = { + label = nil, + edit = {}, + } + return vim.lsp.handlers['workspace/applyEdit']( + nil, + apply_edit, + { client_id = _G.TEST_RPC_CLIENT_ID } + ) + end) ) eq(table.remove(expected_handlers), { ... }) end, @@ -2200,34 +2404,30 @@ describe('LSP', function() } end - local target_bufnr, changedtick = nil, nil + local target_bufnr --- @type integer + local changedtick --- @type integer before_each(function() - local ret = exec_lua [[ - local bufnr = vim.uri_to_bufnr("file:///fake/uri") + exec_lua(function() + target_bufnr = vim.uri_to_bufnr('file:///fake/uri') local lines = { - "Original Line #1", - "Original Line #2" + 'Original Line #1', + 'Original Line #2', } - vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines) + vim.api.nvim_buf_set_lines(target_bufnr, 0, -1, false, lines) - local update_changed_tick = function() - vim.lsp.util.buf_versions[bufnr] = vim.api.nvim_buf_get_var(bufnr, 'changedtick') + local function update_changed_tick() + vim.lsp.util.buf_versions[target_bufnr] = vim.b[target_bufnr].changedtick end update_changed_tick() - vim.api.nvim_buf_attach(bufnr, false, { - on_changedtick = function() - update_changed_tick() - end + vim.api.nvim_buf_attach(target_bufnr, false, { + on_changedtick = update_changed_tick, }) - return {bufnr, vim.api.nvim_buf_get_var(bufnr, 'changedtick')} - ]] - - target_bufnr = ret[1] - changedtick = ret[2] + changedtick = vim.b[target_bufnr].changedtick + end) end) it('apply_workspace_edit applies a single edit', function() @@ -2245,19 +2445,11 @@ describe('LSP', function() 'First Line', 'Original Line #2', }, - exec_lua( - [[ - local args = {...} - local workspace_edits = args[1] - local target_bufnr = args[2] - - vim.lsp.util.apply_workspace_edit(workspace_edits, 'utf-16') + exec_lua(function(workspace_edits) + vim.lsp.util.apply_workspace_edit(workspace_edits, 'utf-16') - return vim.api.nvim_buf_get_lines(target_bufnr, 0, -1, false) - ]], - make_workspace_edit(edits), - target_bufnr - ) + return vim.api.nvim_buf_get_lines(target_bufnr, 0, -1, false) + end, make_workspace_edit(edits)) ) end) @@ -2274,24 +2466,15 @@ describe('LSP', function() eq( new_lines, - exec_lua( - [[ - local args = {...} - local workspace_edits = args[1] - local target_bufnr = args[2] - - vim.lsp.util.apply_workspace_edit(workspace_edits, 'utf-16') - - return vim.api.nvim_buf_get_lines(target_bufnr, 0, -1, false) - ]], - make_workspace_edit(edits), - target_bufnr - ) + exec_lua(function(workspace_edits) + vim.lsp.util.apply_workspace_edit(workspace_edits, 'utf-16') + return vim.api.nvim_buf_get_lines(target_bufnr, 0, -1, false) + end, make_workspace_edit(edits)) ) end) + it('Supports file creation with CreateFile payload', function() - local tmpfile = tmpname() - os.remove(tmpfile) -- Should not exist, only interested in a tmpname + local tmpfile = tmpname(false) local uri = exec_lua('return vim.uri_from_fname(...)', tmpfile) local edit = { documentChanges = { @@ -2301,15 +2484,16 @@ describe('LSP', function() }, }, } - exec_lua('vim.lsp.util.apply_workspace_edit(...)', edit, 'utf-16') + exec_lua(function() + vim.lsp.util.apply_workspace_edit(edit, 'utf-16') + end) eq(true, vim.uv.fs_stat(tmpfile) ~= nil) end) + it( 'Supports file creation in folder that needs to be created with CreateFile payload', function() - local tmpfile = tmpname() - os.remove(tmpfile) -- Should not exist, only interested in a tmpname - tmpfile = tmpfile .. '/dummy/x/' + local tmpfile = tmpname(false) .. '/dummy/x/' local uri = exec_lua('return vim.uri_from_fname(...)', tmpfile) local edit = { documentChanges = { @@ -2319,10 +2503,13 @@ describe('LSP', function() }, }, } - exec_lua('vim.lsp.util.apply_workspace_edit(...)', edit, 'utf-16') + exec_lua(function() + vim.lsp.util.apply_workspace_edit(edit, 'utf-16') + end) eq(true, vim.uv.fs_stat(tmpfile) ~= nil) end ) + it('createFile does not touch file if it exists and ignoreIfExists is set', function() local tmpfile = tmpname() write_file(tmpfile, 'Dummy content') @@ -2342,6 +2529,7 @@ describe('LSP', function() eq(true, vim.uv.fs_stat(tmpfile) ~= nil) eq('Dummy content', read_file(tmpfile)) end) + it('createFile overrides file if overwrite is set', function() local tmpfile = tmpname() write_file(tmpfile, 'Dummy content') @@ -2362,18 +2550,15 @@ describe('LSP', function() eq(true, vim.uv.fs_stat(tmpfile) ~= nil) eq('', read_file(tmpfile)) end) + it('DeleteFile delete file and buffer', function() local tmpfile = tmpname() write_file(tmpfile, 'Be gone') - local uri = exec_lua( - [[ - local fname = select(1, ...) - local bufnr = vim.fn.bufadd(fname) + local uri = exec_lua(function() + local bufnr = vim.fn.bufadd(tmpfile) vim.fn.bufload(bufnr) - return vim.uri_from_fname(fname) - ]], - tmpfile - ) + return vim.uri_from_fname(tmpfile) + end) local edit = { documentChanges = { { @@ -2386,9 +2571,9 @@ describe('LSP', function() eq(false, vim.uv.fs_stat(tmpfile) ~= nil) eq(false, api.nvim_buf_is_loaded(fn.bufadd(tmpfile))) end) + it('DeleteFile fails if file does not exist and ignoreIfNotExists is false', function() - local tmpfile = tmpname() - os.remove(tmpfile) + local tmpfile = tmpname(false) local uri = exec_lua('return vim.uri_from_fname(...)', tmpfile) local edit = { documentChanges = { @@ -2412,12 +2597,8 @@ describe('LSP', function() it('Can rename an existing file', function() local old = tmpname() write_file(old, 'Test content') - local new = tmpname() - os.remove(new) -- only reserve the name, file must not exist for the test scenario - local lines = exec_lua( - [[ - local old = select(1, ...) - local new = select(2, ...) + local new = tmpname(false) + local lines = exec_lua(function() local old_bufnr = vim.fn.bufadd(old) vim.fn.bufload(old_bufnr) vim.lsp.util.rename(old, new) @@ -2425,10 +2606,7 @@ describe('LSP', function() local new_bufnr = vim.fn.bufadd(new) vim.fn.bufload(new_bufnr) return (old_bufnr == new_bufnr) and vim.api.nvim_buf_get_lines(new_bufnr, 0, -1, true) - ]], - old, - new - ) + end) eq({ 'Test content' }, lines) local exists = vim.uv.fs_stat(old) ~= nil eq(false, exists) @@ -2436,24 +2614,18 @@ describe('LSP', function() eq(true, exists) os.remove(new) end) + it('Can rename a directory', function() -- only reserve the name, file must not exist for the test scenario - local old_dir = tmpname() - local new_dir = tmpname() - os.remove(old_dir) - os.remove(new_dir) + local old_dir = tmpname(false) + local new_dir = tmpname(false) n.mkdir_p(old_dir) local file = 'file.txt' write_file(old_dir .. pathsep .. file, 'Test content') - local lines = exec_lua( - [[ - local old_dir = select(1, ...) - local new_dir = select(2, ...) - local pathsep = select(3, ...) - local file = select(4, ...) + local lines = exec_lua(function() local old_bufnr = vim.fn.bufadd(old_dir .. pathsep .. file) vim.fn.bufload(old_bufnr) vim.lsp.util.rename(old_dir, new_dir) @@ -2461,12 +2633,7 @@ describe('LSP', function() local new_bufnr = vim.fn.bufadd(new_dir .. pathsep .. file) vim.fn.bufload(new_bufnr) return (old_bufnr == new_bufnr) and vim.api.nvim_buf_get_lines(new_bufnr, 0, -1, true) - ]], - old_dir, - new_dir, - pathsep, - file - ) + end) eq({ 'Test content' }, lines) eq(false, vim.uv.fs_stat(old_dir) ~= nil) eq(true, vim.uv.fs_stat(new_dir) ~= nil) @@ -2474,47 +2641,41 @@ describe('LSP', function() os.remove(new_dir) end) + it('Does not touch buffers that do not match path prefix', function() - local old = tmpname() - local new = tmpname() - os.remove(old) - os.remove(new) + local old = tmpname(false) + local new = tmpname(false) n.mkdir_p(old) - local result = exec_lua( - [[ - local old = select(1, ...) - local new = select(2, ...) - - local old_prefixed = 'explorer://' .. old - local old_suffixed = old .. '.bak' - local new_prefixed = 'explorer://' .. new - local new_suffixed = new .. '.bak' + eq( + true, + exec_lua(function() + local old_prefixed = 'explorer://' .. old + local old_suffixed = old .. '.bak' + local new_prefixed = 'explorer://' .. new + local new_suffixed = new .. '.bak' - local old_prefixed_buf = vim.fn.bufadd(old_prefixed) - local old_suffixed_buf = vim.fn.bufadd(old_suffixed) - local new_prefixed_buf = vim.fn.bufadd(new_prefixed) - local new_suffixed_buf = vim.fn.bufadd(new_suffixed) + local old_prefixed_buf = vim.fn.bufadd(old_prefixed) + local old_suffixed_buf = vim.fn.bufadd(old_suffixed) + local new_prefixed_buf = vim.fn.bufadd(new_prefixed) + local new_suffixed_buf = vim.fn.bufadd(new_suffixed) - vim.lsp.util.rename(old, new) + vim.lsp.util.rename(old, new) - return - vim.api.nvim_buf_is_valid(old_prefixed_buf) and - vim.api.nvim_buf_is_valid(old_suffixed_buf) and - vim.api.nvim_buf_is_valid(new_prefixed_buf) and - vim.api.nvim_buf_is_valid(new_suffixed_buf) and - vim.api.nvim_buf_get_name(old_prefixed_buf) == old_prefixed and - vim.api.nvim_buf_get_name(old_suffixed_buf) == old_suffixed and - vim.api.nvim_buf_get_name(new_prefixed_buf) == new_prefixed and - vim.api.nvim_buf_get_name(new_suffixed_buf) == new_suffixed - ]], - old, - new + return vim.api.nvim_buf_is_valid(old_prefixed_buf) + and vim.api.nvim_buf_is_valid(old_suffixed_buf) + and vim.api.nvim_buf_is_valid(new_prefixed_buf) + and vim.api.nvim_buf_is_valid(new_suffixed_buf) + and vim.api.nvim_buf_get_name(old_prefixed_buf) == old_prefixed + and vim.api.nvim_buf_get_name(old_suffixed_buf) == old_suffixed + and vim.api.nvim_buf_get_name(new_prefixed_buf) == new_prefixed + and vim.api.nvim_buf_get_name(new_suffixed_buf) == new_suffixed + end) ) - eq(true, result) os.remove(new) end) + it( 'Does not rename file if target exists and ignoreIfExists is set or overwrite is false', function() @@ -2523,45 +2684,28 @@ describe('LSP', function() local new = tmpname() write_file(new, 'New file') - exec_lua( - [[ - local old = select(1, ...) - local new = select(2, ...) - - vim.lsp.util.rename(old, new, { ignoreIfExists = true }) - ]], - old, - new - ) + exec_lua(function() + vim.lsp.util.rename(old, new, { ignoreIfExists = true }) + end) eq(true, vim.uv.fs_stat(old) ~= nil) eq('New file', read_file(new)) - exec_lua( - [[ - local old = select(1, ...) - local new = select(2, ...) - - vim.lsp.util.rename(old, new, { overwrite = false }) - ]], - old, - new - ) + exec_lua(function() + vim.lsp.util.rename(old, new, { overwrite = false }) + end) eq(true, vim.uv.fs_stat(old) ~= nil) eq('New file', read_file(new)) end ) + it('Maintains undo information for loaded buffer', function() local old = tmpname() write_file(old, 'line') - local new = tmpname() - os.remove(new) + local new = tmpname(false) - local undo_kept = exec_lua( - [[ - local old = select(1, ...) - local new = select(2, ...) + local undo_kept = exec_lua(function() vim.opt.undofile = true vim.cmd.edit(old) vim.cmd.normal('dd') @@ -2574,24 +2718,18 @@ describe('LSP', function() undotree.save_last = undotree.save_last + 1 undotree.entries[1].save = undotree.entries[1].save + 1 return vim.deep_equal(undotree, vim.fn.undotree()) - ]], - old, - new - ) + end) eq(false, vim.uv.fs_stat(old) ~= nil) eq(true, vim.uv.fs_stat(new) ~= nil) eq(true, undo_kept) end) + it('Maintains undo information for unloaded buffer', function() local old = tmpname() write_file(old, 'line') - local new = tmpname() - os.remove(new) + local new = tmpname(false) - local undo_kept = exec_lua( - [[ - local old = select(1, ...) - local new = select(2, ...) + local undo_kept = exec_lua(function() vim.opt.undofile = true vim.cmd.split(old) vim.cmd.normal('dd') @@ -2601,54 +2739,39 @@ describe('LSP', function() vim.lsp.util.rename(old, new) vim.cmd.edit(new) return vim.deep_equal(undotree, vim.fn.undotree()) - ]], - old, - new - ) + end) eq(false, vim.uv.fs_stat(old) ~= nil) eq(true, vim.uv.fs_stat(new) ~= nil) eq(true, undo_kept) end) + it('Does not rename file when it conflicts with a buffer without file', function() local old = tmpname() write_file(old, 'Old File') - local new = tmpname() - os.remove(new) - - local lines = exec_lua( - [[ - local old = select(1, ...) - local new = select(2, ...) - local old_buf = vim.fn.bufadd(old) - vim.fn.bufload(old_buf) - local conflict_buf = vim.api.nvim_create_buf(true, false) - vim.api.nvim_buf_set_name(conflict_buf, new) - vim.api.nvim_buf_set_lines(conflict_buf, 0, -1, true, {'conflict'}) - vim.api.nvim_win_set_buf(0, conflict_buf) - vim.lsp.util.rename(old, new) - return vim.api.nvim_buf_get_lines(conflict_buf, 0, -1, true) - ]], - old, - new - ) + local new = tmpname(false) + + local lines = exec_lua(function() + local old_buf = vim.fn.bufadd(old) + vim.fn.bufload(old_buf) + local conflict_buf = vim.api.nvim_create_buf(true, false) + vim.api.nvim_buf_set_name(conflict_buf, new) + vim.api.nvim_buf_set_lines(conflict_buf, 0, -1, true, { 'conflict' }) + vim.api.nvim_win_set_buf(0, conflict_buf) + vim.lsp.util.rename(old, new) + return vim.api.nvim_buf_get_lines(conflict_buf, 0, -1, true) + end) eq({ 'conflict' }, lines) eq('Old File', read_file(old)) end) + it('Does override target if overwrite is true', function() local old = tmpname() write_file(old, 'Old file') local new = tmpname() write_file(new, 'New file') - exec_lua( - [[ - local old = select(1, ...) - local new = select(2, ...) - + exec_lua(function() vim.lsp.util.rename(old, new, { overwrite = true }) - ]], - old, - new - ) + end) eq(false, vim.uv.fs_stat(old) ~= nil) eq(true, vim.uv.fs_stat(new) ~= nil) @@ -2658,44 +2781,59 @@ describe('LSP', function() describe('lsp.util.locations_to_items', function() it('Convert Location[] to items', function() - local expected = { + local expected_template = { { filename = '/fake/uri', lnum = 1, + end_lnum = 2, col = 3, + end_col = 4, text = 'testing', - user_data = { + user_data = {}, + }, + } + local test_params = { + { + { uri = 'file:///fake/uri', range = { start = { line = 0, character = 2 }, - ['end'] = { line = 0, character = 3 }, + ['end'] = { line = 1, character = 3 }, }, }, }, - } - local actual = exec_lua [[ - local bufnr = vim.uri_to_bufnr("file:///fake/uri") - local lines = {"testing", "123"} - vim.api.nvim_buf_set_lines(bufnr, 0, 1, false, lines) - local locations = { + { { uri = 'file:///fake/uri', range = { start = { line = 0, character = 2 }, - ['end'] = { line = 0, character = 3 }, - } + -- LSP spec: if character > line length, default to the line length. + ['end'] = { line = 1, character = 10000 }, + }, }, - } - return vim.lsp.util.locations_to_items(locations, 'utf-16') - ]] - eq(expected, actual) + }, + } + for _, params in ipairs(test_params) do + local actual = exec_lua(function(params0) + local bufnr = vim.uri_to_bufnr('file:///fake/uri') + local lines = { 'testing', '123' } + vim.api.nvim_buf_set_lines(bufnr, 0, 1, false, lines) + return vim.lsp.util.locations_to_items(params0, 'utf-16') + end, params) + local expected = vim.deepcopy(expected_template) + expected[1].user_data = params[1] + eq(expected, actual) + end end) + it('Convert LocationLink[] to items', function() local expected = { { filename = '/fake/uri', lnum = 1, + end_lnum = 1, col = 3, + end_col = 4, text = 'testing', user_data = { targetUri = 'file:///fake/uri', @@ -2710,9 +2848,9 @@ describe('LSP', function() }, }, } - local actual = exec_lua [[ - local bufnr = vim.uri_to_bufnr("file:///fake/uri") - local lines = {"testing", "123"} + local actual = exec_lua(function() + local bufnr = vim.uri_to_bufnr('file:///fake/uri') + local lines = { 'testing', '123' } vim.api.nvim_buf_set_lines(bufnr, 0, 1, false, lines) local locations = { { @@ -2724,11 +2862,11 @@ describe('LSP', function() targetSelectionRange = { start = { line = 0, character = 2 }, ['end'] = { line = 0, character = 3 }, - } + }, }, } return vim.lsp.util.locations_to_items(locations, 'utf-16') - ]] + end) eq(expected, actual) end) end) @@ -2761,94 +2899,95 @@ describe('LSP', function() } eq( expected, - exec_lua [[ - local doc_syms = { - { - deprecated = false, - detail = "A", - kind = 1, - name = "TestA", - range = { - start = { - character = 0, - line = 1 + exec_lua(function() + local doc_syms = { + { + deprecated = false, + detail = 'A', + kind = 1, + name = 'TestA', + range = { + start = { + character = 0, + line = 1, + }, + ['end'] = { + character = 0, + line = 2, + }, }, - ["end"] = { - character = 0, - line = 2 - } - }, - selectionRange = { - start = { - character = 0, - line = 1 + selectionRange = { + start = { + character = 0, + line = 1, + }, + ['end'] = { + character = 4, + line = 1, + }, }, - ["end"] = { - character = 4, - line = 1 - } - }, - children = { - { - children = {}, - deprecated = false, - detail = "B", - kind = 2, - name = "TestB", - range = { - start = { - character = 0, - line = 3 + children = { + { + children = {}, + deprecated = false, + detail = 'B', + kind = 2, + name = 'TestB', + range = { + start = { + character = 0, + line = 3, + }, + ['end'] = { + character = 0, + line = 4, + }, }, - ["end"] = { - character = 0, - line = 4 - } - }, - selectionRange = { - start = { - character = 0, - line = 3 + selectionRange = { + start = { + character = 0, + line = 3, + }, + ['end'] = { + character = 4, + line = 3, + }, }, - ["end"] = { - character = 4, - line = 3 - } - } - } - } - }, - { - deprecated = false, - detail = "C", - kind = 3, - name = "TestC", - range = { - start = { - character = 0, - line = 5 + }, }, - ["end"] = { - character = 0, - line = 6 - } }, - selectionRange = { - start = { - character = 0, - line = 5 + { + deprecated = false, + detail = 'C', + kind = 3, + name = 'TestC', + range = { + start = { + character = 0, + line = 5, + }, + ['end'] = { + character = 0, + line = 6, + }, }, - ["end"] = { - character = 4, - line = 5 - } - } + selectionRange = { + start = { + character = 0, + line = 5, + }, + ['end'] = { + character = 4, + line = 5, + }, + }, + }, } - } - return vim.lsp.util.symbols_to_items(doc_syms, nil) - ]] + return vim.lsp.util.symbols_to_items(doc_syms, nil) + end) ) end) + it('DocumentSymbol has no children', function() local expected = { { @@ -2868,66 +3007,67 @@ describe('LSP', function() } eq( expected, - exec_lua [[ - local doc_syms = { - { - deprecated = false, - detail = "A", - kind = 1, - name = "TestA", - range = { - start = { - character = 0, - line = 1 + exec_lua(function() + local doc_syms = { + { + deprecated = false, + detail = 'A', + kind = 1, + name = 'TestA', + range = { + start = { + character = 0, + line = 1, + }, + ['end'] = { + character = 0, + line = 2, + }, }, - ["end"] = { - character = 0, - line = 2 - } - }, - selectionRange = { - start = { - character = 0, - line = 1 + selectionRange = { + start = { + character = 0, + line = 1, + }, + ['end'] = { + character = 4, + line = 1, + }, }, - ["end"] = { - character = 4, - line = 1 - } }, - }, - { - deprecated = false, - detail = "C", - kind = 3, - name = "TestC", - range = { - start = { - character = 0, - line = 5 + { + deprecated = false, + detail = 'C', + kind = 3, + name = 'TestC', + range = { + start = { + character = 0, + line = 5, + }, + ['end'] = { + character = 0, + line = 6, + }, }, - ["end"] = { - character = 0, - line = 6 - } - }, - selectionRange = { - start = { - character = 0, - line = 5 + selectionRange = { + start = { + character = 0, + line = 5, + }, + ['end'] = { + character = 4, + line = 5, + }, }, - ["end"] = { - character = 4, - line = 5 - } - } + }, } - } - return vim.lsp.util.symbols_to_items(doc_syms, nil) - ]] + return vim.lsp.util.symbols_to_items(doc_syms, nil) + end) ) end) end) + it('convert SymbolInformation[] to items', function() local expected = { { @@ -2947,62 +3087,88 @@ describe('LSP', function() } eq( expected, - exec_lua [[ + exec_lua(function() local sym_info = { { deprecated = false, kind = 1, - name = "TestA", + name = 'TestA', location = { range = { start = { character = 0, - line = 1 + line = 1, }, - ["end"] = { + ['end'] = { character = 0, - line = 2 - } + line = 2, + }, }, - uri = "file:///test_a" + uri = 'file:///test_a', }, - containerName = "TestAContainer" + containerName = 'TestAContainer', }, { deprecated = false, kind = 2, - name = "TestB", + name = 'TestB', location = { range = { start = { character = 0, - line = 3 + line = 3, }, - ["end"] = { + ['end'] = { character = 0, - line = 4 - } + line = 4, + }, }, - uri = "file:///test_b" + uri = 'file:///test_b', }, - containerName = "TestBContainer" - } + containerName = 'TestBContainer', + }, } return vim.lsp.util.symbols_to_items(sym_info, nil) - ]] + end) ) end) end) describe('lsp.util._get_symbol_kind_name', function() it('returns the name specified by protocol', function() - eq('File', exec_lua('return vim.lsp.util._get_symbol_kind_name(1)')) - eq('TypeParameter', exec_lua('return vim.lsp.util._get_symbol_kind_name(26)')) + eq( + 'File', + exec_lua(function() + return vim.lsp.util._get_symbol_kind_name(1) + end) + ) + eq( + 'TypeParameter', + exec_lua(function() + return vim.lsp.util._get_symbol_kind_name(26) + end) + ) end) + it('returns the name not specified by protocol', function() - eq('Unknown', exec_lua('return vim.lsp.util._get_symbol_kind_name(nil)')) - eq('Unknown', exec_lua('return vim.lsp.util._get_symbol_kind_name(vim.NIL)')) - eq('Unknown', exec_lua('return vim.lsp.util._get_symbol_kind_name(1000)')) + eq( + 'Unknown', + exec_lua(function() + return vim.lsp.util._get_symbol_kind_name(nil) + end) + ) + eq( + 'Unknown', + exec_lua(function() + return vim.lsp.util._get_symbol_kind_name(vim.NIL) + end) + ) + eq( + 'Unknown', + exec_lua(function() + return vim.lsp.util._get_symbol_kind_name(1000) + end) + ) end) end) @@ -3010,12 +3176,12 @@ describe('LSP', function() local target_bufnr --- @type integer before_each(function() - target_bufnr = exec_lua [[ - local bufnr = vim.uri_to_bufnr("file:///fake/uri") - local lines = {"1st line of text", "å å ɧ 汉语 ↥ 🤦 🦄"} + target_bufnr = exec_lua(function() + local bufnr = vim.uri_to_bufnr('file:///fake/uri') + local lines = { '1st line of text', 'å å ɧ 汉语 ↥ 🤦 🦄' } vim.api.nvim_buf_set_lines(bufnr, 0, 1, false, lines) return bufnr - ]] + end) end) local location = function(start_line, start_char, end_line, end_char) @@ -3084,19 +3250,19 @@ describe('LSP', function() local target_bufnr2 --- @type integer before_each(function() - target_bufnr = exec_lua([[ - local bufnr = vim.uri_to_bufnr("file:///fake/uri") - local lines = {"1st line of text", "å å ɧ 汉语 ↥ 🤦 🦄"} + target_bufnr = exec_lua(function() + local bufnr = vim.uri_to_bufnr('file:///fake/uri') + local lines = { '1st line of text', 'å å ɧ 汉语 ↥ 🤦 🦄' } vim.api.nvim_buf_set_lines(bufnr, 0, 1, false, lines) return bufnr - ]]) + end) - target_bufnr2 = exec_lua([[ - local bufnr = vim.uri_to_bufnr("file:///fake/uri2") - local lines = {"1st line of text", "å å ɧ 汉语 ↥ 🤦 🦄"} + target_bufnr2 = exec_lua(function() + local bufnr = vim.uri_to_bufnr('file:///fake/uri2') + local lines = { '1st line of text', 'å å ɧ 汉语 ↥ 🤦 🦄' } vim.api.nvim_buf_set_lines(bufnr, 0, 1, false, lines) return bufnr - ]]) + end) end) local location = function(start_line, start_char, end_line, end_char, second_uri) @@ -3136,14 +3302,14 @@ describe('LSP', function() it('jumps to a Location if focus is true via handler', function() exec_lua(create_server_definition) - local result = exec_lua([[ - local server = _create_server() - local client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd }) + local result = exec_lua(function() + local server = _G._create_server() + local client_id = assert(vim.lsp.start({ name = 'dummy', cmd = server.cmd })) local result = { uri = 'file:///fake/uri', selection = { start = { line = 0, character = 9 }, - ['end'] = { line = 0, character = 9 } + ['end'] = { line = 0, character = 9 }, }, takeFocus = true, } @@ -3154,9 +3320,9 @@ describe('LSP', function() vim.lsp.handlers['window/showDocument'](nil, result, ctx) vim.lsp.stop_client(client_id) return { - cursor = vim.api.nvim_win_get_cursor(0) + cursor = vim.api.nvim_win_get_cursor(0), } - ]]) + end) eq(1, result.cursor[1]) eq(9, result.cursor[2]) end) @@ -3231,7 +3397,9 @@ describe('LSP', function() api.nvim_win_set_buf(0, target_bufnr) api.nvim_win_set_cursor(0, { 2, 3 }) - exec_lua([[vim.cmd.new()]]) + exec_lua(function() + vim.cmd.new() + end) api.nvim_win_set_buf(0, target_bufnr2) api.nvim_win_set_cursor(0, { 2, 3 }) @@ -3247,7 +3415,9 @@ describe('LSP', function() api.nvim_win_set_buf(0, target_bufnr) local win = api.nvim_get_current_win() - exec_lua([[vim.cmd.new()]]) + exec_lua(function() + vim.cmd.new() + end) api.nvim_win_set_buf(0, target_bufnr2) api.nvim_win_set_cursor(0, { 2, 3 }) local split = api.nvim_get_current_win() @@ -3268,34 +3438,53 @@ describe('LSP', function() describe('lsp.util._make_floating_popup_size', function() before_each(function() - exec_lua [[ contents = - {"text tαxt txtα tex", - "text tααt tααt text", - "text tαxt tαxt"} - ]] + exec_lua(function() + _G.contents = { 'text tαxt txtα tex', 'text tααt tααt text', 'text tαxt tαxt' } + end) end) it('calculates size correctly', function() - eq({ 19, 3 }, exec_lua [[ return {vim.lsp.util._make_floating_popup_size(contents)} ]]) + eq( + { 19, 3 }, + exec_lua(function() + return { vim.lsp.util._make_floating_popup_size(_G.contents) } + end) + ) end) it('calculates size correctly with wrapping', function() eq( { 15, 5 }, - exec_lua [[ return {vim.lsp.util._make_floating_popup_size(contents,{width = 15, wrap_at = 14})} ]] + exec_lua(function() + return { + vim.lsp.util._make_floating_popup_size(_G.contents, { width = 15, wrap_at = 14 }), + } + end) ) end) it('handles NUL bytes in text', function() - exec_lua([[ contents = { - '\000\001\002\003\004\005\006\007\008\009', - '\010\011\012\013\014\015\016\017\018\019', - '\020\021\022\023\024\025\026\027\028\029', - } ]]) + exec_lua(function() + _G.contents = { + '\000\001\002\003\004\005\006\007\008\009', + '\010\011\012\013\014\015\016\017\018\019', + '\020\021\022\023\024\025\026\027\028\029', + } + end) command('set list listchars=') - eq({ 20, 3 }, exec_lua [[ return {vim.lsp.util._make_floating_popup_size(contents)} ]]) + eq( + { 20, 3 }, + exec_lua(function() + return { vim.lsp.util._make_floating_popup_size(_G.contents) } + end) + ) command('set display+=uhex') - eq({ 40, 3 }, exec_lua [[ return {vim.lsp.util._make_floating_popup_size(contents)} ]]) + eq( + { 40, 3 }, + exec_lua(function() + return { vim.lsp.util._make_floating_popup_size(_G.contents) } + end) + ) end) end) @@ -3303,27 +3492,30 @@ describe('LSP', function() it('properly trims empty lines', function() eq( { { 'foo', 'bar' } }, - exec_lua [[ return vim.lsp.util.trim_empty_lines({{ "foo", "bar" }, nil}) ]] + exec_lua(function() + --- @diagnostic disable-next-line:deprecated + return vim.lsp.util.trim_empty_lines({ { 'foo', 'bar' }, nil }) + end) ) end) end) describe('lsp.util.convert_signature_help_to_markdown_lines', function() it('can handle negative activeSignature', function() - local result = exec_lua [[ + local result = exec_lua(function() local signature_help = { activeParameter = 0, activeSignature = -1, signatures = { { - documentation = "some doc", - label = "TestEntity.TestEntity()", - parameters = {} + documentation = 'some doc', + label = 'TestEntity.TestEntity()', + parameters = {}, }, - } + }, } - return vim.lsp.util.convert_signature_help_to_markdown_lines(signature_help, 'cs', {','}) - ]] + return vim.lsp.util.convert_signature_help_to_markdown_lines(signature_help, 'cs', { ',' }) + end) local expected = { '```cs', 'TestEntity.TestEntity()', '```', 'some doc' } eq(expected, result) end) @@ -3338,12 +3530,18 @@ describe('LSP', function() ]], shiftwidth )) - eq(tabsize, exec_lua('return vim.lsp.util.get_effective_tabstop()')) + eq( + tabsize, + exec_lua(function() + return vim.lsp.util.get_effective_tabstop() + end) + ) end it('with shiftwidth = 1', function() test_tabstop(1, 1) end) + it('with shiftwidth = 0', function() test_tabstop(2, 0) end) @@ -3351,57 +3549,61 @@ describe('LSP', function() describe('vim.lsp.buf.outgoing_calls', function() it('does nothing for an empty response', function() - local qflist_count = exec_lua([=[ - require'vim.lsp.handlers'['callHierarchy/outgoingCalls'](nil, nil, {}, nil) + local qflist_count = exec_lua(function() + require 'vim.lsp.handlers'['callHierarchy/outgoingCalls'](nil, nil, {}, nil) return #vim.fn.getqflist() - ]=]) + end) eq(0, qflist_count) end) it('opens the quickfix list with the right caller', function() - local qflist = exec_lua([=[ - local rust_analyzer_response = { { - fromRanges = { { - ['end'] = { - character = 7, - line = 3 - }, - start = { - character = 4, - line = 3 - } - } }, - to = { - detail = "fn foo()", - kind = 12, - name = "foo", - range = { - ['end'] = { - character = 11, - line = 0 + local qflist = exec_lua(function() + local rust_analyzer_response = { + { + fromRanges = { + { + ['end'] = { + character = 7, + line = 3, + }, + start = { + character = 4, + line = 3, + }, }, - start = { - character = 0, - line = 0 - } }, - selectionRange = { - ['end'] = { - character = 6, - line = 0 + to = { + detail = 'fn foo()', + kind = 12, + name = 'foo', + range = { + ['end'] = { + character = 11, + line = 0, + }, + start = { + character = 0, + line = 0, + }, }, - start = { - character = 3, - line = 0 - } + selectionRange = { + ['end'] = { + character = 6, + line = 0, + }, + start = { + character = 3, + line = 0, + }, + }, + uri = 'file:///src/main.rs', }, - uri = "file:///src/main.rs" - } - } } - local handler = require'vim.lsp.handlers'['callHierarchy/outgoingCalls'] + }, + } + local handler = require 'vim.lsp.handlers'['callHierarchy/outgoingCalls'] handler(nil, rust_analyzer_response, {}) return vim.fn.getqflist() - ]=]) + end) local expected = { { @@ -3426,58 +3628,62 @@ describe('LSP', function() describe('vim.lsp.buf.incoming_calls', function() it('does nothing for an empty response', function() - local qflist_count = exec_lua([=[ - require'vim.lsp.handlers'['callHierarchy/incomingCalls'](nil, nil, {}) + local qflist_count = exec_lua(function() + require 'vim.lsp.handlers'['callHierarchy/incomingCalls'](nil, nil, {}) return #vim.fn.getqflist() - ]=]) + end) eq(0, qflist_count) end) it('opens the quickfix list with the right callee', function() - local qflist = exec_lua([=[ - local rust_analyzer_response = { { - from = { - detail = "fn main()", - kind = 12, - name = "main", - range = { - ['end'] = { - character = 1, - line = 4 + local qflist = exec_lua(function() + local rust_analyzer_response = { + { + from = { + detail = 'fn main()', + kind = 12, + name = 'main', + range = { + ['end'] = { + character = 1, + line = 4, + }, + start = { + character = 0, + line = 2, + }, }, - start = { - character = 0, - line = 2 - } + selectionRange = { + ['end'] = { + character = 7, + line = 2, + }, + start = { + character = 3, + line = 2, + }, + }, + uri = 'file:///src/main.rs', }, - selectionRange = { - ['end'] = { - character = 7, - line = 2 + fromRanges = { + { + ['end'] = { + character = 7, + line = 3, + }, + start = { + character = 4, + line = 3, + }, }, - start = { - character = 3, - line = 2 - } }, - uri = "file:///src/main.rs" }, - fromRanges = { { - ['end'] = { - character = 7, - line = 3 - }, - start = { - character = 4, - line = 3 - } - } } - } } + } - local handler = require'vim.lsp.handlers'['callHierarchy/incomingCalls'] + local handler = require 'vim.lsp.handlers'['callHierarchy/incomingCalls'] handler(nil, rust_analyzer_response, {}) return vim.fn.getqflist() - ]=]) + end) local expected = { { @@ -3502,103 +3708,126 @@ describe('LSP', function() describe('vim.lsp.buf.typehierarchy subtypes', function() it('does nothing for an empty response', function() - local qflist_count = exec_lua([=[ - require'vim.lsp.handlers'['typeHierarchy/subtypes'](nil, nil, {}) + local qflist_count = exec_lua(function() + require 'vim.lsp.handlers'['typeHierarchy/subtypes'](nil, nil, {}) return #vim.fn.getqflist() - ]=]) + end) eq(0, qflist_count) end) it('opens the quickfix list with the right subtypes', function() clear() exec_lua(create_server_definition) - local qflist = exec_lua([=[ - local clangd_response = { { - data = { - parents = { { - parents = { { - parents = { { - parents = {}, - symbolID = "62B3D268A01B9978" - } }, - symbolID = "DC9B0AD433B43BEC" - } }, - symbolID = "06B5F6A19BA9F6A8" - } }, - symbolID = "EDC336589C09ABB2" - }, - kind = 5, - name = "D2", - range = { - ["end"] = { - character = 8, - line = 9 + local qflist = exec_lua(function() + local clangd_response = { + { + data = { + parents = { + { + parents = { + { + parents = { + { + parents = {}, + symbolID = '62B3D268A01B9978', + }, + }, + symbolID = 'DC9B0AD433B43BEC', + }, + }, + symbolID = '06B5F6A19BA9F6A8', + }, + }, + symbolID = 'EDC336589C09ABB2', }, - start = { - character = 6, - line = 9 - } - }, - selectionRange = { - ["end"] = { - character = 8, - line = 9 + kind = 5, + name = 'D2', + range = { + ['end'] = { + character = 8, + line = 3, + }, + start = { + character = 6, + line = 3, + }, }, - start = { - character = 6, - line = 9 - } - }, - uri = "file:///home/jiangyinzuo/hello.cpp" - }, { - data = { - parents = { { - parents = { { - parents = { { - parents = {}, - symbolID = "62B3D268A01B9978" - } }, - symbolID = "DC9B0AD433B43BEC" - } }, - symbolID = "06B5F6A19BA9F6A8" - } }, - symbolID = "AFFCAED15557EF08" - }, - kind = 5, - name = "D1", - range = { - ["end"] = { - character = 8, - line = 8 + selectionRange = { + ['end'] = { + character = 8, + line = 3, + }, + start = { + character = 6, + line = 3, + }, }, - start = { - character = 6, - line = 8 - } + uri = 'file:///home/jiangyinzuo/hello.cpp', }, - selectionRange = { - ["end"] = { - character = 8, - line = 8 + { + data = { + parents = { + { + parents = { + { + parents = { + { + parents = {}, + symbolID = '62B3D268A01B9978', + }, + }, + symbolID = 'DC9B0AD433B43BEC', + }, + }, + symbolID = '06B5F6A19BA9F6A8', + }, + }, + symbolID = 'AFFCAED15557EF08', }, - start = { - character = 6, - line = 8 - } + kind = 5, + name = 'D1', + range = { + ['end'] = { + character = 8, + line = 2, + }, + start = { + character = 6, + line = 2, + }, + }, + selectionRange = { + ['end'] = { + character = 8, + line = 2, + }, + start = { + character = 6, + line = 2, + }, + }, + uri = 'file:///home/jiangyinzuo/hello.cpp', }, - uri = "file:///home/jiangyinzuo/hello.cpp" - } } + } - local server = _create_server({ + local server = _G._create_server({ capabilities = { - positionEncoding = "utf-8" + positionEncoding = 'utf-8', }, }) local client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd }) - local handler = require'vim.lsp.handlers'['typeHierarchy/subtypes'] - handler(nil, clangd_response, { client_id = client_id, bufnr = 1 }) + local handler = require 'vim.lsp.handlers'['typeHierarchy/subtypes'] + local bufnr = vim.api.nvim_get_current_buf() + vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { + 'class B : public A{};', + 'class C : public B{};', + 'class D1 : public C{};', + 'class D2 : public C{};', + 'class E : public D1, D2 {};', + }) + handler(nil, clangd_response, { client_id = client_id, bufnr = bufnr }) return vim.fn.getqflist() - ]=]) + end) local expected = { { @@ -3606,7 +3835,7 @@ describe('LSP', function() col = 7, end_col = 0, end_lnum = 0, - lnum = 10, + lnum = 4, module = '', nr = 0, pattern = '', @@ -3620,7 +3849,7 @@ describe('LSP', function() col = 7, end_col = 0, end_lnum = 0, - lnum = 9, + lnum = 3, module = '', nr = 0, pattern = '', @@ -3637,7 +3866,7 @@ describe('LSP', function() it('opens the quickfix list with the right subtypes and details', function() clear() exec_lua(create_server_definition) - local qflist = exec_lua([=[ + local qflist = exec_lua(function() local jdtls_response = { { data = { element = '=hello-java_ed323c3c/_<{Main.java[Main[A' }, @@ -3673,16 +3902,24 @@ describe('LSP', function() }, } - local server = _create_server({ + local server = _G._create_server({ capabilities = { - positionEncoding = "utf-8" + positionEncoding = 'utf-8', }, }) local client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd }) - local handler = require'vim.lsp.handlers'['typeHierarchy/subtypes'] - handler(nil, jdtls_response, { client_id = client_id, bufnr = 1 }) + local handler = require 'vim.lsp.handlers'['typeHierarchy/subtypes'] + local bufnr = vim.api.nvim_get_current_buf() + vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { + 'package mylist;', + '', + 'public class MyList {', + ' static class Inner extends MyList{}', + '~}', + }) + handler(nil, jdtls_response, { client_id = client_id, bufnr = bufnr }) return vim.fn.getqflist() - ]=]) + end) local expected = { { @@ -3720,103 +3957,127 @@ describe('LSP', function() describe('vim.lsp.buf.typehierarchy supertypes', function() it('does nothing for an empty response', function() - local qflist_count = exec_lua([=[ - require'vim.lsp.handlers'['typeHierarchy/supertypes'](nil, nil, {}) + local qflist_count = exec_lua(function() + require 'vim.lsp.handlers'['typeHierarchy/supertypes'](nil, nil, {}) return #vim.fn.getqflist() - ]=]) + end) eq(0, qflist_count) end) it('opens the quickfix list with the right supertypes', function() clear() exec_lua(create_server_definition) - local qflist = exec_lua([=[ - local clangd_response = { { - data = { - parents = { { - parents = { { - parents = { { - parents = {}, - symbolID = "62B3D268A01B9978" - } }, - symbolID = "DC9B0AD433B43BEC" - } }, - symbolID = "06B5F6A19BA9F6A8" - } }, - symbolID = "EDC336589C09ABB2" - }, - kind = 5, - name = "D2", - range = { - ["end"] = { - character = 8, - line = 9 + local qflist = exec_lua(function() + local clangd_response = { + { + data = { + parents = { + { + parents = { + { + parents = { + { + parents = {}, + symbolID = '62B3D268A01B9978', + }, + }, + symbolID = 'DC9B0AD433B43BEC', + }, + }, + symbolID = '06B5F6A19BA9F6A8', + }, + }, + symbolID = 'EDC336589C09ABB2', }, - start = { - character = 6, - line = 9 - } - }, - selectionRange = { - ["end"] = { - character = 8, - line = 9 + kind = 5, + name = 'D2', + range = { + ['end'] = { + character = 8, + line = 3, + }, + start = { + character = 6, + line = 3, + }, }, - start = { - character = 6, - line = 9 - } - }, - uri = "file:///home/jiangyinzuo/hello.cpp" - }, { - data = { - parents = { { - parents = { { - parents = { { - parents = {}, - symbolID = "62B3D268A01B9978" - } }, - symbolID = "DC9B0AD433B43BEC" - } }, - symbolID = "06B5F6A19BA9F6A8" - } }, - symbolID = "AFFCAED15557EF08" - }, - kind = 5, - name = "D1", - range = { - ["end"] = { - character = 8, - line = 8 + selectionRange = { + ['end'] = { + character = 8, + line = 3, + }, + start = { + character = 6, + line = 3, + }, }, - start = { - character = 6, - line = 8 - } + uri = 'file:///home/jiangyinzuo/hello.cpp', }, - selectionRange = { - ["end"] = { - character = 8, - line = 8 + { + data = { + parents = { + { + parents = { + { + parents = { + { + parents = {}, + symbolID = '62B3D268A01B9978', + }, + }, + symbolID = 'DC9B0AD433B43BEC', + }, + }, + symbolID = '06B5F6A19BA9F6A8', + }, + }, + symbolID = 'AFFCAED15557EF08', }, - start = { - character = 6, - line = 8 - } + kind = 5, + name = 'D1', + range = { + ['end'] = { + character = 8, + line = 2, + }, + start = { + character = 6, + line = 2, + }, + }, + selectionRange = { + ['end'] = { + character = 8, + line = 2, + }, + start = { + character = 6, + line = 2, + }, + }, + uri = 'file:///home/jiangyinzuo/hello.cpp', }, - uri = "file:///home/jiangyinzuo/hello.cpp" - } } + } - local server = _create_server({ + local server = _G._create_server({ capabilities = { - positionEncoding = "utf-8" + positionEncoding = 'utf-8', }, }) local client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd }) - local handler = require'vim.lsp.handlers'['typeHierarchy/supertypes'] - handler(nil, clangd_response, { client_id = client_id, bufnr = 1 }) + local handler = require 'vim.lsp.handlers'['typeHierarchy/supertypes'] + local bufnr = vim.api.nvim_get_current_buf() + vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { + 'class B : public A{};', + 'class C : public B{};', + 'class D1 : public C{};', + 'class D2 : public C{};', + 'class E : public D1, D2 {};', + }) + + handler(nil, clangd_response, { client_id = client_id, bufnr = bufnr }) return vim.fn.getqflist() - ]=]) + end) local expected = { { @@ -3824,7 +4085,7 @@ describe('LSP', function() col = 7, end_col = 0, end_lnum = 0, - lnum = 10, + lnum = 4, module = '', nr = 0, pattern = '', @@ -3838,7 +4099,7 @@ describe('LSP', function() col = 7, end_col = 0, end_lnum = 0, - lnum = 9, + lnum = 3, module = '', nr = 0, pattern = '', @@ -3855,7 +4116,7 @@ describe('LSP', function() it('opens the quickfix list with the right supertypes and details', function() clear() exec_lua(create_server_definition) - local qflist = exec_lua([=[ + local qflist = exec_lua(function() local jdtls_response = { { data = { element = '=hello-java_ed323c3c/_<{Main.java[Main[A' }, @@ -3891,16 +4152,24 @@ describe('LSP', function() }, } - local server = _create_server({ + local server = _G._create_server({ capabilities = { - positionEncoding = "utf-8" + positionEncoding = 'utf-8', }, }) local client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd }) - local handler = require'vim.lsp.handlers'['typeHierarchy/supertypes'] - handler(nil, jdtls_response, { client_id = client_id, bufnr = 1 }) + local handler = require 'vim.lsp.handlers'['typeHierarchy/supertypes'] + local bufnr = vim.api.nvim_get_current_buf() + vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { + 'package mylist;', + '', + 'public class MyList {', + ' static class Inner extends MyList{}', + '~}', + }) + handler(nil, jdtls_response, { client_id = client_id, bufnr = bufnr }) return vim.fn.getqflist() - ]=]) + end) local expected = { { @@ -3984,18 +4253,19 @@ describe('LSP', function() eq(true, client.server_capabilities().renameProvider.prepareProvider) end, on_setup = function() - exec_lua([=[ - local bufnr = vim.api.nvim_get_current_buf() - lsp.buf_attach_client(bufnr, TEST_RPC_CLIENT_ID) - vim.lsp._stubs = {} - vim.fn.input = function(opts, on_confirm) - vim.lsp._stubs.input_prompt = opts.prompt - vim.lsp._stubs.input_text = opts.default - return 'renameto' -- expect this value in fake lsp - end - vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, {'', 'this is line two'}) - vim.fn.cursor(2, 13) -- the space between "line" and "two" - ]=]) + exec_lua(function() + local bufnr = vim.api.nvim_get_current_buf() + vim.lsp.buf_attach_client(bufnr, _G.TEST_RPC_CLIENT_ID) + vim.lsp._stubs = {} + --- @diagnostic disable-next-line:duplicate-set-field + vim.fn.input = function(opts, _) + vim.lsp._stubs.input_prompt = opts.prompt + vim.lsp._stubs.input_text = opts.default + return 'renameto' -- expect this value in fake lsp + end + vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { '', 'this is line two' }) + vim.fn.cursor(2, 13) -- the space between "line" and "two" + end) end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -4009,12 +4279,24 @@ describe('LSP', function() eq(table.remove(test.expected_handlers), { err, result, ctx }, 'expected handler') if ctx.method == 'start' then - exec_lua('vim.lsp.buf.rename()') + exec_lua(function() + vim.lsp.buf.rename() + end) end if ctx.method == 'shutdown' then if test.expected_text then - eq('New Name: ', exec_lua('return vim.lsp._stubs.input_prompt')) - eq(test.expected_text, exec_lua('return vim.lsp._stubs.input_text')) + eq( + 'New Name: ', + exec_lua(function() + return vim.lsp._stubs.input_prompt + end) + ) + eq( + test.expected_text, + exec_lua(function() + return vim.lsp._stubs.input_text + end) + ) end client.stop() end @@ -4044,25 +4326,31 @@ describe('LSP', function() on_handler = function(err, result, ctx) eq(table.remove(expected_handlers), { err, result, ctx }) if ctx.method == 'start' then - exec_lua([[ - vim.lsp.commands['dummy1'] = function(cmd) - vim.lsp.commands['dummy2'] = function() - end + exec_lua(function() + vim.lsp.commands['dummy1'] = function(_) + vim.lsp.commands['dummy2'] = function() end end local bufnr = vim.api.nvim_get_current_buf() - vim.lsp.buf_attach_client(bufnr, TEST_RPC_CLIENT_ID) + vim.lsp.buf_attach_client(bufnr, _G.TEST_RPC_CLIENT_ID) + --- @diagnostic disable-next-line:duplicate-set-field vim.fn.inputlist = function() return 1 end vim.lsp.buf.code_action() - ]]) + end) elseif ctx.method == 'shutdown' then - eq('function', exec_lua [[return type(vim.lsp.commands['dummy2'])]]) + eq( + 'function', + exec_lua(function() + return type(vim.lsp.commands['dummy2']) + end) + ) client.stop() end end, } end) + it('Calls workspace/executeCommand if no client side command', function() local client --- @type vim.lsp.Client local expected_handlers = { @@ -4089,20 +4377,21 @@ describe('LSP', function() ctx.version = nil eq(table.remove(expected_handlers), { err, result, ctx }) if ctx.method == 'start' then - exec_lua([[ + exec_lua(function() local bufnr = vim.api.nvim_get_current_buf() - vim.lsp.buf_attach_client(bufnr, TEST_RPC_CLIENT_ID) + vim.lsp.buf_attach_client(bufnr, _G.TEST_RPC_CLIENT_ID) vim.fn.inputlist = function() return 1 end vim.lsp.buf.code_action() - ]]) + end) elseif ctx.method == 'shutdown' then client.stop() end end, }) end) + it('Filters and automatically applies action if requested', function() local client --- @type vim.lsp.Client local expected_handlers = { @@ -4122,83 +4411,102 @@ describe('LSP', function() on_handler = function(err, result, ctx) eq(table.remove(expected_handlers), { err, result, ctx }) if ctx.method == 'start' then - exec_lua([[ - vim.lsp.commands['preferred_command'] = function(cmd) - vim.lsp.commands['executed_preferred'] = function() - end + exec_lua(function() + vim.lsp.commands['preferred_command'] = function(_) + vim.lsp.commands['executed_preferred'] = function() end end - vim.lsp.commands['type_annotate_command'] = function(cmd) - vim.lsp.commands['executed_type_annotate'] = function() - end + vim.lsp.commands['type_annotate_command'] = function(_) + vim.lsp.commands['executed_type_annotate'] = function() end end local bufnr = vim.api.nvim_get_current_buf() - vim.lsp.buf_attach_client(bufnr, TEST_RPC_CLIENT_ID) - vim.lsp.buf.code_action({ filter = function(a) return a.isPreferred end, apply = true, }) + vim.lsp.buf_attach_client(bufnr, _G.TEST_RPC_CLIENT_ID) + vim.lsp.buf.code_action({ + filter = function(a) + return a.isPreferred + end, + apply = true, + }) vim.lsp.buf.code_action({ - -- expect to be returned actions 'type-annotate' and 'type-annotate.foo' - context = { only = { 'type-annotate' }, }, - apply = true, - filter = function(a) - if a.kind == 'type-annotate.foo' then - vim.lsp.commands['filtered_type_annotate_foo'] = function() end - return false - elseif a.kind == 'type-annotate' then - return true - else - assert(nil, 'unreachable') - end - end, + -- expect to be returned actions 'type-annotate' and 'type-annotate.foo' + context = { only = { 'type-annotate' } }, + apply = true, + filter = function(a) + if a.kind == 'type-annotate.foo' then + vim.lsp.commands['filtered_type_annotate_foo'] = function() end + return false + elseif a.kind == 'type-annotate' then + return true + else + assert(nil, 'unreachable') + end + end, }) - ]]) + end) elseif ctx.method == 'shutdown' then - eq('function', exec_lua [[return type(vim.lsp.commands['executed_preferred'])]]) - eq('function', exec_lua [[return type(vim.lsp.commands['filtered_type_annotate_foo'])]]) - eq('function', exec_lua [[return type(vim.lsp.commands['executed_type_annotate'])]]) + eq( + 'function', + exec_lua(function() + return type(vim.lsp.commands['executed_preferred']) + end) + ) + eq( + 'function', + exec_lua(function() + return type(vim.lsp.commands['filtered_type_annotate_foo']) + end) + ) + eq( + 'function', + exec_lua(function() + return type(vim.lsp.commands['executed_type_annotate']) + end) + ) client.stop() end end, } end) + it('Fallback to command execution on resolve error', function() clear() exec_lua(create_server_definition) - local result = exec_lua([[ - local server = _create_server({ + local result = exec_lua(function() + local server = _G._create_server({ capabilities = { executeCommandProvider = { - commands = {"command:1"}, + commands = { 'command:1' }, }, codeActionProvider = { - resolveProvider = true - } + resolveProvider = true, + }, }, handlers = { - ["textDocument/codeAction"] = function(_, _, callback) + ['textDocument/codeAction'] = function(_, _, callback) callback(nil, { { - title = "Code Action 1", + title = 'Code Action 1', command = { - title = "Command 1", - command = "command:1", - } - } + title = 'Command 1', + command = 'command:1', + }, + }, }) end, - ["codeAction/resolve"] = function(_, _, callback) - callback("resolve failed", nil) + ['codeAction/resolve'] = function(_, _, callback) + callback('resolve failed', nil) end, - } + }, }) - local client_id = vim.lsp.start({ - name = "dummy", + local client_id = assert(vim.lsp.start({ + name = 'dummy', cmd = server.cmd, - }) + })) vim.lsp.buf.code_action({ apply = true }) vim.lsp.stop_client(client_id) return server.messages - ]]) + end) eq('codeAction/resolve', result[4].method) eq('workspace/executeCommand', result[5].method) eq('command:1', result[5].params.command) @@ -4212,6 +4520,7 @@ describe('LSP', function() pcall_err(exec_lua, 'vim.lsp.commands[1] = function() end') ) end) + it('Accepts only function values', function() matches( '.*Command added to `vim.lsp.commands` must be a function', @@ -4241,32 +4550,32 @@ describe('LSP', function() eq(table.remove(expected_handlers), { err, result, ctx }) if ctx.method == 'start' then local fake_uri = 'file:///fake/uri' - local cmd = exec_lua( - [[ - fake_uri = ... + local cmd = exec_lua(function() local bufnr = vim.uri_to_bufnr(fake_uri) vim.fn.bufload(bufnr) - vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, {'One line'}) + vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { 'One line' }) local lenses = { { range = { - start = { line = 0, character = 0, }, - ['end'] = { line = 0, character = 8 } + start = { line = 0, character = 0 }, + ['end'] = { line = 0, character = 8 }, }, - command = { title = 'Lens1', command = 'Dummy' } + command = { title = 'Lens1', command = 'Dummy' }, }, } - vim.lsp.codelens.on_codelens(nil, lenses, {method='textDocument/codeLens', client_id=1, bufnr=bufnr}) + vim.lsp.codelens.on_codelens( + nil, + lenses, + { method = 'textDocument/codeLens', client_id = 1, bufnr = bufnr } + ) local cmd_called = nil - vim.lsp.commands['Dummy'] = function(command) - cmd_called = command + vim.lsp.commands['Dummy'] = function(command0) + cmd_called = command0 end vim.api.nvim_set_current_buf(bufnr) vim.lsp.codelens.run() return cmd_called - ]], - fake_uri - ) + end) eq({ command = 'Dummy', title = 'Lens1' }, cmd) elseif ctx.method == 'shutdown' then client.stop() @@ -4287,20 +4596,20 @@ describe('LSP', function() client = client_ end, on_setup = function() - exec_lua([=[ - local bufnr = vim.api.nvim_get_current_buf() - vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, {'One line'}) - vim.lsp.buf_attach_client(bufnr, TEST_RPC_CLIENT_ID) - - CALLED = false - RESPONSE = nil - local on_codelens = vim.lsp.codelens.on_codelens - vim.lsp.codelens.on_codelens = function (err, result, ...) - CALLED = true - RESPONSE = { err = err, result = result } - return on_codelens(err, result, ...) - end - ]=]) + exec_lua(function() + local bufnr = vim.api.nvim_get_current_buf() + vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { 'One line' }) + vim.lsp.buf_attach_client(bufnr, _G.TEST_RPC_CLIENT_ID) + + _G.CALLED = false + _G.RESPONSE = nil + local on_codelens = vim.lsp.codelens.on_codelens + vim.lsp.codelens.on_codelens = function(err, result, ...) + _G.CALLED = true + _G.RESPONSE = { err = err, result = result } + return on_codelens(err, result, ...) + end + end) end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -4310,42 +4619,52 @@ describe('LSP', function() eq(table.remove(expected_handlers), { err, result, ctx }) if ctx.method == 'start' then -- 1. first codelens request errors - local response = exec_lua([=[ - CALLED = false + local response = exec_lua(function() + _G.CALLED = false vim.lsp.codelens.refresh() - vim.wait(100, function () return CALLED end) - return RESPONSE - ]=]) + vim.wait(100, function() + return _G.CALLED + end) + return _G.RESPONSE + end) eq({ err = { code = -32002, message = 'ServerNotInitialized' } }, response) -- 2. second codelens request runs - response = exec_lua([=[ - CALLED = false - local cmd_called = nil - vim.lsp.commands["Dummy"] = function (command) - cmd_called = command + response = exec_lua(function() + _G.CALLED = false + local cmd_called --- @type string? + vim.lsp.commands['Dummy'] = function(command0) + cmd_called = command0 end vim.lsp.codelens.refresh() - vim.wait(100, function () return CALLED end) + vim.wait(100, function() + return _G.CALLED + end) vim.lsp.codelens.run() - vim.wait(100, function () return cmd_called end) + vim.wait(100, function() + return cmd_called ~= nil + end) return cmd_called - ]=]) + end) eq({ command = 'Dummy', title = 'Lens1' }, response) -- 3. third codelens request runs - response = exec_lua([=[ - CALLED = false - local cmd_called = nil - vim.lsp.commands["Dummy"] = function (command) - cmd_called = command + response = exec_lua(function() + _G.CALLED = false + local cmd_called --- @type string? + vim.lsp.commands['Dummy'] = function(command0) + cmd_called = command0 end vim.lsp.codelens.refresh() - vim.wait(100, function () return CALLED end) + vim.wait(100, function() + return _G.CALLED + end) vim.lsp.codelens.run() - vim.wait(100, function () return cmd_called end) + vim.wait(100, function() + return cmd_called ~= nil + end) return cmd_called - ]=]) + end) eq({ command = 'Dummy', title = 'Lens2' }, response) elseif ctx.method == 'shutdown' then client.stop() @@ -4363,79 +4682,73 @@ describe('LSP', function() exec_lua(create_server_definition) -- setup lsp - exec_lua( - [[ - local lens_title_per_fake_uri = ... - local server = _create_server({ - capabilities = { - codeLensProvider = { - resolveProvider = true - }, + exec_lua(function() + local server = _G._create_server({ + capabilities = { + codeLensProvider = { + resolveProvider = true, }, - handlers = { - ["textDocument/codeLens"] = function(method, params, callback) - local lenses = { - { - range = { - start = { line = 0, character = 0 }, - ['end'] = { line = 0, character = 0 }, - }, - command = { - title = lens_title_per_fake_uri[params.textDocument.uri], - command = 'Dummy', - }, + }, + handlers = { + ['textDocument/codeLens'] = function(_, params, callback) + local lenses = { + { + range = { + start = { line = 0, character = 0 }, + ['end'] = { line = 0, character = 0 }, }, - } - callback(nil, lenses) - end, - } - }) + command = { + title = lens_title_per_fake_uri[params.textDocument.uri], + command = 'Dummy', + }, + }, + } + callback(nil, lenses) + end, + }, + }) - CLIENT_ID = vim.lsp.start({ - name = "dummy", - cmd = server.cmd, - }) - ]], - lens_title_per_fake_uri - ) + _G.CLIENT_ID = vim.lsp.start({ + name = 'dummy', + cmd = server.cmd, + }) + end) -- create buffers and setup handler - exec_lua( - [[ - local lens_title_per_fake_uri = ... - local default_buf = vim.api.nvim_get_current_buf() - for fake_uri, _ in pairs(lens_title_per_fake_uri) do - local bufnr = vim.uri_to_bufnr(fake_uri) - vim.api.nvim_set_current_buf(bufnr) - vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, {'Some contents'}) - vim.lsp.buf_attach_client(bufnr, CLIENT_ID) - end - vim.api.nvim_buf_delete(default_buf, {force = true}) - - REQUEST_COUNT = vim.tbl_count(lens_title_per_fake_uri) - RESPONSES = {} - local on_codelens = vim.lsp.codelens.on_codelens - vim.lsp.codelens.on_codelens = function (err, result, ctx, ...) - table.insert(RESPONSES, { err = err, result = result, ctx = ctx }) - return on_codelens(err, result, ctx, ...) - end - ]], - lens_title_per_fake_uri - ) + exec_lua(function() + local default_buf = vim.api.nvim_get_current_buf() + for fake_uri in pairs(lens_title_per_fake_uri) do + local bufnr = vim.uri_to_bufnr(fake_uri) + vim.api.nvim_set_current_buf(bufnr) + vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { 'Some contents' }) + vim.lsp.buf_attach_client(bufnr, _G.CLIENT_ID) + end + vim.api.nvim_buf_delete(default_buf, { force = true }) + + _G.REQUEST_COUNT = vim.tbl_count(lens_title_per_fake_uri) + _G.RESPONSES = {} + local on_codelens = vim.lsp.codelens.on_codelens + vim.lsp.codelens.on_codelens = function(err, result, ctx, ...) + table.insert(_G.RESPONSES, { err = err, result = result, ctx = ctx }) + return on_codelens(err, result, ctx, ...) + end + end) -- call codelens refresh - local cmds = exec_lua([[ - RESPONSES = {} + local cmds = exec_lua(function() + _G.RESPONSES = {} vim.lsp.codelens.refresh() - vim.wait(100, function () return #RESPONSES >= REQUEST_COUNT end) + vim.wait(100, function() + return #_G.RESPONSES >= _G.REQUEST_COUNT + end) local cmds = {} - for _, resp in ipairs(RESPONSES) do + for _, resp in ipairs(_G.RESPONSES) do local uri = resp.ctx.params.textDocument.uri cmds[uri] = resp.result[1].command end return cmds - ]]) + end) eq({ command = 'Dummy', title = 'Lens1' }, cmds['file:///fake/uri1']) eq({ command = 'Dummy', title = 'Lens2' }, cmds['file:///fake/uri2']) end) @@ -4450,23 +4763,24 @@ describe('LSP', function() client = c end, on_handler = function() - local notify_msg = exec_lua([[ + local notify_msg = exec_lua(function() local bufnr = vim.api.nvim_get_current_buf() - vim.lsp.buf_attach_client(bufnr, TEST_RPC_CLIENT_ID) - local notify_msg + vim.lsp.buf_attach_client(bufnr, _G.TEST_RPC_CLIENT_ID) + local notify_msg --- @type string? local notify = vim.notify - vim.notify = function(msg, log_level) + vim.notify = function(msg, _) notify_msg = msg end vim.lsp.buf.format({ name = 'does-not-exist' }) vim.notify = notify return notify_msg - ]]) + end) eq('[LSP] Format request failed, no matching language servers.', notify_msg) client.stop() end, } end) + it('Sends textDocument/formatting request to format buffer', function() local expected_handlers = { { NIL, {}, { method = 'shutdown', client_id = 1 } }, @@ -4481,25 +4795,114 @@ describe('LSP', function() on_handler = function(_, _, ctx) table.remove(expected_handlers) if ctx.method == 'start' then - local notify_msg = exec_lua([[ + local notify_msg = exec_lua(function() local bufnr = vim.api.nvim_get_current_buf() - vim.lsp.buf_attach_client(bufnr, TEST_RPC_CLIENT_ID) - local notify_msg + vim.lsp.buf_attach_client(bufnr, _G.TEST_RPC_CLIENT_ID) + local notify_msg --- @type string? local notify = vim.notify - vim.notify = function(msg, log_level) + vim.notify = function(msg, _) notify_msg = msg end vim.lsp.buf.format({ bufnr = bufnr }) vim.notify = notify return notify_msg - ]]) - eq(NIL, notify_msg) + end) + eq(nil, notify_msg) + elseif ctx.method == 'shutdown' then + client.stop() + end + end, + } + end) + + it('Sends textDocument/rangeFormatting request to format a range', function() + local expected_handlers = { + { NIL, {}, { method = 'shutdown', client_id = 1 } }, + { NIL, {}, { method = 'start', client_id = 1 } }, + } + local client --- @type vim.lsp.Client + test_rpc_server { + test_name = 'range_formatting', + on_init = function(c) + client = c + end, + on_handler = function(_, _, ctx) + table.remove(expected_handlers) + if ctx.method == 'start' then + local notify_msg = exec_lua(function() + local bufnr = vim.api.nvim_get_current_buf() + vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, { 'foo', 'bar' }) + vim.lsp.buf_attach_client(bufnr, _G.TEST_RPC_CLIENT_ID) + local notify_msg --- @type string? + local notify = vim.notify + vim.notify = function(msg, _) + notify_msg = msg + end + vim.lsp.buf.format({ + bufnr = bufnr, + range = { + start = { 1, 1 }, + ['end'] = { 1, 1 }, + }, + }) + vim.notify = notify + return notify_msg + end) + eq(nil, notify_msg) + elseif ctx.method == 'shutdown' then + client.stop() + end + end, + } + end) + + it('Sends textDocument/rangesFormatting request to format multiple ranges', function() + local expected_handlers = { + { NIL, {}, { method = 'shutdown', client_id = 1 } }, + { NIL, {}, { method = 'start', client_id = 1 } }, + } + local client --- @type vim.lsp.Client + test_rpc_server { + test_name = 'ranges_formatting', + on_init = function(c) + client = c + end, + on_handler = function(_, _, ctx) + table.remove(expected_handlers) + if ctx.method == 'start' then + local notify_msg = exec_lua(function() + local bufnr = vim.api.nvim_get_current_buf() + vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, { 'foo', 'bar', 'baz' }) + vim.lsp.buf_attach_client(bufnr, _G.TEST_RPC_CLIENT_ID) + local notify_msg --- @type string? + local notify = vim.notify + vim.notify = function(msg, _) + notify_msg = msg + end + vim.lsp.buf.format({ + bufnr = bufnr, + range = { + { + start = { 1, 1 }, + ['end'] = { 1, 1 }, + }, + { + start = { 2, 2 }, + ['end'] = { 2, 2 }, + }, + }, + }) + vim.notify = notify + return notify_msg + end) + eq(nil, notify_msg) elseif ctx.method == 'shutdown' then client.stop() end end, } end) + it('Can format async', function() local expected_handlers = { { NIL, {}, { method = 'shutdown', client_id = 1 } }, @@ -4514,29 +4917,31 @@ describe('LSP', function() on_handler = function(_, _, ctx) table.remove(expected_handlers) if ctx.method == 'start' then - local result = exec_lua([[ + local result = exec_lua(function() local bufnr = vim.api.nvim_get_current_buf() - vim.lsp.buf_attach_client(bufnr, TEST_RPC_CLIENT_ID) + vim.lsp.buf_attach_client(bufnr, _G.TEST_RPC_CLIENT_ID) - local notify_msg + local notify_msg --- @type string? local notify = vim.notify - vim.notify = function(msg, log_level) + vim.notify = function(msg, _) notify_msg = msg end local handler = vim.lsp.handlers['textDocument/formatting'] local handler_called = false - vim.lsp.handlers['textDocument/formatting'] = function(...) + vim.lsp.handlers['textDocument/formatting'] = function() handler_called = true end vim.lsp.buf.format({ bufnr = bufnr, async = true }) - vim.wait(1000, function() return handler_called end) + vim.wait(1000, function() + return handler_called + end) vim.notify = notify vim.lsp.handlers['textDocument/formatting'] = handler - return {notify = notify_msg, handler_called = handler_called} - ]]) + return { notify = notify_msg, handler_called = handler_called } + end) eq({ handler_called = true }, result) elseif ctx.method == 'shutdown' then client.stop() @@ -4544,24 +4949,27 @@ describe('LSP', function() end, } end) + it('format formats range in visual mode', function() exec_lua(create_server_definition) - local result = exec_lua([[ - local server = _create_server({ capabilities = { - documentFormattingProvider = true, - documentRangeFormattingProvider = true, - }}) + local result = exec_lua(function() + local server = _G._create_server({ + capabilities = { + documentFormattingProvider = true, + documentRangeFormattingProvider = true, + }, + }) local bufnr = vim.api.nvim_get_current_buf() - local client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd }) + local client_id = assert(vim.lsp.start({ name = 'dummy', cmd = server.cmd })) vim.api.nvim_win_set_buf(0, bufnr) - vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, {'foo', 'bar'}) + vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, { 'foo', 'bar' }) vim.api.nvim_win_set_cursor(0, { 1, 0 }) vim.cmd.normal('v') vim.api.nvim_win_set_cursor(0, { 2, 3 }) vim.lsp.buf.format({ bufnr = bufnr, false }) vim.lsp.stop_client(client_id) return server.messages - ]]) + end) eq('textDocument/rangeFormatting', result[3].method) local expected_range = { start = { line = 0, character = 0 }, @@ -4569,17 +4977,20 @@ describe('LSP', function() } eq(expected_range, result[3].params.range) end) + it('format formats range in visual line mode', function() exec_lua(create_server_definition) - local result = exec_lua([[ - local server = _create_server({ capabilities = { - documentFormattingProvider = true, - documentRangeFormattingProvider = true, - }}) + local result = exec_lua(function() + local server = _G._create_server({ + capabilities = { + documentFormattingProvider = true, + documentRangeFormattingProvider = true, + }, + }) local bufnr = vim.api.nvim_get_current_buf() - local client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd }) + local client_id = assert(vim.lsp.start({ name = 'dummy', cmd = server.cmd })) vim.api.nvim_win_set_buf(0, bufnr) - vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, {'foo', 'bar baz'}) + vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, { 'foo', 'bar baz' }) vim.api.nvim_win_set_cursor(0, { 1, 2 }) vim.cmd.normal('V') vim.api.nvim_win_set_cursor(0, { 2, 1 }) @@ -4587,7 +4998,7 @@ describe('LSP', function() -- Format again with visual lines going from bottom to top -- Must result in same formatting - vim.cmd.normal("<ESC>") + vim.cmd.normal('<ESC>') vim.api.nvim_win_set_cursor(0, { 2, 1 }) vim.cmd.normal('V') vim.api.nvim_win_set_cursor(0, { 1, 2 }) @@ -4595,7 +5006,7 @@ describe('LSP', function() vim.lsp.stop_client(client_id) return server.messages - ]]) + end) local expected_methods = { 'initialize', 'initialized', @@ -4620,40 +5031,57 @@ describe('LSP', function() eq(expected_range, result[3].params.range) eq(expected_range, result[5].params.range) end) + it('Aborts with notify if no clients support requested method', function() exec_lua(create_server_definition) - exec_lua([[ + exec_lua(function() vim.notify = function(msg, _) - notify_msg = msg + _G.notify_msg = msg end - ]]) + end) local fail_msg = '[LSP] Format request failed, no matching language servers.' --- @param name string --- @param formatting boolean --- @param range_formatting boolean local function check_notify(name, formatting, range_formatting) local timeout_msg = '[LSP][' .. name .. '] timeout' - exec_lua( - [[ - local formatting, range_formatting, name = ... - local server = _create_server({ capabilities = { - documentFormattingProvider = formatting, - documentRangeFormattingProvider = range_formatting, - }}) + exec_lua(function() + local server = _G._create_server({ + capabilities = { + documentFormattingProvider = formatting, + documentRangeFormattingProvider = range_formatting, + }, + }) vim.lsp.start({ name = name, cmd = server.cmd }) - notify_msg = nil + _G.notify_msg = nil vim.lsp.buf.format({ name = name, timeout_ms = 1 }) - ]], - formatting, - range_formatting, - name + end) + eq( + formatting and timeout_msg or fail_msg, + exec_lua(function() + return _G.notify_msg + end) + ) + exec_lua(function() + _G.notify_msg = nil + vim.lsp.buf.format({ + name = name, + timeout_ms = 1, + range = { + start = { 1, 0 }, + ['end'] = { + 1, + 0, + }, + }, + }) + end) + eq( + range_formatting and timeout_msg or fail_msg, + exec_lua(function() + return _G.notify_msg + end) ) - eq(formatting and timeout_msg or fail_msg, exec_lua('return notify_msg')) - exec_lua([[ - notify_msg = nil - vim.lsp.buf.format({ name = name, timeout_ms = 1, range = {start={1, 0}, ['end']={1, 0}}}) - ]]) - eq(range_formatting and timeout_msg or fail_msg, exec_lua('return notify_msg')) end check_notify('none', false, false) check_notify('formatting', true, false) @@ -4683,10 +5111,9 @@ describe('LSP', function() }, } exec_lua(create_server_definition) - exec_lua( - [[ - _G.mock_locations = ... - _G.server = _create_server({ + exec_lua(function() + _G.mock_locations = mock_locations + _G.server = _G._create_server({ ---@type lsp.ServerCapabilities capabilities = { definitionProvider = true, @@ -4710,26 +5137,25 @@ describe('LSP', function() name = 'vim.foobar', kind = 12, ---@type lsp.SymbolKind location = _G.mock_locations[2], - } + }, }) end, }, }) - _G.client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd }) - ]], - mock_locations - ) + _G.client_id = vim.lsp.start({ name = 'dummy', cmd = _G.server.cmd }) + end) end) + after_each(function() - exec_lua [[ + exec_lua(function() vim.lsp.stop_client(_G.client_id) - ]] + end) end) it('with flags=c, returns matching tags using textDocument/definition', function() - local result = exec_lua [[ + local result = exec_lua(function() return vim.lsp.tagfunc('foobar', 'c') - ]] + end) eq({ { cmd = '/\\%6l\\%1c/', -- for location (5, 23) @@ -4740,9 +5166,9 @@ describe('LSP', function() end) it('without flags=c, returns all matching tags using workspace/symbol', function() - local result = exec_lua [[ + local result = exec_lua(function() return vim.lsp.tagfunc('foobar', '') - ]] + end) eq({ { cmd = '/\\%6l\\%1c/', -- for location (5, 23) @@ -4761,80 +5187,81 @@ describe('LSP', function() end) describe('cmd', function() - it('can connect to lsp server via rpc.connect', function() - local result = exec_lua [[ - local uv = vim.uv - local server = uv.new_tcp() - local init = nil - server:bind('127.0.0.1', 0) - server:listen(127, function(err) - assert(not err, err) - local socket = uv.new_tcp() - server:accept(socket) - socket:read_start(require('vim.lsp.rpc').create_read_loop(function(body) - init = body - socket:close() - end)) - end) - local port = server:getsockname().port + it('connects to lsp server via rpc.connect using ip address', function() + exec_lua(create_tcp_echo_server) + local result = exec_lua(function() + local server, port, last_message = _G._create_tcp_server('127.0.0.1') vim.lsp.start({ name = 'dummy', cmd = vim.lsp.rpc.connect('127.0.0.1', port) }) - vim.wait(1000, function() return init ~= nil end) - assert(init, "server must receive `initialize` request") + vim.wait(1000, function() + return last_message() ~= nil + end) + local init = last_message() + assert(init, 'server must receive `initialize` request') + server:close() + server:shutdown() + return vim.json.decode(init) + end) + eq('initialize', result.method) + end) + + it('connects to lsp server via rpc.connect using hostname', function() + skip(is_os('bsd'), 'issue with host resolution in ci') + exec_lua(create_tcp_echo_server) + local result = exec_lua(function() + local server, port, last_message = _G._create_tcp_server('::1') + vim.lsp.start({ name = 'dummy', cmd = vim.lsp.rpc.connect('localhost', port) }) + vim.wait(1000, function() + return last_message() ~= nil + end) + local init = last_message() + assert(init, 'server must receive `initialize` request') server:close() server:shutdown() return vim.json.decode(init) - ]] + end) eq('initialize', result.method) end) + it('can connect to lsp server via pipe or domain_socket', function() - local tmpfile --- @type string - if is_os('win') then - tmpfile = '\\\\.\\\\pipe\\pipe.test' - else - tmpfile = tmpname() - os.remove(tmpfile) - end - local result = exec_lua( - [[ - local SOCK = ... + local tmpfile = is_os('win') and '\\\\.\\\\pipe\\pipe.test' or tmpname(false) + local result = exec_lua(function() local uv = vim.uv - local server = uv.new_pipe(false) - server:bind(SOCK) + local server = assert(uv.new_pipe(false)) + server:bind(tmpfile) local init = nil server:listen(127, function(err) - assert(not err, err) - local client = uv.new_pipe() - server:accept(client) - client:read_start(require("vim.lsp.rpc").create_read_loop(function(body) - init = body - client:close() - end)) + assert(not err, err) + local client = assert(vim.uv.new_pipe()) + server:accept(client) + client:read_start(require('vim.lsp.rpc').create_read_loop(function(body) + init = body + client:close() + end)) + end) + vim.lsp.start({ name = 'dummy', cmd = vim.lsp.rpc.connect(tmpfile) }) + vim.wait(1000, function() + return init ~= nil end) - vim.lsp.start({ name = "dummy", cmd = vim.lsp.rpc.connect(SOCK) }) - vim.wait(1000, function() return init ~= nil end) - assert(init, "server must receive `initialize` request") + assert(init, 'server must receive `initialize` request') server:close() server:shutdown() return vim.json.decode(init) - ]], - tmpfile - ) + end) eq('initialize', result.method) end) end) describe('handlers', function() it('handler can return false as response', function() - local result = exec_lua [[ - local uv = vim.uv - local server = uv.new_tcp() + local result = exec_lua(function() + local server = assert(vim.uv.new_tcp()) local messages = {} local responses = {} server:bind('127.0.0.1', 0) server:listen(127, function(err) assert(not err, err) - local socket = uv.new_tcp() + local socket = assert(vim.uv.new_tcp()) server:accept(socket) socket:read_start(require('vim.lsp.rpc').create_read_loop(function(body) local payload = vim.json.decode(body) @@ -4845,10 +5272,10 @@ describe('LSP', function() id = payload.id, jsonrpc = '2.0', result = { - capabilities = {} + capabilities = {}, }, }) - socket:write(table.concat({'Content-Length: ', tostring(#msg), '\r\n\r\n', msg})) + socket:write(table.concat({ 'Content-Length: ', tostring(#msg), '\r\n\r\n', msg })) elseif payload.method == 'initialized' then local msg = vim.json.encode({ id = 10, @@ -4856,7 +5283,7 @@ describe('LSP', function() method = 'dummy', params = {}, }) - socket:write(table.concat({'Content-Length: ', tostring(#msg), '\r\n\r\n', msg})) + socket:write(table.concat({ 'Content-Length: ', tostring(#msg), '\r\n\r\n', msg })) end else table.insert(responses, payload) @@ -4866,20 +5293,24 @@ describe('LSP', function() end) local port = server:getsockname().port local handler_called = false - vim.lsp.handlers['dummy'] = function(err, result) + vim.lsp.handlers['dummy'] = function(_, _) handler_called = true return false end - local client_id = vim.lsp.start({ name = 'dummy', cmd = vim.lsp.rpc.connect('127.0.0.1', port) }) - local client = vim.lsp.get_client_by_id(client_id) - vim.wait(1000, function() return #messages == 2 and handler_called and #responses == 1 end) + local client_id = + assert(vim.lsp.start({ name = 'dummy', cmd = vim.lsp.rpc.connect('127.0.0.1', port) })) + vim.lsp.get_client_by_id(client_id) + vim.wait(1000, function() + return #messages == 2 and handler_called and #responses == 1 + end) server:close() server:shutdown() return { messages = messages, handler_called = handler_called, - responses = responses } - ]] + responses = responses, + } + end) local expected = { messages = { 'initialize', 'initialized' }, handler_called = true, @@ -4897,9 +5328,7 @@ describe('LSP', function() describe('#dynamic vim.lsp._dynamic', function() it('supports dynamic registration', function() - ---@type string - local root_dir = tmpname() - os.remove(root_dir) + local root_dir = tmpname(false) mkdir(root_dir) local tmpfile = root_dir .. '/dynamic.foo' local file = io.open(tmpfile, 'w') @@ -4908,17 +5337,14 @@ describe('LSP', function() end exec_lua(create_server_definition) - local result = exec_lua( - [[ - local root_dir, tmpfile = ... - - local server = _create_server() - local client_id = vim.lsp.start({ + local result = exec_lua(function() + local server = _G._create_server() + local client_id = assert(vim.lsp.start({ name = 'dynamic-test', cmd = server.cmd, root_dir = root_dir, get_language_id = function() - return "dummy-lang" + return 'dummy-lang' end, capabilities = { textDocument = { @@ -4930,9 +5356,7 @@ describe('LSP', function() }, }, }, - }) - - local expected_messages = 2 -- initialize, initialized + })) vim.lsp.handlers['client/registerCapability'](nil, { registrations = { @@ -4940,9 +5364,11 @@ describe('LSP', function() id = 'formatting', method = 'textDocument/formatting', registerOptions = { - documentSelector = {{ - pattern = root_dir .. '/*.foo', - }}, + documentSelector = { + { + pattern = root_dir .. '/*.foo', + }, + }, }, }, }, @@ -4954,12 +5380,12 @@ describe('LSP', function() id = 'range-formatting', method = 'textDocument/rangeFormatting', registerOptions = { - documentSelector = { + documentSelector = { { - language = "dummy-lang" + language = 'dummy-lang', }, - } - } + }, + }, }, }, }, { client_id = client_id }) @@ -4976,26 +5402,22 @@ describe('LSP', function() local result = {} local function check(method, fname) local bufnr = fname and vim.fn.bufadd(fname) or nil - local client = vim.lsp.get_client_by_id(client_id) + local client = assert(vim.lsp.get_client_by_id(client_id)) result[#result + 1] = { method = method, fname = fname, - supported = client.supports_method(method, {bufnr = bufnr}) + supported = client.supports_method(method, { bufnr = bufnr }), } end - - check("textDocument/formatting") - check("textDocument/formatting", tmpfile) - check("textDocument/rangeFormatting") - check("textDocument/rangeFormatting", tmpfile) - check("textDocument/completion") + check('textDocument/formatting') + check('textDocument/formatting', tmpfile) + check('textDocument/rangeFormatting') + check('textDocument/rangeFormatting', tmpfile) + check('textDocument/completion') return result - ]], - root_dir, - tmpfile - ) + end) eq(5, #result) eq({ method = 'textDocument/formatting', supported = false }, result[1]) @@ -5007,16 +5429,26 @@ describe('LSP', function() end) describe('vim.lsp._watchfiles', function() + --- @type integer, integer, integer + local created, changed, deleted + + setup(function() + clear() + created = exec_lua([[return vim.lsp.protocol.FileChangeType.Created]]) + changed = exec_lua([[return vim.lsp.protocol.FileChangeType.Changed]]) + deleted = exec_lua([[return vim.lsp.protocol.FileChangeType.Deleted]]) + end) + local function test_filechanges(watchfunc) it( string.format('sends notifications when files change (watchfunc=%s)', watchfunc), function() - if watchfunc == 'fswatch' then + if watchfunc == 'inotify' then skip(is_os('win'), 'not supported on windows') skip(is_os('mac'), 'flaky test on mac') skip( - not is_ci() and fn.executable('fswatch') == 0, - 'fswatch not installed and not on CI' + not is_ci() and fn.executable('inotifywait') == 0, + 'inotify-tools not installed and not on CI' ) end @@ -5033,87 +5465,80 @@ describe('LSP', function() ) end - local root_dir = tmpname() - os.remove(root_dir) + local root_dir = tmpname(false) mkdir(root_dir) exec_lua(create_server_definition) - local result = exec_lua( - [[ - local root_dir, watchfunc = ... - - local server = _create_server() - local client_id = vim.lsp.start({ - name = 'watchfiles-test', - cmd = server.cmd, - root_dir = root_dir, - capabilities = { - workspace = { - didChangeWatchedFiles = { - dynamicRegistration = true, + local result = exec_lua(function() + local server = _G._create_server() + local client_id = assert(vim.lsp.start({ + name = 'watchfiles-test', + cmd = server.cmd, + root_dir = root_dir, + capabilities = { + workspace = { + didChangeWatchedFiles = { + dynamicRegistration = true, + }, }, }, - }, - }) + })) - require('vim.lsp._watchfiles')._watchfunc = require('vim._watch')[watchfunc] + require('vim.lsp._watchfiles')._watchfunc = require('vim._watch')[watchfunc] - local expected_messages = 0 + local expected_messages = 0 - local msg_wait_timeout = watchfunc == 'watch' and 200 or 2500 + local msg_wait_timeout = watchfunc == 'watch' and 200 or 2500 - local function wait_for_message(incr) - expected_messages = expected_messages + (incr or 1) - assert( - vim.wait(msg_wait_timeout, function() - return #server.messages == expected_messages - end), - 'Timed out waiting for expected number of messages. Current messages seen so far: ' - .. vim.inspect(server.messages) - ) - end + local function wait_for_message(incr) + expected_messages = expected_messages + (incr or 1) + assert( + vim.wait(msg_wait_timeout, function() + return #server.messages == expected_messages + end), + 'Timed out waiting for expected number of messages. Current messages seen so far: ' + .. vim.inspect(server.messages) + ) + end - wait_for_message(2) -- initialize, initialized + wait_for_message(2) -- initialize, initialized - vim.lsp.handlers['client/registerCapability'](nil, { - registrations = { - { - id = 'watchfiles-test-0', - method = 'workspace/didChangeWatchedFiles', - registerOptions = { - watchers = { - { - globPattern = '**/watch', - kind = 7, + vim.lsp.handlers['client/registerCapability'](nil, { + registrations = { + { + id = 'watchfiles-test-0', + method = 'workspace/didChangeWatchedFiles', + registerOptions = { + watchers = { + { + globPattern = '**/watch', + kind = 7, + }, }, }, }, }, - }, - }, { client_id = client_id }) + }, { client_id = client_id }) - if watchfunc ~= 'watch' then - vim.wait(100) - end + if watchfunc ~= 'watch' then + vim.wait(100) + end - local path = root_dir .. '/watch' - local tmp = vim.fn.tempname() - io.open(tmp, 'w'):close() - vim.uv.fs_rename(tmp, path) + local path = root_dir .. '/watch' + local tmp = vim.fn.tempname() + io.open(tmp, 'w'):close() + vim.uv.fs_rename(tmp, path) - wait_for_message() + wait_for_message() - os.remove(path) + os.remove(path) - wait_for_message() + wait_for_message() - vim.lsp.stop_client(client_id) + vim.lsp.stop_client(client_id) - return server.messages - ]], - root_dir, - watchfunc - ) + return server.messages + end) local uri = vim.uri_from_fname(root_dir .. '/watch') @@ -5124,7 +5549,7 @@ describe('LSP', function() params = { changes = { { - type = exec_lua([[return vim.lsp.protocol.FileChangeType.Created]]), + type = created, uri = uri, }, }, @@ -5136,7 +5561,7 @@ describe('LSP', function() params = { changes = { { - type = exec_lua([[return vim.lsp.protocol.FileChangeType.Deleted]]), + type = deleted, uri = uri, }, }, @@ -5148,17 +5573,14 @@ describe('LSP', function() test_filechanges('watch') test_filechanges('watchdirs') - test_filechanges('fswatch') + test_filechanges('inotify') it('correctly registers and unregisters', function() local root_dir = '/some_dir' exec_lua(create_server_definition) - local result = exec_lua( - [[ - local root_dir = ... - - local server = _create_server() - local client_id = vim.lsp.start({ + local result = exec_lua(function() + local server = _G._create_server() + local client_id = assert(vim.lsp.start({ name = 'watchfiles-test', cmd = server.cmd, root_dir = root_dir, @@ -5169,16 +5591,22 @@ describe('LSP', function() }, }, }, - }) + })) local expected_messages = 2 -- initialize, initialized local function wait_for_messages() - assert(vim.wait(200, function() return #server.messages == expected_messages end), 'Timed out waiting for expected number of messages. Current messages seen so far: ' .. vim.inspect(server.messages)) + assert( + vim.wait(200, function() + return #server.messages == expected_messages + end), + 'Timed out waiting for expected number of messages. Current messages seen so far: ' + .. vim.inspect(server.messages) + ) end wait_for_messages() - local send_event + local send_event --- @type function require('vim.lsp._watchfiles')._watchfunc = function(_, _, callback) local stopped = false send_event = function(...) @@ -5245,9 +5673,7 @@ describe('LSP', function() wait_for_messages() return server.messages - ]], - root_dir - ) + end) local function watched_uri(fname) return vim.uri_from_fname(root_dir .. '/' .. fname) @@ -5258,7 +5684,7 @@ describe('LSP', function() eq({ changes = { { - type = exec_lua([[return vim.lsp.protocol.FileChangeType.Created]]), + type = created, uri = watched_uri('file.watch0'), }, }, @@ -5267,7 +5693,7 @@ describe('LSP', function() eq({ changes = { { - type = exec_lua([[return vim.lsp.protocol.FileChangeType.Created]]), + type = created, uri = watched_uri('file.watch1'), }, }, @@ -5277,12 +5703,9 @@ describe('LSP', function() it('correctly handles the registered watch kind', function() local root_dir = 'some_dir' exec_lua(create_server_definition) - local result = exec_lua( - [[ - local root_dir = ... - - local server = _create_server() - local client_id = vim.lsp.start({ + local result = exec_lua(function() + local server = _G._create_server() + local client_id = assert(vim.lsp.start({ name = 'watchfiles-test', cmd = server.cmd, root_dir = root_dir, @@ -5293,16 +5716,22 @@ describe('LSP', function() }, }, }, - }) + })) local expected_messages = 2 -- initialize, initialized local function wait_for_messages() - assert(vim.wait(200, function() return #server.messages == expected_messages end), 'Timed out waiting for expected number of messages. Current messages seen so far: ' .. vim.inspect(server.messages)) + assert( + vim.wait(200, function() + return #server.messages == expected_messages + end), + 'Timed out waiting for expected number of messages. Current messages seen so far: ' + .. vim.inspect(server.messages) + ) end wait_for_messages() - local watch_callbacks = {} + local watch_callbacks = {} --- @type function[] local function send_event(...) for _, cb in ipairs(watch_callbacks) do cb(...) @@ -5318,12 +5747,14 @@ describe('LSP', function() local protocol = require('vim.lsp.protocol') local watchers = {} - local max_kind = protocol.WatchKind.Create + protocol.WatchKind.Change + protocol.WatchKind.Delete + local max_kind = protocol.WatchKind.Create + + protocol.WatchKind.Change + + protocol.WatchKind.Delete for i = 0, max_kind do table.insert(watchers, { globPattern = { baseUri = vim.uri_from_fname('/dir'), - pattern = 'watch'..tostring(i), + pattern = 'watch' .. tostring(i), }, kind = i, }) @@ -5351,9 +5782,7 @@ describe('LSP', function() wait_for_messages() return server.messages - ]], - root_dir - ) + end) local function watched_uri(fname) return vim.uri_from_fname('/dir/' .. fname) @@ -5364,51 +5793,51 @@ describe('LSP', function() eq({ changes = { { - type = exec_lua([[return vim.lsp.protocol.FileChangeType.Created]]), + type = created, uri = watched_uri('watch1'), }, { - type = exec_lua([[return vim.lsp.protocol.FileChangeType.Changed]]), + type = changed, uri = watched_uri('watch2'), }, { - type = exec_lua([[return vim.lsp.protocol.FileChangeType.Created]]), + type = created, uri = watched_uri('watch3'), }, { - type = exec_lua([[return vim.lsp.protocol.FileChangeType.Changed]]), + type = changed, uri = watched_uri('watch3'), }, { - type = exec_lua([[return vim.lsp.protocol.FileChangeType.Deleted]]), + type = deleted, uri = watched_uri('watch4'), }, { - type = exec_lua([[return vim.lsp.protocol.FileChangeType.Created]]), + type = created, uri = watched_uri('watch5'), }, { - type = exec_lua([[return vim.lsp.protocol.FileChangeType.Deleted]]), + type = deleted, uri = watched_uri('watch5'), }, { - type = exec_lua([[return vim.lsp.protocol.FileChangeType.Changed]]), + type = changed, uri = watched_uri('watch6'), }, { - type = exec_lua([[return vim.lsp.protocol.FileChangeType.Deleted]]), + type = deleted, uri = watched_uri('watch6'), }, { - type = exec_lua([[return vim.lsp.protocol.FileChangeType.Created]]), + type = created, uri = watched_uri('watch7'), }, { - type = exec_lua([[return vim.lsp.protocol.FileChangeType.Changed]]), + type = changed, uri = watched_uri('watch7'), }, { - type = exec_lua([[return vim.lsp.protocol.FileChangeType.Deleted]]), + type = deleted, uri = watched_uri('watch7'), }, }, @@ -5418,12 +5847,9 @@ describe('LSP', function() it('prunes duplicate events', function() local root_dir = 'some_dir' exec_lua(create_server_definition) - local result = exec_lua( - [[ - local root_dir = ... - - local server = _create_server() - local client_id = vim.lsp.start({ + local result = exec_lua(function() + local server = _G._create_server() + local client_id = assert(vim.lsp.start({ name = 'watchfiles-test', cmd = server.cmd, root_dir = root_dir, @@ -5434,16 +5860,22 @@ describe('LSP', function() }, }, }, - }) + })) local expected_messages = 2 -- initialize, initialized local function wait_for_messages() - assert(vim.wait(200, function() return #server.messages == expected_messages end), 'Timed out waiting for expected number of messages. Current messages seen so far: ' .. vim.inspect(server.messages)) + assert( + vim.wait(200, function() + return #server.messages == expected_messages + end), + 'Timed out waiting for expected number of messages. Current messages seen so far: ' + .. vim.inspect(server.messages) + ) end wait_for_messages() - local send_event + local send_event --- @type function require('vim.lsp._watchfiles')._watchfunc = function(_, _, callback) send_event = callback return function() @@ -5477,24 +5909,22 @@ describe('LSP', function() wait_for_messages() return server.messages - ]], - root_dir - ) + end) eq(3, #result) eq('workspace/didChangeWatchedFiles', result[3].method) eq({ changes = { { - type = exec_lua([[return vim.lsp.protocol.FileChangeType.Created]]), + type = created, uri = vim.uri_from_fname('file1'), }, { - type = exec_lua([[return vim.lsp.protocol.FileChangeType.Changed]]), + type = changed, uri = vim.uri_from_fname('file1'), }, { - type = exec_lua([[return vim.lsp.protocol.FileChangeType.Created]]), + type = created, uri = vim.uri_from_fname('file2'), }, }, @@ -5503,27 +5933,28 @@ describe('LSP', function() it("ignores registrations by servers when the client doesn't advertise support", function() exec_lua(create_server_definition) - exec_lua([[ - server = _create_server() - require('vim.lsp._watchfiles')._watchfunc = function(_, _, callback) + exec_lua(function() + _G.server = _G._create_server() + require('vim.lsp._watchfiles')._watchfunc = function(_, _, _) -- Since the registration is ignored, this should not execute and `watching` should stay false - watching = true + _G.watching = true return function() end end - ]]) + end) local function check_registered(capabilities) - return exec_lua( - [[ - watching = false - local client_id = vim.lsp.start({ + return exec_lua(function() + _G.watching = false + local client_id = assert(vim.lsp.start({ name = 'watchfiles-test', - cmd = server.cmd, + cmd = _G.server.cmd, root_dir = 'some_dir', - capabilities = ..., + capabilities = capabilities, }, { - reuse_client = function() return false end, - }) + reuse_client = function() + return false + end, + })) vim.lsp.handlers['client/registerCapability'](nil, { registrations = { @@ -5552,10 +5983,8 @@ describe('LSP', function() }, { client_id = client_id }) vim.lsp.stop_client(client_id, true) - return watching - ]], - capabilities - ) + return _G.watching + end) end eq(is_os('mac') or is_os('win'), check_registered(nil)) -- start{_client}() defaults to make_client_capabilities(). diff --git a/test/functional/plugin/man_spec.lua b/test/functional/plugin/man_spec.lua index 978178191c..6f0eeff748 100644 --- a/test/functional/plugin/man_spec.lua +++ b/test/functional/plugin/man_spec.lua @@ -8,7 +8,6 @@ local exec_lua = n.exec_lua local fn = n.fn local nvim_prog = n.nvim_prog local matches = t.matches -local write_file = t.write_file local tmpname = t.tmpname local eq = t.eq local pesc = vim.pesc @@ -17,21 +16,20 @@ local is_ci = t.is_ci -- Collects all names passed to find_path() after attempting ":Man foo". local function get_search_history(name) - local args = vim.split(name, ' ') - local code = [[ - local args = ... - local man = require('runtime.lua.man') + return exec_lua(function() + local args = vim.split(name, ' ') + local man = require('man') local res = {} - man.find_path = function(sect, name) - table.insert(res, {sect, name}) + --- @diagnostic disable-next-line:duplicate-set-field + man.find_path = function(sect, name0) + table.insert(res, { sect, name0 }) return nil end - local ok, rv = pcall(man.open_page, -1, {tab = 0}, args) + local ok, rv = pcall(man.open_page, -1, { tab = 0 }, args) assert(not ok) assert(rv and rv:match('no manual entry')) return res - ]] - return exec_lua(code, args) + end) end clear() @@ -117,6 +115,29 @@ describe(':Man', function() ]]) end) + it('clears OSC 8 hyperlink markup from text', function() + feed( + [[ + ithis <C-v><ESC>]8;;http://example.com<C-v><ESC>\Link Title<C-v><ESC>]8;;<C-v><ESC>\<ESC>]] + ) + + screen:expect { + grid = [=[ + this {c:^[}]8;;http://example.com{c:^[}\Link Title{c:^[}]8;;{c:^[}^\ | + {eob:~ }|*3 + | + ]=], + } + + exec_lua [[require'man'.init_pager()]] + + screen:expect([[ + ^this Link Title | + {eob:~ }|*3 + | + ]]) + end) + it('highlights multibyte text', function() feed( [[ @@ -203,7 +224,6 @@ describe(':Man', function() local actual_file = tmpname() -- actual_file must be an absolute path to an existent file for us to test against it matches('^/.+', actual_file) - write_file(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( diff --git a/test/functional/plugin/msgpack_spec.lua b/test/functional/plugin/msgpack_spec.lua index 1d5d20ec02..61ab730da8 100644 --- a/test/functional/plugin/msgpack_spec.lua +++ b/test/functional/plugin/msgpack_spec.lua @@ -58,23 +58,11 @@ describe('autoload/msgpack.vim', function() msgpack_eq(1, '"abc\\ndef"', '"abc\\ndef"') msgpack_eq(0, '"abc\\ndef"', '"abc\\nghi"') end) - it('compares binary specials correctly', function() - msgpack_eq(1, sp('binary', '["abc\\n", "def"]'), sp('binary', '["abc\\n", "def"]')) - msgpack_eq(0, sp('binary', '["abc", "def"]'), sp('binary', '["abc\\n", "def"]')) - end) - it('compares binary specials with raw binaries correctly', function() - msgpack_eq(1, sp('binary', '["abc", "def"]'), '"abc\\ndef"') - msgpack_eq(0, sp('binary', '["abc", "def"]'), '"abcdef"') - end) it('compares string specials correctly', function() msgpack_eq(1, sp('string', '["abc\\n", "def"]'), sp('string', '["abc\\n", "def"]')) msgpack_eq(0, sp('string', '["abc", "def"]'), sp('string', '["abc\\n", "def"]')) - end) - it('compares string specials with binary correctly', function() - msgpack_eq(0, sp('string', '["abc\\n", "def"]'), sp('binary', '["abc\\n", "def"]')) - msgpack_eq(0, sp('string', '["abc", "def"]'), '"abc\\ndef"') - msgpack_eq(0, sp('binary', '["abc\\n", "def"]'), sp('string', '["abc\\n", "def"]')) - msgpack_eq(0, '"abc\\ndef"', sp('string', '["abc", "def"]')) + msgpack_eq(1, sp('string', '["abc", "def"]'), '"abc\\ndef"') + msgpack_eq(1, '"abc\\ndef"', sp('string', '["abc", "def"]')) end) it('compares ext specials correctly', function() msgpack_eq(1, sp('ext', '[1, ["", "ac"]]'), sp('ext', '[1, ["", "ac"]]')) @@ -92,20 +80,16 @@ describe('autoload/msgpack.vim', function() end) it('compares map specials correctly', function() msgpack_eq(1, mapsp(), mapsp()) - msgpack_eq(1, mapsp(sp('binary', '[""]'), '""'), mapsp(sp('binary', '[""]'), '""')) msgpack_eq( 1, mapsp(mapsp('1', '1'), mapsp('1', '1')), mapsp(mapsp('1', '1'), mapsp('1', '1')) ) msgpack_eq(0, mapsp(), mapsp('1', '1')) - msgpack_eq(0, mapsp(sp('binary', '["a"]'), '""'), mapsp(sp('binary', '[""]'), '""')) - msgpack_eq(0, mapsp(sp('binary', '[""]'), '"a"'), mapsp(sp('binary', '[""]'), '""')) - msgpack_eq(0, mapsp(sp('binary', '["a"]'), '"a"'), mapsp(sp('binary', '[""]'), '""')) msgpack_eq( 0, mapsp(mapsp('1', '1'), mapsp('1', '1')), - mapsp(sp('binary', '[""]'), mapsp('1', '1')) + mapsp(sp('string', '[""]'), mapsp('1', '1')) ) msgpack_eq( 0, @@ -138,7 +122,7 @@ describe('autoload/msgpack.vim', function() msgpack_eq(1, mapsp(sp('string', '["1"]'), '1'), '{"1": 1}') msgpack_eq(1, mapsp(sp('string', '["1"]'), sp('integer', '[1, 0, 0, 1]')), '{"1": 1}') msgpack_eq(0, mapsp(sp('integer', '[1, 0, 0, 1]'), sp('string', '["1"]')), '{1: "1"}') - msgpack_eq(0, mapsp('"1"', sp('integer', '[1, 0, 0, 1]')), '{"1": 1}') + msgpack_eq(1, mapsp('"1"', sp('integer', '[1, 0, 0, 1]')), '{"1": 1}') msgpack_eq(0, mapsp(sp('string', '["1"]'), '1', sp('string', '["2"]'), '2'), '{"1": 1}') msgpack_eq(0, mapsp(sp('string', '["1"]'), '1'), '{"1": 1, "2": 2}') end) @@ -290,7 +274,6 @@ describe('autoload/msgpack.vim', function() it('works for special dictionaries', function() type_eq('string', sp('string', '[""]')) - type_eq('binary', sp('binary', '[""]')) type_eq('ext', sp('ext', '[1, [""]]')) type_eq('array', sp('array', '[]')) type_eq('map', sp('map', '[]')) @@ -301,7 +284,7 @@ describe('autoload/msgpack.vim', function() end) it('works for regular values', function() - type_eq('binary', '""') + type_eq('string', '""') type_eq('array', '[]') type_eq('map', '{}') type_eq('integer', '1') @@ -319,7 +302,6 @@ describe('autoload/msgpack.vim', function() it('works for special dictionaries', function() sp_type_eq('string', sp('string', '[""]')) - sp_type_eq('binary', sp('binary', '[""]')) sp_type_eq('ext', sp('ext', '[1, [""]]')) sp_type_eq('array', sp('array', '[]')) sp_type_eq('map', sp('map', '[]')) @@ -347,12 +329,9 @@ describe('autoload/msgpack.vim', function() end it('works for special dictionaries', function() - string_eq('=""', sp('string', '[""]')) - string_eq('="\\n"', sp('string', '["", ""]')) - string_eq('="ab\\0c\\nde"', sp('string', '["ab\\nc", "de"]')) - string_eq('""', sp('binary', '[""]')) - string_eq('"\\n"', sp('binary', '["", ""]')) - string_eq('"ab\\0c\\nde"', sp('binary', '["ab\\nc", "de"]')) + string_eq('""', sp('string', '[""]')) + string_eq('"\\n"', sp('string', '["", ""]')) + string_eq('"ab\\0c\\nde"', sp('string', '["ab\\nc", "de"]')) string_eq('+(2)""', sp('ext', '[2, [""]]')) string_eq('+(2)"\\n"', sp('ext', '[2, ["", ""]]')) string_eq('+(2)"ab\\0c\\nde"', sp('ext', '[2, ["ab\\nc", "de"]]')) @@ -397,8 +376,8 @@ describe('autoload/msgpack.vim', function() string_eq('[]', '[]') string_eq('[[[{}]]]', '[[[{}]]]') string_eq('{}', '{}') - string_eq('{="2": 10}', '{2: 10}') - string_eq('{="2": [{}]}', '{2: [{}]}') + string_eq('{"2": 10}', '{2: 10}') + string_eq('{"2": [{}]}', '{2: [{}]}') string_eq('1', '1') string_eq('0.0', '0.0') string_eq('inf', '(1.0/0.0)') @@ -422,7 +401,6 @@ describe('autoload/msgpack.vim', function() nvim_command('let spflt = ' .. sp('float', '1.0')) nvim_command('let spext = ' .. sp('ext', '[2, ["abc", "def"]]')) nvim_command('let spstr = ' .. sp('string', '["abc", "def"]')) - nvim_command('let spbin = ' .. sp('binary', '["abc", "def"]')) nvim_command('let spbln = ' .. sp('boolean', '0')) nvim_command('let spnil = ' .. sp('nil', '0')) @@ -432,7 +410,6 @@ describe('autoload/msgpack.vim', function() nvim_command('let spflt2 = msgpack#deepcopy(spflt)') nvim_command('let spext2 = msgpack#deepcopy(spext)') nvim_command('let spstr2 = msgpack#deepcopy(spstr)') - nvim_command('let spbin2 = msgpack#deepcopy(spbin)') nvim_command('let spbln2 = msgpack#deepcopy(spbln)') nvim_command('let spnil2 = msgpack#deepcopy(spnil)') @@ -442,7 +419,6 @@ describe('autoload/msgpack.vim', function() eq('float', nvim_eval('msgpack#type(spflt2)')) eq('ext', nvim_eval('msgpack#type(spext2)')) eq('string', nvim_eval('msgpack#type(spstr2)')) - eq('binary', nvim_eval('msgpack#type(spbin2)')) eq('boolean', nvim_eval('msgpack#type(spbln2)')) eq('nil', nvim_eval('msgpack#type(spnil2)')) @@ -457,7 +433,6 @@ describe('autoload/msgpack.vim', function() nvim_command('let spext._VAL[0] = 3') nvim_command('let spext._VAL[1][0] = "gh"') nvim_command('let spstr._VAL[0] = "gh"') - nvim_command('let spbin._VAL[0] = "gh"') nvim_command('let spbln._VAL = 1') nvim_command('let spnil._VAL = 1') @@ -467,7 +442,6 @@ describe('autoload/msgpack.vim', function() eq({ _TYPE = {}, _VAL = 1.0 }, nvim_eval('spflt2')) eq({ _TYPE = {}, _VAL = { 2, { 'abc', 'def' } } }, nvim_eval('spext2')) eq({ _TYPE = {}, _VAL = { 'abc', 'def' } }, nvim_eval('spstr2')) - eq({ _TYPE = {}, _VAL = { 'abc', 'def' } }, nvim_eval('spbin2')) eq({ _TYPE = {}, _VAL = 0 }, nvim_eval('spbln2')) eq({ _TYPE = {}, _VAL = 0 }, nvim_eval('spnil2')) @@ -477,7 +451,6 @@ describe('autoload/msgpack.vim', function() nvim_command('let spflt._TYPE = []') nvim_command('let spext._TYPE = []') nvim_command('let spstr._TYPE = []') - nvim_command('let spbin._TYPE = []') nvim_command('let spbln._TYPE = []') nvim_command('let spnil._TYPE = []') @@ -487,7 +460,6 @@ describe('autoload/msgpack.vim', function() eq('float', nvim_eval('msgpack#special_type(spflt2)')) eq('ext', nvim_eval('msgpack#special_type(spext2)')) eq('string', nvim_eval('msgpack#special_type(spstr2)')) - eq('binary', nvim_eval('msgpack#special_type(spbin2)')) eq('boolean', nvim_eval('msgpack#special_type(spbln2)')) eq('nil', nvim_eval('msgpack#special_type(spnil2)')) end) @@ -509,7 +481,7 @@ describe('autoload/msgpack.vim', function() eq('map', nvim_eval('msgpack#type(map2)')) eq('integer', nvim_eval('msgpack#type(int2)')) eq('float', nvim_eval('msgpack#type(flt2)')) - eq('binary', nvim_eval('msgpack#type(bin2)')) + eq('string', nvim_eval('msgpack#type(bin2)')) nvim_command('call add(arr, 0)') nvim_command('call add(arr[0], 0)') @@ -566,21 +538,6 @@ describe('autoload/msgpack.vim', function() nvim_command('unlet g:__val') end - it('correctly loads binary strings', function() - eval_eq('binary', { 'abcdef' }, '"abcdef"') - eval_eq('binary', { 'abc', 'def' }, '"abc\\ndef"') - eval_eq('binary', { 'abc\ndef' }, '"abc\\0def"') - eval_eq('binary', { '\nabc\ndef\n' }, '"\\0abc\\0def\\0"') - eval_eq('binary', { 'abc\n\n\ndef' }, '"abc\\0\\0\\0def"') - eval_eq('binary', { 'abc\n', '\ndef' }, '"abc\\0\\n\\0def"') - eval_eq('binary', { 'abc', '', '', 'def' }, '"abc\\n\\n\\ndef"') - eval_eq('binary', { 'abc', '', '', 'def', '' }, '"abc\\n\\n\\ndef\\n"') - eval_eq('binary', { '', 'abc', '', '', 'def' }, '"\\nabc\\n\\n\\ndef"') - eval_eq('binary', { '' }, '""') - eval_eq('binary', { '"' }, '"\\""') - eval_eq('binary', { 'py3 print(sys.version_info)' }, '"py3 print(sys.version_info)"') - end) - it('correctly loads strings', function() eval_eq('string', { 'abcdef' }, '="abcdef"') eval_eq('string', { 'abc', 'def' }, '="abc\\ndef"') diff --git a/test/functional/plugin/shada_spec.lua b/test/functional/plugin/shada_spec.lua index 1c2bcbd497..c9d49f7d01 100644 --- a/test/functional/plugin/shada_spec.lua +++ b/test/functional/plugin/shada_spec.lua @@ -68,7 +68,7 @@ describe('autoload/shada.vim', function() endfor return ret elseif type(a:val) == type('') - return {'_TYPE': v:msgpack_types.binary, '_VAL': split(a:val, "\n", 1)} + return {'_TYPE': v:msgpack_types.string, '_VAL': split(a:val, "\n", 1)} else return a:val endif @@ -253,8 +253,7 @@ describe('autoload/shada.vim', function() ' + sm magic value "TRUE"', ' # Expected integer', ' + so offset value "TRUE"', - ' # Expected binary string', - ' + sp pattern ="abc"', + ' + sp pattern "abc"', }, ([[ [{'type': 1, 'timestamp': 0, 'data': { 'sm': 'TRUE', @@ -267,7 +266,7 @@ describe('autoload/shada.vim', function() 'n': -0x40, 'l': -10, 'c': 'abc', - 'f': {'_TYPE': v:msgpack_types.binary, '_VAL': ["abc\ndef"]}, + 'f': {'_TYPE': v:msgpack_types.string, '_VAL': ["abc\ndef"]}, }}] ]]):gsub('\n', '') ) sd2strings_eq( @@ -276,15 +275,14 @@ describe('autoload/shada.vim', function() ' % Key Description Value', ' # Expected no NUL bytes', ' + f file name "abc\\0def"', - ' # Expected array of binary strings', - ' + rc contents ["abc", ="abc"]', + ' + rc contents ["abc", "abc"]', ' # Expected integer', ' + rt type "ABC"', }, ([[ [{'type': 1, 'timestamp': 0, 'data': { 'rt': 'ABC', 'rc': ["abc", {'_TYPE': v:msgpack_types.string, '_VAL': ["abc"]}], - 'f': {'_TYPE': v:msgpack_types.binary, '_VAL': ["abc\ndef"]}, + 'f': {'_TYPE': v:msgpack_types.string, '_VAL': ["abc\ndef"]}, }}] ]]):gsub('\n', '') ) sd2strings_eq( @@ -295,7 +293,7 @@ describe('autoload/shada.vim', function() ' + rc contents ["abc", "a\\nd\\0"]', }, ([[ [{'type': 1, 'timestamp': 0, 'data': { - 'rc': ["abc", {'_TYPE': v:msgpack_types.binary, '_VAL': ["a", "d\n"]}], + 'rc': ["abc", {'_TYPE': v:msgpack_types.string, '_VAL': ["a", "d\n"]}], }}] ]]):gsub('\n', '') ) end) @@ -468,7 +466,7 @@ describe('autoload/shada.vim', function() sd2strings_eq({ 'Replacement string with timestamp ' .. epoch .. ':', ' # Unexpected type: map instead of array', - ' = {="a": [10]}', + ' = {"a": [10]}', }, { { type = 3, timestamp = 0, data = { a = { 10 } } } }) sd2strings_eq( { @@ -498,7 +496,7 @@ describe('autoload/shada.vim', function() ' - :s replacement string "abc\\0def"', }, ([[ [{'type': 3, 'timestamp': 0, 'data': [ - {'_TYPE': v:msgpack_types.binary, '_VAL': ["abc\ndef"]}, + {'_TYPE': v:msgpack_types.string, '_VAL': ["abc\ndef"]}, ]}] ]]):gsub('\n', '') ) sd2strings_eq( @@ -508,7 +506,7 @@ describe('autoload/shada.vim', function() ' - :s replacement string "abc\\ndef"', }, ([[ [{'type': 3, 'timestamp': 0, 'data': [ - {'_TYPE': v:msgpack_types.binary, '_VAL': ["abc", "def"]}, + {'_TYPE': v:msgpack_types.string, '_VAL': ["abc", "def"]}, ]}] ]]):gsub('\n', '') ) sd2strings_eq( @@ -519,7 +517,7 @@ describe('autoload/shada.vim', function() ' - 0', }, ([[ [{'type': 3, 'timestamp': 0, 'data': [ - {'_TYPE': v:msgpack_types.binary, '_VAL': ["abc", "def"]}, + {'_TYPE': v:msgpack_types.string, '_VAL': ["abc", "def"]}, 0, ]}] ]]):gsub('\n', '') ) @@ -529,7 +527,7 @@ describe('autoload/shada.vim', function() sd2strings_eq({ 'History entry with timestamp ' .. epoch .. ':', ' # Unexpected type: map instead of array', - ' = {="a": [10]}', + ' = {"a": [10]}', }, { { type = 4, timestamp = 0, data = { a = { 10 } } } }) sd2strings_eq( { @@ -682,7 +680,7 @@ describe('autoload/shada.vim', function() }, ([[ [{'type': 4, 'timestamp': 0, 'data': [ 4, - {'_TYPE': v:msgpack_types.binary, '_VAL': ["abc\ndef"]}, + {'_TYPE': v:msgpack_types.string, '_VAL': ["abc\ndef"]}, ]}] ]]):gsub('\n', '') ) sd2strings_eq( @@ -909,7 +907,7 @@ describe('autoload/shada.vim', function() sd2strings_eq({ 'Variable with timestamp ' .. epoch .. ':', ' # Unexpected type: map instead of array', - ' = {="a": [10]}', + ' = {"a": [10]}', }, { { type = 6, timestamp = 0, data = { a = { 10 } } } }) sd2strings_eq( { @@ -941,7 +939,7 @@ describe('autoload/shada.vim', function() ' # Expected more elements in list', }, ([[ [{'type': 6, 'timestamp': 0, 'data': [ - {'_TYPE': v:msgpack_types.binary, '_VAL': ["\n"]}, + {'_TYPE': v:msgpack_types.string, '_VAL': ["\n"]}, ]}] ]]):gsub('\n', '') ) sd2strings_eq( @@ -952,7 +950,7 @@ describe('autoload/shada.vim', function() ' # Expected more elements in list', }, ([[ [{'type': 6, 'timestamp': 0, 'data': [ - {'_TYPE': v:msgpack_types.binary, '_VAL': ["foo"]}, + {'_TYPE': v:msgpack_types.string, '_VAL': ["foo"]}, ]}] ]]):gsub('\n', '') ) sd2strings_eq( @@ -963,7 +961,7 @@ describe('autoload/shada.vim', function() ' - value NIL', }, ([[ [{'type': 6, 'timestamp': 0, 'data': [ - {'_TYPE': v:msgpack_types.binary, '_VAL': ["foo"]}, + {'_TYPE': v:msgpack_types.string, '_VAL': ["foo"]}, {'_TYPE': v:msgpack_types.nil, '_VAL': ["foo"]}, ]}] ]]):gsub('\n', '') ) @@ -976,7 +974,7 @@ describe('autoload/shada.vim', function() ' - NIL', }, ([[ [{'type': 6, 'timestamp': 0, 'data': [ - {'_TYPE': v:msgpack_types.binary, '_VAL': ["foo"]}, + {'_TYPE': v:msgpack_types.string, '_VAL': ["foo"]}, {'_TYPE': v:msgpack_types.nil, '_VAL': ["foo"]}, {'_TYPE': v:msgpack_types.nil, '_VAL': ["foo"]}, ]}] ]]):gsub('\n', '') @@ -1041,7 +1039,7 @@ describe('autoload/shada.vim', function() }, ([[ [{'type': 7, 'timestamp': 0, 'data': { 'n': -10, - 'f': {'_TYPE': v:msgpack_types.binary, '_VAL': ["\n"]}, + 'f': {'_TYPE': v:msgpack_types.string, '_VAL': ["\n"]}, }}] ]]):gsub('\n', '') ) sd2strings_eq( @@ -1174,7 +1172,7 @@ describe('autoload/shada.vim', function() }, ([[ [{'type': 8, 'timestamp': 0, 'data': { 'n': -10, - 'f': {'_TYPE': v:msgpack_types.binary, '_VAL': ["\n"]}, + 'f': {'_TYPE': v:msgpack_types.string, '_VAL': ["\n"]}, }}] ]]):gsub('\n', '') ) sd2strings_eq( @@ -1237,7 +1235,7 @@ describe('autoload/shada.vim', function() sd2strings_eq({ 'Buffer list with timestamp ' .. epoch .. ':', ' # Unexpected type: map instead of array', - ' = {="a": [10]}', + ' = {"a": [10]}', }, { { type = 9, timestamp = 0, data = { a = { 10 } } } }) sd2strings_eq({ 'Buffer list with timestamp ' .. epoch .. ':', @@ -1247,7 +1245,7 @@ describe('autoload/shada.vim', function() sd2strings_eq({ 'Buffer list with timestamp ' .. epoch .. ':', ' # Expected array of maps', - ' = [{="a": 10}, []]', + ' = [{"a": 10}, []]', }, { { type = 9, timestamp = 0, data = { { a = 10 }, {} } } }) sd2strings_eq({ 'Buffer list with timestamp ' .. epoch .. ':', @@ -1322,7 +1320,7 @@ describe('autoload/shada.vim', function() }, ([[ [{'type': 9, 'timestamp': 0, 'data': [ {'f': 10}, - {'f': {'_TYPE': v:msgpack_types.binary, '_VAL': ["\n"]}}, + {'f': {'_TYPE': v:msgpack_types.string, '_VAL': ["\n"]}}, ]}] ]]):gsub('\n', '') ) end) @@ -1385,7 +1383,7 @@ describe('autoload/shada.vim', function() }, ([[ [{'type': 10, 'timestamp': 0, 'data': { 'n': -10, - 'f': {'_TYPE': v:msgpack_types.binary, '_VAL': ["\n"]}, + 'f': {'_TYPE': v:msgpack_types.string, '_VAL': ["\n"]}, }}] ]]):gsub('\n', '') ) sd2strings_eq( @@ -1504,7 +1502,7 @@ describe('autoload/shada.vim', function() }, ([[ [{'type': 11, 'timestamp': 0, 'data': { 'n': -10, - 'f': {'_TYPE': v:msgpack_types.binary, '_VAL': ["\n"]}, + 'f': {'_TYPE': v:msgpack_types.string, '_VAL': ["\n"]}, }}] ]]):gsub('\n', '') ) sd2strings_eq( @@ -1616,7 +1614,7 @@ describe('autoload/shada.vim', function() timestamp = 0, data = { c = 'abc', - f = { '!binary', { 'abc\ndef' } }, + f = { '!string', { 'abc\ndef' } }, l = -10, n = -64, rc = '10', @@ -1711,7 +1709,7 @@ describe('autoload/shada.vim', function() timestamp = 0, data = { c = 'abc', - f = { '!binary', { 'abc\ndef' } }, + f = { '!string', { 'abc\ndef' } }, l = -10, n = -64, rc = '10', @@ -1892,7 +1890,7 @@ describe('autoload/shada.vim', function() } } }, { 'Replacement string with timestamp ' .. epoch .. ':', ' # Unexpected type: map instead of array', - ' = {="a": [10]}', + ' = {"a": [10]}', }) strings2sd_eq({ { type = 3, timestamp = 0, data = {} } }, { 'Replacement string with timestamp ' .. epoch .. ':', @@ -1934,7 +1932,7 @@ describe('autoload/shada.vim', function() } } }, { 'History entry with timestamp ' .. epoch .. ':', ' # Unexpected type: map instead of array', - ' = {="a": [10]}', + ' = {"a": [10]}', }) strings2sd_eq({ { type = 4, timestamp = 0, data = {} } }, { 'History entry with timestamp ' .. epoch .. ':', @@ -2184,7 +2182,7 @@ describe('autoload/shada.vim', function() } } }, { 'Variable with timestamp ' .. epoch .. ':', ' # Unexpected type: map instead of array', - ' = {="a": [10]}', + ' = {"a": [10]}', }) strings2sd_eq({ { type = 6, timestamp = 0, data = {} } }, { 'Variable with timestamp ' .. epoch .. ':', @@ -2315,7 +2313,7 @@ describe('autoload/shada.vim', function() } } }, { 'Buffer list with timestamp ' .. epoch .. ':', ' # Unexpected type: map instead of array', - ' = {="a": [10]}', + ' = {"a": [10]}', }) strings2sd_eq( { { type = 9, timestamp = 0, data = { @@ -2325,7 +2323,7 @@ describe('autoload/shada.vim', function() { 'Buffer list with timestamp ' .. epoch .. ':', ' # Expected array of maps', - ' = [{="a": 10}, []]', + ' = [{"a": 10}, []]', } ) strings2sd_eq({ { type = 9, timestamp = 0, data = { @@ -2421,7 +2419,7 @@ describe('autoload/shada.vim', function() timestamp = 0, data = { { f = 10 }, - { f = { '!binary', { '\n' } } }, + { f = { '!string', { '\n' } } }, }, }, }, { @@ -2955,7 +2953,7 @@ describe('ftplugin/shada.vim', function() ' - :s replacement string "abc\\ndef"', ' Buffer list with timestamp ' .. epoch .. ':', ' # Expected array of maps', - '= [{="a": 10}, []]', + '= [{"a": 10}, []]', ' Buffer list with timestamp ' .. epoch .. ':', ' % Key Description Value', ' # Expected binary string', @@ -2992,7 +2990,7 @@ describe('ftplugin/shada.vim', function() ' - :s replacement string "abc\\ndef"', 'Buffer list with timestamp ' .. epoch .. ':', ' # Expected array of maps', - ' = [{="a": 10}, []]', + ' = [{"a": 10}, []]', 'Buffer list with timestamp ' .. epoch .. ':', ' % Key Description Value', ' # Expected binary string', @@ -3083,7 +3081,7 @@ describe('syntax/shada.vim', function() ' - :s replacement string DEBUG', 'Buffer list with timestamp ' .. epoch .. ':', ' # Expected array of maps', - ' = [{="a": +(10)"ac\\0df\\ngi\\"tt\\.", TRUE: FALSE}, [NIL, +(-10)""]]', + ' = [{"a": +(10)"ac\\0df\\ngi\\"tt\\.", TRUE: FALSE}, [NIL, +(-10)""]]', 'Buffer list with timestamp ' .. epoch .. ':', ' % Key Description Value', '', @@ -3119,8 +3117,8 @@ describe('syntax/shada.vim', function() {1: -} {4::s replacement string} {1:DEBUG} | {1:Buffer list} with timestamp 1970{1:-}01{1:-}01{1:T}00{1::}00{1::}00: | {4: # Expected array of maps} | - = {1:[{="}{3:a}{1:":} {1:+(}{5:10}{1:)"}{3:ac}{6:\0}{3:df}{6:\n}{3:gi}{6:\"}{3:tt\.}{1:",} {1:TRUE:} {1:FALSE},} {1:[NIL,} {1:+(}{5:-}| - {5:10}{1:)""]]} | + = {1:[{"}{3:a}{1:":} {1:+(}{5:10}{1:)"}{3:ac}{6:\0}{3:df}{6:\n}{3:gi}{6:\"}{3:tt\.}{1:",} {1:TRUE:} {1:FALSE},} {1:[NIL,} {1:+(}{5:-1}| + {5:0}{1:)""]]} | {1:Buffer list} with timestamp 1970{1:-}01{1:-}01{1:T}00{1::}00{1::}00: | {2: % Key Description Value} | | @@ -3464,7 +3462,6 @@ describe('syntax/shada.vim', function() { { 'ShaDaEntryRawMsgpack' }, ' = ' }, { { 'ShaDaMsgpackArray', 'ShaDaMsgpackArrayBraces' }, '[' }, { { 'ShaDaMsgpackArray', 'ShaDaMsgpackMap', 'ShaDaMsgpackMapBraces' }, '{' }, - { { 'ShaDaMsgpackArray', 'ShaDaMsgpackMap', 'ShaDaMsgpackString' }, '=' }, { { 'ShaDaMsgpackArray', diff --git a/test/functional/plugin/tohtml_spec.lua b/test/functional/plugin/tohtml_spec.lua index 200a5f34b2..1d05f4d6b4 100644 --- a/test/functional/plugin/tohtml_spec.lua +++ b/test/functional/plugin/tohtml_spec.lua @@ -33,6 +33,10 @@ local function html_syntax_match() attr.underline = nil attr.undercurl = true end + attr.sp = style:match('text%-decoration%-color: #(%x+)') + if attr.sp then + attr.sp = tonumber(attr.sp, 16) + end attr.bg = style:match('background%-color: #(%x+)') if attr.bg then attr.bg = tonumber(attr.bg, 16) @@ -49,7 +53,7 @@ local function html_syntax_match() local whitelist = { 'fg', 'bg', - --'sp', + 'sp', --'blend', 'bold', --'standout', @@ -132,6 +136,50 @@ local function run_tohtml_and_assert(screen, func) screen:expect({ grid = expected.grid, attr_ids = expected.attr_ids }) end +---@param guifont boolean +local function test_generates_html(guifont, expect_font) + insert([[line]]) + exec('set termguicolors') + local bg = fn.synIDattr(fn.hlID('Normal'), 'bg#', 'gui') + local fg = fn.synIDattr(fn.hlID('Normal'), 'fg#', 'gui') + local tmpfile = t.tmpname() + + exec_lua( + [[ + local guifont, outfile = ... + local html = (guifont + and require('tohtml').tohtml(0,{title="title"}) + or require('tohtml').tohtml(0,{title="title",font={ "dumyfont","anotherfont" }})) + vim.fn.writefile(html, outfile) + vim.cmd.split(outfile) + ]], + guifont, + tmpfile + ) + + local out_file = api.nvim_buf_get_name(api.nvim_get_current_buf()) + eq({ + '<!DOCTYPE html>', + '<html>', + '<head>', + '<meta charset="UTF-8">', + '<title>title</title>', + ('<meta name="colorscheme" content="%s"></meta>'):format(api.nvim_get_var('colors_name')), + '<style>', + ('* {font-family: %s,monospace}'):format(expect_font), + ('body {background-color: %s; color: %s}'):format(bg, fg), + '</style>', + '</head>', + '<body style="display: flex">', + '<pre>', + 'line', + '', + '</pre>', + '</body>', + '</html>', + }, fn.readfile(out_file)) +end + describe(':TOhtml', function() --- @type test.functional.ui.screen local screen @@ -142,33 +190,44 @@ describe(':TOhtml', function() exec('colorscheme default') end) - it('expected internal html generated', function() - insert([[line]]) + it('generates html with given font', function() + test_generates_html(false, '"dumyfont","anotherfont"') + end) + + it("generates html, respects 'guifont'", function() + exec_lua [[vim.o.guifont='Font,Escape\\,comma, Ignore space after comma']] + test_generates_html(true, '"Font","Escape,comma","Ignore space after comma"') + end) + + it('generates html from range', function() + insert([[ + line1 + line2 + line3 + ]]) + local ns = api.nvim_create_namespace '' + api.nvim_buf_set_extmark(0, ns, 0, 0, { end_col = 1, end_row = 1, hl_group = 'Visual' }) exec('set termguicolors') local bg = fn.synIDattr(fn.hlID('Normal'), 'bg#', 'gui') local fg = fn.synIDattr(fn.hlID('Normal'), 'fg#', 'gui') - exec_lua [[ - local outfile = vim.fn.tempname() .. '.html' - local html = require('tohtml').tohtml(0,{title="title",font="dumyfont"}) - vim.fn.writefile(html, outfile) - vim.cmd.split(outfile) - ]] + n.command('2,2TOhtml') local out_file = api.nvim_buf_get_name(api.nvim_get_current_buf()) eq({ '<!DOCTYPE html>', '<html>', '<head>', '<meta charset="UTF-8">', - '<title>title</title>', + '<title></title>', ('<meta name="colorscheme" content="%s"></meta>'):format(api.nvim_get_var('colors_name')), '<style>', - '* {font-family: dumyfont,monospace}', + '* {font-family: monospace}', ('body {background-color: %s; color: %s}'):format(bg, fg), + '.Visual {background-color: #9b9ea4}', '</style>', '</head>', '<body style="display: flex">', - '<pre>', - 'line', + '<pre><span class="Visual">', + 'l</span>ine2', '', '</pre>', '</body>', @@ -176,9 +235,9 @@ describe(':TOhtml', function() }, fn.readfile(out_file)) end) - it('highlight attributes generated', function() + it('generates highlight attributes', function() --Make sure to uncomment the attribute in `html_syntax_match()` - exec('hi LINE gui=' .. table.concat({ + exec('hi LINE guisp=#00ff00 gui=' .. table.concat({ 'bold', 'underline', 'italic', @@ -287,7 +346,13 @@ describe(':TOhtml', function() 0, { virt_text = { { 'foo' } }, virt_text_pos = 'overlay' } ) - api.nvim_buf_set_extmark(0, ns, 2, 0, { virt_text = { { 'foo' } }, virt_text_pos = 'inline' }) + api.nvim_buf_set_extmark( + 0, + ns, + 2, + 0, + { virt_text = { { 'fo┊o', { 'Conceal', 'Comment' } } }, virt_text_pos = 'inline' } + ) --api.nvim_buf_set_extmark(0,ns,3,0,{virt_text={{'foo'}},virt_text_pos='right_align'}) run_tohtml_and_assert(screen) end) @@ -341,12 +406,12 @@ describe(':TOhtml', function() local function run() local buf = api.nvim_get_current_buf() run_tohtml_and_assert(screen, function() - exec_lua [[ - local outfile = vim.fn.tempname() .. '.html' - local html = require('tohtml').tohtml(0,{number_lines=true}) - vim.fn.writefile(html, outfile) - vim.cmd.split(outfile) - ]] + exec_lua(function() + local outfile = vim.fn.tempname() .. '.html' + local html = require('tohtml').tohtml(0, { number_lines = true }) + vim.fn.writefile(html, outfile) + vim.cmd.split(outfile) + end) end) api.nvim_set_current_buf(buf) end diff --git a/test/functional/provider/clipboard_spec.lua b/test/functional/provider/clipboard_spec.lua index 9e7df0ba6b..1094f9f4e5 100644 --- a/test/functional/provider/clipboard_spec.lua +++ b/test/functional/provider/clipboard_spec.lua @@ -94,12 +94,6 @@ describe('clipboard', function() before_each(function() clear() screen = Screen.new(72, 4) - screen:set_default_attr_ids({ - [0] = { bold = true, foreground = Screen.colors.Blue }, - [1] = { foreground = Screen.colors.Grey100, background = Screen.colors.Red }, - [2] = { bold = true, foreground = Screen.colors.SeaGreen4 }, - [3] = { bold = true, reverse = true }, - }) screen:attach() end) @@ -114,13 +108,13 @@ describe('clipboard', function() feed('"+yl') screen:expect([[ ^a | - {0:~ }|*2 + {1:~ }|*2 clipboard: No provider. Try ":checkhealth" or ":h clipboard". | ]]) feed('"+p') screen:expect([[ a^a | - {0:~ }|*2 + {1:~ }|*2 clipboard: No provider. Try ":checkhealth" or ":h clipboard". | ]]) end) @@ -132,19 +126,19 @@ describe('clipboard', function() feed('yl') screen:expect([[ ^a | - {0:~ }|*2 + {1:~ }|*2 clipboard: No provider. Try ":checkhealth" or ":h clipboard". | ]]) feed(':<CR>') screen:expect([[ ^a | - {0:~ }|*2 + {1:~ }|*2 : | ]]) feed('p') screen:expect([[ a^a | - {0:~ }|*2 + {1:~ }|*2 : | ]]) end) @@ -154,7 +148,7 @@ describe('clipboard', function() feed_command('redir @+> | :silent echo system("cat CONTRIBUTING.md") | redir END') screen:expect([[ ^ | - {0:~ }|*2 + {1:~ }|*2 clipboard: No provider. Try ":checkhealth" or ":h clipboard". | ]]) end) @@ -166,8 +160,8 @@ describe('clipboard', function() grid = [[ {3: }| clipboard: No provider. Try ":checkhealth" or ":h clipboard". | - {1:E492: Not an editor command: bogus_cmd | redir END} | - {2:Press ENTER or type command to continue}^ | + {9:E492: Not an editor command: bogus_cmd | redir END} | + {6:Press ENTER or type command to continue}^ | ]], } end) @@ -182,7 +176,7 @@ describe('clipboard', function() feed_command('let @+="foo"') screen:expect([[ ^ | - {0:~ }|*2 + {1:~ }|*2 clipboard: No provider. Try ":checkhealth" or ":h clipboard". | ]]) end) @@ -325,15 +319,11 @@ describe('clipboard (with fake clipboard.vim)', function() it('`:redir @+>|bogus_cmd|redir END` must not recurse #7184', function() local screen = Screen.new(72, 4) screen:attach() - screen:set_default_attr_ids({ - [0] = { bold = true, foreground = Screen.colors.Blue }, - [1] = { foreground = Screen.colors.Grey100, background = Screen.colors.Red }, - }) feed_command('redir @+> | bogus_cmd | redir END') screen:expect([[ ^ | - {0:~ }|*2 - {1:E492: Not an editor command: bogus_cmd | redir END} | + {1:~ }|*2 + {9:E492: Not an editor command: bogus_cmd | redir END} | ]]) end) @@ -719,9 +709,6 @@ describe('clipboard (with fake clipboard.vim)', function() feed_command('set mouse=a') local screen = Screen.new(30, 5) - screen:set_default_attr_ids({ - [0] = { bold = true, foreground = Screen.colors.Blue }, - }) screen:attach() insert([[ the source @@ -731,7 +718,7 @@ describe('clipboard (with fake clipboard.vim)', function() screen:expect([[ the ^source | a target | - {0:~ }|*2 + {1:~ }|*2 | ]]) diff --git a/test/functional/script/luacats_grammar_spec.lua b/test/functional/script/luacats_grammar_spec.lua index 6d444e1888..8bc55879d4 100644 --- a/test/functional/script/luacats_grammar_spec.lua +++ b/test/functional/script/luacats_grammar_spec.lua @@ -159,4 +159,129 @@ describe('luacats grammar', function() name = 'type', type = '`T`', }) + + test('@param type [number,string,"good"|"bad"] this is a tuple type', { + desc = 'this is a tuple type', + kind = 'param', + name = 'type', + type = '[number,string,"good"|"bad"]', + }) + + test('@class vim.diagnostic.JumpOpts', { + kind = 'class', + name = 'vim.diagnostic.JumpOpts', + }) + + test('@class vim.diagnostic.JumpOpts : vim.diagnostic.GetOpts', { + kind = 'class', + name = 'vim.diagnostic.JumpOpts', + parent = 'vim.diagnostic.GetOpts', + }) + + test('@param opt? { cmd?: string[] } Options', { + kind = 'param', + name = 'opt?', + type = '{ cmd?: string[] }', + desc = 'Options', + }) + + ---@type [string, string?][] + local test_cases = { + { 'foo' }, + { 'foo ', 'foo' }, -- trims whitespace + { 'true' }, + { 'vim.type' }, + { 'vim-type' }, + { 'vim_type' }, + { 'foo.bar-baz_baz' }, + { '`ABC`' }, + { '42' }, + { '-42' }, + { '(foo)', 'foo' }, -- removes unnecessary parens + { 'true?' }, + { '(true)?' }, + { 'string[]' }, + { 'string|number' }, + { '(string)[]' }, + { '(string|number)[]' }, + { 'coalesce??', 'coalesce?' }, -- removes unnecessary ? + { 'number?|string' }, + { "'foo'|'bar'|'baz'" }, + { '"foo"|"bar"|"baz"' }, + { '(number)?|string' }, -- + { 'number[]|string' }, + { 'string[]?' }, + { 'foo?[]' }, + { 'vim.type?|string? ', 'vim.type?|string?' }, + { 'number[][]' }, + { 'number[][][]' }, + { 'number[][]?' }, + { 'string|integer[][]?' }, + + -- tuples + { '[string]' }, + { '[1]' }, + { '[string, number]' }, + { '[string, number]?' }, + { '[string, number][]' }, + { '[string, number]|string' }, + { '[string|number, number?]' }, + { 'string|[string, number]' }, + { '(true)?|[foo]' }, + { '[fun(a: string):boolean]' }, + + -- dict + { '{[string]:string}' }, + { '{ [ string ] : string }' }, + { '{ [ string|any ] : string }' }, + { '{[string]: string, [number]: boolean}' }, + + -- key-value table + { 'table<string,any>' }, + { 'table' }, + { 'string|table|boolean' }, + { 'string|table|(boolean)' }, + + -- table literal + { '{foo: number}' }, + { '{foo: string, bar: [number, boolean]?}' }, + { 'boolean|{reverse?:boolean}' }, + { '{ cmd?: string[] }' }, + + -- function + { 'fun(a: string, b:foo|bar): string' }, + { 'fun(a?: string): string' }, + { 'fun(a?: string): number?,string?' }, + { '(fun(a: string, b:foo|bar): string)?' }, + { 'fun(a: string, b:foo|bar): string, string' }, + { 'fun(a: string, b:foo|bar)' }, + { 'fun(_, foo, bar): string' }, + { 'fun(...): number' }, + { 'fun( ... ): number' }, + { 'fun(...:number): number' }, + { 'fun( ... : number): number' }, + + -- generics + { 'elem_or_list<string>' }, + { + 'elem_or_list<fun(client: vim.lsp.Client, initialize_result: lsp.InitializeResult)>', + nil, + }, + } + + for _, tc in ipairs(test_cases) do + local ty, exp_ty = tc[1], tc[2] + if exp_ty == nil then + exp_ty = ty + end + + local var, desc = 'x', 'some desc' + local param = string.format('@param %s %s %s', var, ty, desc) + test(param, { + kind = 'param', + name = var, + type = exp_ty, + desc = desc, + }) + end end) diff --git a/test/functional/script/text_utils_spec.lua b/test/functional/script/text_utils_spec.lua index 176c2ef816..74098b9287 100644 --- a/test/functional/script/text_utils_spec.lua +++ b/test/functional/script/text_utils_spec.lua @@ -11,8 +11,8 @@ local function md_to_vimdoc(text, start_indent, indent, text_width) start_indent = start_indent or 0 indent = indent or 0 text_width = text_width or 70 - local text_utils = require('scripts/text_utils') - return text_utils.md_to_vimdoc(table.concat(text, '\n'), start_indent, indent, text_width) + local util = require('scripts/util') + return util.md_to_vimdoc(table.concat(text, '\n'), start_indent, indent, text_width) ]], text, start_indent, diff --git a/test/functional/shada/errors_spec.lua b/test/functional/shada/errors_spec.lua index a9084da929..0016e0bdf9 100644 --- a/test/functional/shada/errors_spec.lua +++ b/test/functional/shada/errors_spec.lua @@ -28,7 +28,7 @@ describe('ShaDa error handling', function() it('fails on zero', function() wshada('\000') eq( - 'Vim(rshada):E576: Error while reading ShaDa file: expected positive integer at position 0, but got nothing', + 'Vim(rshada):E576: Error while reading ShaDa file: expected positive integer at position 1, but got nothing', exc_exec(sdrcmd()) ) end) @@ -58,7 +58,7 @@ describe('ShaDa error handling', function() it('fails on search pattern item with zero length', function() wshada('\002\000\000') eq( - 'Vim(rshada):E576: Failed to parse ShaDa file: incomplete msgpack string at position 3', + 'Vim(rshada):E575: Error while reading ShaDa file: search pattern entry at position 0 is not a dict', exc_exec(sdrcmd()) ) end) @@ -89,18 +89,10 @@ describe('ShaDa error handling', function() it('fails on search pattern item with invalid byte', function() -- 195 (== 0xC1) cannot start any valid messagepack entry (the only byte - -- that cannot do this). Specifically unpack_template.h contains - -- - -- //case 0xc1: // string - -- // again_terminal_trail(NEXT_CS(p), p+1); - -- - -- (literally: commented out code) which means that in place of this code - -- `goto _failed` is used from default: case. I do not know any other way to - -- get MSGPACK_UNPACK_PARSE_ERROR and not MSGPACK_UNPACK_CONTINUE or - -- MSGPACK_UNPACK_EXTRA_BYTES. + -- that cannot do this) wshada('\002\000\001\193') eq( - 'Vim(rshada):E576: Failed to parse ShaDa file due to a msgpack parser error at position 3', + 'Vim(rshada):E575: Error while reading ShaDa file: search pattern entry at position 0 is not a dict', exc_exec(sdrcmd()) ) end) @@ -108,7 +100,7 @@ describe('ShaDa error handling', function() it('fails on search pattern item with incomplete map', function() wshada('\002\000\001\129') eq( - 'Vim(rshada):E576: Failed to parse ShaDa file: incomplete msgpack string at position 3', + 'Vim(rshada):E575: Error while reading ShaDa file: search pattern entry at position 0 has key value which is not a string', exc_exec(sdrcmd()) ) end) @@ -124,7 +116,7 @@ describe('ShaDa error handling', function() it('fails on search pattern with extra bytes', function() wshada('\002\000\002\128\000') eq( - 'Vim(rshada):E576: Failed to parse ShaDa file: extra bytes in msgpack string at position 3', + 'Vim(rshada):E575: Error while reading ShaDa file: search pattern entry at position 0 has no pattern', exc_exec(sdrcmd()) ) end) @@ -132,16 +124,7 @@ describe('ShaDa error handling', function() it('fails on search pattern item with NIL value', function() wshada('\002\000\001\192') eq( - 'Vim(rshada):E575: Error while reading ShaDa file: search pattern entry at position 0 is not a dictionary', - exc_exec(sdrcmd()) - ) - end) - - -- sp entry is here because it causes an allocation. - it('fails on search pattern item with BIN key', function() - wshada('\002\000\014\131\162sp\196\001a\162sX\192\196\000\000') - eq( - 'Vim(rshada):E575: Error while reading ShaDa file: search pattern entry at position 0 has key which is not a string', + 'Vim(rshada):E575: Error while reading ShaDa file: search pattern entry at position 0 is not a dict', exc_exec(sdrcmd()) ) end) @@ -235,35 +218,16 @@ describe('ShaDa error handling', function() ) end) - it('fails on search pattern item with STR pat key value', function() - wshada('\002\000\011\130\162sX\192\162sp\162sp') - eq( - 'Vim(rshada):E575: Error while reading ShaDa file: search pattern entry at position 0 has sp key value which is not a binary', - exc_exec(sdrcmd()) - ) - end) - for _, v in ipairs({ { name = 'global mark', mpack = '\007' }, { name = 'jump', mpack = '\008' }, { name = 'local mark', mpack = '\010' }, { name = 'change', mpack = '\011' }, }) do - local is_mark_test = ({ ['global mark'] = true, ['local mark'] = true })[v.name] - it('fails on ' .. v.name .. ' item with NIL value', function() wshada(v.mpack .. '\000\001\192') eq( - 'Vim(rshada):E575: Error while reading ShaDa file: mark entry at position 0 is not a dictionary', - exc_exec(sdrcmd()) - ) - end) - - -- f entry is here because it causes an allocation. - it('fails on ' .. v.name .. ' item with BIN key', function() - wshada(v.mpack .. '\000\013\131\161f\196\001/\162mX\192\196\000\000') - eq( - 'Vim(rshada):E575: Error while reading ShaDa file: mark entry at position 0 has key which is not a string', + 'Vim(rshada):E575: Error while reading ShaDa file: mark entry at position 0 is not a dict', exc_exec(sdrcmd()) ) end) @@ -312,9 +276,7 @@ describe('ShaDa error handling', function() it('fails on ' .. v.name .. ' item with STR n key value', function() wshada(v.mpack .. '\000\011\130\162mX\192\161n\163spa') eq( - is_mark_test - and 'Vim(rshada):E575: Error while reading ShaDa file: mark entry at position 0 has n key value which is not an unsigned integer' - or 'Vim(rshada):E575: Error while reading ShaDa file: mark entry at position 0 has n key which is only valid for local and global mark entries', + 'Vim(rshada):E575: Error while reading ShaDa file: mark entry at position 0 has n key value which is not an integer', exc_exec(sdrcmd()) ) end) @@ -334,29 +296,12 @@ describe('ShaDa error handling', function() exc_exec(sdrcmd()) ) end) - - it('fails on ' .. v.name .. ' item with STR f key value', function() - wshada(v.mpack .. '\000\010\130\162mX\192\161f\162sp') - eq( - 'Vim(rshada):E575: Error while reading ShaDa file: mark entry at position 0 has f key value which is not a binary', - exc_exec(sdrcmd()) - ) - end) end it('fails on register item with NIL value', function() wshada('\005\000\001\192') eq( - 'Vim(rshada):E575: Error while reading ShaDa file: register entry at position 0 is not a dictionary', - exc_exec(sdrcmd()) - ) - end) - - -- rc entry is here because it causes an allocation - it('fails on register item with BIN key', function() - wshada('\005\000\015\131\162rc\145\196\001a\162rX\192\196\000\000') - eq( - 'Vim(rshada):E575: Error while reading ShaDa file: register entry at position 0 has key which is not a string', + 'Vim(rshada):E575: Error while reading ShaDa file: register entry at position 0 is not a dict', exc_exec(sdrcmd()) ) end) @@ -373,7 +318,7 @@ describe('ShaDa error handling', function() it('fails on register item with NIL rt key value', function() wshada('\005\000\009\130\162rX\192\162rt\192') eq( - 'Vim(rshada):E575: Error while reading ShaDa file: register entry at position 0 has rt key value which is not an unsigned integer', + 'Vim(rshada):E575: Error while reading ShaDa file: register entry at position 0 has rt key value which is not an integer', exc_exec(sdrcmd()) ) end) @@ -381,7 +326,7 @@ describe('ShaDa error handling', function() it('fails on register item with NIL rw key value', function() wshada('\005\000\009\130\162rX\192\162rw\192') eq( - 'Vim(rshada):E575: Error while reading ShaDa file: register entry at position 0 has rw key value which is not an unsigned integer', + 'Vim(rshada):E575: Error while reading ShaDa file: register entry at position 0 has rw key value which is not an integer', exc_exec(sdrcmd()) ) end) @@ -397,7 +342,7 @@ describe('ShaDa error handling', function() it('fails on register item with empty rc key value', function() wshada('\005\000\009\130\162rX\192\162rc\144') eq( - 'Vim(rshada):E575: Error while reading ShaDa file: register entry at position 0 has rc key with empty array', + 'Vim(rshada):E575: Error while reading ShaDa file: register entry at position 0 has rc key with missing or empty array', exc_exec(sdrcmd()) ) end) @@ -413,7 +358,7 @@ describe('ShaDa error handling', function() it('fails on register item without rc array', function() wshada('\005\000\009\129\162rX\146\196\001a\192') eq( - 'Vim(rshada):E575: Error while reading ShaDa file: register entry at position 0 has missing rc array', + 'Vim(rshada):E575: Error while reading ShaDa file: register entry at position 0 has rc key with missing or empty array', exc_exec(sdrcmd()) ) end) @@ -421,7 +366,7 @@ describe('ShaDa error handling', function() it('fails on history item with NIL value', function() wshada('\004\000\001\192') eq( - 'Vim(rshada):E575: Error while reading ShaDa file: history entry at position 0 is not an array', + 'Vim(rshada):E575: Error while reading ShaDa file: history entry at position 0 is not an array with enough elements', exc_exec(sdrcmd()) ) end) @@ -429,7 +374,7 @@ describe('ShaDa error handling', function() it('fails on history item with empty value', function() wshada('\004\000\001\144') eq( - 'Vim(rshada):E575: Error while reading ShaDa file: history entry at position 0 does not have enough elements', + 'Vim(rshada):E575: Error while reading ShaDa file: history entry at position 0 is not an array with enough elements', exc_exec(sdrcmd()) ) end) @@ -437,7 +382,7 @@ describe('ShaDa error handling', function() it('fails on history item with single element value', function() wshada('\004\000\002\145\000') eq( - 'Vim(rshada):E575: Error while reading ShaDa file: history entry at position 0 does not have enough elements', + 'Vim(rshada):E575: Error while reading ShaDa file: history entry at position 0 is not an array with enough elements', exc_exec(sdrcmd()) ) end) @@ -485,7 +430,7 @@ describe('ShaDa error handling', function() it('fails on variable item with NIL value', function() wshada('\006\000\001\192') eq( - 'Vim(rshada):E575: Error while reading ShaDa file: variable entry at position 0 is not an array', + 'Vim(rshada):E575: Error while reading ShaDa file: variable entry at position 0 is not an array with enough elements', exc_exec(sdrcmd()) ) end) @@ -493,7 +438,7 @@ describe('ShaDa error handling', function() it('fails on variable item with empty value', function() wshada('\006\000\001\144') eq( - 'Vim(rshada):E575: Error while reading ShaDa file: variable entry at position 0 does not have enough elements', + 'Vim(rshada):E575: Error while reading ShaDa file: variable entry at position 0 is not an array with enough elements', exc_exec(sdrcmd()) ) end) @@ -501,7 +446,7 @@ describe('ShaDa error handling', function() it('fails on variable item with single element value', function() wshada('\006\000\002\145\000') eq( - 'Vim(rshada):E575: Error while reading ShaDa file: variable entry at position 0 does not have enough elements', + 'Vim(rshada):E575: Error while reading ShaDa file: variable entry at position 0 is not an array with enough elements', exc_exec(sdrcmd()) ) end) @@ -525,7 +470,7 @@ describe('ShaDa error handling', function() it('fails on replacement item with NIL value', function() wshada('\003\000\001\192') eq( - 'Vim(rshada):E575: Error while reading ShaDa file: sub string entry at position 0 is not an array', + 'Vim(rshada):E575: Error while reading ShaDa file: sub string entry at position 0 is not an array with enough elements', exc_exec(sdrcmd()) ) end) @@ -533,7 +478,7 @@ describe('ShaDa error handling', function() it('fails on replacement item with empty value', function() wshada('\003\000\001\144') eq( - 'Vim(rshada):E575: Error while reading ShaDa file: sub string entry at position 0 does not have enough elements', + 'Vim(rshada):E575: Error while reading ShaDa file: sub string entry at position 0 is not an array with enough elements', exc_exec(sdrcmd()) ) end) @@ -559,7 +504,7 @@ describe('ShaDa error handling', function() nvim_command('set shada+=%') wshada('\009\000\008\146\129\161f\196\001/\192') eq( - 'Vim(rshada):E575: Error while reading ShaDa file: buffer list at position 0 contains entry that is not a dictionary', + 'Vim(rshada):E575: Error while reading ShaDa file: buffer list at position 0 contains entry that is not a dict', exc_exec(sdrcmd()) ) end) @@ -577,7 +522,7 @@ describe('ShaDa error handling', function() nvim_command('set shada+=%') wshada('\009\000\017\146\129\161f\196\001/\130\161f\196\002/a\161l\192') eq( - 'Vim(rshada):E575: Error while reading ShaDa file: buffer list entry entry at position 0 has l key value which is not an integer', + 'Vim(rshada):E575: Error while reading ShaDa file: buffer list at position 0 contains entry that has l key value which is not an integer', exc_exec(sdrcmd()) ) end) @@ -613,7 +558,7 @@ describe('ShaDa error handling', function() nvim_command('set shada+=%') wshada('\009\000\017\146\129\161f\196\001/\130\161f\196\002/a\161c\192') eq( - 'Vim(rshada):E575: Error while reading ShaDa file: buffer list entry entry at position 0 has c key value which is not an integer', + 'Vim(rshada):E575: Error while reading ShaDa file: buffer list at position 0 contains entry that has c key value which is not an integer', exc_exec(sdrcmd()) ) end) diff --git a/test/functional/terminal/altscreen_spec.lua b/test/functional/terminal/altscreen_spec.lua index 12c8615799..4a61e0203d 100644 --- a/test/functional/terminal/altscreen_spec.lua +++ b/test/functional/terminal/altscreen_spec.lua @@ -1,7 +1,7 @@ local t = require('test.testutil') local n = require('test.functional.testnvim')() -local tt = require('test.functional.terminal.testutil') +local tt = require('test.functional.testterm') local clear, eq, api = n.clear, t.eq, n.api local feed = n.feed local feed_data = tt.feed_data @@ -17,7 +17,7 @@ describe(':terminal altscreen', function() before_each(function() clear() - screen = tt.screen_setup() + screen = tt.setup_screen() feed_data({ 'line1', 'line2', diff --git a/test/functional/terminal/api_spec.lua b/test/functional/terminal/api_spec.lua index 1f10dda551..b550df80c3 100644 --- a/test/functional/terminal/api_spec.lua +++ b/test/functional/terminal/api_spec.lua @@ -1,7 +1,7 @@ local t = require('test.testutil') local n = require('test.functional.testnvim')() -local tt = require('test.functional.terminal.testutil') +local tt = require('test.functional.testterm') local ok = t.ok if t.skip(t.is_os('win')) then diff --git a/test/functional/terminal/buffer_spec.lua b/test/functional/terminal/buffer_spec.lua index 96abd9f543..7a30367917 100644 --- a/test/functional/terminal/buffer_spec.lua +++ b/test/functional/terminal/buffer_spec.lua @@ -1,7 +1,7 @@ local t = require('test.testutil') local n = require('test.functional.testnvim')() local Screen = require('test.functional.ui.screen') -local tt = require('test.functional.terminal.testutil') +local tt = require('test.functional.testterm') local assert_alive = n.assert_alive local feed, clear = n.feed, n.clear @@ -29,7 +29,7 @@ describe(':terminal buffer', function() before_each(function() clear() command('set modifiable swapfile undolevels=20') - screen = tt.screen_setup() + screen = tt.setup_screen() end) it('terminal-mode forces various options', function() @@ -199,7 +199,7 @@ describe(':terminal buffer', function() {5:========== }| rows: 2, cols: 50 | {2: } | - {1:========== }| + {18:========== }| | ]]) @@ -312,6 +312,16 @@ describe(':terminal buffer', function() pcall_err(command, 'write test/functional/fixtures/tty-test.c') ) end) + + it('external interrupt (got_int) does not hang #20726', function() + eq({ mode = 't', blocking = false }, api.nvim_get_mode()) + command('call timer_start(0, {-> interrupt()})') + feed('<Ignore>') -- Add input to separate two RPC requests + eq({ mode = 't', blocking = false }, api.nvim_get_mode()) + feed([[<C-\><C-N>]]) + eq({ mode = 'nt', blocking = false }, api.nvim_get_mode()) + command('bd!') + end) end) describe(':terminal buffer', function() @@ -332,7 +342,7 @@ describe(':terminal buffer', function() command('wincmd p') -- cwd will be inserted in a file URI, which cannot contain backs - local cwd = fn.getcwd():gsub('\\', '/') + local cwd = t.fix_slashes(fn.getcwd()) local parent = cwd:match('^(.+/)') local expected = '\027]7;file://host' .. parent api.nvim_chan_send(term, string.format('%s\027\\', expected)) @@ -340,7 +350,7 @@ describe(':terminal buffer', function() eq(termbuf, eval('g:termbuf')) end) - it('TermReqeust synchronization #27572', function() + it('TermRequest synchronization #27572', function() command('autocmd! nvim_terminal TermRequest') local term = exec_lua([[ _G.input = {} @@ -564,7 +574,7 @@ if is_os('win') then feed_command('set modifiable swapfile undolevels=20') poke_eventloop() local cmd = { 'cmd.exe', '/K', 'PROMPT=$g$s' } - screen = tt.screen_setup(nil, cmd) + screen = tt.setup_screen(nil, cmd) end) it('"put" operator sends data normally', function() diff --git a/test/functional/terminal/clipboard_spec.lua b/test/functional/terminal/clipboard_spec.lua new file mode 100644 index 0000000000..4a1a0e29fd --- /dev/null +++ b/test/functional/terminal/clipboard_spec.lua @@ -0,0 +1,65 @@ +local t = require('test.testutil') +local n = require('test.functional.testnvim')() + +local eq = t.eq +local retry = t.retry + +local clear = n.clear +local fn = n.fn +local testprg = n.testprg +local exec_lua = n.exec_lua +local eval = n.eval + +describe(':terminal', function() + before_each(function() + clear() + + exec_lua([[ + local function clipboard(reg, type) + if type == 'copy' then + return function(lines) + local data = table.concat(lines, '\n') + vim.g.clipboard_data = data + end + end + + if type == 'paste' then + return function() + error() + end + end + + error('invalid type: ' .. type) + end + + vim.g.clipboard = { + name = 'Test', + copy = { + ['+'] = clipboard('+', 'copy'), + ['*'] = clipboard('*', 'copy'), + }, + paste = { + ['+'] = clipboard('+', 'paste'), + ['*'] = clipboard('*', 'paste'), + }, + } + ]]) + end) + + it('can write to the system clipboard', function() + eq('Test', eval('g:clipboard.name')) + + local text = 'Hello, world! This is some\nexample text\nthat spans multiple\nlines' + local encoded = exec_lua('return vim.base64.encode(...)', text) + + local function osc52(arg) + return string.format('\027]52;;%s\027\\', arg) + end + + fn.termopen({ testprg('shell-test'), '-t', osc52(encoded) }) + + retry(nil, 1000, function() + eq(text, exec_lua([[ return vim.g.clipboard_data ]])) + end) + end) +end) diff --git a/test/functional/terminal/cursor_spec.lua b/test/functional/terminal/cursor_spec.lua index 51c6b12e62..0c5de45829 100644 --- a/test/functional/terminal/cursor_spec.lua +++ b/test/functional/terminal/cursor_spec.lua @@ -1,7 +1,7 @@ local t = require('test.testutil') local n = require('test.functional.testnvim')() local Screen = require('test.functional.ui.screen') -local tt = require('test.functional.terminal.testutil') +local tt = require('test.functional.testterm') local feed, clear = n.feed, n.clear local testprg, command = n.testprg, n.command @@ -18,7 +18,7 @@ describe(':terminal cursor', function() before_each(function() clear() - screen = tt.screen_setup() + screen = tt.setup_screen() end) it('moves the screen cursor when focused', function() diff --git a/test/functional/terminal/highlight_spec.lua b/test/functional/terminal/highlight_spec.lua index 4f3d010d02..05d68f6754 100644 --- a/test/functional/terminal/highlight_spec.lua +++ b/test/functional/terminal/highlight_spec.lua @@ -1,7 +1,7 @@ local t = require('test.testutil') local n = require('test.functional.testnvim')() local Screen = require('test.functional.ui.screen') -local tt = require('test.functional.terminal.testutil') +local tt = require('test.functional.testterm') local feed, clear = n.feed, n.clear local api = n.api @@ -380,3 +380,23 @@ describe(':terminal highlight with custom palette', function() ]]) end) end) + +describe(':terminal', function() + before_each(clear) + + it('can display URLs', function() + local screen = Screen.new(50, 7) + screen:add_extra_attr_ids { + [100] = { url = 'https://example.com' }, + } + screen:attach() + local chan = api.nvim_open_term(0, {}) + api.nvim_chan_send(chan, '\027]8;;https://example.com\027\\Example\027]8;;\027\\') + screen:expect({ + grid = [[ + {100:^Example} | + |*6 + ]], + }) + end) +end) diff --git a/test/functional/terminal/mouse_spec.lua b/test/functional/terminal/mouse_spec.lua index ad98dfc6c3..38d6b83417 100644 --- a/test/functional/terminal/mouse_spec.lua +++ b/test/functional/terminal/mouse_spec.lua @@ -1,7 +1,7 @@ local t = require('test.testutil') local n = require('test.functional.testnvim')() -local tt = require('test.functional.terminal.testutil') +local tt = require('test.functional.testterm') local clear, eq, eval = n.clear, t.eq, n.eval local feed, api, command = n.feed, n.api, n.command local feed_data = tt.feed_data @@ -14,10 +14,12 @@ describe(':terminal mouse', function() before_each(function() clear() api.nvim_set_option_value('statusline', '==========', {}) - command('highlight StatusLine cterm=NONE') - command('highlight StatusLineNC cterm=NONE') - command('highlight VertSplit cterm=NONE') - screen = tt.screen_setup() + screen = tt.setup_screen() + command('highlight StatusLine NONE') + command('highlight StatusLineNC NONE') + command('highlight StatusLineTerm NONE') + command('highlight StatusLineTermNC NONE') + command('highlight VertSplit NONE') local lines = {} for i = 1, 30 do table.insert(lines, 'line' .. tostring(i)) diff --git a/test/functional/terminal/parser_spec.lua b/test/functional/terminal/parser_spec.lua new file mode 100644 index 0000000000..67f47c7888 --- /dev/null +++ b/test/functional/terminal/parser_spec.lua @@ -0,0 +1,15 @@ +local n = require('test.functional.testnvim')() + +local clear = n.clear +local api = n.api +local assert_alive = n.assert_alive + +describe(':terminal', function() + before_each(clear) + + it('handles invalid OSC terminators #30084', function() + local chan = api.nvim_open_term(0, {}) + api.nvim_chan_send(chan, '\027]8;;https://example.com\027\\Example\027]8;;\027\n') + assert_alive() + end) +end) diff --git a/test/functional/terminal/scrollback_spec.lua b/test/functional/terminal/scrollback_spec.lua index 229a169996..da0bd97270 100644 --- a/test/functional/terminal/scrollback_spec.lua +++ b/test/functional/terminal/scrollback_spec.lua @@ -1,7 +1,7 @@ local t = require('test.testutil') local n = require('test.functional.testnvim')() local Screen = require('test.functional.ui.screen') -local tt = require('test.functional.terminal.testutil') +local tt = require('test.functional.testterm') local clear, eq = n.clear, t.eq local feed, testprg = n.feed, n.testprg @@ -22,7 +22,7 @@ describe(':terminal scrollback', function() before_each(function() clear() - screen = tt.screen_setup(nil, nil, 30) + screen = tt.setup_screen(nil, nil, 30) end) describe('when the limit is exceeded', function() @@ -399,9 +399,9 @@ describe("'scrollback' option", function() it('set to 0 behaves as 1', function() local screen if is_os('win') then - screen = tt.screen_setup(nil, { 'cmd.exe' }, 30) + screen = tt.setup_screen(nil, { 'cmd.exe' }, 30) else - screen = tt.screen_setup(nil, { 'sh' }, 30) + screen = tt.setup_screen(nil, { 'sh' }, 30) end api.nvim_set_option_value('scrollback', 0, {}) @@ -416,10 +416,10 @@ describe("'scrollback' option", function() local screen if is_os('win') then command([[let $PROMPT='$$']]) - screen = tt.screen_setup(nil, { 'cmd.exe' }, 30) + screen = tt.setup_screen(nil, { 'cmd.exe' }, 30) else command('let $PS1 = "$"') - screen = tt.screen_setup(nil, { 'sh' }, 30) + screen = tt.setup_screen(nil, { 'sh' }, 30) end api.nvim_set_option_value('scrollback', 200, {}) @@ -480,8 +480,8 @@ describe("'scrollback' option", function() end) it('deletes extra lines immediately', function() - -- Scrollback is 10 on screen_setup - local screen = tt.screen_setup(nil, nil, 30) + -- Scrollback is 10 on setup_screen + local screen = tt.setup_screen(nil, nil, 30) local lines = {} for i = 1, 30 do table.insert(lines, 'line' .. tostring(i)) diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua index d4628ea626..a7d87bb231 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -7,7 +7,7 @@ local t = require('test.testutil') local n = require('test.functional.testnvim')() local Screen = require('test.functional.ui.screen') -local tt = require('test.functional.terminal.testutil') +local tt = require('test.functional.testterm') local eq = t.eq local feed_data = tt.feed_data @@ -40,8 +40,8 @@ if t.skip(is_os('win')) then end describe('TUI', function() - local screen - local child_session + local screen --[[@type test.functional.ui.screen]] + local child_session --[[@type test.Session]] local child_exec_lua before_each(function() @@ -630,6 +630,8 @@ describe('TUI', function() set mouse=a mousemodel=popup aunmenu PopUp + " Delete the default MenuPopup event handler. + 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> @@ -973,6 +975,7 @@ describe('TUI', function() {3:-- TERMINAL --} | ]]) feed_data('\027[201~') -- End paste. + screen:expect_unchanged() feed_data('\027[27u') -- ESC: go to Normal mode. wait_for_mode('n') screen:expect([[ @@ -1056,6 +1059,11 @@ describe('TUI', function() if is_ci('github') then pending('tty-test complains about not owning the terminal -- actions/runner#241') end + screen:set_default_attr_ids({ + [1] = { reverse = true }, -- focused cursor + [3] = { bold = true }, + [19] = { bold = true, background = 121, foreground = 0 }, -- StatusLineTerm + }) child_exec_lua('vim.o.statusline="^^^^^^^"') child_exec_lua('vim.cmd.terminal(...)', testprg('tty-test')) feed_data('i') @@ -1063,7 +1071,7 @@ describe('TUI', function() tty ready | {1: } | |*2 - {5:^^^^^^^ }| + {19:^^^^^^^ }| {3:-- TERMINAL --} |*2 ]]) feed_data('\027[200~') @@ -1073,7 +1081,7 @@ describe('TUI', function() tty ready | hallo{1: } | |*2 - {5:^^^^^^^ }| + {19:^^^^^^^ }| {3:-- TERMINAL --} |*2 ]]) end) @@ -1099,7 +1107,7 @@ describe('TUI', function() screen:expect(expected_grid1) -- Dot-repeat/redo. feed_data('.') - screen:expect([[ + local expected_grid2 = [[ ESC:{6:^[} / CR: | xline 1 | ESC:{6:^[} / CR: | @@ -1107,7 +1115,8 @@ describe('TUI', function() {5:[No Name] [+] 5,1 Bot}| | {3:-- TERMINAL --} | - ]]) + ]] + screen:expect(expected_grid2) -- Undo. feed_data('u') expect_child_buf_lines(expected_crlf) @@ -1121,6 +1130,14 @@ describe('TUI', function() feed_data('\027[200~' .. table.concat(expected_lf, '\r\n') .. '\027[201~') screen:expect(expected_grid1) expect_child_buf_lines(expected_crlf) + -- Dot-repeat/redo. + feed_data('.') + screen:expect(expected_grid2) + -- Undo. + feed_data('u') + expect_child_buf_lines(expected_crlf) + feed_data('u') + expect_child_buf_lines({ '' }) end) it('paste: cmdline-mode inserts 1 line', function() @@ -1184,6 +1201,7 @@ describe('TUI', function() expect_cmdline('"stuff 1 more"') -- End the paste sequence. feed_data('\027[201~') + expect_cmdline('"stuff 1 more"') feed_data(' typed') expect_cmdline('"stuff 1 more typed"') end) @@ -1227,6 +1245,7 @@ describe('TUI', function() feed_data('line 7\nline 8\n') -- Stop paste. feed_data('\027[201~') + screen:expect_unchanged() feed_data('\n') -- <CR> to dismiss hit-enter prompt expect_child_buf_lines({ 'foo', '' }) -- Dot-repeat/redo is not modified by failed paste. @@ -1274,10 +1293,46 @@ describe('TUI', function() {} ) feed_data('\027[200~line A\nline B\n\027[201~') + expect_child_buf_lines({ '' }) feed_data('ifoo\n\027[27u') expect_child_buf_lines({ 'foo', '' }) end) + it('paste: vim.paste() cancel (retval=false) with streaming #30462', function() + child_session:request( + 'nvim_exec_lua', + [[ + vim.paste = (function(overridden) + return function(lines, phase) + for i, line in ipairs(lines) do + if line:find('!') then + return false + end + end + return overridden(lines, phase) + end + end)(vim.paste) + ]], + {} + ) + feed_data('A') + wait_for_mode('i') + feed_data('\027[200~aaa') + expect_child_buf_lines({ 'aaa' }) + feed_data('bbb') + expect_child_buf_lines({ 'aaabbb' }) + feed_data('ccc!') -- This chunk is cancelled. + expect_child_buf_lines({ 'aaabbb' }) + feed_data('ddd\027[201~') -- This chunk is ignored. + expect_child_buf_lines({ 'aaabbb' }) + feed_data('\027[27u') + wait_for_mode('n') + feed_data('.') -- Dot-repeat only includes chunks actually pasted. + expect_child_buf_lines({ 'aaabbbaaabbb' }) + feed_data('$\027[200~eee\027[201~') -- A following paste works normally. + expect_child_buf_lines({ 'aaabbbaaabbbeee' }) + end) + it("paste: 'nomodifiable' buffer", function() child_session:request('nvim_command', 'set nomodifiable') child_session:request( @@ -1396,7 +1451,6 @@ describe('TUI', function() feed_data('\n') -- Send the "stop paste" sequence. feed_data('\027[201~') - screen:expect([[ | pasted from terminal (1) | @@ -1548,10 +1602,32 @@ describe('TUI', function() screen:set_rgb_cterm(true) screen:set_default_attr_ids({ [1] = { { reverse = true }, { reverse = true } }, - [2] = { { bold = true, reverse = true }, { bold = true, reverse = true } }, + [2] = { + { bold = true, background = Screen.colors.LightGreen, foreground = Screen.colors.Black }, + { bold = true }, + }, [3] = { { bold = true }, { bold = true } }, [4] = { { fg_indexed = true, foreground = tonumber('0xe0e000') }, { foreground = 3 } }, [5] = { { foreground = tonumber('0xff8000') }, {} }, + [6] = { + { + fg_indexed = true, + bg_indexed = true, + bold = true, + background = tonumber('0x66ff99'), + foreground = Screen.colors.Black, + }, + { bold = true, background = 121, foreground = 0 }, + }, + [7] = { + { + fg_indexed = true, + bg_indexed = true, + background = tonumber('0x66ff99'), + foreground = Screen.colors.Black, + }, + { background = 121, foreground = 0 }, + }, }) child_exec_lua('vim.o.statusline="^^^^^^^"') @@ -1586,7 +1662,7 @@ describe('TUI', function() {1:t}ty ready | {4:text}colortext | |*2 - {2:^^^^^^^ }| + {6:^^^^^^^}{7: }| :set notermguicolors | {3:-- TERMINAL --} | ]], @@ -1622,12 +1698,13 @@ describe('TUI', function() ]]) end) - it('in nvim_list_uis()', function() + it('in nvim_list_uis(), sets nvim_set_client_info()', function() -- $TERM in :terminal. local exp_term = is_os('bsd') and 'builtin_xterm' or 'xterm-256color' + local ui_chan = 1 local expected = { { - chan = 1, + chan = ui_chan, ext_cmdline = false, ext_hlstate = false, ext_linegrid = true, @@ -1650,6 +1727,43 @@ describe('TUI', function() } local _, rv = child_session:request('nvim_list_uis') eq(expected, rv) + + ---@type table + local expected_version = ({ + child_session:request('nvim_exec_lua', 'return vim.version()', {}), + })[2] + -- vim.version() returns `prerelease` string. Coerce it to boolean. + expected_version.prerelease = not not expected_version.prerelease + + local expected_chan_info = { + client = { + attributes = { + license = 'Apache 2', + -- pid = 5371, + website = 'https://neovim.io', + }, + methods = {}, + name = 'nvim-tui', + type = 'ui', + version = expected_version, + }, + id = ui_chan, + mode = 'rpc', + stream = 'stdio', + } + + local status, chan_info = child_session:request('nvim_get_chan_info', ui_chan) + ok(status) + local info = chan_info.client + ok(info.attributes.pid and info.attributes.pid > 0, 'PID', info.attributes.pid or 'nil') + ok(info.version.major >= 0) + ok(info.version.minor >= 0) + ok(info.version.patch >= 0) + + -- Delete variable fields so we can deep-compare. + info.attributes.pid = nil + + eq(expected_chan_info, chan_info) end) it('allows grid to assume wider ambiwidth chars than host terminal', function() @@ -1941,9 +2055,9 @@ describe('TUI', function() if not req then return end - local url = req:match('\027]8;;(.*)$') - if url ~= nil then - table.insert(_G.urls, url) + local id, url = req:match('\027]8;id=(%d+);(.*)$') + if id ~= nil and url ~= nil then + table.insert(_G.urls, { id = tonumber(id), url = url }) end end, }) @@ -1957,7 +2071,7 @@ describe('TUI', function() }) ]]) retry(nil, 1000, function() - eq({ 'https://example.com', '' }, exec_lua([[return _G.urls]])) + eq({ { id = 0xE1EA0000, url = 'https://example.com' } }, exec_lua([[return _G.urls]])) end) end) end) @@ -1973,6 +2087,7 @@ describe('TUI', function() [3] = { bold = true }, [4] = { foreground = tonumber('0x4040ff'), fg_indexed = true }, [5] = { bold = true, reverse = true }, + [6] = { foreground = Screen.colors.White, background = Screen.colors.DarkGreen }, }) screen:attach() fn.termopen({ @@ -1998,43 +2113,44 @@ describe('TUI', function() {2:~ }│{4:~ }|*5 {2:~ }│{5:[No Name] 0,0-1 All}| {2:~ }│ | - {5:new }{1:{MATCH:<.*[/\]nvim }}| - | - ]]) - end) - - it('invalidated regions are cleared with terminal background attr', function() - local screen = Screen.new(50, 10) - screen:set_default_attr_ids({ [1] = { foreground = Screen.colors.Black } }) - screen:attach() - fn.termopen({ - nvim_prog, - '--clean', - '--cmd', - 'set termguicolors', - '--cmd', - 'sleep 10', - }, { - env = { - VIMRUNTIME = os.getenv('VIMRUNTIME'), - }, - }) - screen:expect({ - grid = [[ - {1:^ }| - {1: }|*8 - | - ]], - }) - screen:try_resize(51, 11) - screen:expect({ - grid = [[ - {1:^ }| - {1: }|*9 - | - ]], - }) - end) + {5:new }{6:{MATCH:<.*[/\]nvim }}| + | + ]]) + end) + + -- #28667, #28668 + 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) + screen:attach() + fn.termopen({ + nvim_prog, + '--clean', + '--cmd', + 'set ' .. guicolors, + '--cmd', + 'sleep 10', + }, { + env = { + VIMRUNTIME = os.getenv('VIMRUNTIME'), + }, + }) + screen:expect({ + grid = [[ + ^ | + |*9 + ]], + intermediate = true, + }) + screen:try_resize(51, 11) + screen:expect({ + grid = [[ + ^ | + |*10 + ]], + }) + end) + end it('argv[0] can be overridden #23953', function() if not exec_lua('return pcall(require, "ffi")') then @@ -2080,7 +2196,7 @@ describe('TUI', function() finally(function() os.remove('testF') end) - local screen = tt.screen_setup( + local screen = tt.setup_screen( 0, ('"%s" -u NONE -i NONE --cmd "set noswapfile noshowcmd noruler" --cmd "normal iabc" > /dev/null 2>&1 && cat testF && rm testF'):format( nvim_prog @@ -2170,6 +2286,47 @@ describe('TUI', function() } end) + it('draws screen lines with leading spaces correctly #29711', function() + local screen = tt.setup_child_nvim({ + '-u', + 'NONE', + '-i', + 'NONE', + '--cmd', + 'set foldcolumn=6 | call setline(1, ["", repeat("aabb", 1000)]) | echo 42', + }, { extra_rows = 10, cols = 66 }) + screen:expect { + grid = [[ + | + aabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabb|*12 + aabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabba@@@| + [No Name] [+] 1,0-1 Top| + 42 | + -- TERMINAL -- | + ]], + attr_ids = {}, + } + feed_data('\12') -- Ctrl-L + -- The first line counts as 3 cells. + -- For the second line, 6 repeated spaces at the start counts as 2 cells, + -- so each screen line of the second line counts as 62 cells. + -- After drawing the first line and 8 screen lines of the second line, + -- 3 + 8 * 62 = 499 cells have been counted. + -- The 6 repeated spaces at the start of the next screen line exceeds the + -- 500-cell limit, so the buffer is flushed after these spaces. + screen:expect { + grid = [[ + | + aabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabb|*12 + aabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabba@@@| + [No Name] [+] 1,0-1 Top| + | + -- TERMINAL -- | + ]], + attr_ids = {}, + } + end) + it('no heap-buffer-overflow when changing &columns', function() -- Set a different bg colour and change $TERM to something dumber so the `print_spaces()` -- codepath in `clear_region()` is hit. @@ -2938,6 +3095,61 @@ describe('TUI', function() end) end) + it('does not query the terminal for truecolor support if $COLORTERM is set', function() + clear() + exec_lua([[ + vim.api.nvim_create_autocmd('TermRequest', { + callback = function(args) + local req = args.data + vim.g.termrequest = req + local xtgettcap = req:match('^\027P%+q([%x;]+)$') + if xtgettcap then + local t = {} + for cap in vim.gsplit(xtgettcap, ';') do + local resp = string.format('\027P1+r%s\027\\', xtgettcap) + vim.api.nvim_chan_send(vim.bo[args.buf].channel, resp) + t[vim.text.hexdecode(cap)] = true + end + vim.g.xtgettcap = t + return true + elseif req:match('^\027P$qm\027\\$') then + vim.g.decrqss = true + end + end, + }) + ]]) + + local child_server = new_pipename() + screen = tt.setup_child_nvim({ + '--listen', + child_server, + '-u', + 'NONE', + '-i', + 'NONE', + }, { + env = { + VIMRUNTIME = os.getenv('VIMRUNTIME'), + -- With COLORTERM=256, Nvim should not query the terminal and should not set 'tgc' + COLORTERM = '256', + TERM = 'xterm-256colors', + }, + }) + + screen:expect({ any = '%[No Name%]' }) + + local child_session = n.connect(child_server) + retry(nil, 1000, function() + local xtgettcap = eval("get(g:, 'xtgettcap', {})") + eq(nil, xtgettcap['Tc']) + eq(nil, xtgettcap['RGB']) + eq(nil, xtgettcap['setrgbf']) + eq(nil, xtgettcap['setrgbb']) + eq(0, eval([[get(g:, 'decrqss')]])) + eq({ true, 0 }, { child_session:request('nvim_eval', '&termguicolors') }) + end) + end) + it('queries the terminal for OSC 52 support', function() clear() exec_lua([[ diff --git a/test/functional/terminal/window_spec.lua b/test/functional/terminal/window_spec.lua index f85e26a66d..fdb606e959 100644 --- a/test/functional/terminal/window_spec.lua +++ b/test/functional/terminal/window_spec.lua @@ -1,7 +1,7 @@ local t = require('test.testutil') local n = require('test.functional.testnvim')() -local tt = require('test.functional.terminal.testutil') +local tt = require('test.functional.testterm') local feed_data = tt.feed_data local feed, clear = n.feed, n.clear local poke_eventloop = n.poke_eventloop @@ -13,11 +13,31 @@ local skip = t.skip local is_os = t.is_os describe(':terminal window', function() + before_each(clear) + + it('sets local values of window options #29325', function() + command('setglobal wrap list') + command('terminal') + eq({ 0, 0, 1 }, eval('[&l:wrap, &wrap, &g:wrap]')) + eq({ 0, 0, 1 }, eval('[&l:list, &list, &g:list]')) + command('enew') + eq({ 1, 1, 1 }, eval('[&l:wrap, &wrap, &g:wrap]')) + eq({ 1, 1, 1 }, eval('[&l:list, &list, &g:list]')) + command('buffer #') + eq({ 0, 0, 1 }, eval('[&l:wrap, &wrap, &g:wrap]')) + eq({ 0, 0, 1 }, eval('[&l:list, &list, &g:list]')) + command('new') + eq({ 1, 1, 1 }, eval('[&l:wrap, &wrap, &g:wrap]')) + eq({ 1, 1, 1 }, eval('[&l:list, &list, &g:list]')) + end) +end) + +describe(':terminal window', function() local screen before_each(function() clear() - screen = tt.screen_setup() + screen = tt.setup_screen() end) it('sets topline correctly #8556', function() @@ -37,7 +57,6 @@ describe(':terminal window', function() describe("with 'number'", function() it('wraps text', function() - skip(is_os('win')) -- todo(clason): unskip when reenabling reflow feed([[<C-\><C-N>]]) feed([[:set numberwidth=1 number<CR>i]]) screen:expect([[ @@ -67,7 +86,7 @@ describe(':terminal window', function() {7: 1 }tty ready | {7: 2 }rows: 6, cols: 48 | {7: 3 }abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNO| - {7: 4 }WXYZrows: 6, cols: 41 | + {7: 4 }PQRSTUVWXYZrows: 6, cols: 41 | {7: 5 }{1: } | {7: 6 } | {3:-- TERMINAL --} | @@ -77,7 +96,7 @@ describe(':terminal window', function() {7: 1 }tty ready | {7: 2 }rows: 6, cols: 48 | {7: 3 }abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNO| - {7: 4 }WXYZrows: 6, cols: 41 | + {7: 4 }PQRSTUVWXYZrows: 6, cols: 41 | {7: 5 } abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMN| {7: 6 }OPQRSTUVWXYZ{1: } | {3:-- TERMINAL --} | @@ -87,7 +106,6 @@ describe(':terminal window', function() describe("with 'statuscolumn'", function() it('wraps text', function() - skip(is_os('win')) -- todo(clason): unskip when reenabling reflow command([[set number statuscolumn=++%l\ \ ]]) screen:expect([[ {7:++1 }tty ready | @@ -110,11 +128,11 @@ describe(':terminal window', function() ]]) feed_data('\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ') screen:expect([[ - {7:++7 } | - {7:++8 }abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQR| - {7:++9 }TUVWXYZ | + {7:++ 7 } | + {7:++ 8 }abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQR| + {7:++ 9 }STUVWXYZ | {7:++10 }abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQR| - {7:++11 }TUVWXYZrows: 6, cols: 44 | + {7:++11 }STUVWXYZrows: 6, cols: 44 | {7:++12 }{1: } | {3:-- TERMINAL --} | ]]) @@ -178,7 +196,7 @@ describe(':terminal with multigrid', function() before_each(function() clear() - screen = tt.screen_setup(0, nil, 50, nil, { ext_multigrid = true }) + screen = tt.setup_screen(0, nil, 50, nil, { ext_multigrid = true }) end) it('resizes to requested size', function() diff --git a/test/functional/terminal/window_split_tab_spec.lua b/test/functional/terminal/window_split_tab_spec.lua index 04d2e0bca7..e9218e9a3b 100644 --- a/test/functional/terminal/window_split_tab_spec.lua +++ b/test/functional/terminal/window_split_tab_spec.lua @@ -1,7 +1,7 @@ local t = require('test.testutil') local n = require('test.functional.testnvim')() -local tt = require('test.functional.terminal.testutil') +local tt = require('test.functional.testterm') local assert_alive = n.assert_alive local clear = n.clear local feed = n.feed @@ -22,10 +22,12 @@ describe(':terminal', function() -- set the statusline to a constant value because of variables like pid -- and current directory and to improve visibility of splits api.nvim_set_option_value('statusline', '==========', {}) - command('highlight StatusLine cterm=NONE') - command('highlight StatusLineNC cterm=NONE') - command('highlight VertSplit cterm=NONE') - screen = tt.screen_setup(3) + screen = tt.setup_screen(3) + command('highlight StatusLine NONE') + command('highlight StatusLineNC NONE') + command('highlight StatusLineTerm NONE') + command('highlight StatusLineTermNC NONE') + command('highlight VertSplit NONE') end) after_each(function() diff --git a/test/functional/testnvim.lua b/test/functional/testnvim.lua index 6b858e4d69..8a2281e2a1 100644 --- a/test/functional/testnvim.lua +++ b/test/functional/testnvim.lua @@ -14,15 +14,14 @@ local is_os = t.is_os local ok = t.ok local sleep = uv.sleep ---- This module uses functions from the context of the test session, i.e. in the context of the ---- nvim being tests. +--- Functions executing in the current nvim session/process being tested. local M = {} local runtime_set = 'set runtimepath^=./build/lib/nvim/' M.nvim_prog = (os.getenv('NVIM_PRG') or t.paths.test_build_dir .. '/bin/nvim') -- Default settings for the test session. M.nvim_set = ( - 'set shortmess+=IS background=light termguicolors noswapfile noautoindent startofline' + 'set shortmess+=IS background=light noswapfile noautoindent startofline' .. ' laststatus=1 undodir=. directory=. viewdir=. backupdir=.' .. ' belloff= wildoptions-=pum joinspaces noshowcmd noruler nomore redrawdebug=invalid' ) @@ -251,12 +250,14 @@ function M.set_method_error(err) method_error = err end +--- Runs the event loop of the given session. +--- --- @param lsession test.Session --- @param request_cb function? --- @param notification_cb function? --- @param setup_cb function? --- @param timeout integer ---- @return {[1]: integer, [2]: string} +--- @return [integer, string] function M.run_session(lsession, request_cb, notification_cb, setup_cb, timeout) local on_request --- @type function? local on_notification --- @type function? @@ -297,6 +298,7 @@ function M.run_session(lsession, request_cb, notification_cb, setup_cb, timeout) return lsession.eof_err end +--- Runs the event loop of the current global session. function M.run(request_cb, notification_cb, setup_cb, timeout) assert(session) return M.run_session(session, request_cb, notification_cb, setup_cb, timeout) @@ -456,7 +458,7 @@ end --- @param argv string[] --- @param merge boolean? --- @param env string[]? ---- @param keep boolean +--- @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) @@ -757,58 +759,21 @@ function M.assert_visible(bufnr, visible) end end ---- @param path string -local function do_rmdir(path) - local stat = uv.fs_stat(path) - if stat == nil then - return - end - if stat.type ~= 'directory' then - error(string.format('rmdir: not a directory: %s', path)) - end - for file in vim.fs.dir(path) do - if file ~= '.' and file ~= '..' then - local abspath = path .. '/' .. file - if t.isdir(abspath) then - do_rmdir(abspath) -- recurse - else - local ret, err = os.remove(abspath) - if not ret then - if not session then - error('os.remove: ' .. err) - else - -- Try Nvim delete(): it handles `readonly` attribute on Windows, - -- and avoids Lua cross-version/platform incompatibilities. - if -1 == M.call('delete', abspath) then - local hint = (is_os('win') and ' (hint: try :%bwipeout! before rmdir())' or '') - error('delete() failed' .. hint .. ': ' .. abspath) - end - end - end - end - end - end - local ret, err = uv.fs_rmdir(path) - if not ret then - error('luv.fs_rmdir(' .. path .. '): ' .. err) - end -end - local start_dir = uv.cwd() function M.rmdir(path) - local ret, _ = pcall(do_rmdir, path) + local ret, _ = pcall(vim.fs.rm, path, { recursive = true, force = true }) if not ret and is_os('win') then -- Maybe "Permission denied"; try again after changing the nvim -- process to the top-level directory. M.command([[exe 'cd '.fnameescape(']] .. start_dir .. "')") - ret, _ = pcall(do_rmdir, path) + ret, _ = pcall(vim.fs.rm, path, { recursive = true, force = true }) end -- During teardown, the nvim process may not exit quickly enough, then rmdir() -- will fail (on Windows). if not ret then -- Try again. sleep(1000) - do_rmdir(path) + vim.fs.rm(path, { recursive = true, force = true }) end end @@ -835,10 +800,171 @@ function M.exec_capture(code) return M.api.nvim_exec2(code, { output = true }).output end ---- @param code string +--- @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. +--- +--- Direct upvalues are copied over, however upvalues contained +--- within nested functions are not. Upvalues are also copied back when `code` +--- finishes executing. See `:help lua-upvalue`. +--- +--- Only types which can be serialized can be transferred over, e.g: +--- `table`, `number`, `boolean`, `string`. +--- +--- `code` runs with a different environment and thus will have a different global +--- environment. See `:help lua-environments`. +--- +--- Example: +--- ```lua +--- local upvalue1 = 'upvalue1' +--- exec_lua(function(a, b, c) +--- print(upvalue1, a, b, c) +--- (function() +--- print(upvalue2) +--- end)() +--- end, 'a', 'b', 'c' +--- ``` +--- Prints: +--- ``` +--- upvalue1 a b c +--- nil +--- ``` +--- +--- Not supported: +--- ```lua +--- local a = vim.uv.new_timer() +--- exec_lua(function() +--- print(a) -- Error: a is of type 'userdata' which cannot be serialized. +--- end) +--- ``` +--- @param code string|function +--- @param ... any --- @return any function M.exec_lua(code, ...) - return M.api.nvim_exec_lua(code, { ... }) + if type(code) == 'string' then + return M.api.nvim_exec_lua(code, { ... }) + end + + assert(session) + + if not session.exec_lua_setup then + M.api.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 + + --- @type any[], table<string,any> + local ret, upvalues = unpack(M.api.nvim_exec_lua( + [[ + return { + _G.__test_exec_lua:handler(...) + } + ]], + { + string.dump(code), + get_upvalues(code), + ..., + } + )) + + -- 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)) end function M.get_pathsep() @@ -894,26 +1020,6 @@ function M.missing_provider(provider) assert(false, 'Unknown provider: ' .. provider) end ---- @param obj string|table ---- @return any -function M.alter_slashes(obj) - if not is_os('win') then - return obj - end - if type(obj) == 'string' then - local ret = obj:gsub('/', '\\') - return ret - elseif type(obj) == 'table' then - --- @cast obj table<any,any> - local ret = {} --- @type table<any,any> - for k, v in pairs(obj) do - ret[k] = M.alter_slashes(v) - end - return ret - end - assert(false, 'expected string or table of strings, got ' .. type(obj)) -end - local load_factor = 1 if t.is_ci() then -- Compute load factor only once (but outside of any tests). diff --git a/test/functional/terminal/testutil.lua b/test/functional/testterm.lua index f3fc5d3f93..e46ae0793c 100644 --- a/test/functional/terminal/testutil.lua +++ b/test/functional/testterm.lua @@ -1,6 +1,13 @@ --- To test tui/input.c, this module spawns `nvim` inside :terminal and sends --- bytes via jobsend(). Note: the functional/testutil.lua test-session methods --- operate on the _host_ session, _not_ the child session. +-- Functions to test :terminal and the Nvim TUI. +-- Starts a child process in a `:terminal` and sends bytes to the child via nvim_chan_send(). +-- Note: the global functional/testutil.lua test-session is _host_ session, _not_ +-- the child session. +-- +-- - Use `setup_screen()` to test `:terminal` behavior with an arbitrary command. +-- - Use `setup_child_nvim()` to test the Nvim TUI. +-- - NOTE: Only use this if your test actually needs the full lifecycle/capabilities of the +-- builtin Nvim TUI. Most tests should just use `Screen.new()` directly, or plain old API calls. + local n = require('test.functional.testnvim')() local Screen = require('test.functional.ui.screen') @@ -9,18 +16,20 @@ local exec_lua = n.exec_lua local api = n.api local nvim_prog = n.nvim_prog -local function feed_data(data) +local M = {} + +function M.feed_data(data) if type(data) == 'table' then data = table.concat(data, '\n') end exec_lua('vim.api.nvim_chan_send(vim.b.terminal_job_id, ...)', data) end -local function feed_termcode(data) - feed_data('\027' .. data) +function M.feed_termcode(data) + M.feed_data('\027' .. data) end -local function make_lua_executor(session) +function M.make_lua_executor(session) return function(code, ...) local status, rv = session:request('nvim_exec_lua', code, { ... }) if not status then @@ -34,64 +43,74 @@ end -- some t for controlling the terminal. the codes were taken from -- infocmp xterm-256color which is less what libvterm understands -- civis/cnorm -local function hide_cursor() - feed_termcode('[?25l') +function M.hide_cursor() + M.feed_termcode('[?25l') end -local function show_cursor() - feed_termcode('[?25h') +function M.show_cursor() + M.feed_termcode('[?25h') end -- smcup/rmcup -local function enter_altscreen() - feed_termcode('[?1049h') +function M.enter_altscreen() + M.feed_termcode('[?1049h') end -local function exit_altscreen() - feed_termcode('[?1049l') +function M.exit_altscreen() + M.feed_termcode('[?1049l') end -- character attributes -local function set_fg(num) - feed_termcode('[38;5;' .. num .. 'm') +function M.set_fg(num) + M.feed_termcode('[38;5;' .. num .. 'm') end -local function set_bg(num) - feed_termcode('[48;5;' .. num .. 'm') +function M.set_bg(num) + M.feed_termcode('[48;5;' .. num .. 'm') end -local function set_bold() - feed_termcode('[1m') +function M.set_bold() + M.feed_termcode('[1m') end -local function set_italic() - feed_termcode('[3m') +function M.set_italic() + M.feed_termcode('[3m') end -local function set_underline() - feed_termcode('[4m') +function M.set_underline() + M.feed_termcode('[4m') end -local function set_underdouble() - feed_termcode('[4:2m') +function M.set_underdouble() + M.feed_termcode('[4:2m') end -local function set_undercurl() - feed_termcode('[4:3m') +function M.set_undercurl() + M.feed_termcode('[4:3m') end -local function set_strikethrough() - feed_termcode('[9m') +function M.set_strikethrough() + M.feed_termcode('[9m') end -local function clear_attrs() - feed_termcode('[0;10m') +function M.clear_attrs() + M.feed_termcode('[0;10m') end -- mouse -local function enable_mouse() - feed_termcode('[?1002h') +function M.enable_mouse() + M.feed_termcode('[?1002h') end -local function disable_mouse() - feed_termcode('[?1002l') +function M.disable_mouse() + M.feed_termcode('[?1002l') end local default_command = { testprg('tty-test') } -local function screen_setup(extra_rows, command, cols, env, screen_opts) +--- Runs `cmd` in a :terminal, and returns a `Screen` object. +--- +---@param extra_rows? integer Extra rows to add to the default screen. +---@param cmd? string|string[] Command to run in the terminal (default: `{ 'tty-test' }`) +---@param cols? integer Create screen with this many columns (default: 50) +---@param env? table Environment set on the `cmd` job. +---@param screen_opts? table Options for `Screen.new()`. +---@return test.functional.ui.screen # Screen attached to the global (not child) Nvim session. +function M.setup_screen(extra_rows, cmd, cols, env, screen_opts) extra_rows = extra_rows and extra_rows or 0 - command = command and command or default_command + cmd = cmd and cmd or default_command 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') local screen = Screen.new(cols, 7 + extra_rows) screen:set_default_attr_ids({ @@ -111,12 +130,14 @@ local function screen_setup(extra_rows, command, cols, env, screen_opts) [14] = { underline = true, reverse = true, bold = true }, [15] = { underline = true, foreground = 12 }, [16] = { background = 248, foreground = 0 }, -- Visual in :terminal session + [17] = { background = 2, foreground = 0 }, -- StatusLineTerm + [18] = { background = 2, foreground = 8 }, -- StatusLineTermNC }) screen:attach(screen_opts or { rgb = false }) api.nvim_command('enew') - api.nvim_call_function('termopen', { command, env and { env = env } or nil }) + api.nvim_call_function('termopen', { cmd, env and { env = env } or nil }) api.nvim_input('<CR>') local vim_errmsg = api.nvim_eval('v:errmsg') if vim_errmsg and '' ~= vim_errmsg then @@ -129,7 +150,7 @@ local function screen_setup(extra_rows, command, cols, env, screen_opts) -- tty-test puts the terminal into raw mode and echoes input. Tests work by -- feeding termcodes to control the display and asserting by screen:expect. - if command == default_command and screen_opts == nil then + if cmd == default_command and screen_opts == nil then -- Wait for "tty ready" to be printed before each test or the terminal may -- still be in canonical mode (will echo characters for example). local empty_line = (' '):rep(cols) @@ -156,37 +177,24 @@ local function screen_setup(extra_rows, command, cols, env, screen_opts) return screen end -local function setup_child_nvim(args, opts) +--- Spawns Nvim with `args` in a :terminal, and returns a `Screen` object. +--- +--- @note Only use this if you actually need the full lifecycle/capabilities of the builtin Nvim +--- TUI. Most tests should just use `Screen.new()` directly, or plain old API calls. +--- +---@param args? string[] Args passed to child Nvim. +---@param opts? table Options +---@return test.functional.ui.screen # Screen attached to the global (not child) Nvim session. +function M.setup_child_nvim(args, opts) opts = opts or {} - local argv = { nvim_prog, unpack(args) } + local argv = { nvim_prog, unpack(args or {}) } local env = opts.env or {} if not env.VIMRUNTIME then env.VIMRUNTIME = os.getenv('VIMRUNTIME') end - return screen_setup(0, argv, opts.cols, env) -end - -return { - feed_data = feed_data, - feed_termcode = feed_termcode, - make_lua_executor = make_lua_executor, - hide_cursor = hide_cursor, - show_cursor = show_cursor, - enter_altscreen = enter_altscreen, - exit_altscreen = exit_altscreen, - set_fg = set_fg, - set_bg = set_bg, - set_bold = set_bold, - set_italic = set_italic, - set_underline = set_underline, - set_underdouble = set_underdouble, - set_undercurl = set_undercurl, - set_strikethrough = set_strikethrough, - clear_attrs = clear_attrs, - enable_mouse = enable_mouse, - disable_mouse = disable_mouse, - screen_setup = screen_setup, - setup_child_nvim = setup_child_nvim, -} + return M.setup_screen(opts.extra_rows, argv, opts.cols, env) +end + +return M diff --git a/test/functional/treesitter/fold_spec.lua b/test/functional/treesitter/fold_spec.lua index a7f278aa01..24b085920c 100644 --- a/test/functional/treesitter/fold_spec.lua +++ b/test/functional/treesitter/fold_spec.lua @@ -48,13 +48,13 @@ void ui_refresh(void) end local function get_fold_levels() - return exec_lua([[ - local res = {} - for i = 1, vim.api.nvim_buf_line_count(0) do - res[i] = vim.treesitter.foldexpr(i) - end - return res - ]]) + return exec_lua(function() + local res = {} + for i = 1, vim.api.nvim_buf_line_count(0) do + res[i] = vim.treesitter.foldexpr(i) + end + return res + end) end it('can compute fold levels', function() @@ -246,9 +246,13 @@ function f() end -- comment]]) - exec_lua( - [[vim.treesitter.query.set('lua', 'folds', '[(function_declaration) (parameters) (arguments)] @fold')]] - ) + exec_lua(function() + vim.treesitter.query.set( + 'lua', + 'folds', + '[(function_declaration) (parameters) (arguments)] @fold' + ) + end) parse('lua') eq({ @@ -290,9 +294,13 @@ function f() ) end]]) - exec_lua( - [[vim.treesitter.query.set('lua', 'folds', '[(function_declaration) (function_definition) (parameters) (arguments)] @fold')]] - ) + exec_lua(function() + vim.treesitter.query.set( + 'lua', + 'folds', + '[(function_declaration) (function_definition) (parameters) (arguments)] @fold' + ) + end) parse('lua') -- If fold1.stop = fold2.start, then move fold1's stop up so that fold2.start gets proper level. @@ -333,9 +341,13 @@ function f(a) end end]]) - exec_lua( - [[vim.treesitter.query.set('lua', 'folds', '[(if_statement) (function_declaration) (parameters) (arguments) (table_constructor)] @fold')]] - ) + exec_lua(function() + vim.treesitter.query.set( + 'lua', + 'folds', + '[(if_statement) (function_declaration) (parameters) (arguments) (table_constructor)] @fold' + ) + end) parse('lua') eq({ @@ -408,15 +420,15 @@ t3]]) it('handles quantified patterns', function() insert([[ -import hello -import hello -import hello -import hello -import hello -import hello]]) - - exec_lua([[vim.treesitter.query.set('python', 'folds', '(import_statement)+ @fold')]]) - parse('python') +-- hello +-- hello +-- hello +-- hello +-- hello +-- hello]]) + + exec_lua([[vim.treesitter.query.set('lua', 'folds', '(comment)+ @fold')]]) + parse('lua') eq({ [1] = '>1', @@ -646,6 +658,67 @@ import hello]]) } end) + it('does not extend closed fold with `o`/`O`', function() + local screen = Screen.new(60, 24) + screen:attach() + + insert(test_text) + parse('c') + command([[set foldmethod=expr foldexpr=v:lua.vim.treesitter.foldexpr() foldcolumn=1]]) + + feed('5ggzco') + screen:expect({ + grid = [[ + {7:-}void ui_refresh(void) | + {7:│}{ | + {7:│} int width = INT_MAX, height = INT_MAX; | + {7:│} bool ext_widgets[kUIExtCount]; | + {7:+}{13:+--- 3 lines: for (UIExtension i = 0; (int)i < kUIExtCount}| + {7:│}^ | + {7:│} | + {7:│} bool inclusive = ui_override(); | + {7:-} for (size_t i = 0; i < ui_count; i++) { | + {7:2} UI *ui = uis[i]; | + {7:2} width = MIN(ui->width, width); | + {7:2} height = MIN(ui->height, height); | + {7:2} foo = BAR(ui->bazaar, bazaar); | + {7:-} for (UIExtension j = 0; (int)j < kUIExtCount; j++) { | + {7:3} ext_widgets[j] &= (ui->ui_ext[j] || inclusive); | + {7:3} } | + {7:2} } | + {7:│}} | + {1:~ }|*5 + {5:-- INSERT --} | + ]], + }) + + feed('<Esc>O') + screen:expect({ + grid = [[ + {7:-}void ui_refresh(void) | + {7:│}{ | + {7:│} int width = INT_MAX, height = INT_MAX; | + {7:│} bool ext_widgets[kUIExtCount]; | + {7:+}{13:+--- 3 lines: for (UIExtension i = 0; (int)i < kUIExtCount}| + {7:│}^ | + {7:│} |*2 + {7:│} bool inclusive = ui_override(); | + {7:-} for (size_t i = 0; i < ui_count; i++) { | + {7:2} UI *ui = uis[i]; | + {7:2} width = MIN(ui->width, width); | + {7:2} height = MIN(ui->height, height); | + {7:2} foo = BAR(ui->bazaar, bazaar); | + {7:-} for (UIExtension j = 0; (int)j < kUIExtCount; j++) { | + {7:3} ext_widgets[j] &= (ui->ui_ext[j] || inclusive); | + {7:3} } | + {7:2} } | + {7:│}} | + {1:~ }|*4 + {5:-- INSERT --} | + ]], + }) + end) + it("doesn't open folds that are not touched", function() local screen = Screen.new(40, 8) screen:set_default_attr_ids({ @@ -674,7 +747,7 @@ t2]]) grid = [[ {1:-}# h1 | {1:│}t1 | - {1:│}^ | + {1:-}^ | {1:+}{2:+-- 2 lines: # h2·····················}| {3:~ }|*3 {4:-- INSERT --} | diff --git a/test/functional/treesitter/highlight_spec.lua b/test/functional/treesitter/highlight_spec.lua index 69984b3233..b5a6cb5c17 100644 --- a/test/functional/treesitter/highlight_spec.lua +++ b/test/functional/treesitter/highlight_spec.lua @@ -8,10 +8,9 @@ local exec_lua = n.exec_lua local feed = n.feed local command = n.command local api = n.api +local fn = n.fn local eq = t.eq -before_each(clear) - local hl_query_c = [[ (ERROR) @error @@ -65,6 +64,46 @@ static int nlua_schedule(lua_State *const lstate) return 0; }]] +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) | + { | + {4:if} (lua_type(lstate, {5:1}) != LUA_TFUNCTION | + || lstate != lstate) { | + lua_pushliteral(lstate, {5:"vim.schedule: expected function"}); | + {4:return} lua_error(lstate); | + } | + | + LuaRef cb = nlua_ref(lstate, {5:1}); | + | + multiqueue_put(main_loop.events, nlua_schedule_event, | + {5:1}, ({3:void} *)({3:ptrdiff_t})cb); | + {4:return} {5: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) | + { | + {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 + | +]] + local test_text_c = [[ void ui_refresh(void) { @@ -117,9 +156,10 @@ local injection_grid_expected_c = [[ ]] describe('treesitter highlighting (C)', function() - local screen + local screen --- @type test.functional.ui.screen before_each(function() + clear() screen = Screen.new(65, 18) screen:attach() screen:set_default_attr_ids { @@ -136,16 +176,49 @@ describe('treesitter highlighting (C)', function() [11] = { foreground = Screen.colors.Cyan4 }, } - exec_lua([[ hl_query = ... ]], hl_query_c) command [[ hi link @error ErrorMsg ]] command [[ hi link @warning WarningMsg ]] end) + it('starting and stopping treesitter highlight works', function() + command('setfiletype c | syntax on') + fn.setreg('r', hl_text_c) + feed('i<C-R><C-O>r<Esc>gg') + -- legacy syntax highlighting is used by default + screen:expect(hl_grid_legacy_c) + + exec_lua(function() + vim.treesitter.query.set('c', 'highlights', hl_query_c) + vim.treesitter.start() + end) + -- treesitter highlighting is used + screen:expect(hl_grid_ts_c) + + exec_lua(function() + vim.treesitter.stop() + end) + -- legacy syntax highlighting is used + screen:expect(hl_grid_legacy_c) + + exec_lua(function() + vim.treesitter.start() + end) + -- treesitter highlighting is used + screen:expect(hl_grid_ts_c) + + exec_lua(function() + vim.treesitter.stop() + end) + -- legacy syntax highlighting is used + screen:expect(hl_grid_legacy_c) + end) + it('is updated with edits', function() insert(hl_text_c) + feed('gg') screen:expect { grid = [[ - /// Schedule Lua callback on main loop's event queue | + ^/// Schedule Lua callback on main loop's event queue | static int nlua_schedule(lua_State *const lstate) | { | if (lua_type(lstate, 1) != LUA_TFUNCTION | @@ -159,38 +232,18 @@ describe('treesitter highlighting (C)', function() multiqueue_put(main_loop.events, nlua_schedule_event, | 1, (void *)(ptrdiff_t)cb); | return 0; | - ^} | + } | {1:~ }|*2 | ]], } - exec_lua [[ - local parser = vim.treesitter.get_parser(0, "c") + exec_lua(function() + local parser = vim.treesitter.get_parser(0, 'c') local highlighter = vim.treesitter.highlighter - test_hl = highlighter.new(parser, {queries = {c = hl_query}}) - ]] - 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 - | - ]], - } + highlighter.new(parser, { queries = { c = hl_query_c } }) + end) + screen:expect(hl_grid_ts_c) feed('5Goc<esc>dd') @@ -316,10 +369,10 @@ describe('treesitter highlighting (C)', function() it('is updated with :sort', function() insert(test_text_c) - exec_lua [[ - local parser = vim.treesitter.get_parser(0, "c") - test_hl = vim.treesitter.highlighter.new(parser, {queries = {c = hl_query}}) - ]] + exec_lua(function() + local parser = vim.treesitter.get_parser(0, 'c') + vim.treesitter.highlighter.new(parser, { queries = { c = hl_query_c } }) + end) screen:expect { grid = [[ {3:int} width = {5:INT_MAX}, height = {5:INT_MAX}; | @@ -422,19 +475,19 @@ describe('treesitter highlighting (C)', function() ]], } - exec_lua [[ - parser = vim.treesitter.get_parser(0, "c") - query = vim.treesitter.query.parse("c", "(declaration) @decl") + exec_lua(function() + local parser = vim.treesitter.get_parser(0, 'c') + local query = vim.treesitter.query.parse('c', '(declaration) @decl') local nodes = {} for _, node in query:iter_captures(parser:parse()[1]:root(), 0, 0, 19) do table.insert(nodes, node) end - parser:set_included_regions({nodes}) + parser:set_included_regions({ nodes }) - local hl = vim.treesitter.highlighter.new(parser, {queries = {c = "(identifier) @type"}}) - ]] + vim.treesitter.highlighter.new(parser, { queries = { c = '(identifier) @type' } }) + end) screen:expect { grid = [[ @@ -465,13 +518,15 @@ describe('treesitter highlighting (C)', function() screen:expect { grid = injection_grid_c } - exec_lua [[ - local parser = vim.treesitter.get_parser(0, "c", { - injections = {c = '(preproc_def (preproc_arg) @injection.content (#set! injection.language "c")) (preproc_function_def value: (preproc_arg) @injection.content (#set! injection.language "c"))'} + exec_lua(function() + local parser = vim.treesitter.get_parser(0, 'c', { + injections = { + c = '(preproc_def (preproc_arg) @injection.content (#set! injection.language "c")) (preproc_function_def value: (preproc_arg) @injection.content (#set! injection.language "c"))', + }, }) local highlighter = vim.treesitter.highlighter - test_hl = highlighter.new(parser, {queries = {c = hl_query}}) - ]] + highlighter.new(parser, { queries = { c = hl_query_c } }) + end) screen:expect { grid = injection_grid_expected_c } end) @@ -481,14 +536,16 @@ describe('treesitter highlighting (C)', function() screen:expect { grid = injection_grid_c } - exec_lua [[ - vim.treesitter.language.register("c", "foo") - local parser = vim.treesitter.get_parser(0, "c", { - injections = {c = '(preproc_def (preproc_arg) @injection.content (#set! injection.language "foo")) (preproc_function_def value: (preproc_arg) @injection.content (#set! injection.language "foo"))'} + exec_lua(function() + vim.treesitter.language.register('c', 'foo') + local parser = vim.treesitter.get_parser(0, 'c', { + injections = { + c = '(preproc_def (preproc_arg) @injection.content (#set! injection.language "foo")) (preproc_function_def value: (preproc_arg) @injection.content (#set! injection.language "foo"))', + }, }) local highlighter = vim.treesitter.highlighter - test_hl = highlighter.new(parser, {queries = {c = hl_query}}) - ]] + highlighter.new(parser, { queries = { c = hl_query_c } }) + end) screen:expect { grid = injection_grid_expected_c } end) @@ -502,13 +559,14 @@ describe('treesitter highlighting (C)', function() } ]]) - exec_lua [[ - local injection_query = '(preproc_def (preproc_arg) @injection.content (#set! injection.language "c")) (preproc_function_def value: (preproc_arg) @injection.content (#set! injection.language "c"))' - vim.treesitter.query.set("c", "highlights", hl_query) - vim.treesitter.query.set("c", "injections", injection_query) + exec_lua(function() + local injection_query = + '(preproc_def (preproc_arg) @injection.content (#set! injection.language "c")) (preproc_function_def value: (preproc_arg) @injection.content (#set! injection.language "c"))' + vim.treesitter.query.set('c', 'highlights', hl_query_c) + vim.treesitter.query.set('c', 'injections', injection_query) - vim.treesitter.highlighter.new(vim.treesitter.get_parser(0, "c")) - ]] + vim.treesitter.highlighter.new(vim.treesitter.get_parser(0, 'c')) + end) screen:expect { grid = [[ @@ -526,40 +584,21 @@ describe('treesitter highlighting (C)', function() it('supports highlighting with custom highlight groups', function() insert(hl_text_c) + feed('gg') - exec_lua [[ - local parser = vim.treesitter.get_parser(0, "c") - test_hl = vim.treesitter.highlighter.new(parser, {queries = {c = hl_query}}) - ]] + exec_lua(function() + local parser = vim.treesitter.get_parser(0, 'c') + vim.treesitter.highlighter.new(parser, { queries = { c = hl_query_c } }) + end) - 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 - | - ]], - } + screen:expect(hl_grid_ts_c) -- 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 { grid = [[ - {2:/// Schedule Lua callback on main loop's event queue} | + {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} | @@ -573,7 +612,7 @@ describe('treesitter highlighting (C)', function() multiqueue_put(main_loop.events, {11:nlua_schedule_event}, | {5:1}, ({3:void} *)({3:ptrdiff_t})cb); | {4:return} {5:0}; | - ^} | + } | {1:~ }|*2 | ]], @@ -590,10 +629,14 @@ describe('treesitter highlighting (C)', function() } ]]) - exec_lua [[ - local parser = vim.treesitter.get_parser(0, "c") - test_hl = vim.treesitter.highlighter.new(parser, {queries = {c = hl_query..'\n((translation_unit) @constant (#set! "priority" 101))\n'}}) - ]] + exec_lua(function() + local parser = vim.treesitter.get_parser(0, 'c') + vim.treesitter.highlighter.new(parser, { + queries = { + c = hl_query_c .. '\n((translation_unit) @constant (#set! "priority" 101))\n', + }, + }) + end) -- expect everything to have Constant highlight screen:expect { grid = [[ @@ -640,11 +683,14 @@ describe('treesitter highlighting (C)', function() hi link @foo.bar Type hi link @foo String ]] - exec_lua [[ - local parser = vim.treesitter.get_parser(0, "c", {}) - local highlighter = vim.treesitter.highlighter - test_hl = highlighter.new(parser, {queries = {c = "(primitive_type) @foo.bar (string_literal) @foo"}}) - ]] + exec_lua(function() + local parser = vim.treesitter.get_parser(0, 'c', {}) + local highlighter = vim.treesitter.highlighter + highlighter.new( + parser, + { queries = { c = '(primitive_type) @foo.bar (string_literal) @foo' } } + ) + end) screen:expect { grid = [[ @@ -672,10 +718,12 @@ describe('treesitter highlighting (C)', function() insert(hl_text_c) -- conceal can be empty or a single cchar. - exec_lua [=[ + exec_lua(function() vim.opt.cole = 2 - local parser = vim.treesitter.get_parser(0, "c") - test_hl = vim.treesitter.highlighter.new(parser, {queries = {c = [[ + local parser = vim.treesitter.get_parser(0, 'c') + vim.treesitter.highlighter.new(parser, { + queries = { + c = [[ ("static" @keyword (#set! conceal "R")) @@ -688,8 +736,10 @@ describe('treesitter highlighting (C)', function() arguments: (argument_list) @arguments) (#eq? @function "multiqueue_put") (#set! @function conceal "V")) - ]]}}) - ]=] + ]], + }, + }) + end) screen:expect { grid = [[ @@ -746,11 +796,11 @@ describe('treesitter highlighting (C)', function() int z = 6; ]]) - exec_lua([[ + exec_lua(function() local query = '((declaration)+ @string)' vim.treesitter.query.set('c', 'highlights', query) vim.treesitter.highlighter.new(vim.treesitter.get_parser(0, 'c')) - ]]) + end) screen:expect { grid = [[ @@ -776,14 +826,10 @@ describe('treesitter highlighting (C)', function() declarator: (pointer_declarator) @variable.parameter) ]] - exec_lua( - [[ - local query = ... + exec_lua(function() vim.treesitter.query.set('c', 'highlights', query) vim.treesitter.highlighter.new(vim.treesitter.get_parser(0, 'c')) - ]], - query - ) + end) screen:expect { grid = [[ @@ -800,6 +846,7 @@ describe('treesitter highlighting (lua)', function() local screen before_each(function() + clear() screen = Screen.new(65, 18) screen:attach() screen:set_default_attr_ids { @@ -817,10 +864,10 @@ describe('treesitter highlighting (lua)', function() ffi.cdef("int (*fun)(int, char *);") ]] - exec_lua [[ + exec_lua(function() vim.bo.filetype = 'lua' vim.treesitter.start() - ]] + end) screen:expect { grid = [[ @@ -838,6 +885,7 @@ describe('treesitter highlighting (help)', function() local screen before_each(function() + clear() screen = Screen.new(40, 6) screen:attach() screen:set_default_attr_ids { @@ -846,9 +894,45 @@ describe('treesitter highlighting (help)', function() [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() + -- Avoid regressions when syncing upstream vimdoc queries. + + insert [[ + ============================================================================== + NVIM DOCUMENTATION + + ------------------------------------------------------------------------------ + ABOUT NVIM *tag-1* *tag-2* + + |news| News + |nvim| NVim + ]] + + feed('gg') + exec_lua(function() + vim.wo.wrap = false + vim.bo.filetype = 'help' + vim.treesitter.start() + end) + + screen:expect({ + grid = [[ + {h1_delim:^========================================}| + {title:NVIM DOCUMENTATION} | + | + {h2_delim:----------------------------------------}| + {title:ABOUT NVIM} | + | + ]], + }) + end) + it('correctly redraws added/removed injections', function() insert [[ >ruby @@ -857,10 +941,10 @@ describe('treesitter highlighting (help)', function() < ]] - exec_lua [[ + exec_lua(function() vim.bo.filetype = 'help' vim.treesitter.start() - ]] + end) screen:expect { grid = [[ @@ -912,15 +996,15 @@ describe('treesitter highlighting (help)', function() ]] ]=]) - exec_lua [[ - parser = vim.treesitter.get_parser(0, "lua", { + exec_lua(function() + local parser = vim.treesitter.get_parser(0, 'lua', { injections = { - lua = '(string content: (_) @injection.content (#set! injection.language lua))' - } + lua = '(string content: (_) @injection.content (#set! injection.language lua))', + }, }) vim.treesitter.highlighter.new(parser) - ]] + end) screen:expect { grid = [=[ @@ -936,9 +1020,10 @@ describe('treesitter highlighting (help)', function() end) describe('treesitter highlighting (nested injections)', function() - local screen + local screen --- @type test.functional.ui.screen before_each(function() + clear() screen = Screen.new(80, 7) screen:attach() screen:set_default_attr_ids { @@ -964,11 +1049,11 @@ vim.cmd([[ ]]) ]=] - exec_lua [[ + exec_lua(function() vim.opt.scrolloff = 0 vim.bo.filetype = 'lua' vim.treesitter.start() - ]] + end) -- invalidate the language tree feed('ggi--[[<ESC>04x') @@ -1006,41 +1091,93 @@ describe('treesitter highlighting (markdown)', function() local screen before_each(function() + clear() screen = Screen.new(40, 6) screen:attach() - 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 }, - } + exec_lua(function() + vim.bo.filetype = 'markdown' + vim.treesitter.start() + end) end) it('supports hyperlinks', function() local url = 'https://example.com' insert(string.format('[This link text](%s) is a hyperlink.', url)) - exec_lua([[ - vim.bo.filetype = 'markdown' - vim.treesitter.start() - ]]) + screen:add_extra_attr_ids({ + [100] = { foreground = Screen.colors.DarkCyan, url = 'https://example.com' }, + [101] = { + foreground = Screen.colors.SlateBlue, + url = 'https://example.com', + underline = true, + }, + }) + screen:expect({ + grid = [[ + {25:[}{100:This link text}{25:](}{101:https://example.com}{25:)} is| + a hyperlink^. | + {1:~ }|*3 + | + ]], + }) + end) - screen:expect { + it('works with spellchecked and smoothscrolled topline', function() + insert([[ +- $f(0)=\sum_{k=1}^{\infty}\frac{2}{\pi^{2}k^{2}}+\lim_{w \to 0}x$. + +```c +printf('Hello World!'); +``` + ]]) + command('set spell smoothscroll') + feed('gg<C-E>') + screen:add_extra_attr_ids({ [100] = { undercurl = true, special = Screen.colors.Red } }) + screen:expect({ grid = [[ - {4:[}{6:This link text}{4:](}{7:https://example.com}{4:)} is| - a hyperlink^. | - {2:~ }|*3 - | - ]], - 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.Magenta }, - [6] = { foreground = Screen.colors.Cyan4, url = url }, - [7] = { underline = true, foreground = Screen.colors.SlateBlue }, - }, - } + {1:<<<}k^{2}}+\{100:lim}_{w \to 0}x$^. | + | + {18:```}{15:c} | + {25:printf}{16:(}{26:'Hello World!'}{16:);} | + {18:```} | + | + ]], + }) end) end) + +it('starting and stopping treesitter highlight in init.lua works #29541', function() + t.write_file( + 'Xinit.lua', + [[ + vim.bo.ft = 'c' + vim.treesitter.start() + vim.treesitter.stop() + ]] + ) + finally(function() + os.remove('Xinit.lua') + end) + clear({ args = { '-u', 'Xinit.lua' } }) + eq('', api.nvim_get_vvar('errmsg')) + + local screen = Screen.new(65, 18) + screen:attach() + 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 + screen:expect(hl_grid_legacy_c) +end) diff --git a/test/functional/treesitter/inspect_tree_spec.lua b/test/functional/treesitter/inspect_tree_spec.lua index f5acfe7c4a..1f7d15cc96 100644 --- a/test/functional/treesitter/inspect_tree_spec.lua +++ b/test/functional/treesitter/inspect_tree_spec.lua @@ -22,10 +22,10 @@ describe('vim.treesitter.inspect_tree', function() print() ]]) - exec_lua([[ + exec_lua(function() vim.treesitter.start(0, 'lua') vim.treesitter.inspect_tree() - ]]) + end) expect_tree [[ (chunk ; [0, 0] - [2, 0] @@ -37,22 +37,26 @@ describe('vim.treesitter.inspect_tree', function() it('can toggle to show anonymous nodes', function() insert([[ - print() + print('hello') ]]) - exec_lua([[ + exec_lua(function() vim.treesitter.start(0, 'lua') vim.treesitter.inspect_tree() - ]]) + end) feed('a') expect_tree [[ (chunk ; [0, 0] - [2, 0] - (function_call ; [0, 0] - [0, 7] + (function_call ; [0, 0] - [0, 14] name: (identifier) ; [0, 0] - [0, 5] - arguments: (arguments ; [0, 5] - [0, 7] + arguments: (arguments ; [0, 5] - [0, 14] "(" ; [0, 5] - [0, 6] - ")"))) ; [0, 6] - [0, 7] + (string ; [0, 6] - [0, 13] + start: "'" ; [0, 6] - [0, 7] + content: (string_content) ; [0, 7] - [0, 12] + end: "'") ; [0, 12] - [0, 13] + ")"))) ; [0, 13] - [0, 14] ]] end) @@ -63,11 +67,11 @@ describe('vim.treesitter.inspect_tree', function() ``` ]]) - exec_lua([[ + exec_lua(function() vim.treesitter.start(0, 'markdown') vim.treesitter.get_parser():parse() vim.treesitter.inspect_tree() - ]]) + end) expect_tree [[ (document ; [0, 0] - [4, 0] @@ -92,11 +96,11 @@ describe('vim.treesitter.inspect_tree', function() ``` ]]) - exec_lua([[ + exec_lua(function() vim.treesitter.start(0, 'markdown') vim.treesitter.get_parser():parse() vim.treesitter.inspect_tree() - ]]) + end) feed('I') expect_tree [[ @@ -114,4 +118,57 @@ describe('vim.treesitter.inspect_tree', function() (fenced_code_block_delimiter)))) ; [2, 0] - [2, 3] markdown ]] end) + + it('updates source and tree buffer windows and closes them correctly', function() + insert([[ + print() + ]]) + + -- 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, { + win = 0, + split = 'left', + }) + end) + + -- 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, { + win = 0, + split = 'left', + }) + _G.tree_win_copy_2 = 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)') + + -- navigates correctly to the remaining source buffer window + feed('<CR>') + eq('', n.api.nvim_get_vvar('errmsg')) + + -- close original tree window + exec_lua(function() + vim.api.nvim_set_current_win(_G.tree_win_copy_1) + 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')) + + -- close source buffer window and all remaining tree windows + t.pcall_err(exec_lua, 'vim.api.nvim_win_close(0, false)') + + 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)')) + end) end) diff --git a/test/functional/treesitter/language_spec.lua b/test/functional/treesitter/language_spec.lua index 40c974beee..e1e34fcecc 100644 --- a/test/functional/treesitter/language_spec.lua +++ b/test/functional/treesitter/language_spec.lua @@ -8,6 +8,7 @@ local exec_lua = n.exec_lua local pcall_err = t.pcall_err local matches = t.matches local insert = n.insert +local NIL = vim.NIL before_each(clear) @@ -15,10 +16,12 @@ describe('treesitter language API', function() -- error tests not requiring a parser library it('handles missing language', function() eq( - ".../language.lua:0: no parser for 'borklang' language, see :help treesitter-parsers", + '.../treesitter.lua:0: Parser could not be created for buffer 1 and language "borklang"', pcall_err(exec_lua, "parser = vim.treesitter.get_parser(0, 'borklang')") ) + eq(NIL, exec_lua("return vim.treesitter.get_parser(0, 'borklang', { error = false })")) + -- actual message depends on platform matches( "Failed to load parser for language 'borklang': uv_dlopen: .+", @@ -28,37 +31,33 @@ describe('treesitter language API', function() ) ) - eq(false, exec_lua("return pcall(vim.treesitter.language.add, 'borklang')")) + eq(NIL, exec_lua("return vim.treesitter.language.add('borklang')")) eq( false, exec_lua("return pcall(vim.treesitter.language.add, 'borklang', { path = 'borkbork.so' })") ) - eq( - ".../language.lua:0: no parser for 'borklang' language, see :help treesitter-parsers", - pcall_err(exec_lua, "parser = vim.treesitter.language.inspect('borklang')") - ) - matches( 'Failed to load parser: uv_dlsym: .+', pcall_err(exec_lua, 'vim.treesitter.language.add("c", { symbol_name = "borklang" })') ) end) - it('shows error for invalid language name', function() - eq( - ".../language.lua:0: '/foo/' is not a valid language name", - pcall_err(exec_lua, 'vim.treesitter.language.add("/foo/")') - ) + it('does not load parser for invalid language name', function() + eq(NIL, exec_lua('vim.treesitter.language.add("/foo/")')) end) it('inspects language', function() - local keys, fields, symbols = unpack(exec_lua([[ + local keys, fields, symbols = unpack(exec_lua(function() local lang = vim.treesitter.language.inspect('c') local keys, symbols = {}, {} - for k,_ in pairs(lang) do - keys[k] = true + for k, v in pairs(lang) do + if type(v) == 'boolean' then + keys[k] = v + else + keys[k] = true + end end -- symbols array can have "holes" and is thus not a valid msgpack array @@ -66,10 +65,10 @@ describe('treesitter language API', function() for _, v in pairs(lang.symbols) do table.insert(symbols, v) end - return {keys, lang.fields, symbols} - ]])) + return { keys, lang.fields, symbols } + end)) - eq({ fields = true, symbols = true, _abi_version = true }, keys) + eq({ fields = true, symbols = true, _abi_version = true, _wasm = false }, keys) local fset = {} for _, f in pairs(fields) do @@ -101,9 +100,10 @@ describe('treesitter language API', function() command('set filetype=borklang') -- Should throw an error when filetype changes to borklang eq( - ".../language.lua:0: no parser for 'borklang' language, see :help treesitter-parsers", + '.../treesitter.lua:0: Parser could not be created for buffer 1 and language "borklang"', pcall_err(exec_lua, "new_parser = vim.treesitter.get_parser(0, 'borklang')") ) + eq(NIL, exec_lua("return vim.treesitter.get_parser(0, 'borklang', { error = false })")) end ) @@ -113,12 +113,14 @@ describe('treesitter language API', function() int x = 3; }]]) - exec_lua([[ - langtree = vim.treesitter.get_parser(0, "c") - tree = langtree:tree_for_range({1, 3, 1, 3}) - ]]) - - eq('<node translation_unit>', exec_lua('return tostring(tree:root())')) + eq( + '<node translation_unit>', + exec_lua(function() + local langtree = vim.treesitter.get_parser(0, 'c') + local tree = langtree:tree_for_range({ 1, 3, 1, 3 }) + return tostring(tree:root()) + end) + ) end) it('retrieve the tree given a range when range is out of bounds relative to buffer', function() @@ -127,12 +129,14 @@ describe('treesitter language API', function() int x = 3; }]]) - exec_lua([[ - langtree = vim.treesitter.get_parser(0, "c") - tree = langtree:tree_for_range({10, 10, 10, 10}) - ]]) - - eq('<node translation_unit>', exec_lua('return tostring(tree:root())')) + eq( + '<node translation_unit>', + exec_lua(function() + local langtree = vim.treesitter.get_parser(0, 'c') + local tree = langtree:tree_for_range({ 10, 10, 10, 10 }) + return tostring(tree:root()) + end) + ) end) it('retrieve the node given a range', function() @@ -141,11 +145,24 @@ describe('treesitter language API', function() int x = 3; }]]) - exec_lua([[ - langtree = vim.treesitter.get_parser(0, "c") - node = langtree:named_node_for_range({1, 3, 1, 3}) - ]]) + eq( + '<node primitive_type>', + exec_lua(function() + local langtree = vim.treesitter.get_parser(0, 'c') + local node = langtree:named_node_for_range({ 1, 3, 1, 3 }) + return tostring(node) + end) + ) + end) + + it('retrieve an anonymous node given a range', function() + insert([[vim.fn.input()]]) + + exec_lua(function() + _G.langtree = vim.treesitter.get_parser(0, 'lua') + _G.node = _G.langtree:node_for_range({ 0, 3, 0, 3 }) + end) - eq('<node primitive_type>', exec_lua('return tostring(node)')) + eq('.', exec_lua('return node:type()')) end) end) diff --git a/test/functional/treesitter/node_spec.lua b/test/functional/treesitter/node_spec.lua index 96579f296b..d07ed35368 100644 --- a/test/functional/treesitter/node_spec.lua +++ b/test/functional/treesitter/node_spec.lua @@ -18,43 +18,60 @@ describe('treesitter node API', function() it('double free tree', function() insert('F') - exec_lua([[ + exec_lua(function() vim.treesitter.start(0, 'lua') vim.treesitter.get_node():tree() vim.treesitter.get_node():tree() collectgarbage() - ]]) + end) assert_alive() end) it('double free tree 2', function() - exec_lua([[ - parser = vim.treesitter.get_parser(0, "c") + exec_lua(function() + local parser = vim.treesitter.get_parser(0, 'c') local x = parser:parse()[1]:root():tree() - vim.api.nvim_buf_set_text(0, 0,0, 0,0, {'y'}) + vim.api.nvim_buf_set_text(0, 0, 0, 0, 0, { 'y' }) parser:parse() - vim.api.nvim_buf_set_text(0, 0,0, 0,1, {'z'}) + vim.api.nvim_buf_set_text(0, 0, 0, 0, 1, { 'z' }) parser:parse() collectgarbage() x:root() - ]]) + end) assert_alive() end) it('get_node() with lang given', function() -- this buffer doesn't have filetype set! insert('local foo = function() end') - exec_lua([[ - node = vim.treesitter.get_node({ + exec_lua(function() + _G.node = vim.treesitter.get_node({ bufnr = 0, - pos = { 0, 6 }, -- on "foo" + pos = { 0, 6 }, -- on "foo" lang = 'lua', }) - ]]) + end) eq('foo', lua_eval('vim.treesitter.get_node_text(node, 0)')) eq('identifier', lua_eval('node:type()')) end) + it('get_node() with anonymous nodes included', function() + insert([[print('test')]]) + + exec_lua(function() + _G.parser = vim.treesitter.get_parser(0, 'lua') + _G.tree = _G.parser:parse()[1] + _G.node = vim.treesitter.get_node({ + bufnr = 0, + pos = { 0, 6 }, -- on the first apostrophe + include_anonymous = true, + }) + end) + + eq("'", lua_eval('node:type()')) + eq(false, lua_eval('node:named()')) + end) + it('can move between siblings', function() insert([[ int main(int x, int y, int z) { @@ -62,16 +79,16 @@ describe('treesitter node API', function() } ]]) - exec_lua([[ - parser = vim.treesitter.get_parser(0, "c") - tree = parser:parse()[1] - root = tree:root() - lang = vim.treesitter.language.inspect('c') + exec_lua(function() + local parser = vim.treesitter.get_parser(0, 'c') + local tree = parser:parse()[1] + _G.root = tree:root() + vim.treesitter.language.inspect('c') - function node_text(node) + function _G.node_text(node) return vim.treesitter.get_node_text(node, 0) end - ]]) + end) exec_lua 'node = root:descendant_for_range(0, 11, 0, 16)' eq('int x', lua_eval('node_text(node)')) @@ -101,13 +118,13 @@ describe('treesitter node API', function() int x = 3; }]]) - local len = exec_lua([[ - tree = vim.treesitter.get_parser(0, "c"):parse()[1] - node = tree:root():child(0) - children = node:named_children() + local len = exec_lua(function() + local tree = vim.treesitter.get_parser(0, 'c'):parse()[1] + local node = assert(tree:root():child(0)) + _G.children = node:named_children() - return #children - ]]) + return #_G.children + end) eq(3, len) eq('<node compound_statement>', lua_eval('tostring(children[3])')) @@ -119,11 +136,11 @@ describe('treesitter node API', function() int x = 3; }]]) - exec_lua([[ - tree = vim.treesitter.get_parser(0, "c"):parse()[1] - root = tree:root() - node = root:child(0):child(2) - ]]) + exec_lua(function() + local tree = vim.treesitter.get_parser(0, 'c'):parse()[1] + _G.root = tree:root() + _G.node = _G.root:child(0):child(2) + end) eq(lua_eval('tostring(root)'), lua_eval('tostring(node:root())')) end) @@ -134,11 +151,11 @@ describe('treesitter node API', function() int x = 3; }]]) - exec_lua([[ - tree = vim.treesitter.get_parser(0, "c"):parse()[1] - root = tree:root() - child = root:child(0):child(0) - ]]) + exec_lua(function() + local tree = vim.treesitter.get_parser(0, 'c'):parse()[1] + _G.root = tree:root() + _G.child = _G.root:child(0):child(0) + end) eq(28, lua_eval('root:byte_length()')) eq(3, lua_eval('child:byte_length()')) @@ -150,15 +167,15 @@ describe('treesitter node API', function() int x = 3; }]]) - exec_lua([[ - tree = vim.treesitter.get_parser(0, "c"):parse()[1] - root = tree:root() - main = root:child(0) - body = main:child(2) - statement = body:child(1) - declarator = statement:child(1) - value = declarator:child(1) - ]]) + 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()')) diff --git a/test/functional/treesitter/parser_spec.lua b/test/functional/treesitter/parser_spec.lua index dbd6bb3c23..c8829f4785 100644 --- a/test/functional/treesitter/parser_spec.lua +++ b/test/functional/treesitter/parser_spec.lua @@ -12,9 +12,9 @@ local feed = n.feed describe('treesitter parser API', function() before_each(function() clear() - exec_lua [[ + exec_lua(function() vim.g.__ts_debug = 1 - ]] + end) end) it('parses buffer', function() @@ -23,12 +23,12 @@ describe('treesitter parser API', function() int x = 3; }]]) - exec_lua([[ - parser = vim.treesitter.get_parser(0, "c") - tree = parser:parse()[1] - root = tree:root() - lang = vim.treesitter.language.inspect('c') - ]]) + exec_lua(function() + _G.parser = vim.treesitter.get_parser(0, 'c') + _G.tree = _G.parser:parse()[1] + _G.root = _G.tree:root() + _G.lang = vim.treesitter.language.inspect('c') + end) eq('<tree>', exec_lua('return tostring(tree)')) eq('<node translation_unit>', exec_lua('return tostring(root)')) @@ -59,11 +59,11 @@ describe('treesitter parser API', function() ) feed('2G7|ay') - exec_lua([[ - tree2 = parser:parse()[1] - root2 = tree2:root() - descendant2 = root2:descendant_for_range(1,2,1,13) - ]]) + exec_lua(function() + _G.tree2 = _G.parser:parse()[1] + _G.root2 = _G.tree2:root() + _G.descendant2 = _G.root2:descendant_for_range(1, 2, 1, 13) + end) eq(false, exec_lua('return tree2 == tree1')) eq(false, exec_lua('return root2 == root')) eq('<node declaration>', exec_lua('return tostring(descendant2)')) @@ -112,17 +112,17 @@ void ui_refresh(void) it('allows to iterate over nodes children', function() insert(test_text) - local res = exec_lua([[ - parser = vim.treesitter.get_parser(0, "c") + local res = exec_lua(function() + local parser = vim.treesitter.get_parser(0, 'c') - func_node = parser:parse()[1]:root():child(0) + local func_node = parser:parse()[1]:root():child(0) - res = {} + local res = {} for node, field in func_node:iter_children() do table.insert(res, { node:type(), field }) end return res - ]]) + end) eq({ { 'primitive_type', 'type' }, @@ -135,9 +135,7 @@ void ui_refresh(void) insert(test_text) eq( - '.../treesitter.lua:0: There is no parser available for buffer 1 and one' - .. ' could not be created because lang could not be determined. Either' - .. ' pass lang or set the buffer filetype', + '.../treesitter.lua:0: Parser not found for buffer 1: language could not be determined', pcall_err(exec_lua, 'vim.treesitter.get_parser(0)') ) @@ -148,43 +146,43 @@ void ui_refresh(void) it('allows to get a child by field', function() insert(test_text) - local res = exec_lua([[ - parser = vim.treesitter.get_parser(0, "c") + local res = exec_lua(function() + local parser = vim.treesitter.get_parser(0, 'c') - func_node = parser:parse()[1]:root():child(0) + _G.func_node = parser:parse()[1]:root():child(0) local res = {} - for _, node in ipairs(func_node:field("type")) do + for _, node in ipairs(_G.func_node:field('type')) do table.insert(res, { node:type(), node:range() }) end return res - ]]) + end) eq({ { 'primitive_type', 0, 0, 0, 4 } }, res) - local res_fail = exec_lua([[ - parser = vim.treesitter.get_parser(0, "c") + local res_fail = exec_lua(function() + vim.treesitter.get_parser(0, 'c') - return #func_node:field("foo") == 0 - ]]) + return #_G.func_node:field('foo') == 0 + end) assert(res_fail) end) it('supports getting text of multiline node', function() insert(test_text) - local res = exec_lua([[ - local parser = vim.treesitter.get_parser(0, "c") + local res = exec_lua(function() + local parser = vim.treesitter.get_parser(0, 'c') local tree = parser:parse()[1] return vim.treesitter.get_node_text(tree:root(), 0) - ]]) + end) eq(test_text, res) - local res2 = exec_lua([[ - local parser = vim.treesitter.get_parser(0, "c") + local res2 = exec_lua(function() + local parser = vim.treesitter.get_parser(0, 'c') local root = parser:parse()[1]:root() return vim.treesitter.get_node_text(root:child(0):child(0), 0) - ]]) + end) eq('void', res2) end) @@ -196,7 +194,7 @@ end]] insert(text) eq( '', - exec_lua [[ + exec_lua(function() local fake_node = {} function fake_node:start() return 3, 0, 23 @@ -211,7 +209,7 @@ end]] return 3, 0, 3, 0 end return vim.treesitter.get_node_text(fake_node, 0) - ]] + end) ) end) @@ -221,7 +219,7 @@ end]] {} ```]] insert(text) - local result = exec_lua([[ + local result = exec_lua(function() local fake_node = {} function fake_node:start() return 1, 0, 7 @@ -233,38 +231,38 @@ end]] return 1, 0, 1, 0 end return vim.treesitter.get_node_text(fake_node, 0) == '' - ]]) + end) eq(true, result) end) it('allows to set simple ranges', function() insert(test_text) - local res = exec_lua [[ - parser = vim.treesitter.get_parser(0, "c") - return { parser:parse()[1]:root():range() } - ]] + local res = exec_lua(function() + _G.parser = vim.treesitter.get_parser(0, 'c') + return { _G.parser:parse()[1]:root():range() } + end) eq({ 0, 0, 19, 0 }, res) -- The following sets the included ranges for the current parser -- As stated here, this only includes the function (thus the whole buffer, without the last line) - local res2 = exec_lua [[ - local root = parser:parse()[1]:root() - parser:set_included_regions({{root:child(0)}}) - parser:invalidate() - return { parser:parse(true)[1]:root():range() } - ]] + local res2 = exec_lua(function() + local root = _G.parser:parse()[1]:root() + _G.parser:set_included_regions({ { root:child(0) } }) + _G.parser:invalidate() + return { _G.parser:parse(true)[1]:root():range() } + end) eq({ 0, 0, 18, 1 }, res2) eq({ { { 0, 0, 0, 18, 1, 512 } } }, exec_lua [[ return parser:included_regions() ]]) - local range_tbl = exec_lua [[ - parser:set_included_regions { { { 0, 0, 17, 1 } } } - parser:parse() - return parser:included_regions() - ]] + local range_tbl = exec_lua(function() + _G.parser:set_included_regions { { { 0, 0, 17, 1 } } } + _G.parser:parse() + return _G.parser:included_regions() + end) eq({ { { 0, 0, 0, 17, 1, 508 } } }, range_tbl) end) @@ -272,25 +270,25 @@ end]] it('allows to set complex ranges', function() insert(test_text) - local res = exec_lua [[ - parser = vim.treesitter.get_parser(0, "c") - query = vim.treesitter.query.parse("c", "(declaration) @decl") + local res = exec_lua(function() + local parser = vim.treesitter.get_parser(0, 'c') + local query = vim.treesitter.query.parse('c', '(declaration) @decl') - local nodes = {} - for _, node in query:iter_captures(parser:parse()[1]:root(), 0) do - table.insert(nodes, node) - end + local nodes = {} + for _, node in query:iter_captures(parser:parse()[1]:root(), 0) do + table.insert(nodes, node) + end - parser:set_included_regions({nodes}) + parser:set_included_regions({ nodes }) - local root = parser:parse(true)[1]:root() + local root = parser:parse(true)[1]:root() - local res = {} - for i=0,(root:named_child_count() - 1) do - table.insert(res, { root:named_child(i):range() }) - end - return res - ]] + local res = {} + for i = 0, (root:named_child_count() - 1) do + table.insert(res, { root:named_child(i):range() }) + end + return res + end) eq({ { 2, 2, 2, 40 }, @@ -304,10 +302,10 @@ end]] end) it('allows to create string parsers', function() - local ret = exec_lua [[ - local parser = vim.treesitter.get_string_parser("int foo = 42;", "c") + local ret = exec_lua(function() + local parser = vim.treesitter.get_string_parser('int foo = 42;', 'c') return { parser:parse()[1]:root():range() } - ]] + end) eq({ 0, 0, 0, 13 }, ret) end) @@ -318,33 +316,31 @@ end]] int bar = 13; ]] - local ret = exec_lua( - [[ - local str = ... - local parser = vim.treesitter.get_string_parser(str, "c") + local ret = exec_lua(function(str) + local parser = vim.treesitter.get_string_parser(str, 'c') - local nodes = {} - local query = vim.treesitter.query.parse("c", '((identifier) @id (#eq? @id "foo"))') + local nodes = {} + local query = vim.treesitter.query.parse('c', '((identifier) @id (#eq? @id "foo"))') - for _, node in query:iter_captures(parser:parse()[1]:root(), str) do - table.insert(nodes, { node:range() }) - end + for _, node in query:iter_captures(parser:parse()[1]:root(), str) do + table.insert(nodes, { node:range() }) + end - return nodes - ]], - txt - ) + return nodes + end, txt) eq({ { 0, 10, 0, 13 } }, ret) end) describe('when creating a language tree', function() local function get_ranges() - return exec_lua [[ + return exec_lua(function() local result = {} - parser:for_each_tree(function(tree) table.insert(result, {tree:root():range()}) end) + _G.parser:for_each_tree(function(tree) + table.insert(result, { tree:root():range() }) + end) return result - ]] + end) end before_each(function() @@ -360,16 +356,17 @@ int x = INT_MAX; describe('when parsing regions independently', function() it('should inject a language', function() - exec_lua([[ - parser = vim.treesitter.get_parser(0, "c", { + exec_lua(function() + _G.parser = vim.treesitter.get_parser(0, 'c', { injections = { c = ( - '(preproc_def (preproc_arg) @injection.content (#set! injection.language "c")) ' .. - '(preproc_function_def value: (preproc_arg) @injection.content (#set! injection.language "c"))' - ) - }}) - parser:parse(true) - ]]) + '(preproc_def (preproc_arg) @injection.content (#set! injection.language "c")) ' + .. '(preproc_function_def value: (preproc_arg) @injection.content (#set! injection.language "c"))' + ), + }, + }) + _G.parser:parse(true) + end) eq('table', exec_lua('return type(parser:children().c)')) eq(5, exec_lua('return #parser:children().c:trees()')) @@ -397,16 +394,17 @@ int x = INT_MAX; describe('when parsing regions combined', function() it('should inject a language', function() - exec_lua([[ - parser = vim.treesitter.get_parser(0, "c", { + exec_lua(function() + _G.parser = vim.treesitter.get_parser(0, 'c', { injections = { c = ( - '(preproc_def (preproc_arg) @injection.content (#set! injection.language "c") (#set! injection.combined)) ' .. - '(preproc_function_def value: (preproc_arg) @injection.content (#set! injection.language "c") (#set! injection.combined))' - ) - }}) - parser:parse(true) - ]]) + '(preproc_def (preproc_arg) @injection.content (#set! injection.language "c") (#set! injection.combined)) ' + .. '(preproc_function_def value: (preproc_arg) @injection.content (#set! injection.language "c") (#set! injection.combined))' + ), + }, + }) + _G.parser:parse(true) + end) eq('table', exec_lua('return type(parser:children().c)')) eq(2, exec_lua('return #parser:children().c:trees()')) @@ -447,16 +445,17 @@ int x = INT_MAX; describe('when using injection.self', function() it('should inject the source language', function() - exec_lua([[ - parser = vim.treesitter.get_parser(0, "c", { + exec_lua(function() + _G.parser = vim.treesitter.get_parser(0, 'c', { injections = { c = ( - '(preproc_def (preproc_arg) @injection.content (#set! injection.self)) ' .. - '(preproc_function_def value: (preproc_arg) @injection.content (#set! injection.self))' - ) - }}) - parser:parse(true) - ]]) + '(preproc_def (preproc_arg) @injection.content (#set! injection.self)) ' + .. '(preproc_function_def value: (preproc_arg) @injection.content (#set! injection.self))' + ), + }, + }) + _G.parser:parse(true) + end) eq('table', exec_lua('return type(parser:children().c)')) eq(5, exec_lua('return #parser:children().c:trees()')) @@ -484,16 +483,17 @@ int x = INT_MAX; describe('when using the offset directive', function() it('should shift the range by the directive amount', function() - exec_lua([[ - parser = vim.treesitter.get_parser(0, "c", { + exec_lua(function() + _G.parser = vim.treesitter.get_parser(0, 'c', { injections = { c = ( - '(preproc_def ((preproc_arg) @injection.content (#set! injection.language "c") (#offset! @injection.content 0 2 0 -1))) ' .. - '(preproc_function_def value: (preproc_arg) @injection.content (#set! injection.language "c"))' - ) - }}) - parser:parse(true) - ]]) + '(preproc_def ((preproc_arg) @injection.content (#set! injection.language "c") (#offset! @injection.content 0 2 0 -1))) ' + .. '(preproc_function_def value: (preproc_arg) @injection.content (#set! injection.language "c"))' + ), + }, + }) + _G.parser:parse(true) + end) eq('table', exec_lua('return type(parser:children().c)')) eq({ @@ -506,7 +506,7 @@ int x = INT_MAX; }, get_ranges()) end) it('should list all directives', function() - local res_list = exec_lua [[ + local res_list = exec_lua(function() local query = vim.treesitter.query local list = query.list_directives() @@ -514,7 +514,7 @@ int x = INT_MAX; table.sort(list) return list - ]] + end) eq({ 'gsub!', 'offset!', 'set!', 'trim!' }, res_list) end) @@ -530,18 +530,18 @@ int x = INT_MAX; end) it('should return the correct language tree', function() - local result = exec_lua([[ - parser = vim.treesitter.get_parser(0, "c", { + local result = exec_lua(function() + local parser = vim.treesitter.get_parser(0, 'c', { injections = { - c = '(preproc_def (preproc_arg) @injection.content (#set! injection.language "c"))' - } + c = '(preproc_def (preproc_arg) @injection.content (#set! injection.language "c"))', + }, }) parser:parse(true) - local sub_tree = parser:language_for_range({1, 18, 1, 19}) + local sub_tree = parser:language_for_range({ 1, 18, 1, 19 }) return sub_tree == parser:children().c - ]]) + end) eq(true, result) end) @@ -555,23 +555,23 @@ print() end) it('ignores optional captures #23100', function() - local result = exec_lua([[ - parser = vim.treesitter.get_parser(0, "lua", { + local result = exec_lua(function() + local parser = vim.treesitter.get_parser(0, 'lua', { injections = { lua = ( - '(function_call ' .. - '(arguments ' .. - '(string)? @injection.content ' .. - '(number)? @injection.content ' .. - '(#offset! @injection.content 0 1 0 -1) ' .. - '(#set! injection.language "c")))' - ) - } + '(function_call ' + .. '(arguments ' + .. '(string)? @injection.content ' + .. '(number)? @injection.content ' + .. '(#offset! @injection.content 0 1 0 -1) ' + .. '(#set! injection.language "c")))' + ), + }, }) parser:parse(true) return parser:is_valid() - ]]) + end) eq(true, result) end) @@ -584,18 +584,14 @@ print() int x = 3; ]]) - local result = exec_lua([[ - local result - - query = vim.treesitter.query.parse("c", '((number_literal) @number (#set! "key" "value"))') - parser = vim.treesitter.get_parser(0, "c") - - for pattern, match, metadata in query:iter_matches(parser:parse()[1]:root(), 0, 0, -1, { all = true }) do - result = metadata.key - end + local result = exec_lua(function() + local query = + vim.treesitter.query.parse('c', '((number_literal) @number (#set! "key" "value"))') + local parser = vim.treesitter.get_parser(0, 'c') - return result - ]]) + local _, _, metadata = query:iter_matches(parser:parse()[1]:root(), 0, 0, -1)() + return metadata.key + end) eq('value', result) end) @@ -606,19 +602,17 @@ print() int x = 3; ]]) - local result = exec_lua([[ - local query = vim.treesitter.query - local value + local result = exec_lua(function() + local query = vim.treesitter.query.parse( + 'c', + '((number_literal) @number (#set! @number "key" "value"))' + ) + local parser = vim.treesitter.get_parser(0, 'c') - query = vim.treesitter.query.parse("c", '((number_literal) @number (#set! @number "key" "value"))') - parser = vim.treesitter.get_parser(0, "c") - - for pattern, match, metadata in query:iter_matches(parser:parse()[1]:root(), 0, 0, -1, { all = true }) do - for _, nested_tbl in pairs(metadata) do - return nested_tbl.key - end - end - ]]) + local _, _, metadata = query:iter_matches(parser:parse()[1]:root(), 0, 0, -1)() + local _, nested_tbl = next(metadata) + return nested_tbl.key + end) eq('value', result) end) @@ -628,19 +622,17 @@ print() int x = 3; ]]) - local result = exec_lua([[ - local query = vim.treesitter.query - local result - - query = vim.treesitter.query.parse("c", '((number_literal) @number (#set! @number "key" "value") (#set! @number "key2" "value2"))') - parser = vim.treesitter.get_parser(0, "c") + local result = exec_lua(function() + local query = vim.treesitter.query.parse( + 'c', + '((number_literal) @number (#set! @number "key" "value") (#set! @number "key2" "value2"))' + ) + local parser = vim.treesitter.get_parser(0, 'c') - for pattern, match, metadata in query:iter_matches(parser:parse()[1]:root(), 0, 0, -1, { all = true }) do - for _, nested_tbl in pairs(metadata) do - return nested_tbl - end - end - ]]) + local _, _, metadata = query:iter_matches(parser:parse()[1]:root(), 0, 0, -1)() + local _, nested_tbl = next(metadata) + return nested_tbl + end) local expected = { ['key'] = 'value', ['key2'] = 'value2', @@ -663,24 +655,21 @@ print() (function_definition) @function ]] - exec_lua([[ + exec_lua(function() vim.treesitter.start(0, 'c') - ]]) + end) local function run_query() - return exec_lua( - [[ - local query = vim.treesitter.query.parse("c", ...) - parser = vim.treesitter.get_parser() - tree = parser:parse()[1] - res = {} - for id, node in query:iter_captures(tree:root()) do - table.insert(res, {query.captures[id], node:range()}) - end - return res - ]], - query0 - ) + 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({ @@ -718,18 +707,15 @@ print() ]] ]==] - local r = exec_lua( - [[ - local parser = vim.treesitter.get_string_parser(..., 'lua') - parser:parse(true) - local ranges = {} - parser:for_each_tree(function(tstree, tree) - ranges[tree:lang()] = { tstree:root():range(true) } - end) - return ranges - ]], - source - ) + local r = exec_lua(function() + local parser = vim.treesitter.get_string_parser(source, 'lua') + parser:parse(true) + local ranges = {} + parser:for_each_tree(function(tstree, tree) + ranges[tree:lang()] = { tstree:root():range(true) } + end) + return ranges + end) eq({ lua = { 0, 6, 6, 16, 4, 438 }, @@ -741,19 +727,14 @@ print() -- the ranges but only provide a Range4. Strip the byte entries from the ranges and make sure -- add_bytes() produces the same result. - local rb = exec_lua( - [[ - local r, source = ... - local add_bytes = require('vim.treesitter._range').add_bytes - for lang, range in pairs(r) do - r[lang] = {range[1], range[2], range[4], range[5]} - r[lang] = add_bytes(source, r[lang]) - end - return r - ]], - r, - source - ) + local rb = exec_lua(function() + local add_bytes = require('vim.treesitter._range').add_bytes + for lang, range in pairs(r) do + r[lang] = { range[1], range[2], range[4], range[5] } + r[lang] = add_bytes(source, r[lang]) + end + return r + end) eq(rb, r) end) @@ -766,25 +747,25 @@ print() ]] -- This is not a valid injection since (code) has children and include-children is not set - exec_lua [[ - parser1 = require('vim.treesitter.languagetree').new(0, "vimdoc", { + exec_lua(function() + _G.parser1 = require('vim.treesitter.languagetree').new(0, 'vimdoc', { injections = { - vimdoc = "((codeblock (language) @injection.language (code) @injection.content))" - } + vimdoc = '((codeblock (language) @injection.language (code) @injection.content))', + }, }) - parser1:parse(true) - ]] + _G.parser1:parse(true) + end) eq(0, exec_lua('return #vim.tbl_keys(parser1:children())')) - exec_lua [[ - parser2 = require('vim.treesitter.languagetree').new(0, "vimdoc", { + exec_lua(function() + _G.parser2 = require('vim.treesitter.languagetree').new(0, 'vimdoc', { injections = { - vimdoc = "((codeblock (language) @injection.language (code) @injection.content) (#set! injection.include-children))" - } + vimdoc = '((codeblock (language) @injection.language (code) @injection.content) (#set! injection.include-children))', + }, }) - parser2:parse(true) - ]] + _G.parser2:parse(true) + end) eq(1, exec_lua('return #vim.tbl_keys(parser2:children())')) eq({ { { 1, 0, 21, 2, 0, 42 } } }, exec_lua('return parser2:children().lua:included_regions()')) @@ -821,46 +802,46 @@ print() < ]]) - exec_lua [[ - parser = require('vim.treesitter.languagetree').new(0, "vimdoc", { + exec_lua(function() + _G.parser = require('vim.treesitter.languagetree').new(0, 'vimdoc', { injections = { - vimdoc = "((codeblock (language) @injection.language (code) @injection.content) (#set! injection.include-children))" - } + vimdoc = '((codeblock (language) @injection.language (code) @injection.content) (#set! injection.include-children))', + }, }) - ]] + end) --- Do not parse injections by default eq( 0, - exec_lua [[ - parser:parse() - return #vim.tbl_keys(parser:children()) - ]] + exec_lua(function() + _G.parser:parse() + return #vim.tbl_keys(_G.parser:children()) + end) ) --- Only parse injections between lines 0, 2 eq( 1, - exec_lua [[ - parser:parse({0, 2}) - return #parser:children().lua:trees() - ]] + exec_lua(function() + _G.parser:parse({ 0, 2 }) + return #_G.parser:children().lua:trees() + end) ) eq( 2, - exec_lua [[ - parser:parse({2, 6}) - return #parser:children().lua:trees() - ]] + exec_lua(function() + _G.parser:parse({ 2, 6 }) + return #_G.parser:children().lua:trees() + end) ) eq( 7, - exec_lua [[ - parser:parse(true) - return #parser:children().lua:trees() - ]] + exec_lua(function() + _G.parser:parse(true) + return #_G.parser:children().lua:trees() + end) ) end) @@ -876,13 +857,13 @@ print() feed(':set ft=help<cr>') - exec_lua [[ - vim.treesitter.get_parser(0, "vimdoc", { + exec_lua(function() + vim.treesitter.get_parser(0, 'vimdoc', { injections = { - vimdoc = "((codeblock (language) @injection.language (code) @injection.content) (#set! injection.include-children))" - } + vimdoc = '((codeblock (language) @injection.language (code) @injection.content) (#set! injection.include-children))', + }, }) - ]] + end) end) it('is valid excluding, invalid including children initially', function() diff --git a/test/functional/treesitter/query_spec.lua b/test/functional/treesitter/query_spec.lua index c3a376cd71..c97619c913 100644 --- a/test/functional/treesitter/query_spec.lua +++ b/test/functional/treesitter/query_spec.lua @@ -10,28 +10,26 @@ local pcall_err = t.pcall_err local api = n.api local fn = n.fn -local get_query_result_code = [[ - function get_query_result(query_text) - cquery = vim.treesitter.query.parse("c", query_text) - parser = vim.treesitter.get_parser(0, "c") - tree = parser:parse()[1] - res = {} - for cid, node in cquery:iter_captures(tree:root(), 0) do - -- can't transmit node over RPC. just check the name, range, and text - local text = vim.treesitter.get_node_text(node, 0) - local range = {node:range()} - table.insert(res, { cquery.captures[cid], node:type(), range, text }) - end - return res +local function get_query_result(query_text) + local cquery = vim.treesitter.query.parse('c', query_text) + local parser = vim.treesitter.get_parser(0, 'c') + local tree = parser:parse()[1] + local res = {} + for cid, node in cquery:iter_captures(tree:root(), 0) do + -- can't transmit node over RPC. just check the name, range, and text + local text = vim.treesitter.get_node_text(node, 0) + local range = { node:range() } + table.insert(res, { cquery.captures[cid], node:type(), range, text }) end -]] + return res +end describe('treesitter query API', function() before_each(function() clear() - exec_lua [[ + exec_lua(function() vim.g.__ts_debug = 1 - ]] + end) end) local test_text = [[ @@ -71,9 +69,9 @@ void ui_refresh(void) it('supports runtime queries', function() ---@type string[] - local ret = exec_lua [[ - return vim.treesitter.query.get("c", "highlights").captures - ]] + local ret = exec_lua(function() + return vim.treesitter.query.get('c', 'highlights').captures + end) -- see $VIMRUNTIME/queries/c/highlights.scm eq('variable', ret[1]) @@ -84,22 +82,17 @@ void ui_refresh(void) local long_query = test_query:rep(100) ---@return number local function q(_n) - return exec_lua( - [[ - local query, n = ... - local before = vim.api.nvim__stats().ts_query_parse_count - collectgarbage("stop") - for i=1, n, 1 do - cquery = vim.treesitter.query.parse("c", ...) - end - collectgarbage("restart") - collectgarbage("collect") - local after = vim.api.nvim__stats().ts_query_parse_count - return after - before - ]], - long_query, - _n - ) + return exec_lua(function() + 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) + end + collectgarbage('restart') + collectgarbage('collect') + local after = vim.api.nvim__stats().ts_query_parse_count + return after - before + end) end eq(1, q(1)) @@ -110,20 +103,17 @@ void ui_refresh(void) it('supports query and iter by capture (iter_captures)', function() insert(test_text) - local res = exec_lua( - [[ - cquery = vim.treesitter.query.parse("c", ...) - parser = vim.treesitter.get_parser(0, "c") - tree = parser:parse()[1] - res = {} - for cid, node in cquery:iter_captures(tree:root(), 0, 7, 14) do - -- can't transmit node over RPC. just check the name and range - table.insert(res, { '@' .. cquery.captures[cid], node:type(), node:range() }) - end - return res - ]], - test_query - ) + local res = exec_lua(function() + local cquery = vim.treesitter.query.parse('c', test_query) + local parser = vim.treesitter.get_parser(0, 'c') + local tree = parser:parse()[1] + local res = {} + for cid, node in cquery:iter_captures(tree:root(), 0, 7, 14) do + -- can't transmit node over RPC. just check the name and range + table.insert(res, { '@' .. cquery.captures[cid], node:type(), node:range() }) + end + return res + end) eq({ { '@type', 'primitive_type', 8, 2, 8, 6 }, -- bool @@ -143,26 +133,23 @@ void ui_refresh(void) insert(test_text) ---@type table - local res = exec_lua( - [[ - cquery = vim.treesitter.query.parse("c", ...) - parser = vim.treesitter.get_parser(0, "c") - tree = parser:parse()[1] - res = {} - for pattern, match in cquery:iter_matches(tree:root(), 0, 7, 14, { all = true }) do - -- can't transmit node over RPC. just check the name and range - local mrepr = {} - for cid, nodes in pairs(match) do - for _, node in ipairs(nodes) do - table.insert(mrepr, { '@' .. cquery.captures[cid], node:type(), node:range() }) - end + local res = exec_lua(function() + local cquery = vim.treesitter.query.parse('c', test_query) + local parser = vim.treesitter.get_parser(0, 'c') + local tree = parser:parse()[1] + local res = {} + for pattern, match in cquery:iter_matches(tree:root(), 0, 7, 14) do + -- can't transmit node over RPC. just check the name and range + local mrepr = {} + for cid, nodes in pairs(match) do + for _, node in ipairs(nodes) do + table.insert(mrepr, { '@' .. cquery.captures[cid], node:type(), node:range() }) end - table.insert(res, { pattern, mrepr }) end - return res - ]], - test_query - ) + table.insert(res, { pattern, mrepr }) + end + return res + end) eq({ { 3, { { '@type', 'primitive_type', 8, 2, 8, 6 } } }, @@ -191,20 +178,20 @@ void ui_refresh(void) it('supports query and iter by capture for quantifiers', function() insert(test_text) - local res = exec_lua( - [[ - cquery = vim.treesitter.query.parse("c", ...) - parser = vim.treesitter.get_parser(0, "c") - tree = parser:parse()[1] - res = {} - for cid, node in cquery:iter_captures(tree:root(), 0, 7, 14) do - -- can't transmit node over RPC. just check the name and range - table.insert(res, { '@' .. cquery.captures[cid], node:type(), node:range() }) - end - return res - ]], - '(expression_statement (assignment_expression (call_expression)))+ @funccall' - ) + local res = exec_lua(function() + local cquery = vim.treesitter.query.parse( + 'c', + '(expression_statement (assignment_expression (call_expression)))+ @funccall' + ) + local parser = vim.treesitter.get_parser(0, 'c') + local tree = parser:parse()[1] + local res = {} + for cid, node in cquery:iter_captures(tree:root(), 0, 7, 14) do + -- can't transmit node over RPC. just check the name and range + table.insert(res, { '@' .. cquery.captures[cid], node:type(), node:range() }) + end + return res + end) eq({ { '@funccall', 'expression_statement', 11, 4, 11, 34 }, @@ -216,26 +203,26 @@ void ui_refresh(void) it('supports query and iter by match for quantifiers', function() insert(test_text) - local res = exec_lua( - [[ - cquery = vim.treesitter.query.parse("c", ...) - parser = vim.treesitter.get_parser(0, "c") - tree = parser:parse()[1] - res = {} - for pattern, match in cquery:iter_matches(tree:root(), 0, 7, 14, { all = true }) do - -- can't transmit node over RPC. just check the name and range - local mrepr = {} - for cid, nodes in pairs(match) do - for _, node in ipairs(nodes) do - table.insert(mrepr, { '@' .. cquery.captures[cid], node:type(), node:range() }) - end + local res = exec_lua(function() + local cquery = vim.treesitter.query.parse( + 'c', + '(expression_statement (assignment_expression (call_expression)))+ @funccall' + ) + local parser = vim.treesitter.get_parser(0, 'c') + local tree = parser:parse()[1] + local res = {} + for pattern, match in cquery:iter_matches(tree:root(), 0, 7, 14) do + -- can't transmit node over RPC. just check the name and range + local mrepr = {} + for cid, nodes in pairs(match) do + for _, node in ipairs(nodes) do + table.insert(mrepr, { '@' .. cquery.captures[cid], node:type(), node:range() }) end - table.insert(res, {pattern, mrepr}) end - return res - ]], - '(expression_statement (assignment_expression (call_expression)))+ @funccall' - ) + table.insert(res, { pattern, mrepr }) + end + return res + end, '(expression_statement (assignment_expression (call_expression)))+ @funccall') eq({ { @@ -249,23 +236,78 @@ void ui_refresh(void) }, res) end) + it('returns quantified matches in order of range #29344', function() + insert([[ + int main() { + int a, b, c, d, e, f, g, h, i; + a = MIN(0, 1); + b = MIN(0, 1); + c = MIN(0, 1); + d = MIN(0, 1); + e = MIN(0, 1); + f = MIN(0, 1); + g = MIN(0, 1); + h = MIN(0, 1); + i = MIN(0, 1); + } + ]]) + + local res = exec_lua(function() + local cquery = vim.treesitter.query.parse( + 'c', + '(expression_statement (assignment_expression (call_expression)))+ @funccall' + ) + local parser = vim.treesitter.get_parser(0, 'c') + local tree = parser:parse()[1] + local res = {} + for pattern, match in cquery:iter_matches(tree:root(), 0, 7, 14) do + -- can't transmit node over RPC. just check the name and range + local mrepr = {} + for cid, nodes in pairs(match) do + for _, node in ipairs(nodes) do + table.insert(mrepr, { '@' .. cquery.captures[cid], node:type(), node:range() }) + end + end + table.insert(res, { pattern, mrepr }) + end + return res + end) + + eq({ + { + 1, + { + { '@funccall', 'expression_statement', 2, 2, 2, 16 }, + { '@funccall', 'expression_statement', 3, 2, 3, 16 }, + { '@funccall', 'expression_statement', 4, 2, 4, 16 }, + { '@funccall', 'expression_statement', 5, 2, 5, 16 }, + { '@funccall', 'expression_statement', 6, 2, 6, 16 }, + { '@funccall', 'expression_statement', 7, 2, 7, 16 }, + { '@funccall', 'expression_statement', 8, 2, 8, 16 }, + { '@funccall', 'expression_statement', 9, 2, 9, 16 }, + { '@funccall', 'expression_statement', 10, 2, 10, 16 }, + }, + }, + }, res) + end) + it('can match special regex characters like \\ * + ( with `vim-match?`', function() insert('char* astring = "\\n"; (1 + 1) * 2 != 2;') ---@type table - local res = exec_lua([[ - query = ( - '([_] @plus (#vim-match? @plus "^\\\\+$"))' .. - '([_] @times (#vim-match? @times "^\\\\*$"))' .. - '([_] @paren (#vim-match? @paren "^\\\\($"))' .. - '([_] @escape (#vim-match? @escape "^\\\\\\\\n$"))' .. - '([_] @string (#vim-match? @string "^\\"\\\\\\\\n\\"$"))' + local res = exec_lua(function() + local query = ( + '([_] @plus (#vim-match? @plus "^\\\\+$"))' + .. '([_] @times (#vim-match? @times "^\\\\*$"))' + .. '([_] @paren (#vim-match? @paren "^\\\\($"))' + .. '([_] @escape (#vim-match? @escape "^\\\\\\\\n$"))' + .. '([_] @string (#vim-match? @string "^\\"\\\\\\\\n\\"$"))' ) - cquery = vim.treesitter.query.parse("c", query) - parser = vim.treesitter.get_parser(0, "c") - tree = parser:parse()[1] - res = {} - for pattern, match in cquery:iter_matches(tree:root(), 0, 0, -1, { all = true }) do + local cquery = vim.treesitter.query.parse('c', query) + local parser = vim.treesitter.get_parser(0, 'c') + local tree = parser:parse()[1] + local res = {} + for pattern, match in cquery:iter_matches(tree:root(), 0, 0, -1) do -- can't transmit node over RPC. just check the name and range local mrepr = {} for cid, nodes in pairs(match) do @@ -276,7 +318,7 @@ void ui_refresh(void) table.insert(res, { pattern, mrepr }) end return res - ]]) + end) eq({ { 2, { { '@times', '*', 0, 4, 0, 5 } } }, @@ -307,10 +349,9 @@ void ui_refresh(void) return 0; } ]]) - exec_lua(get_query_result_code) local res0 = exec_lua( - [[return get_query_result(...)]], + get_query_result, [[((primitive_type) @c-keyword (#any-of? @c-keyword "int" "float"))]] ) eq({ @@ -319,7 +360,7 @@ void ui_refresh(void) }, res0) local res1 = exec_lua( - [[return get_query_result(...)]], + get_query_result, [[ ((string_literal) @fizzbuzz-strings (#any-of? @fizzbuzz-strings "\"number= %d FizzBuzz\\n\"" @@ -340,16 +381,15 @@ void ui_refresh(void) int x = 123; enum C { y = 124 }; int main() { int z = 125; }]]) - exec_lua(get_query_result_code) local result = exec_lua( - [[return get_query_result(...)]], + get_query_result, [[((number_literal) @literal (#has-ancestor? @literal "function_definition"))]] ) eq({ { 'literal', 'number_literal', { 2, 21, 2, 24 }, '125' } }, result) result = exec_lua( - [[return get_query_result(...)]], + get_query_result, [[((number_literal) @literal (#has-ancestor? @literal "function_definition" "enum_specifier"))]] ) eq({ @@ -358,7 +398,7 @@ void ui_refresh(void) }, result) result = exec_lua( - [[return get_query_result(...)]], + get_query_result, [[((number_literal) @literal (#not-has-ancestor? @literal "enum_specifier"))]] ) eq({ @@ -370,12 +410,15 @@ void ui_refresh(void) it('allows loading query with escaped quotes and capture them `#{lua,vim}-match`?', function() insert('char* astring = "Hello World!";') - local res = exec_lua([[ - cquery = vim.treesitter.query.parse("c", '([_] @quote (#vim-match? @quote "^\\"$")) ([_] @quote (#lua-match? @quote "^\\"$"))') - parser = vim.treesitter.get_parser(0, "c") - tree = parser:parse()[1] - res = {} - for pattern, match in cquery:iter_matches(tree:root(), 0, 0, -1, { all = true }) do + local res = exec_lua(function() + local cquery = vim.treesitter.query.parse( + 'c', + '([_] @quote (#vim-match? @quote "^\\"$")) ([_] @quote (#lua-match? @quote "^\\"$"))' + ) + local parser = vim.treesitter.get_parser(0, 'c') + local tree = parser:parse()[1] + local res = {} + for pattern, match in cquery:iter_matches(tree:root(), 0, 0, -1) do -- can't transmit node over RPC. just check the name and range local mrepr = {} for cid, nodes in pairs(match) do @@ -386,7 +429,7 @@ void ui_refresh(void) table.insert(res, { pattern, mrepr }) end return res - ]]) + end) eq({ { 1, { { '@quote', '"', 0, 16, 0, 17 } } }, @@ -406,70 +449,60 @@ void ui_refresh(void) local custom_query = '((identifier) @main (#is-main? @main))' do - local res = exec_lua( - [[ - local query = vim.treesitter.query - - local function is_main(match, pattern, bufnr, predicate) - local nodes = match[ predicate[2] ] - for _, node in ipairs(nodes) do - if vim.treesitter.get_node_text(node, bufnr) == 'main' then - return true - end + local res = exec_lua(function() + local query = vim.treesitter.query + + local function is_main(match, _pattern, bufnr, predicate) + local nodes = match[predicate[2]] + for _, node in ipairs(nodes) do + if vim.treesitter.get_node_text(node, bufnr) == 'main' then + return true end - return false end + return false + end - local parser = vim.treesitter.get_parser(0, "c") + local parser = vim.treesitter.get_parser(0, 'c') - -- Time bomb: update this in 0.12 - if vim.fn.has('nvim-0.12') == 1 then - return 'Update this test to remove this message and { all = true } from add_predicate' - end - query.add_predicate("is-main?", is_main, { all = true }) + query.add_predicate('is-main?', is_main) - local query = query.parse("c", ...) + local query0 = query.parse('c', custom_query) - local nodes = {} - for _, node in query:iter_captures(parser:parse()[1]:root(), 0) do - table.insert(nodes, {node:range()}) - end + local nodes = {} + for _, node in query0:iter_captures(parser:parse()[1]:root(), 0) do + table.insert(nodes, { node:range() }) + end - return nodes - ]], - custom_query - ) + return nodes + end) eq({ { 0, 4, 0, 8 } }, res) end -- Once with the old API. Remove this whole 'do' block in 0.12 do - local res = exec_lua( - [[ - local query = vim.treesitter.query + local res = exec_lua(function() + local query = vim.treesitter.query - local function is_main(match, pattern, bufnr, predicate) - local node = match[ predicate[2] ] + local function is_main(match, _pattern, bufnr, predicate) + local node = match[predicate[2]] - return vim.treesitter.get_node_text(node, bufnr) == 'main' - end + return vim.treesitter.get_node_text(node, bufnr) == 'main' + end - local parser = vim.treesitter.get_parser(0, "c") + local parser = vim.treesitter.get_parser(0, 'c') - query.add_predicate("is-main?", is_main, true) + query.add_predicate('is-main?', is_main, { all = false, force = true }) - local query = query.parse("c", ...) + local query0 = query.parse('c', custom_query) - local nodes = {} - for _, node in query:iter_captures(parser:parse()[1]:root(), 0) do - table.insert(nodes, {node:range()}) - end + local nodes = {} + for _, node in query0:iter_captures(parser:parse()[1]:root(), 0) do + table.insert(nodes, { node:range() }) + end - return nodes - ]], - custom_query - ) + return nodes + end) -- Remove this 'do' block in 0.12 eq(0, fn.has('nvim-0.12')) @@ -477,16 +510,16 @@ void ui_refresh(void) end do - local res = exec_lua [[ + local res = exec_lua(function() local query = vim.treesitter.query - local t = {} + local r = {} for _, v in ipairs(query.list_predicates()) do - t[v] = true + r[v] = true end - return t - ]] + return r + end) eq(true, res['is-main?']) end @@ -505,18 +538,15 @@ void ui_refresh(void) local function test(input, query) api.nvim_buf_set_lines(0, 0, -1, true, vim.split(dedent(input), '\n')) - return exec_lua( - [[ - local parser = vim.treesitter.get_parser(0, "lua") - local query = vim.treesitter.query.parse("lua", ...) - local nodes = {} - for _, node in query:iter_captures(parser:parse()[1]:root(), 0) do - nodes[#nodes+1] = { node:range() } - end - return nodes - ]], - query - ) + return exec_lua(function() + local parser = vim.treesitter.get_parser(0, 'lua') + local query0 = vim.treesitter.query.parse('lua', query) + local nodes = {} + for _, node in query0:iter_captures(parser:parse()[1]:root(), 0) do + nodes[#nodes + 1] = { node:range() } + end + return nodes + end) end eq( @@ -583,23 +613,21 @@ void ui_refresh(void) -- Comment ]]) - local query = [[ + local result = exec_lua(function() + local parser = vim.treesitter.get_parser(0, 'lua') + local query = vim.treesitter.query.parse( + 'lua', + [[ (((comment (comment_content))+) @bar (#lua-match? @bar "Comment")) ]] - - local result = exec_lua( - [[ - local parser = vim.treesitter.get_parser(0, "lua") - local query = vim.treesitter.query.parse("lua", ...) - local nodes = {} - for _, node in query:iter_captures(parser:parse()[1]:root(), 0) do - nodes[#nodes+1] = { node:range() } - end - return nodes - ]], - query - ) + ) + local nodes = {} + for _, node in query:iter_captures(parser:parse()[1]:root(), 0) do + nodes[#nodes + 1] = { node:range() } + end + return nodes + end) eq({ { 0, 2, 0, 12 }, @@ -613,23 +641,20 @@ void ui_refresh(void) eq(0, fn.has('nvim-0.12')) insert(test_text) - local res = exec_lua( - [[ - cquery = vim.treesitter.query.parse("c", ...) - parser = vim.treesitter.get_parser(0, "c") - tree = parser:parse()[1] - res = {} - for pattern, match in cquery:iter_matches(tree:root(), 0, 7, 14) do - local mrepr = {} - for cid, node in pairs(match) do - table.insert(mrepr, { '@' .. cquery.captures[cid], node:type(), node:range() }) - end - table.insert(res, { pattern, mrepr }) + local res = exec_lua(function() + local cquery = vim.treesitter.query.parse('c', test_query) + local parser = vim.treesitter.get_parser(0, 'c') + local tree = parser:parse()[1] + local res = {} + for pattern, match in cquery:iter_matches(tree:root(), 0, 7, 14, { all = false }) do + local mrepr = {} + for cid, node in pairs(match) do + table.insert(mrepr, { '@' .. cquery.captures[cid], node:type(), node:range() }) end - return res - ]], - test_query - ) + table.insert(res, { pattern, mrepr }) + end + return res + end) eq({ { 3, { { '@type', 'primitive_type', 8, 2, 8, 6 } } }, @@ -661,23 +686,19 @@ void ui_refresh(void) int bar = 13; ]] - local ret = exec_lua( - [[ - local str = ... - local parser = vim.treesitter.get_string_parser(str, "c") + local ret = exec_lua(function() + local parser = vim.treesitter.get_string_parser(txt, 'c') - local nodes = {} - local query = vim.treesitter.query.parse("c", '((identifier) @foo)') - local first_child = parser:parse()[1]:root():child(1) + local nodes = {} + local query = vim.treesitter.query.parse('c', '((identifier) @foo)') + local first_child = assert(parser:parse()[1]:root():child(1)) - for _, node in query:iter_captures(first_child, str) do - table.insert(nodes, { node:range() }) - end + for _, node in query:iter_captures(first_child, txt) do + table.insert(nodes, { node:range() }) + end - return nodes - ]], - txt - ) + return nodes + end) eq({ { 1, 10, 1, 13 } }, ret) end) @@ -734,7 +755,10 @@ void ui_refresh(void) const char *sql = "SELECT * FROM Students WHERE name = 'Robert'); DROP TABLE Students;--"; ]]) - local query = [[ + local result = exec_lua(function() + local query = vim.treesitter.query.parse( + 'c', + [[ (declaration type: (_) declarator: (init_declarator @@ -745,20 +769,15 @@ void ui_refresh(void) (#set! injection.language "sql") (#contains? @_id "sql")) ]] - - local result = exec_lua( - [=[ - local query = vim.treesitter.query.parse("c", ...) - local parser = vim.treesitter.get_parser(0, "c") + ) + local parser = vim.treesitter.get_parser(0, 'c') local root = parser:parse()[1]:root() - local t = {} - for id, node, metadata in query:iter_captures(root, 0) do - t[query.captures[id]] = metadata + local res = {} + for id, _, metadata in query:iter_captures(root, 0) do + res[query.captures[id]] = metadata end - return t - ]=], - query - ) + return res + end) eq({ ['_id'] = { ['injection.language'] = 'sql' }, @@ -782,25 +801,22 @@ void ui_refresh(void) (#eq? @function.name "foo")) ]] - local result = exec_lua( - [[ - local query = vim.treesitter.query.parse("c", ...) - local match_preds = query.match_preds + local result = exec_lua(function() + local query0 = vim.treesitter.query.parse('c', query) + local match_preds = query0.match_preds local called = 0 - function query:match_preds(...) + function query0:match_preds(...) called = called + 1 return match_preds(self, ...) end - local parser = vim.treesitter.get_parser(0, "c") + local parser = vim.treesitter.get_parser(0, 'c') local root = parser:parse()[1]:root() local captures = {} - for id, node in query:iter_captures(root, 0) do + for id in query0:iter_captures(root, 0) do captures[#captures + 1] = id end return { called, captures } - ]], - query - ) + end) eq({ 2, { 1, 1, 2, 2 } }, result) end) diff --git a/test/functional/treesitter/utils_spec.lua b/test/functional/treesitter/utils_spec.lua index bca0aca0cb..34bea349f6 100644 --- a/test/functional/treesitter/utils_spec.lua +++ b/test/functional/treesitter/utils_spec.lua @@ -17,26 +17,30 @@ describe('treesitter utils', function() int x = 3; }]]) - exec_lua([[ - parser = vim.treesitter.get_parser(0, "c") - tree = parser:parse()[1] - root = tree:root() - ancestor = root:child(0) - child = ancestor:child(0) - ]]) - - eq(true, exec_lua('return vim.treesitter.is_ancestor(ancestor, child)')) - eq(false, exec_lua('return vim.treesitter.is_ancestor(child, ancestor)')) + exec_lua(function() + local parser = vim.treesitter.get_parser(0, 'c') + local tree = parser:parse()[1] + local root = tree:root() + _G.ancestor = assert(root:child(0)) + _G.child = assert(_G.ancestor:named_child(1)) + _G.child_sibling = assert(_G.ancestor:named_child(2)) + _G.grandchild = assert(_G.child:named_child(0)) + end) + + eq(true, exec_lua('return vim.treesitter.is_ancestor(_G.ancestor, _G.child)')) + eq(true, exec_lua('return vim.treesitter.is_ancestor(_G.ancestor, _G.grandchild)')) + eq(false, exec_lua('return vim.treesitter.is_ancestor(_G.child, _G.ancestor)')) + eq(false, exec_lua('return vim.treesitter.is_ancestor(_G.child, _G.child_sibling)')) end) it('can detect if a position is contained in a node', function() - exec_lua([[ - node = { + exec_lua(function() + _G.node = { range = function() return 0, 4, 0, 8 end, } - ]]) + end) eq(false, exec_lua('return vim.treesitter.is_in_node_range(node, 0, 3)')) for i = 4, 7 do diff --git a/test/functional/ui/cursor_spec.lua b/test/functional/ui/cursor_spec.lua index 8bfceb8cce..619153724b 100644 --- a/test/functional/ui/cursor_spec.lua +++ b/test/functional/ui/cursor_spec.lua @@ -246,11 +246,11 @@ describe('ui/cursor', function() end end if m.hl_id then - m.hl_id = 64 + m.hl_id = 66 m.attr = { background = Screen.colors.DarkGray } end if m.id_lm then - m.id_lm = 69 + m.id_lm = 73 end end diff --git a/test/functional/ui/decorations_spec.lua b/test/functional/ui/decorations_spec.lua index 746bfb3262..042975f898 100644 --- a/test/functional/ui/decorations_spec.lua +++ b/test/functional/ui/decorations_spec.lua @@ -2341,21 +2341,28 @@ describe('extmark decorations', function() it('supports URLs', function() insert(example_text) - local url = 'https://example.com' + local url1 = 'https://example.com' + local url2 = 'http://127.0.0.1' screen:add_extra_attr_ids { - u = { url = "https://example.com" }, + u = { url = url1 }, + uh = { url = url2, background = Screen.colors.Yellow }, } api.nvim_buf_set_extmark(0, ns, 1, 4, { end_col = 14, - url = url, + url = url1, + }) + api.nvim_buf_set_extmark(0, ns, 2, 4, { + end_col = 17, + hl_group = 'Search', + url = url2, }) - screen:expect{grid=[[ + screen:expect([[ for _,item in ipairs(items) do | {u:local text}, hl_id_cell, count = unpack(item) | - if hl_id_cell ~= nil then | + {uh:if hl_id_cell} ~= nil then | hl_id = hl_id_cell | end | for _ = 1, (count or 1) do | @@ -2368,7 +2375,7 @@ describe('extmark decorations', function() {1:~ }| {1:~ }| | - ]]} + ]]) end) it('can replace marks in place with different decorations #27211', function() @@ -4027,11 +4034,85 @@ describe('decorations: inline virtual text', function() normal! $ ]]) api.nvim_buf_set_extmark(0, ns, 0, 40, { virt_text = { { ('b'):rep(9) } }, virt_text_pos = 'inline' }) - screen:expect{grid=[[ + screen:expect([[ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbb{1:>}| 口1234^5 | | - ]]} + ]]) + feed('g0') + screen:expect([[ + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbb{1:>}| + ^口12345 | + | + ]]) + command('set showbreak=+++') + screen:expect([[ + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbb{1:>}| + {1:+++}^口12345 | + | + ]]) + end) + + it('cursor position is correct if end_row or end_col is specified', function() + screen:try_resize(50, 8) + api.nvim_buf_set_lines(0, 0, -1, false, { ('a'):rep(48), ('b'):rep(48), ('c'):rep(48), ('d'):rep(48) }) + api.nvim_buf_set_extmark(0, ns, 0, 0, {end_row = 2, virt_text_pos = 'inline', virt_text = {{'I1', 'NonText'}}}) + api.nvim_buf_set_extmark(0, ns, 3, 0, {end_col = 2, virt_text_pos = 'inline', virt_text = {{'I2', 'NonText'}}}) + feed('$') + screen:expect([[ + {1:I1}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa^a| + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb | + cccccccccccccccccccccccccccccccccccccccccccccccc | + {1:I2}dddddddddddddddddddddddddddddddddddddddddddddddd| + {1:~ }|*3 + | + ]]) + feed('j') + screen:expect([[ + {1:I1}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb^b | + cccccccccccccccccccccccccccccccccccccccccccccccc | + {1:I2}dddddddddddddddddddddddddddddddddddddddddddddddd| + {1:~ }|*3 + | + ]]) + feed('j') + screen:expect([[ + {1:I1}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb | + ccccccccccccccccccccccccccccccccccccccccccccccc^c | + {1:I2}dddddddddddddddddddddddddddddddddddddddddddddddd| + {1:~ }|*3 + | + ]]) + feed('j') + screen:expect([[ + {1:I1}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb | + cccccccccccccccccccccccccccccccccccccccccccccccc | + {1:I2}ddddddddddddddddddddddddddddddddddddddddddddddd^d| + {1:~ }|*3 + | + ]]) + end) + + it('cursor position is correct with invalidated inline virt text', function() + screen:try_resize(50, 8) + api.nvim_buf_set_lines(0, 0, -1, false, { ('a'):rep(48), ('b'):rep(48) }) + api.nvim_buf_set_extmark(0, ns, 0, 0, { virt_text_pos = 'inline', virt_text = {{'INLINE', 'NonText'}}, invalidate = true }) + screen:expect([[ + {1:INLINE}^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + aaaa | + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb | + {1:~ }|*4 + | + ]]) + feed('dd$') + screen:expect([[ + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb^b | + {1:~ }|*6 + | + ]]) end) end) @@ -4936,6 +5017,28 @@ if (h->n_buckets < new_n_buckets) { // expand | ]]) end) + + it('not drawn when invalid', function() + api.nvim_buf_set_lines(0, 0, -1, false, { 'foo', 'bar' }) + api.nvim_buf_set_extmark(0, ns, 0, 0, { virt_lines = {{{'VIRT1'}}}, invalidate = true }) + screen:expect({ + grid = [[ + ^foo | + VIRT1 | + bar | + {1:~ }|*8 + | + ]] + }) + feed('dd') + screen:expect({ + grid = [[ + ^bar | + {1:~ }|*10 + | + ]] + }) + end) end) describe('decorations: signs', function() @@ -5497,6 +5600,60 @@ l5 api.nvim_buf_clear_namespace(0, ns, 0, -1) end) + + 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 + | + ]] + }) + api.nvim_buf_del_extmark(0, ns, 1) + screen:expect({ + grid = [[ + {8:1 }^ | + {1:~ }|*8 + | + ]] + }) + end) + + it('supports emoji as signs', function() + insert(example_test3) + feed 'gg' + api.nvim_buf_set_extmark(0, ns, 1, 0, {sign_text='🧑🌾'}) + -- VS16 can change width of character + api.nvim_buf_set_extmark(0, ns, 2, 0, {sign_text='❤️'}) + api.nvim_buf_set_extmark(0, ns, 3, 0, {sign_text='❤'}) + api.nvim_buf_set_extmark(0, ns, 4, 0, {sign_text='❤x'}) + screen:expect([[ + {7: }^l1 | + 🧑🌾l2 | + ❤️l3 | + ❤ l4 | + ❤xl5 | + {7: } | + {1:~ }|*3 + | + ]]) + eq("Invalid 'sign_text'", pcall_err(api.nvim_buf_set_extmark, 0, ns, 5, 0, {sign_text='❤️x'})) + end) + + it('auto signcolumn hides with invalidated sign', function() + api.nvim_set_option_value('signcolumn', 'auto', {}) + api.nvim_buf_set_extmark(0, ns, 0, 0, {sign_text='S1', invalidate=true}) + feed('ia<cr>b<esc>dd') + screen:expect({ + grid = [[ + ^a | + {1:~ }|*8 + | + ]] + }) + end) end) describe('decorations: virt_text', function() @@ -5575,20 +5732,26 @@ describe('decorations: virt_text', function() end) describe('decorations: window scoped', function() - local screen, ns + local screen, ns, win_other local url = 'https://example.com' before_each(function() clear() screen = Screen.new(20, 10) screen:attach() screen:add_extra_attr_ids { - [100] = { special = Screen.colors.Red, undercurl = true }, - [101] = { url = "https://example.com" }, + [100] = { special = Screen.colors.Red, undercurl = true }, + [101] = { url = 'https://example.com' }, } ns = api.nvim_create_namespace 'test' insert('12345') + + win_other = api.nvim_open_win(0, false, { + col=0,row=0,width=20,height=10, + relative = 'win',style = 'minimal', + hide = true + }) end) local noextmarks = { @@ -5596,28 +5759,28 @@ describe('decorations: window scoped', function() 1234^5 | {1:~ }|*8 | - ]]} + ]], + } - local function set_scoped_extmark(line, col, opts) - return api.nvim_buf_set_extmark(0, ns, line, col, vim.tbl_extend('error', { scoped = true }, opts)) + local function set_extmark(line, col, opts) + return api.nvim_buf_set_extmark(0, ns, line, col, opts) end it('hl_group', function() - set_scoped_extmark(0, 0, { + set_extmark(0, 0, { hl_group = 'Comment', end_col = 3, }) - screen:expect(noextmarks) - - api.nvim__win_add_ns(0, ns) + api.nvim__ns_set(ns, { wins = { 0 } }) screen:expect { grid = [[ {18:123}4^5 | {1:~ }|*8 | - ]]} + ]], + } command 'split' command 'only' @@ -5626,48 +5789,55 @@ describe('decorations: window scoped', function() end) it('virt_text', function() - set_scoped_extmark(0, 0, { + set_extmark(0, 0, { virt_text = { { 'a', 'Comment' } }, virt_text_pos = 'eol', }) - set_scoped_extmark(0, 5, { + set_extmark(0, 5, { virt_text = { { 'b', 'Comment' } }, virt_text_pos = 'inline', }) - set_scoped_extmark(0, 1, { + set_extmark(0, 1, { virt_text = { { 'c', 'Comment' } }, virt_text_pos = 'overlay', }) - set_scoped_extmark(0, 1, { + set_extmark(0, 1, { virt_text = { { 'd', 'Comment' } }, virt_text_pos = 'right_align', }) - screen:expect(noextmarks) - - api.nvim__win_add_ns(0, ns) + api.nvim__ns_set(ns, { wins = { 0 } }) screen:expect { grid = [[ 1{18:c}34^5{18:b} {18:a} {18:d}| {1:~ }|*8 | - ]]} + ]], + } command 'split' command 'only' screen:expect(noextmarks) + + api.nvim__ns_set(ns, { wins = {} }) + + screen:expect { + grid = [[ + 1{18:c}34^5{18:b} {18:a} {18:d}| + {1:~ }|*8 + | + ]], + } end) it('virt_lines', function() - set_scoped_extmark(0, 0, { + set_extmark(0, 0, { virt_lines = { { { 'a', 'Comment' } } }, }) - screen:expect(noextmarks) - - api.nvim__win_add_ns(0, ns) + api.nvim__ns_set(ns, { wins = { 0 } }) screen:expect { grid = [[ @@ -5675,7 +5845,8 @@ describe('decorations: window scoped', function() {18:a} | {1:~ }|*7 | - ]]} + ]], + } command 'split' command 'only' @@ -5684,14 +5855,12 @@ describe('decorations: window scoped', function() end) it('redraws correctly with inline virt_text and wrapping', function() - set_scoped_extmark(0, 2, { - virt_text = {{ ('b'):rep(18), 'Comment' }}, - virt_text_pos = 'inline' + set_extmark(0, 2, { + virt_text = { { ('b'):rep(18), 'Comment' } }, + virt_text_pos = 'inline', }) - screen:expect(noextmarks) - - api.nvim__win_add_ns(0, ns) + api.nvim__ns_set(ns, { wins = { 0 } }) screen:expect { grid = [[ @@ -5699,9 +5868,10 @@ describe('decorations: window scoped', function() 34^5 | {1:~ }|*7 | - ]]} + ]], + } - api.nvim__win_del_ns(0, ns) + api.nvim__ns_set(ns, { wins = { win_other } }) screen:expect(noextmarks) end) @@ -5709,21 +5879,20 @@ describe('decorations: window scoped', function() pending('sign_text', function() -- TODO(altermo): The window signcolumn width is calculated wrongly (when `signcolumn=auto`) -- This happens in function `win_redraw_signcols` on line containing `buf_meta_total(buf, kMTMetaSignText) > 0` - set_scoped_extmark(0, 0, { + set_extmark(0, 0, { sign_text = 'a', sign_hl_group = 'Comment', }) - screen:expect(noextmarks) - - api.nvim__win_add_ns(0, ns) + api.nvim__ns_set(ns, { wins = { 0 } }) screen:expect { grid = [[ a 1234^5 | {2:~ }|*8 | - ]]} + ]], + } command 'split' command 'only' @@ -5732,30 +5901,34 @@ describe('decorations: window scoped', function() end) it('statuscolumn hl group', function() - set_scoped_extmark(0, 0, { - number_hl_group='comment', + set_extmark(0, 0, { + number_hl_group = 'comment', }) - set_scoped_extmark(0, 0, { - line_hl_group='comment', + set_extmark(0, 0, { + line_hl_group = 'comment', }) command 'set number' + api.nvim__ns_set(ns, { wins = { win_other } }) + screen:expect { grid = [[ {8: 1 }1234^5 | {1:~ }|*8 | - ]]} + ]], + } - api.nvim__win_add_ns(0, ns) + api.nvim__ns_set(ns, { wins = { 0 } }) screen:expect { grid = [[ {18: 1 1234^5 }| {1:~ }|*8 | - ]]} + ]], + } command 'split' command 'only' @@ -5765,36 +5938,43 @@ describe('decorations: window scoped', function() {8: 1 }1234^5 | {1:~ }|*8 | - ]]} + ]], + } end) it('spell', function() - api.nvim_buf_set_lines(0,0,-1,true,{'aa'}) + api.nvim_buf_set_lines(0, 0, -1, true, { 'aa' }) - set_scoped_extmark(0, 0, { - spell=true, - end_col=2, + set_extmark(0, 0, { + spell = true, + end_col = 2, }) command 'set spelloptions=noplainbuffer' command 'set spell' command 'syntax off' + screen:expect({ unchanged = true }) + + api.nvim__ns_set(ns, { wins = { win_other } }) + screen:expect { grid = [[ a^a | {1:~ }|*8 | - ]]} + ]], + } - api.nvim__win_add_ns(0, ns) + api.nvim__ns_set(ns, { wins = { 0 } }) screen:expect { grid = [[ {100:a^a} | {1:~ }|*8 | - ]]} + ]], + } command 'split' command 'only' @@ -5804,25 +5984,25 @@ describe('decorations: window scoped', function() a^a | {1:~ }|*8 | - ]]} + ]], + } end) it('url', function() - set_scoped_extmark(0, 0, { - end_col=3, - url=url, + set_extmark(0, 0, { + end_col = 3, + url = url, }) - screen:expect(noextmarks) - - api.nvim__win_add_ns(0, ns) + api.nvim__ns_set(ns, { wins = { 0 } }) screen:expect { grid = [[ {101:123}4^5 | {1:~ }|*8 | - ]]} + ]], + } command 'split' command 'only' @@ -5830,85 +6010,72 @@ describe('decorations: window scoped', function() screen:expect(noextmarks) end) - it('change extmarks scoped option', function() - local id = set_scoped_extmark(0, 0, { + it('change namespace scope', function() + set_extmark(0, 0, { hl_group = 'Comment', end_col = 3, }) - api.nvim__win_add_ns(0, ns) + api.nvim__ns_set(ns, { wins = { 0 } }) + eq({ wins={ api.nvim_get_current_win() } }, api.nvim__ns_get(ns)) screen:expect { grid = [[ {18:123}4^5 | {1:~ }|*8 | - ]]} + ]], + } command 'split' command 'only' screen:expect(noextmarks) - api.nvim_buf_set_extmark(0, ns, 0, 0, { - id = id, - hl_group = 'Comment', - end_col = 3, - scoped = false, - }) + api.nvim__ns_set(ns, { wins = { 0 } }) + eq({ wins={ api.nvim_get_current_win() } }, api.nvim__ns_get(ns)) screen:expect { grid = [[ {18:123}4^5 | {1:~ }|*8 | - ]]} + ]], + } - api.nvim_buf_set_extmark(0, ns, 0, 0, { - id = id, - hl_group = 'Comment', - end_col = 3, - scoped = true, + local win_new = api.nvim_open_win(0, false, { + col=0,row=0,width=20,height=10, + relative = 'win',style = 'minimal', + hide = true }) + api.nvim__ns_set(ns, { wins = { win_new } }) + eq({ wins={ win_new } }, api.nvim__ns_get(ns)) + screen:expect(noextmarks) end) - it('change namespace scope', function() - set_scoped_extmark(0, 0, { - hl_group = 'Comment', - end_col = 3, - }) + it('namespace get works', function() + eq({ wins = {} }, api.nvim__ns_get(ns)) - eq(true, api.nvim__win_add_ns(0, ns)) - eq({ ns }, api.nvim__win_get_ns(0)) + api.nvim__ns_set(ns, { wins = { 0 } }) - screen:expect { - grid = [[ - {18:123}4^5 | - {1:~ }|*8 - | - ]]} + eq({ wins = { api.nvim_get_current_win() } }, api.nvim__ns_get(ns)) - command 'split' - command 'only' - eq({}, api.nvim__win_get_ns(0)) + api.nvim__ns_set(ns, { wins = {} }) - screen:expect(noextmarks) + eq({ wins = {} }, api.nvim__ns_get(ns)) + end) - eq(true, api.nvim__win_add_ns(0, ns)) - eq({ ns }, api.nvim__win_get_ns(0)) + it('remove window from namespace scope when deleted', function () + api.nvim__ns_set(ns, { wins = { 0 } }) - screen:expect { - grid = [[ - {18:123}4^5 | - {1:~ }|*8 - | - ]]} + eq({ wins = { api.nvim_get_current_win() } }, api.nvim__ns_get(ns)) - eq(true, api.nvim__win_del_ns(0, ns)) - eq({}, api.nvim__win_get_ns(0)) + command 'split' + command 'only' - screen:expect(noextmarks) + eq({ wins = {} }, api.nvim__ns_get(ns)) end) end) + diff --git a/test/functional/ui/diff_spec.lua b/test/functional/ui/diff_spec.lua index e79621f364..d6a04f90f6 100644 --- a/test/functional/ui/diff_spec.lua +++ b/test/functional/ui/diff_spec.lua @@ -12,6 +12,19 @@ local exec = n.exec local eq = t.eq local api = n.api +local function WriteDiffFiles(text1, text2) + write_file('Xdifile1', text1) + write_file('Xdifile2', text2) + command('checktime') +end + +local function WriteDiffFiles3(text1, text2, text3) + write_file('Xdifile1', text1) + write_file('Xdifile2', text2) + write_file('Xdifile3', text3) + command('checktime') +end + before_each(clear) describe('Diff mode screen', function() @@ -614,6 +627,34 @@ int main(int argc, char **argv) ]]) end) + it('Diff empty and non-empty file', function() + write_file(fname, '', false) + write_file(fname_2, 'foo\nbar\nbaz', false) + reread() + + feed(':set diffopt=filler<cr>') + screen:expect([[ + {7: }{23:------------------}│{7: }{22:foo }| + {7: }{23:------------------}│{7: }{22:bar }| + {7: }{23:------------------}│{7: }{22:baz }| + {7: }^ │{1:~ }| + {1:~ }│{1:~ }|*10 + {3:<onal-diff-screen-1 }{2:<l-diff-screen-1.2 }| + :set diffopt=filler | + ]]) + + feed(':set diffopt+=internal<cr>') + screen:expect([[ + {7: }{23:------------------}│{7: }{22:foo }| + {7: }{23:------------------}│{7: }{22:bar }| + {7: }{23:------------------}│{7: }{22:baz }| + {7: }^ │{1:~ }| + {1:~ }│{1:~ }|*10 + {3:<onal-diff-screen-1 }{2:<l-diff-screen-1.2 }| + :set diffopt+=internal | + ]]) + end) + it('diffopt+=icase', function() write_file(fname, 'a\nb\ncd\n', false) write_file(fname_2, 'A\nb\ncDe\n', false) @@ -1346,6 +1387,46 @@ it("diff mode doesn't restore invalid 'foldcolumn' value #21647", function() eq('0', api.nvim_get_option_value('foldcolumn', {})) end) +it("'relativenumber' doesn't draw beyond end of window in diff mode #29403", function() + local screen = Screen.new(60, 12) + screen:attach() + command('set relativenumber') + feed('10aa<CR><Esc>gg') + command('vnew') + feed('ab<CR><Esc>gg') + command('windo diffthis') + command('wincmd |') + screen:expect([[ + {8: }│{7: }{8: 0 }{27:^a}{4: }| + {8: }│{7: }{8: 1 }{22:a }| + {8: }│{7: }{8: 2 }{22:a }| + {8: }│{7: }{8: 3 }{22:a }| + {8: }│{7: }{8: 4 }{22:a }| + {8: }│{7: }{8: 5 }{22:a }| + {8: }│{7: }{8: 6 }{22:a }| + {8: }│{7: }{8: 7 }{22:a }| + {8: }│{7: }{8: 8 }{22:a }| + {8: }│{7: }{8: 9 }{22:a }| + {2:< }{3:[No Name] [+] }| + | + ]]) + feed('j') + screen:expect([[ + {8: }│{7: }{8: 1 }{27:a}{4: }| + {8: }│{7: }{8: 0 }{22:^a }| + {8: }│{7: }{8: 1 }{22:a }| + {8: }│{7: }{8: 2 }{22:a }| + {8: }│{7: }{8: 3 }{22:a }| + {8: }│{7: }{8: 4 }{22:a }| + {8: }│{7: }{8: 5 }{22:a }| + {8: }│{7: }{8: 6 }{22:a }| + {8: }│{7: }{8: 7 }{22:a }| + {8: }│{7: }{8: 8 }{22:a }| + {2:< }{3:[No Name] [+] }| + | + ]]) +end) + -- oldtest: Test_diff_binary() it('diff mode works properly if file contains NUL bytes vim-patch:8.2.3925', function() local screen = Screen.new(40, 20) @@ -1447,3 +1528,529 @@ it("diff mode draws 'breakindent' correctly after filler lines", function() | ]]) end) + +-- oldtest: Test_diff_overlapped_diff_blocks_will_be_merged() +it('diff mode overlapped diff blocks will be merged', function() + write_file('Xdifile1', '') + write_file('Xdifile2', '') + write_file('Xdifile3', '') + + finally(function() + os.remove('Xdifile1') + os.remove('Xdifile2') + os.remove('Xdifile3') + os.remove('Xdiin1') + os.remove('Xdinew1') + os.remove('Xdiout1') + os.remove('Xdiin2') + os.remove('Xdinew2') + os.remove('Xdiout2') + end) + + exec([[ + func DiffExprStub() + let txt_in = readfile(v:fname_in) + let txt_new = readfile(v:fname_new) + if txt_in == ["line1"] && txt_new == ["line2"] + call writefile(["1c1"], v:fname_out) + elseif txt_in == readfile("Xdiin1") && txt_new == readfile("Xdinew1") + call writefile(readfile("Xdiout1"), v:fname_out) + elseif txt_in == readfile("Xdiin2") && txt_new == readfile("Xdinew2") + call writefile(readfile("Xdiout2"), v:fname_out) + endif + endfunc + ]]) + + local screen = Screen.new(35, 20) + screen:attach() + command('set winwidth=10 diffopt=filler,internal') + + command('args Xdifile1 Xdifile2 | vert all | windo diffthis') + + WriteDiffFiles('a\nb', 'x\nx') + write_file('Xdiin1', 'a\nb') + write_file('Xdinew1', 'x\nx') + write_file('Xdiout1', '1c1\n2c2') + command('set diffexpr=DiffExprStub()') + screen:expect([[ + {7: }{27:a}{4: }│{7: }{27:^x}{4: }| + {7: }{27:b}{4: }│{7: }{27:x}{4: }| + {1:~ }│{1:~ }|*16 + {2:Xdifile1 }{3:Xdifile2 }| + | + ]]) + command('set diffexpr&') + + WriteDiffFiles('a\nb\nc', 'x\nc') + write_file('Xdiin1', 'a\nb\nc') + write_file('Xdinew1', 'x\nc') + write_file('Xdiout1', '1c1\n2c1') + command('set diffexpr=DiffExprStub()') + screen:expect([[ + {7: }{27:a}{4: }│{7: }{27:^x}{4: }| + {7: }{22:b }│{7: }{23:---------------}| + {7: }c │{7: }c | + {1:~ }│{1:~ }|*15 + {2:Xdifile1 }{3:Xdifile2 }| + | + ]]) + command('set diffexpr&') + + WriteDiffFiles('a\nc', 'x\nx\nc') + write_file('Xdiin1', 'a\nc') + write_file('Xdinew1', 'x\nx\nc') + write_file('Xdiout1', '1c1\n1a2') + command('set diffexpr=DiffExprStub()') + screen:expect([[ + {7: }{27:a}{4: }│{7: }{27:^x}{4: }| + {7: }{23:---------------}│{7: }{22:x }| + {7: }c │{7: }c | + {1:~ }│{1:~ }|*15 + {2:Xdifile1 }{3:Xdifile2 }| + | + ]]) + command('set diffexpr&') + + command('args Xdifile1 Xdifile2 Xdifile3 | vert all | windo diffthis') + + WriteDiffFiles3('a\nb\nc', 'a\nx\nc', 'y\nb\nc') + screen:expect([[ + {7: }{27:a}{4: }│{7: }{27:a}{4: }│{7: }{27:^y}{4: }| + {7: }{27:b}{4: }│{7: }{27:x}{4: }│{7: }{27:b}{4: }| + {7: }c │{7: }c │{7: }c | + {1:~ }│{1:~ }│{1:~ }|*15 + {2:Xdifile1 Xdifile2 }{3:Xdifile3 }| + | + ]]) + + WriteDiffFiles3('a\nb\nc', 'a\nx\nc', 'a\ny\nc') + screen:expect([[ + {7: }a │{7: }a │{7: }^a | + {7: }{27:b}{4: }│{7: }{27:x}{4: }│{7: }{27:y}{4: }| + {7: }c │{7: }c │{7: }c | + {1:~ }│{1:~ }│{1:~ }|*15 + {2:Xdifile1 Xdifile2 }{3:Xdifile3 }| + | + ]]) + + WriteDiffFiles3('a\nb\nc', 'a\nx\nc', 'a\nb\ny') + screen:expect([[ + {7: }a │{7: }a │{7: }^a | + {7: }{27:b}{4: }│{7: }{27:x}{4: }│{7: }{27:b}{4: }| + {7: }{27:c}{4: }│{7: }{27:c}{4: }│{7: }{27:y}{4: }| + {1:~ }│{1:~ }│{1:~ }|*15 + {2:Xdifile1 Xdifile2 }{3:Xdifile3 }| + | + ]]) + + WriteDiffFiles3('a\nb\nc', 'a\nx\nc', 'y\ny\nc') + screen:expect([[ + {7: }{27:a}{4: }│{7: }{27:a}{4: }│{7: }{27:^y}{4: }| + {7: }{27:b}{4: }│{7: }{27:x}{4: }│{7: }{27:y}{4: }| + {7: }c │{7: }c │{7: }c | + {1:~ }│{1:~ }│{1:~ }|*15 + {2:Xdifile1 Xdifile2 }{3:Xdifile3 }| + | + ]]) + + WriteDiffFiles3('a\nb\nc', 'a\nx\nc', 'a\ny\ny') + screen:expect([[ + {7: }a │{7: }a │{7: }^a | + {7: }{27:b}{4: }│{7: }{27:x}{4: }│{7: }{27:y}{4: }| + {7: }{27:c}{4: }│{7: }{27:c}{4: }│{7: }{27:y}{4: }| + {1:~ }│{1:~ }│{1:~ }|*15 + {2:Xdifile1 Xdifile2 }{3:Xdifile3 }| + | + ]]) + + WriteDiffFiles3('a\nb\nc', 'a\nx\nc', 'y\ny\ny') + screen:expect([[ + {7: }{27:a}{4: }│{7: }{27:a}{4: }│{7: }{27:^y}{4: }| + {7: }{27:b}{4: }│{7: }{27:x}{4: }│{7: }{27:y}{4: }| + {7: }{27:c}{4: }│{7: }{27:c}{4: }│{7: }{27:y}{4: }| + {1:~ }│{1:~ }│{1:~ }|*15 + {2:Xdifile1 Xdifile2 }{3:Xdifile3 }| + | + ]]) + + WriteDiffFiles3('a\nb\nc', 'a\nx\nx', 'y\ny\nc') + screen:expect([[ + {7: }{27:a}{4: }│{7: }{27:a}{4: }│{7: }{27:^y}{4: }| + {7: }{27:b}{4: }│{7: }{27:x}{4: }│{7: }{27:y}{4: }| + {7: }{27:c}{4: }│{7: }{27:x}{4: }│{7: }{27:c}{4: }| + {1:~ }│{1:~ }│{1:~ }|*15 + {2:Xdifile1 Xdifile2 }{3:Xdifile3 }| + | + ]]) + + WriteDiffFiles3('a\nb\nc', 'x\nx\nc', 'a\ny\ny') + screen:expect([[ + {7: }{27:a}{4: }│{7: }{27:x}{4: }│{7: }{27:^a}{4: }| + {7: }{27:b}{4: }│{7: }{27:x}{4: }│{7: }{27:y}{4: }| + {7: }{27:c}{4: }│{7: }{27:c}{4: }│{7: }{27:y}{4: }| + {1:~ }│{1:~ }│{1:~ }|*15 + {2:Xdifile1 Xdifile2 }{3:Xdifile3 }| + | + ]]) + + WriteDiffFiles3('a\nb\nc\nd\ne', 'a\nx\nc\nx\ne', 'y\ny\ny\nd\ne') + screen:expect([[ + {7: }{27:a}{4: }│{7: }{27:a}{4: }│{7: }{27:^y}{4: }| + {7: }{27:b}{4: }│{7: }{27:x}{4: }│{7: }{27:y}{4: }| + {7: }{27:c}{4: }│{7: }{27:c}{4: }│{7: }{27:y}{4: }| + {7: }{27:d}{4: }│{7: }{27:x}{4: }│{7: }{27:d}{4: }| + {7: }e │{7: }e │{7: }e | + {1:~ }│{1:~ }│{1:~ }|*13 + {2:Xdifile1 Xdifile2 }{3:Xdifile3 }| + | + ]]) + + WriteDiffFiles3('a\nb\nc\nd\ne', 'a\nx\nc\nx\ne', 'y\ny\ny\ny\ne') + screen:expect([[ + {7: }{27:a}{4: }│{7: }{27:a}{4: }│{7: }{27:^y}{4: }| + {7: }{27:b}{4: }│{7: }{27:x}{4: }│{7: }{27:y}{4: }| + {7: }{27:c}{4: }│{7: }{27:c}{4: }│{7: }{27:y}{4: }| + {7: }{27:d}{4: }│{7: }{27:x}{4: }│{7: }{27:y}{4: }| + {7: }e │{7: }e │{7: }e | + {1:~ }│{1:~ }│{1:~ }|*13 + {2:Xdifile1 Xdifile2 }{3:Xdifile3 }| + | + ]]) + + WriteDiffFiles3('a\nb\nc\nd\ne', 'a\nx\nc\nx\ne', 'y\ny\ny\ny\ny') + screen:expect([[ + {7: }{27:a}{4: }│{7: }{27:a}{4: }│{7: }{27:^y}{4: }| + {7: }{27:b}{4: }│{7: }{27:x}{4: }│{7: }{27:y}{4: }| + {7: }{27:c}{4: }│{7: }{27:c}{4: }│{7: }{27:y}{4: }| + {7: }{27:d}{4: }│{7: }{27:x}{4: }│{7: }{27:y}{4: }| + {7: }{27:e}{4: }│{7: }{27:e}{4: }│{7: }{27:y}{4: }| + {1:~ }│{1:~ }│{1:~ }|*13 + {2:Xdifile1 Xdifile2 }{3:Xdifile3 }| + | + ]]) + + WriteDiffFiles3('a\nb\nc\nd\ne', 'a\nx\nc\nx\ne', 'a\ny\ny\nd\ne') + screen:expect([[ + {7: }a │{7: }a │{7: }^a | + {7: }{27:b}{4: }│{7: }{27:x}{4: }│{7: }{27:y}{4: }| + {7: }{27:c}{4: }│{7: }{27:c}{4: }│{7: }{27:y}{4: }| + {7: }{27:d}{4: }│{7: }{27:x}{4: }│{7: }{27:d}{4: }| + {7: }e │{7: }e │{7: }e | + {1:~ }│{1:~ }│{1:~ }|*13 + {2:Xdifile1 Xdifile2 }{3:Xdifile3 }| + | + ]]) + + WriteDiffFiles3('a\nb\nc\nd\ne', 'a\nx\nc\nx\ne', 'a\ny\ny\ny\ne') + screen:expect([[ + {7: }a │{7: }a │{7: }^a | + {7: }{27:b}{4: }│{7: }{27:x}{4: }│{7: }{27:y}{4: }| + {7: }{27:c}{4: }│{7: }{27:c}{4: }│{7: }{27:y}{4: }| + {7: }{27:d}{4: }│{7: }{27:x}{4: }│{7: }{27:y}{4: }| + {7: }e │{7: }e │{7: }e | + {1:~ }│{1:~ }│{1:~ }|*13 + {2:Xdifile1 Xdifile2 }{3:Xdifile3 }| + | + ]]) + + WriteDiffFiles3('a\nb\nc\nd\ne', 'a\nx\nc\nx\ne', 'a\ny\ny\ny\ny') + screen:expect([[ + {7: }a │{7: }a │{7: }^a | + {7: }{27:b}{4: }│{7: }{27:x}{4: }│{7: }{27:y}{4: }| + {7: }{27:c}{4: }│{7: }{27:c}{4: }│{7: }{27:y}{4: }| + {7: }{27:d}{4: }│{7: }{27:x}{4: }│{7: }{27:y}{4: }| + {7: }{27:e}{4: }│{7: }{27:e}{4: }│{7: }{27:y}{4: }| + {1:~ }│{1:~ }│{1:~ }|*13 + {2:Xdifile1 Xdifile2 }{3:Xdifile3 }| + | + ]]) + + WriteDiffFiles3('a\nb\nc\nd\ne', 'a\nx\nc\nx\ne', 'a\nb\ny\nd\ne') + screen:expect([[ + {7: }a │{7: }a │{7: }^a | + {7: }{27:b}{4: }│{7: }{27:x}{4: }│{7: }{27:b}{4: }| + {7: }{27:c}{4: }│{7: }{27:c}{4: }│{7: }{27:y}{4: }| + {7: }{27:d}{4: }│{7: }{27:x}{4: }│{7: }{27:d}{4: }| + {7: }e │{7: }e │{7: }e | + {1:~ }│{1:~ }│{1:~ }|*13 + {2:Xdifile1 Xdifile2 }{3:Xdifile3 }| + | + ]]) + + WriteDiffFiles3('a\nb\nc\nd\ne', 'a\nx\nc\nx\ne', 'a\nb\ny\ny\ne') + screen:expect([[ + {7: }a │{7: }a │{7: }^a | + {7: }{27:b}{4: }│{7: }{27:x}{4: }│{7: }{27:b}{4: }| + {7: }{27:c}{4: }│{7: }{27:c}{4: }│{7: }{27:y}{4: }| + {7: }{27:d}{4: }│{7: }{27:x}{4: }│{7: }{27:y}{4: }| + {7: }e │{7: }e │{7: }e | + {1:~ }│{1:~ }│{1:~ }|*13 + {2:Xdifile1 Xdifile2 }{3:Xdifile3 }| + | + ]]) + + WriteDiffFiles3('a\nb\nc\nd\ne', 'a\nx\nc\nx\ne', 'a\nb\ny\ny\ny') + screen:expect([[ + {7: }a │{7: }a │{7: }^a | + {7: }{27:b}{4: }│{7: }{27:x}{4: }│{7: }{27:b}{4: }| + {7: }{27:c}{4: }│{7: }{27:c}{4: }│{7: }{27:y}{4: }| + {7: }{27:d}{4: }│{7: }{27:x}{4: }│{7: }{27:y}{4: }| + {7: }{27:e}{4: }│{7: }{27:e}{4: }│{7: }{27:y}{4: }| + {1:~ }│{1:~ }│{1:~ }|*13 + {2:Xdifile1 Xdifile2 }{3:Xdifile3 }| + | + ]]) + + WriteDiffFiles3('a\nb', 'x\nb', 'y\ny') + write_file('Xdiin1', 'a\nb') + write_file('Xdinew1', 'x\nb') + write_file('Xdiout1', '1c1') + write_file('Xdiin2', 'a\nb') + write_file('Xdinew2', 'y\ny') + write_file('Xdiout2', '1c1\n2c2') + command('set diffexpr=DiffExprStub()') + screen:expect([[ + {7: }{27:a}{4: }│{7: }{27:x}{4: }│{7: }{27:^y}{4: }| + {7: }{27:b}{4: }│{7: }{27:b}{4: }│{7: }{27:y}{4: }| + {1:~ }│{1:~ }│{1:~ }|*16 + {2:Xdifile1 Xdifile2 }{3:Xdifile3 }| + | + ]]) + command('set diffexpr&') + + WriteDiffFiles3('a\nb\nc\nd', 'x\nb\nx\nd', 'y\ny\nc\nd') + write_file('Xdiin1', 'a\nb\nc\nd') + write_file('Xdinew1', 'x\nb\nx\nd') + write_file('Xdiout1', '1c1\n3c3') + write_file('Xdiin2', 'a\nb\nc\nd') + write_file('Xdinew2', 'y\ny\nc\nd') + write_file('Xdiout2', '1c1\n2c2') + command('set diffexpr=DiffExprStub()') + screen:expect([[ + {7: }{27:a}{4: }│{7: }{27:x}{4: }│{7: }{27:^y}{4: }| + {7: }{27:b}{4: }│{7: }{27:b}{4: }│{7: }{27:y}{4: }| + {7: }{27:c}{4: }│{7: }{27:x}{4: }│{7: }{27:c}{4: }| + {7: }d │{7: }d │{7: }d | + {1:~ }│{1:~ }│{1:~ }|*14 + {2:Xdifile1 Xdifile2 }{3:Xdifile3 }| + | + ]]) + command('set diffexpr&') + + WriteDiffFiles3('a\nb\nc\nd', 'x\nb\nx\nd', 'y\ny\ny\nd') + write_file('Xdiin1', 'a\nb\nc\nd') + write_file('Xdinew1', 'x\nb\nx\nd') + write_file('Xdiout1', '1c1\n3c3') + write_file('Xdiin2', 'a\nb\nc\nd') + write_file('Xdinew2', 'y\ny\ny\nd') + write_file('Xdiout2', '1c1\n2,3c2,3') + command('set diffexpr=DiffExprStub()') + screen:expect([[ + {7: }{27:a}{4: }│{7: }{27:x}{4: }│{7: }{27:^y}{4: }| + {7: }{27:b}{4: }│{7: }{27:b}{4: }│{7: }{27:y}{4: }| + {7: }{27:c}{4: }│{7: }{27:x}{4: }│{7: }{27:y}{4: }| + {7: }d │{7: }d │{7: }d | + {1:~ }│{1:~ }│{1:~ }|*14 + {2:Xdifile1 Xdifile2 }{3:Xdifile3 }| + | + ]]) + command('set diffexpr&') + + WriteDiffFiles3('a\nb\nc\nd', 'x\nb\nx\nd', 'y\ny\ny\ny') + write_file('Xdiin1', 'a\nb\nc\nd') + write_file('Xdinew1', 'x\nb\nx\nd') + write_file('Xdiout1', '1c1\n3c3') + write_file('Xdiin2', 'a\nb\nc\nd') + write_file('Xdinew2', 'y\ny\ny\ny') + write_file('Xdiout2', '1c1\n2,4c2,4') + command('set diffexpr=DiffExprStub()') + screen:expect([[ + {7: }{27:a}{4: }│{7: }{27:x}{4: }│{7: }{27:^y}{4: }| + {7: }{27:b}{4: }│{7: }{27:b}{4: }│{7: }{27:y}{4: }| + {7: }{27:c}{4: }│{7: }{27:x}{4: }│{7: }{27:y}{4: }| + {7: }{27:d}{4: }│{7: }{27:d}{4: }│{7: }{27:y}{4: }| + {1:~ }│{1:~ }│{1:~ }|*14 + {2:Xdifile1 Xdifile2 }{3:Xdifile3 }| + | + ]]) + command('set diffexpr&') + + WriteDiffFiles3('a\nb\nc', 'a\nx\nc', 'b\nc') + screen:expect([[ + {7: }{27:a}{4: }│{7: }{27:a}{4: }│{7: }{27:^b}{4: }| + {7: }{27:b}{4: }│{7: }{27:x}{4: }│{7: }{23:---------}| + {7: }c │{7: }c │{7: }c | + {1:~ }│{1:~ }│{1:~ }|*15 + {2:Xdifile1 Xdifile2 }{3:Xdifile3 }| + | + ]]) + + WriteDiffFiles3('a\nb\nc', 'a\nx\nc', 'c') + screen:expect([[ + {7: }{4:a }│{7: }{4:a }│{7: }{23:---------}| + {7: }{27:b}{4: }│{7: }{27:x}{4: }│{7: }{23:---------}| + {7: }c │{7: }c │{7: }^c | + {1:~ }│{1:~ }│{1:~ }|*15 + {2:Xdifile1 Xdifile2 }{3:Xdifile3 }| + | + ]]) + + WriteDiffFiles3('a\nb\nc', 'a\nx\nc', '') + screen:expect([[ + {7: }{4:a }│{7: }{4:a }│{7: }{23:---------}| + {7: }{27:b}{4: }│{7: }{27:x}{4: }│{7: }{23:---------}| + {7: }{4:c }│{7: }{4:c }│{7: }{23:---------}| + {1:~ }│{1:~ }│{7: }^ | + {1:~ }│{1:~ }│{1:~ }|*14 + {2:Xdifile1 Xdifile2 }{3:Xdifile3 }| + | + ]]) + + WriteDiffFiles3('a\nb\nc', 'a\nx\nc', 'a\nc') + screen:expect([[ + {7: }a │{7: }a │{7: }^a | + {7: }{27:b}{4: }│{7: }{27:x}{4: }│{7: }{23:---------}| + {7: }c │{7: }c │{7: }c | + {1:~ }│{1:~ }│{1:~ }|*15 + {2:Xdifile1 Xdifile2 }{3:Xdifile3 }| + | + ]]) + + WriteDiffFiles3('a\nb\nc', 'a\nx\nc', 'a') + screen:expect([[ + {7: }a │{7: }a │{7: }^a | + {7: }{27:b}{4: }│{7: }{27:x}{4: }│{7: }{23:---------}| + {7: }{4:c }│{7: }{4:c }│{7: }{23:---------}| + {1:~ }│{1:~ }│{1:~ }|*15 + {2:Xdifile1 Xdifile2 }{3:Xdifile3 }| + | + ]]) + + WriteDiffFiles3('a\nb\nc', 'a\nx\nc', 'b') + screen:expect([[ + {7: }{27:a}{4: }│{7: }{27:a}{4: }│{7: }{27:^b}{4: }| + {7: }{27:b}{4: }│{7: }{27:x}{4: }│{7: }{23:---------}| + {7: }{4:c }│{7: }{4:c }│{7: }{23:---------}| + {1:~ }│{1:~ }│{1:~ }|*15 + {2:Xdifile1 Xdifile2 }{3:Xdifile3 }| + | + ]]) + + WriteDiffFiles3('a\nb\nc\nd\ne', 'a\nx\nc\nx\ne', 'd\ne') + screen:expect([[ + {7: }{27:a}{4: }│{7: }{27:a}{4: }│{7: }{27:^d}{4: }| + {7: }{27:b}{4: }│{7: }{27:x}{4: }│{7: }{23:---------}| + {7: }{4:c }│{7: }{4:c }│{7: }{23:---------}| + {7: }{27:d}{4: }│{7: }{27:x}{4: }│{7: }{23:---------}| + {7: }e │{7: }e │{7: }e | + {1:~ }│{1:~ }│{1:~ }|*13 + {2:Xdifile1 Xdifile2 }{3:Xdifile3 }| + | + ]]) + + WriteDiffFiles3('a\nb\nc\nd\ne', 'a\nx\nc\nx\ne', 'e') + screen:expect([[ + {7: }{4:a }│{7: }{4:a }│{7: }{23:---------}| + {7: }{27:b}{4: }│{7: }{27:x}{4: }│{7: }{23:---------}| + {7: }{4:c }│{7: }{4:c }│{7: }{23:---------}| + {7: }{27:d}{4: }│{7: }{27:x}{4: }│{7: }{23:---------}| + {7: }e │{7: }e │{7: }^e | + {1:~ }│{1:~ }│{1:~ }|*13 + {2:Xdifile1 Xdifile2 }{3:Xdifile3 }| + | + ]]) + + WriteDiffFiles3('a\nb\nc\nd\ne', 'a\nx\nc\nx\ne', 'a\nd\ne') + screen:expect([[ + {7: }a │{7: }a │{7: }^a | + {7: }{27:b}{4: }│{7: }{27:x}{4: }│{7: }{27:d}{4: }| + {7: }{4:c }│{7: }{4:c }│{7: }{23:---------}| + {7: }{27:d}{4: }│{7: }{27:x}{4: }│{7: }{23:---------}| + {7: }e │{7: }e │{7: }e | + {1:~ }│{1:~ }│{1:~ }|*13 + {2:Xdifile1 Xdifile2 }{3:Xdifile3 }| + | + ]]) + + WriteDiffFiles3('a\nb\nc\nd\ne', 'a\nx\nc\nx\ne', 'a\ne') + screen:expect([[ + {7: }a │{7: }a │{7: }^a | + {7: }{27:b}{4: }│{7: }{27:x}{4: }│{7: }{23:---------}| + {7: }{4:c }│{7: }{4:c }│{7: }{23:---------}| + {7: }{27:d}{4: }│{7: }{27:x}{4: }│{7: }{23:---------}| + {7: }e │{7: }e │{7: }e | + {1:~ }│{1:~ }│{1:~ }|*13 + {2:Xdifile1 Xdifile2 }{3:Xdifile3 }| + | + ]]) + + WriteDiffFiles3('a\nb\nc\nd\ne', 'a\nx\nc\nx\ne', 'a') + screen:expect([[ + {7: }a │{7: }a │{7: }^a | + {7: }{27:b}{4: }│{7: }{27:x}{4: }│{7: }{23:---------}| + {7: }{4:c }│{7: }{4:c }│{7: }{23:---------}| + {7: }{27:d}{4: }│{7: }{27:x}{4: }│{7: }{23:---------}| + {7: }{4:e }│{7: }{4:e }│{7: }{23:---------}| + {1:~ }│{1:~ }│{1:~ }|*13 + {2:Xdifile1 Xdifile2 }{3:Xdifile3 }| + | + ]]) + + WriteDiffFiles3('a\nb\nc\nd\ne', 'a\nx\nc\nx\ne', 'a\nb\nd\ne') + screen:expect([[ + {7: }a │{7: }a │{7: }^a | + {7: }{27:b}{4: }│{7: }{27:x}{4: }│{7: }{27:b}{4: }| + {7: }{27:c}{4: }│{7: }{27:c}{4: }│{7: }{27:d}{4: }| + {7: }{27:d}{4: }│{7: }{27:x}{4: }│{7: }{23:---------}| + {7: }e │{7: }e │{7: }e | + {1:~ }│{1:~ }│{1:~ }|*13 + {2:Xdifile1 Xdifile2 }{3:Xdifile3 }| + | + ]]) + + WriteDiffFiles3('a\nb\nc\nd\ne', 'a\nx\nc\nx\ne', 'a\nb\ne') + screen:expect([[ + {7: }a │{7: }a │{7: }^a | + {7: }{27:b}{4: }│{7: }{27:x}{4: }│{7: }{27:b}{4: }| + {7: }{4:c }│{7: }{4:c }│{7: }{23:---------}| + {7: }{27:d}{4: }│{7: }{27:x}{4: }│{7: }{23:---------}| + {7: }e │{7: }e │{7: }e | + {1:~ }│{1:~ }│{1:~ }|*13 + {2:Xdifile1 Xdifile2 }{3:Xdifile3 }| + | + ]]) + + WriteDiffFiles3('a\nb\nc\nd\ne', 'a\nx\nc\nx\ne', 'a\nb') + screen:expect([[ + {7: }a │{7: }a │{7: }^a | + {7: }{27:b}{4: }│{7: }{27:x}{4: }│{7: }{27:b}{4: }| + {7: }{4:c }│{7: }{4:c }│{7: }{23:---------}| + {7: }{27:d}{4: }│{7: }{27:x}{4: }│{7: }{23:---------}| + {7: }{4:e }│{7: }{4:e }│{7: }{23:---------}| + {1:~ }│{1:~ }│{1:~ }|*13 + {2:Xdifile1 Xdifile2 }{3:Xdifile3 }| + | + ]]) + + WriteDiffFiles3('a\nb\nc', 'a\nx\nc', 'a\ny\nb\nc') + screen:expect([[ + {7: }a │{7: }a │{7: }^a | + {7: }{27:b}{4: }│{7: }{27:x}{4: }│{7: }{27:y}{4: }| + {7: }{23:---------}│{7: }{23:---------}│{7: }{22:b }| + {7: }c │{7: }c │{7: }c | + {1:~ }│{1:~ }│{1:~ }|*14 + {2:Xdifile1 Xdifile2 }{3:Xdifile3 }| + | + ]]) + + WriteDiffFiles3('a\nb\nc', 'a\nx\nc', 'a\nb\ny\nc') + screen:expect([[ + {7: }a │{7: }a │{7: }^a | + {7: }{27:b}{4: }│{7: }{27:x}{4: }│{7: }{27:b}{4: }| + {7: }{23:---------}│{7: }{23:---------}│{7: }{22:y }| + {7: }c │{7: }c │{7: }c | + {1:~ }│{1:~ }│{1:~ }|*14 + {2:Xdifile1 Xdifile2 }{3:Xdifile3 }| + | + ]]) +end) diff --git a/test/functional/ui/float_spec.lua b/test/functional/ui/float_spec.lua index 248220e28b..9b77cb4014 100644 --- a/test/functional/ui/float_spec.lua +++ b/test/functional/ui/float_spec.lua @@ -327,6 +327,35 @@ describe('float window', function() eq(12, pos[2]) end) + it('error message when invalid field specified for split', function() + local bufnr = api.nvim_create_buf(false, true) + eq( + "non-float cannot have 'row'", + pcall_err(api.nvim_open_win, bufnr, true, { split = 'right', row = 10 }) + ) + eq( + "non-float cannot have 'col'", + pcall_err(api.nvim_open_win, bufnr, true, { split = 'right', col = 10 }) + ) + eq( + "non-float cannot have 'bufpos'", + pcall_err(api.nvim_open_win, bufnr, true, { split = 'right', bufpos = { 0, 0 } }) + ) + local winid = api.nvim_open_win(bufnr, true, { split = 'right' }) + eq( + "non-float cannot have 'row'", + pcall_err(api.nvim_win_set_config, winid, { split = 'right', row = 10 }) + ) + eq( + "non-float cannot have 'col'", + pcall_err(api.nvim_win_set_config, winid, { split = 'right', col = 10 }) + ) + eq( + "non-float cannot have 'bufpos'", + pcall_err(api.nvim_win_set_config, winid, { split = 'right', bufpos = { 0, 0 } }) + ) + end) + it('error message when reconfig missing relative field', function() local bufnr = api.nvim_create_buf(false, true) local opts = { @@ -337,15 +366,16 @@ describe('float window', function() relative = 'editor', style = 'minimal', } - local win_id = api.nvim_open_win(bufnr, true, opts) + local winid = api.nvim_open_win(bufnr, true, opts) eq( - "Missing 'relative' field when reconfiguring floating window 1001", - pcall_err(api.nvim_win_set_config, win_id, { - width = 3, - height = 3, - row = 10, - col = 10, - })) + "Missing 'relative' field when reconfiguring floating window 1001", + pcall_err(api.nvim_win_set_config, winid, { + width = 3, + height = 3, + row = 10, + col = 10, + }) + ) end) it('is not operated on by windo when non-focusable #15374', function() @@ -632,6 +662,22 @@ describe('float window', function() screen:detach() end) + it('no crash with relative="win" after %bdelete #30569', function() + exec([[ + botright vsplit + %bdelete + ]]) + api.nvim_open_win(0, false, { + relative = 'win', + win = 0, + row = 0, + col = 5, + width = 5, + height = 5, + }) + assert_alive() + end) + describe('with only one tabpage,', function() local float_opts = {relative = 'editor', row = 1, col = 1, width = 1, height = 1} local old_buf, old_win @@ -1314,6 +1360,53 @@ describe('float window', function() | ]]) end + + -- + -- floating windows inherit NormalFloat from global-ns. + -- + command('fclose') + command('hi NormalFloat guibg=LightRed') + api.nvim_open_win(0, false, { relative = 'win', row = 3, col = 3, width = 12, height = 3, style = 'minimal' }) + api.nvim_set_hl_ns(api.nvim_create_namespace('test1')) + if multigrid then + screen:expect({ + grid = [[ + ## grid 1 + [2:----------------------------------------]|*6 + [3:----------------------------------------]| + ## grid 2 + {14: 1 }^x | + {14: 2 }y | + {14: 3 } | + {0:~ }|*3 + ## grid 3 + | + ## grid 5 + {22:x }| + {22:y }| + {22: }| + ]], float_pos={ + [5] = {1002, "NW", 2, 3, 3, true, 50}; + }, win_viewport={ + [2] = {win = 1000, topline = 0, botline = 4, curline = 0, curcol = 0, linecount = 3, sum_scroll_delta = 0}; + [5] = {win = 1002, topline = 0, botline = 3, curline = 0, curcol = 0, linecount = 3, sum_scroll_delta = 0}; + }, win_viewport_margins={ + [2] = { bottom = 0, left = 0, right = 0, top = 0, win = 1000 }, + [5] = { bottom = 0, left = 0, right = 0, top = 0, win = 1002 } + }}) + else + screen:expect({ + grid = [[ + {14: 1 }^x | + {14: 2 }y | + {14: 3 } | + {0:~ }{22:x }{0: }| + {0:~ }{22:y }{0: }| + {0:~ }{22: }{0: }| + | + ]] + }) + end end) it("can use 'minimal' style", function() @@ -1532,9 +1625,9 @@ describe('float window', function() [2:----------------------------------------]|*6 [3:----------------------------------------]| ## grid 2 - {20:1}{19: }{20: }{22:^x}{21: }| - {14:2}{19: }{14: }{22:y} | - {14:3}{19: }{14: }{22: } | + {20: 1}{19: }{22:^x}{21: }| + {14: 2}{19: }{22:y} | + {14: 3}{19: }{22: } | {0:~ }|*3 ## grid 3 | @@ -1545,9 +1638,9 @@ describe('float window', function() ]], float_pos={[4] = {1001, "NW", 1, 4, 10, true}}} else screen:expect{grid=[[ - {20:1}{19: }{20: }{22:^x}{21: }| - {14:2}{19: }{14: }{22:y} | - {14:3}{19: }{14: }{22: } {15:x } | + {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 | @@ -2127,7 +2220,7 @@ describe('float window', function() ## grid 3 | ## grid 4 - {5:╔═════}🦄BB{5:╗}| + {5:╔═════}{11:🦄BB}{5:╗}| {5:║}{1: halloj! }{5:║}| {5:║}{1: BORDAA }{5:║}| {5:╚═════════╝}| @@ -2141,7 +2234,7 @@ describe('float window', function() screen:expect{grid=[[ ^ | {0:~ }| - {0:~ }{5:╔═════}🦄BB{5:╗}{0: }| + {0:~ }{5:╔═════}{11:🦄BB}{5:╗}{0: }| {0:~ }{5:║}{1: halloj! }{5:║}{0: }| {0:~ }{5:║}{1: BORDAA }{5:║}{0: }| {0:~ }{5:╚═════════╝}{0: }| @@ -2275,7 +2368,7 @@ describe('float window', function() {5:╔═════════╗}| {5:║}{1: halloj! }{5:║}| {5:║}{1: BORDAA }{5:║}| - {5:╚═════}🦄BB{5:╝}| + {5:╚═════}{11:🦄BB}{5:╝}| ]], float_pos={ [4] = { 1001, "NW", 1, 2, 5, true } }, win_viewport={ @@ -2289,7 +2382,7 @@ describe('float window', function() {0:~ }{5:╔═════════╗}{0: }| {0:~ }{5:║}{1: halloj! }{5:║}{0: }| {0:~ }{5:║}{1: BORDAA }{5:║}{0: }| - {0:~ }{5:╚═════}🦄BB{5:╝}{0: }| + {0:~ }{5:╚═════}{11:🦄BB}{5:╝}{0: }| | ]]} end @@ -2423,10 +2516,10 @@ describe('float window', function() ## grid 3 | ## grid 4 - {5:╔═════}🦄{7:BB}{5:╗}| + {5:╔═════}{11:🦄}{7:BB}{5:╗}| {5:║}{1: halloj! }{5:║}| {5:║}{1: BORDAA }{5:║}| - {5:╚═════}🦄{7:BB}{5:╝}| + {5:╚═════}{11:🦄}{7:BB}{5:╝}| ]], float_pos={ [4] = { 1001, "NW", 1, 2, 5, true } }, win_viewport={ @@ -2437,10 +2530,10 @@ describe('float window', function() screen:expect{grid=[[ ^ | {0:~ }| - {0:~ }{5:╔═════}🦄{7:BB}{5:╗}{0: }| + {0:~ }{5:╔═════}{11:🦄}{7:BB}{5:╗}{0: }| {0:~ }{5:║}{1: halloj! }{5:║}{0: }| {0:~ }{5:║}{1: BORDAA }{5:║}{0: }| - {0:~ }{5:╚═════}🦄{7:BB}{5:╝}{0: }| + {0:~ }{5:╚═════}{11:🦄}{7:BB}{5:╝}{0: }| | ]]} end @@ -2485,6 +2578,37 @@ describe('float window', function() end eq({{"🦄", ""}, {"BB", {"B0", "B1", ""}}}, api.nvim_win_get_config(win).title) eq({{"🦄", ""}, {"BB", {"B0", "B1", ""}}}, api.nvim_win_get_config(win).footer) + + -- making it a split should not leak memory + api.nvim_win_set_config(win, { vertical = true }) + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [4:--------------------]{5:│}[2:-------------------]|*5 + {5:[No Name] [+] }{4:[No Name] }| + [3:----------------------------------------]| + ## grid 2 + ^ | + {0:~ }|*4 + ## grid 3 + | + ## grid 4 + halloj! | + BORDAA | + {0:~ }|*3 + ]], 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 = 3, curline = 0, curcol = 0, linecount = 2, sum_scroll_delta = 0}; + }} + else + screen:expect{grid=[[ + halloj! {5:│}^ | + BORDAA {5:│}{0:~ }| + {0:~ }{5:│}{0:~ }|*3 + {5:[No Name] [+] }{4:[No Name] }| + | + ]]} + end end) it('terminates border on edge of viewport when window extends past viewport', function() @@ -8991,6 +9115,7 @@ describe('float window', function() end) it('float window with hide option', function() + local cwin = api.nvim_get_current_win() local buf = api.nvim_create_buf(false,false) local win = api.nvim_open_win(buf, false, {relative='editor', width=10, height=2, row=2, col=5, hide = true}) local expected_pos = { @@ -9070,6 +9195,22 @@ describe('float window', function() | ]]) end + -- check window jump with hide + feed('<C-W><C-W>') + -- should keep on current window + eq(cwin, api.nvim_get_current_win()) + api.nvim_win_set_config(win, {hide=false}) + api.nvim_set_current_win(win) + local win3 = api.nvim_open_win(buf, true, {relative='editor', width=4, height=4, row=2, col=5, hide = false}) + api.nvim_win_set_config(win, {hide=true}) + feed('<C-W>w') + -- should goto the first window with prev + eq(cwin, api.nvim_get_current_win()) + -- windo + command('windo set winheight=6') + eq(win3, api.nvim_get_current_win()) + eq(6, api.nvim_win_get_height(win3)) + eq(2, api.nvim_win_get_height(win)) end) it(':fclose command #9663', function() diff --git a/test/functional/ui/highlight_spec.lua b/test/functional/ui/highlight_spec.lua index b7b46ddfae..87d66fa604 100644 --- a/test/functional/ui/highlight_spec.lua +++ b/test/functional/ui/highlight_spec.lua @@ -596,13 +596,13 @@ describe('highlight', function() ]]) screen:expect( [[ - {1: }^ | + {1: }{5:^ }| {1: }{2:01}{3:234 67}{2:89}{5: }| {4:~ }|*2 {7:[No Name] [+] }| - {1: } | {1: }{6:-----------------------}| - {4:~ }| + {1: }{6:-----------------------}| + {1: } | {8:[No Name] }| | ]], @@ -1078,6 +1078,44 @@ describe('CursorLine and CursorLineNr highlights', function() ]]) end) + -- oldtest: Test_cursorline_screenline_resize() + it("'cursorlineopt' screenline is updated on window resize", function() + local screen = Screen.new(75, 8) + screen:attach() + exec([[ + 50vnew + call setline(1, repeat('xyz ', 30)) + setlocal number cursorline cursorlineopt=screenline + normal! $ + ]]) + screen:expect([[ + {8: 1 }xyz xyz xyz xyz xyz xyz xyz xyz xyz xyz xyz xy│ | + {8: }z xyz xyz xyz xyz xyz xyz xyz xyz xyz xyz xyz │{1:~ }| + {8: }{21:xyz xyz xyz xyz xyz xyz xyz^ }│{1:~ }| + {1:~ }│{1:~ }|*3 + {3:[No Name] [+] }{2:[No Name] }| + | + ]]) + command('vertical resize -4') + screen:expect([[ + {8: 1 }xyz xyz xyz xyz xyz xyz xyz xyz xyz xyz xy│ | + {8: }z xyz xyz xyz xyz xyz xyz xyz xyz xyz xyz │{1:~ }| + {8: }{21:xyz xyz xyz xyz xyz xyz xyz xyz xyz^ }│{1:~ }| + {1:~ }│{1:~ }|*3 + {3:[No Name] [+] }{2:[No Name] }| + | + ]]) + command('set cpoptions+=n') + screen:expect([[ + {8: 1 }xyz xyz xyz xyz xyz xyz xyz xyz xyz xyz xy│ | + z xyz xyz xyz xyz xyz xyz xyz xyz xyz xyz xyz │{1:~ }| + {21:xyz xyz xyz xyz xyz xyz xyz xyz^ }│{1:~ }| + {1:~ }│{1:~ }|*3 + {3:[No Name] [+] }{2:[No Name] }| + | + ]]) + end) + -- oldtest: Test_cursorline_after_yank() it('always updated. vim-patch:8.1.0849', function() local screen = Screen.new(50, 5) diff --git a/test/functional/ui/hlstate_spec.lua b/test/functional/ui/hlstate_spec.lua index 8d14c9f73d..a255047ed7 100644 --- a/test/functional/ui/hlstate_spec.lua +++ b/test/functional/ui/hlstate_spec.lua @@ -1,7 +1,7 @@ local t = require('test.testutil') local n = require('test.functional.testnvim')() local Screen = require('test.functional.ui.screen') -local tt = require('test.functional.terminal.testutil') +local tt = require('test.functional.testterm') local clear, insert = n.clear, n.insert local command = n.command diff --git a/test/functional/ui/messages_spec.lua b/test/functional/ui/messages_spec.lua index ca52a265fa..a3e5068e55 100644 --- a/test/functional/ui/messages_spec.lua +++ b/test/functional/ui/messages_spec.lua @@ -1081,6 +1081,22 @@ stack traceback: }, }) end) + + it('does not do showmode unnecessarily #29086', function() + local screen_showmode = screen._handle_msg_showmode + local showmode = 0 + screen._handle_msg_showmode = function(...) + screen_showmode(...) + showmode = showmode + 1 + end + screen:expect({ + grid = [[ + ^ | + {1:~ }|*4 + ]], + }) + eq(showmode, 1) + end) end) describe('ui/builtin messages', function() @@ -1149,7 +1165,12 @@ describe('ui/builtin messages', function() it(':syntax list langGroup output', function() command('syntax on') - command('set syntax=vim') + exec([[ + syn match vimComment excludenl +\s"[^\-:.%#=*].*$+lc=1 contains=@vimCommentGroup,vimCommentString + syn match vimComment +\<endif\s\+".*$+lc=5 contains=@vimCommentGroup,vimCommentString + syn match vimComment +\<else\s\+".*$+lc=4 contains=@vimCommentGroup,vimCommentString + hi link vimComment Comment + ]]) screen:try_resize(110, 7) feed(':syntax list vimComment<cr>') screen:expect([[ @@ -1420,6 +1441,41 @@ vimComment xxx match /\s"[^\-:.%#=*].*$/ms=s+1,lc=1 excludenl contains=@vim } end) + it('supports nvim_echo messages with emoji', function() + -- stylua: ignore + async_meths.nvim_echo( + { { 'wow, 🏳️⚧️🧑🌾❤️😂🏴☠️\nvariant ❤️ one\nvariant ❤ two' } }, true, {} + ) + + screen:expect([[ + | + {1:~ }| + {3: }| + wow, 🏳️⚧️🧑🌾❤️😂🏴☠️ | + variant ❤️ one | + variant ❤ two | + {6:Press ENTER or type command to continue}^ | + ]]) + + feed '<cr>' + screen:expect([[ + ^ | + {1:~ }|*5 + | + ]]) + + feed ':messages<cr>' + screen:expect([[ + | + {1:~ }| + {3: }| + wow, 🏳️⚧️🧑🌾❤️😂🏴☠️ | + variant ❤️ one | + variant ❤ two | + {6:Press ENTER or type command to continue}^ | + ]]) + end) + it('prints lines in Ex mode correctly with a burst of carriage returns #19341', function() command('set number') api.nvim_buf_set_lines(0, 0, 0, true, { 'aaa', 'bbb', 'ccc' }) diff --git a/test/functional/ui/mouse_spec.lua b/test/functional/ui/mouse_spec.lua index 42c877fd92..bc18680749 100644 --- a/test/functional/ui/mouse_spec.lua +++ b/test/functional/ui/mouse_spec.lua @@ -367,7 +367,7 @@ describe('ui/mouse/input', function() }) end) - it('left click in default tabline (position 4) switches to tab', function() + it('left click in default tabline (tabpage label) switches to tab', function() feed_command('%delete') insert('this is foo') feed_command('silent file foo | tabnew | file bar') @@ -385,9 +385,47 @@ describe('ui/mouse/input', function() {0:~ }|*2 | ]]) + feed('<LeftMouse><6,0>') + screen:expect_unchanged() + feed('<LeftMouse><10,0>') + screen:expect([[ + {tab: + foo }{sel: + bar }{fill: }{tab:X}| + this is ba^r{0:$} | + {0:~ }|*2 + | + ]]) + feed('<LeftMouse><12,0>') + screen:expect_unchanged() + end) + + it('left click in default tabline (blank space) switches tab', function() + feed_command('%delete') + insert('this is foo') + feed_command('silent file foo | tabnew | file bar') + insert('this is bar') + screen:expect([[ + {tab: + foo }{sel: + bar }{fill: }{tab:X}| + this is ba^r{0:$} | + {0:~ }|*2 + | + ]]) + feed('<LeftMouse><20,0>') + screen:expect([[ + {sel: + foo }{tab: + bar }{fill: }{tab:X}| + this is fo^o | + {0:~ }|*2 + | + ]]) + feed('<LeftMouse><22,0>') + screen:expect([[ + {tab: + foo }{sel: + bar }{fill: }{tab:X}| + this is ba^r{0:$} | + {0:~ }|*2 + | + ]]) end) - it('left click in default tabline (position 24) closes tab', function() + it('left click in default tabline (close label) closes tab', function() api.nvim_set_option_value('hidden', true, {}) feed_command('%delete') insert('this is foo') @@ -407,8 +445,7 @@ describe('ui/mouse/input', function() ]]) end) - it('double click in default tabline (position 4) opens new tab', function() - api.nvim_set_option_value('hidden', true, {}) + it('double click in default tabline opens new tab before', function() feed_command('%delete') insert('this is foo') feed_command('silent file foo | tabnew | file bar') @@ -426,6 +463,34 @@ describe('ui/mouse/input', function() {0:~ }|*2 | ]]) + command('tabclose') + screen:expect([[ + {sel: + foo }{tab: + bar }{fill: }{tab:X}| + this is fo^o | + {0:~ }|*2 + | + ]]) + feed('<2-LeftMouse><20,0>') + screen:expect([[ + {tab: + foo + bar }{sel: Name] }{fill: }{tab:X}| + {0:^$} | + {0:~ }|*2 + | + ]]) + command('tabclose') + screen:expect([[ + {tab: + foo }{sel: + bar }{fill: }{tab:X}| + this is ba^r{0:$} | + {0:~ }|*2 + | + ]]) + feed('<2-LeftMouse><10,0>') + screen:expect([[ + {tab: + foo }{sel: Name] }{tab: + bar }{fill: }{tab:X}| + {0:^$} | + {0:~ }|*2 + | + ]]) end) describe('%@ label', function() @@ -987,7 +1052,7 @@ describe('ui/mouse/input', function() command('set sidescroll=0') feed('<esc>:set nowrap<cr>') - feed('a <esc>20Ab<esc>') + feed('a <esc>17Ab<esc>3Ab<esc>') screen:expect([[ |*2 bbbbbbbbbbbbbbb^b | @@ -1017,7 +1082,7 @@ describe('ui/mouse/input', function() command('set sidescroll=0') feed('<esc>:set nowrap<cr>') - feed('a <esc>20Ab<esc>') + feed('a <esc>17Ab<esc>3Ab<esc>') screen:expect([[ |*2 bbbbbbbbbbbbbbb^b | @@ -2002,5 +2067,24 @@ describe('ui/mouse/input', function() feed('<Down><CR>') eq({ 4, 20 }, api.nvim_win_get_cursor(0)) eq('the moon', fn.getreg('"')) + + -- Try clicking in the cmdline + api.nvim_input_mouse('right', 'press', '', 0, 23, 0) + api.nvim_input_mouse('right', 'release', '', 0, 23, 0) + feed('<Down><Down><Down><CR>') + eq('baz', api.nvim_get_var('menustr')) + + -- Try clicking in horizontal separator with global statusline + command('set laststatus=3') + api.nvim_input_mouse('right', 'press', '', 0, 5, 0) + api.nvim_input_mouse('right', 'release', '', 0, 5, 0) + feed('<Down><CR>') + eq('foo', api.nvim_get_var('menustr')) + + -- Try clicking in the cmdline with global statusline + api.nvim_input_mouse('right', 'press', '', 0, 23, 0) + api.nvim_input_mouse('right', 'release', '', 0, 23, 0) + feed('<Down><Down><CR>') + eq('bar', api.nvim_get_var('menustr')) end) end) diff --git a/test/functional/ui/multibyte_spec.lua b/test/functional/ui/multibyte_spec.lua index dc25a09d0d..f16f750ea1 100644 --- a/test/functional/ui/multibyte_spec.lua +++ b/test/functional/ui/multibyte_spec.lua @@ -296,6 +296,86 @@ describe('multibyte rendering', function() ]], } end) + + it('supports emoji with variant selectors and ZWJ', function() + command('set ruler') + insert('🏳️⚧️') + screen:expect([[ + ^🏳️⚧️ | + {1:~ }|*4 + 1,1 All | + ]]) + + feed('a word<esc>') + screen:expect([[ + 🏳️⚧️ wor^d | + {1:~ }|*4 + 1,21-7 All | + ]]) + + feed('0') + screen:expect([[ + ^🏳️⚧️ word | + {1:~ }|*4 + 1,1 All | + ]]) + + feed('l') + screen:expect([[ + 🏳️⚧️^ word | + {1:~ }|*4 + 1,17-3 All | + ]]) + + feed('h') + screen:expect([[ + ^🏳️⚧️ word | + {1:~ }|*4 + 1,1 All | + ]]) + + feed('o❤️ variant selected<esc>') + screen:expect([[ + 🏳️⚧️ word | + ❤️ variant selecte^d | + {1:~ }|*3 + 2,23-19 All | + ]]) + + feed('0') + screen:expect([[ + 🏳️⚧️ word | + ^❤️ variant selected | + {1:~ }|*3 + 2,1 All | + ]]) + + feed('l') + screen:expect([[ + 🏳️⚧️ word | + ❤️^ variant selected | + {1:~ }|*3 + 2,7-3 All | + ]]) + + feed('h') + screen:expect([[ + 🏳️⚧️ word | + ^❤️ variant selected | + {1:~ }|*3 + 2,1 All | + ]]) + + -- without selector: single width (note column 18 and not 19) + feed('o❤ variant selected<esc>') + screen:expect([[ + 🏳️⚧️ word | + ❤️ variant selected | + ❤ variant selecte^d | + {1:~ }|*2 + 3,20-18 All | + ]]) + end) end) describe('multibyte rendering: statusline', function() @@ -348,11 +428,12 @@ describe('multibyte rendering: statusline', function() it('non-printable followed by MAX_MCO unicode combination points', function() command('set statusline≠⃯ᷰ⃐⃧⃝') -- U+9F + U+1DF0 + U+20EF + U+0338 + U+20D0 + U+20E7 + U+20DD + -- TODO: not ideal, better with plain ">" and then space+combining screen:expect([[ - ^ | - {1:~ }| - {3:<9f><1df0><20ef><0338><20d0><20e7><20dd>}| - | + ^ | + {1:~ }| + {3:<9f≯⃯ᷰ⃐⃧⃝ }| + | ]]) end) @@ -368,9 +449,20 @@ describe('multibyte rendering: statusline', function() } end) - it('unprintable chars in filename with default stl', function() + it('emoji with ZWJ in filename with default stl', function() command('file 🧑💻') - -- TODO: this is wrong but avoids a crash + screen:expect { + grid = [[ + ^ | + {1:~ }| + {3:🧑💻 }| + | + ]], + } + end) + + it('unprintable chars in filename with default stl', function() + command('file 🧑💻') screen:expect { grid = [[ ^ | @@ -381,15 +473,27 @@ describe('multibyte rendering: statusline', function() } end) - it('unprintable chars in filename with custom stl', function() + it('emoji with ZWJ in filename with custom stl', function() command('set statusline=xx%#ErrorMsg#%f%##yy') command('file 🧑💻') - -- TODO: this is also wrong but also avoids a crash screen:expect { grid = [[ ^ | {1:~ }| - {3:xx}{9:🧑<200d>💻}{3:yy }| + {3:xx}{9:🧑💻}{3:yy }| + | + ]], + } + end) + + it('unprintable chars in filename with custom stl', function() + command('set statusline=xx%#ErrorMsg#%f%##yy') + command('file 🧑💻') + screen:expect { + grid = [[ + ^ | + {1:~ }| + {3:xx}{9:🧑<200b>💻}{3:yy }| | ]], } diff --git a/test/functional/ui/multigrid_spec.lua b/test/functional/ui/multigrid_spec.lua index dc48061a6c..e009ed0a29 100644 --- a/test/functional/ui/multigrid_spec.lua +++ b/test/functional/ui/multigrid_spec.lua @@ -1095,6 +1095,7 @@ describe('ext_multigrid', function() end) it('supports mouse', function() + command('autocmd! nvim_popupmenu') -- Delete the default MenuPopup event handler. insert('some text\nto be clicked') screen:expect{grid=[[ ## grid 1 @@ -2598,4 +2599,257 @@ describe('ext_multigrid', function() ]]) eq(1, api.nvim_get_option_value('cmdheight', {})) end) + + describe('centered cursorline', function() + before_each(function() + -- Force a centered cursorline, this caused some redrawing problems described in #30576. + -- Most importantly, win_viewport was not received in time, and sum_scroll_delta did not refresh. + command('set cursorline scrolloff=9999') + end) + it('insert line scrolls correctly', function() + for i = 1, 11 do + insert('line' .. i .. '\n') + end + screen:expect({ + grid = [[ + ## grid 1 + [2:-----------------------------------------------------]|*12 + {11:[No Name] [+] }| + [3:-----------------------------------------------------]| + ## grid 2 + line1 | + line2 | + line3 | + line4 | + line5 | + line6 | + line7 | + line8 | + line9 | + line10 | + line11 | + {23:^ }| + ## grid 3 + | + ]], win_viewport={ + [2] = {win = 1000, topline = 0, botline = 12, curline = 11, curcol = 0, linecount = 12, sum_scroll_delta = 0}; + }, win_viewport_margins={ + [2] = { + bottom = 0, + left = 0, + right = 0, + top = 0, + win = 1000 + } + }}) + insert('line12\n') + screen:expect({ + grid = [[ + ## grid 1 + [2:-----------------------------------------------------]|*12 + {11:[No Name] [+] }| + [3:-----------------------------------------------------]| + ## grid 2 + line2 | + line3 | + line4 | + line5 | + line6 | + line7 | + line8 | + line9 | + line10 | + line11 | + line12 | + {23:^ }| + ## grid 3 + | + ]], win_viewport={ + [2] = {win = 1000, topline = 1, botline = 13, curline = 12, curcol = 0, linecount = 13, sum_scroll_delta = 1}; + }, win_viewport_margins={ + [2] = { + bottom = 0, + left = 0, + right = 0, + top = 0, + win = 1000 + } + }}) + end) + + it('got to top scrolls correctly', function() + for i = 1, 20 do + insert('line' .. i .. '\n') + end + screen:expect({ + grid = [[ + ## grid 1 + [2:-----------------------------------------------------]|*12 + {11:[No Name] [+] }| + [3:-----------------------------------------------------]| + ## grid 2 + line10 | + line11 | + line12 | + line13 | + line14 | + line15 | + line16 | + line17 | + line18 | + line19 | + line20 | + {23:^ }| + ## grid 3 + | + ]], win_viewport={ + [2] = {win = 1000, topline = 9, botline = 21, curline = 20, curcol = 0, linecount = 21, sum_scroll_delta = 9}; + }, win_viewport_margins={ + [2] = { + bottom = 0, + left = 0, + right = 0, + top = 0, + win = 1000 + } + }}) + feed('gg') + screen:expect({ + grid = [[ + ## grid 1 + [2:-----------------------------------------------------]|*12 + {11:[No Name] [+] }| + [3:-----------------------------------------------------]| + ## grid 2 + {23:^line1 }| + line2 | + line3 | + line4 | + line5 | + line6 | + line7 | + line8 | + line9 | + line10 | + line11 | + line12 | + ## grid 3 + | + ]], win_viewport={ + [2] = {win = 1000, topline = 0, botline = 13, curline = 0, curcol = 0, linecount = 21, sum_scroll_delta = 0}; + }, win_viewport_margins={ + [2] = { + bottom = 0, + left = 0, + right = 0, + top = 0, + win = 1000 + } + }}) + end) + + it('scrolls in the middle', function() + for i = 1, 20 do + insert('line' .. i .. '\n') + end + screen:expect({ + grid = [[ + ## grid 1 + [2:-----------------------------------------------------]|*12 + {11:[No Name] [+] }| + [3:-----------------------------------------------------]| + ## grid 2 + line10 | + line11 | + line12 | + line13 | + line14 | + line15 | + line16 | + line17 | + line18 | + line19 | + line20 | + {23:^ }| + ## grid 3 + | + ]], win_viewport={ + [2] = {win = 1000, topline = 9, botline = 21, curline = 20, curcol = 0, linecount = 21, sum_scroll_delta = 9}; + }, win_viewport_margins={ + [2] = { + bottom = 0, + left = 0, + right = 0, + top = 0, + win = 1000 + } + }}) + feed('M') + screen:expect({ + grid = [[ + ## grid 1 + [2:-----------------------------------------------------]|*12 + {11:[No Name] [+] }| + [3:-----------------------------------------------------]| + ## grid 2 + line10 | + line11 | + line12 | + line13 | + line14 | + {23:^line15 }| + line16 | + line17 | + line18 | + line19 | + line20 | + | + ## grid 3 + | + ]], win_viewport={ + [2] = {win = 1000, topline = 9, botline = 21, curline = 14, curcol = 0, linecount = 21, sum_scroll_delta = 9}; + }, win_viewport_margins={ + [2] = { + bottom = 0, + left = 0, + right = 0, + top = 0, + win = 1000 + } + }}) + feed('k') + screen:expect({ + grid = [[ + ## grid 1 + [2:-----------------------------------------------------]|*12 + {11:[No Name] [+] }| + [3:-----------------------------------------------------]| + ## grid 2 + line9 | + line10 | + line11 | + line12 | + line13 | + {23:^line14 }| + line15 | + line16 | + line17 | + line18 | + line19 | + line20 | + ## grid 3 + | + ]], win_viewport={ + [2] = {win = 1000, topline = 8, botline = 21, curline = 13, curcol = 0, linecount = 21, sum_scroll_delta = 8}; + }, win_viewport_margins={ + [2] = { + bottom = 0, + left = 0, + right = 0, + top = 0, + win = 1000 + } + }}) + end) + end) end) diff --git a/test/functional/ui/output_spec.lua b/test/functional/ui/output_spec.lua index 4f6454a0fb..220af06f53 100644 --- a/test/functional/ui/output_spec.lua +++ b/test/functional/ui/output_spec.lua @@ -1,7 +1,7 @@ local t = require('test.testutil') local n = require('test.functional.testnvim')() local Screen = require('test.functional.ui.screen') -local tt = require('test.functional.terminal.testutil') +local tt = require('test.functional.testterm') local assert_alive = n.assert_alive local mkdir, write_file, rmdir = t.mkdir, t.write_file, n.rmdir diff --git a/test/functional/ui/popupmenu_spec.lua b/test/functional/ui/popupmenu_spec.lua index 8f8604eecb..f84362ede8 100644 --- a/test/functional/ui/popupmenu_spec.lua +++ b/test/functional/ui/popupmenu_spec.lua @@ -851,6 +851,8 @@ describe('ui/ext_popupmenu', function() set mouse=a mousemodel=popup aunmenu PopUp + " Delete the default MenuPopup event handler. + 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> @@ -1129,7 +1131,7 @@ describe("builtin popupmenu 'pumblend'", function() [10] = { foreground = tonumber('0x000002') }, }) screen:attach({ rgb = false }) - command('set notermguicolors pumblend=10') + command('set pumblend=10') insert([[ Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor @@ -1160,23 +1162,47 @@ describe('builtin popupmenu', function() screen = Screen.new(32, 20) screen:set_default_attr_ids({ -- popup selected item / scrollbar track - ['s'] = { background = Screen.colors.WebGray }, + s = { background = Screen.colors.Grey }, -- popup non-selected item - ['n'] = { background = Screen.colors.LightMagenta }, + n = { background = Screen.colors.Plum1 }, -- popup scrollbar knob - ['c'] = { background = Screen.colors.Grey0 }, + c = { background = Screen.colors.Black }, [1] = { bold = true, foreground = Screen.colors.Blue }, [2] = { bold = true }, [3] = { reverse = true }, [4] = { bold = true, reverse = true }, [5] = { bold = true, foreground = Screen.colors.SeaGreen }, - [6] = { foreground = Screen.colors.Grey100, background = Screen.colors.Red }, + [6] = { foreground = Screen.colors.White, background = Screen.colors.Red }, [7] = { background = Screen.colors.Yellow }, -- Search [8] = { foreground = Screen.colors.Red }, - kn = { foreground = Screen.colors.Red, background = Screen.colors.Magenta }, ks = { foreground = Screen.colors.Red, background = Screen.colors.Grey }, - xn = { foreground = Screen.colors.White, background = Screen.colors.Magenta }, + kn = { foreground = Screen.colors.Red, background = Screen.colors.Plum1 }, xs = { foreground = Screen.colors.Black, background = Screen.colors.Grey }, + xn = { foreground = Screen.colors.White, background = Screen.colors.Plum1 }, + ms = { foreground = Screen.colors.Blue, background = Screen.colors.Grey }, + mn = { foreground = Screen.colors.Blue, background = Screen.colors.Plum1 }, + ds = { foreground = Screen.colors.DarkRed, background = Screen.colors.Grey }, + dn = { foreground = Screen.colors.DarkRed, background = Screen.colors.Plum1 }, + ums = { + foreground = Screen.colors.Blue, + background = Screen.colors.Grey, + underline = true, + }, + umn = { + foreground = Screen.colors.Blue, + background = Screen.colors.Plum1, + underline = true, + }, + uds = { + foreground = Screen.colors.DarkRed, + background = Screen.colors.Grey, + underline = true, + }, + udn = { + foreground = Screen.colors.DarkRed, + background = Screen.colors.Plum1, + underline = true, + }, }) screen:attach({ ext_multigrid = multigrid }) end) @@ -2528,6 +2554,7 @@ describe('builtin popupmenu', function() ]], } + -- oldtest: Test_wildmenu_pum_rightleft() feed('<tab>') screen:expect { grid = [[ @@ -2917,11 +2944,12 @@ describe('builtin popupmenu', function() feed('<C-U>sign define <Tab>') screen:expect([[ | - {1:~ }|*2 + {1:~ }| {1:~ }{s: culhl= }{1: }| {1:~ }{n: icon= }{1: }| {1:~ }{n: linehl= }{1: }| {1:~ }{n: numhl= }{1: }| + {1:~ }{n: priority= }{1: }| {1:~ }{n: text= }{1: }| {1:~ }{n: texthl= }{1: }| :sign define culhl=^ | @@ -2930,11 +2958,12 @@ describe('builtin popupmenu', function() feed('<Space><Tab>') screen:expect([[ | - {1:~ }|*2 + {1:~ }| {1:~ }{s: culhl= }{1: }| {1:~ }{n: icon= }{1: }| {1:~ }{n: linehl= }{1: }| {1:~ }{n: numhl= }{1: }| + {1:~ }{n: priority= }{1: }| {1:~ }{n: text= }{1: }| {1:~ }{n: texthl= }{1: }| :sign define culhl= culhl=^ | @@ -3547,6 +3576,66 @@ describe('builtin popupmenu', function() | ]]) end) + + -- oldtest: Test_wildmenu_pum_hl_match() + it('highlighting matched text in cmdline pum', function() + exec([[ + set wildoptions=pum,fuzzy + hi PmenuMatchSel guifg=Blue guibg=Grey + hi PmenuMatch guifg=Blue guibg=Plum1 + ]]) + + feed(':sign plc<Tab>') + screen:expect([[ + | + {1:~ }|*16 + {1:~ }{s: }{ms:pl}{s:a}{ms:c}{s:e }{1: }| + {1:~ }{n: un}{mn:pl}{n:a}{mn:c}{n:e }{1: }| + :sign place^ | + ]]) + feed('<Tab>') + screen:expect([[ + | + {1:~ }|*16 + {1:~ }{n: }{mn:pl}{n:a}{mn:c}{n:e }{1: }| + {1:~ }{s: un}{ms:pl}{s:a}{ms:c}{s:e }{1: }| + :sign unplace^ | + ]]) + feed('<Tab>') + screen:expect([[ + | + {1:~ }|*16 + {1:~ }{n: }{mn:pl}{n:a}{mn:c}{n:e }{1: }| + {1:~ }{n: un}{mn:pl}{n:a}{mn:c}{n:e }{1: }| + :sign plc^ | + ]]) + feed('<Esc>') + command('set wildoptions-=fuzzy') + feed(':sign un<Tab>') + screen:expect([[ + | + {1:~ }|*16 + {1:~ }{s: }{ms:un}{s:define }{1: }| + {1:~ }{n: }{mn:un}{n:place }{1: }| + :sign undefine^ | + ]]) + feed('<Tab>') + screen:expect([[ + | + {1:~ }|*16 + {1:~ }{n: }{mn:un}{n:define }{1: }| + {1:~ }{s: }{ms:un}{s:place }{1: }| + :sign unplace^ | + ]]) + feed('<Tab>') + screen:expect([[ + | + {1:~ }|*16 + {1:~ }{n: }{mn:un}{n:define }{1: }| + {1:~ }{n: }{mn:un}{n:place }{1: }| + :sign un^ | + ]]) + end) end it("'pumheight'", function() @@ -3718,6 +3807,8 @@ describe('builtin popupmenu', function() call setline(1, 'popup menu test') set mouse=a mousemodel=popup + " Delete the default MenuPopup event handler. + autocmd! nvim_popupmenu aunmenu PopUp menu PopUp.foo :let g:menustr = 'foo'<CR> menu PopUp.bar :let g:menustr = 'bar'<CR> @@ -4402,6 +4493,9 @@ describe('builtin popupmenu', function() -- oldtest: Test_popup_command_dump() it(':popup command', function() exec([[ + " Delete the default MenuPopup event handler. + autocmd! nvim_popupmenu + func ChangeMenu() aunmenu PopUp.&Paste nnoremenu 1.40 PopUp.&Paste :echomsg "pasted"<CR> @@ -4477,6 +4571,27 @@ describe('builtin popupmenu', function() feed('<Esc>') + command('set rightleft') + feed('/X<CR>:popup PopUp<CR>') + screen:expect([[ + evif ruof eerht owt eno| + evif ruof eerht{7:^X} owt eno dna| + {n: odnU }wt erom eno| + {1: }{n: }{1: ~}| + {1: }{n: etsaP }{1: ~}| + {1: }{n: }{1: ~}| + {1: }{n: droW tceleS }{1: ~}| + {1: }{n: ecnetneS tceleS }{1: ~}| + {1: }{n: hpargaraP tceleS }{1: ~}| + {1: }{n: eniL tceleS }{1: ~}| + {1: }{n: kcolB tceleS }{1: ~}| + {1: }{n: llA tceleS }{1: ~}| + {1: ~}|*7 + :popup PopUp | + ]]) + feed('<Esc>') + command('set norightleft') + -- Set an <expr> mapping to change a menu entry while it's displayed. -- The text should not change but the command does. -- Also verify that "changed" shows up, which means the mapping triggered. @@ -4533,6 +4648,57 @@ describe('builtin popupmenu', function() feed('<Esc>') end) + -- oldtest: Test_mouse_popup_position() + it('position of right-click menu when clicking near edge', function() + screen:try_resize(50, 20) + exec([[ + set mousemodel=popup_setpos + " Delete the default MenuPopup event handler. + autocmd! nvim_popupmenu + aunmenu * + source $VIMRUNTIME/menu.vim + call setline(1, join(range(20))) + ]]) + + api.nvim_input_mouse('right', 'press', '', 0, 0, 45 - 1) + screen:expect([[ + 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ^18 19 | + {1:~ }{n: Undo }| + {1:~ }{n: }| + {1:~ }{n: Paste }| + {1:~ }{n: }| + {1:~ }{n: Select Word }| + {1:~ }{n: Select Sentence }| + {1:~ }{n: Select Paragraph}| + {1:~ }{n: Select Line }| + {1:~ }{n: Select Block }| + {1:~ }{n: Select All }| + {1:~ }|*8 + | + ]]) + feed('<Esc>') + + command('set rightleft') + api.nvim_input_mouse('right', 'press', '', 0, 0, 50 - 45) + screen:expect([[ + 91 8^1 71 61 51 41 31 21 11 01 9 8 7 6 5 4 3 2 1 0| + {n: odnU }{1: ~}| + {n: }{1: ~}| + {n: etsaP }{1: ~}| + {n: }{1: ~}| + {n: droW tceleS }{1: ~}| + {n: ecnetneS tceleS }{1: ~}| + {n:hpargaraP tceleS }{1: ~}| + {n: eniL tceleS }{1: ~}| + {n: kcolB tceleS }{1: ~}| + {n: llA tceleS }{1: ~}| + {1: ~}|*8 + | + ]]) + feed('<Esc>') + command('set norightleft') + end) + describe('"kind" and "menu"', function() before_each(function() screen:try_resize(30, 8) @@ -4569,9 +4735,9 @@ describe('builtin popupmenu', function() -- oldtest: Test_pum_highlights_custom() it('custom highlight groups', function() exec([[ - hi PmenuKind guifg=Red guibg=Magenta + hi PmenuKind guifg=Red guibg=Plum1 hi PmenuKindSel guifg=Red guibg=Grey - hi PmenuExtra guifg=White guibg=Magenta + hi PmenuExtra guifg=White guibg=Plum1 hi PmenuExtraSel guifg=Black guibg=Grey ]]) feed('iaw<C-X><C-u>') @@ -4585,6 +4751,443 @@ describe('builtin popupmenu', function() ]]) end) end) + + -- oldtest: Test_pum_highlights_match() + it('can highlight matched text', function() + exec([[ + func Omni_test(findstart, base) + if a:findstart + return col(".") + endif + return { + \ 'words': [ + \ { 'word': 'foo', 'kind': 'fookind' }, + \ { 'word': 'foofoo', 'kind': 'fookind' }, + \ { 'word': 'foobar', 'kind': 'fookind' }, + \ { 'word': 'fooBaz', 'kind': 'fookind' }, + \ { 'word': 'foobala', 'kind': 'fookind' }, + \ { 'word': '你好' }, + \ { 'word': '你好吗' }, + \ { 'word': '你不好吗' }, + \ { 'word': '你可好吗' }, + \]} + endfunc + + func Comp() + let col = col('.') + if getline('.') == 'f' + let col -= 1 + endif + call complete(col, [ + \ #{word: "foo", icase: 1}, + \ #{word: "Foobar", icase: 1}, + \ #{word: "fooBaz", icase: 1}, + \]) + return '' + endfunc + + set omnifunc=Omni_test + set completeopt=menu,noinsert,fuzzy + hi PmenuMatchSel guifg=Blue guibg=Grey + hi PmenuMatch guifg=Blue guibg=Plum1 + ]]) + feed('i<C-X><C-O>') + local pum_start = [[ + ^ | + {s:foo fookind }{1: }| + {n:foofoo fookind }{1: }| + {n:foobar fookind }{1: }| + {n:fooBaz fookind }{1: }| + {n:foobala fookind }{1: }| + {n:你好 }{1: }| + {n:你好吗 }{1: }| + {n:你不好吗 }{1: }| + {n:你可好吗 }{1: }| + {1:~ }|*9 + {2:-- }{5:match 1 of 9} | + ]] + screen:expect(pum_start) + feed('fo') + screen:expect([[ + fo^ | + {ms:fo}{s:o fookind }{1: }| + {mn:fo}{n:ofoo fookind }{1: }| + {mn:fo}{n:obar fookind }{1: }| + {mn:fo}{n:oBaz fookind }{1: }| + {mn:fo}{n:obala fookind }{1: }| + {1:~ }|*13 + {2:-- }{5:match 1 of 9} | + ]]) + feed('<Esc>S<C-X><C-O>') + screen:expect(pum_start) + feed('你') + screen:expect([[ + 你^ | + {ms:你}{s:好 }{1: }| + {mn:你}{n:好吗 }{1: }| + {mn:你}{n:不好吗 }{1: }| + {mn:你}{n:可好吗 }{1: }| + {1:~ }|*14 + {2:-- }{5:match 1 of 9} | + ]]) + feed('吗') + screen:expect([[ + 你吗^ | + {ms:你}{s:好}{ms:吗}{s: }{1: }| + {mn:你}{n:不好}{mn:吗}{n: }{1: }| + {mn:你}{n:可好}{mn:吗}{n: }{1: }| + {1:~ }|*15 + {2:-- }{5:match 1 of 9} | + ]]) + feed('<C-E><Esc>') + + command('set rightleft') + feed('S<C-X><C-O>') + local pum_start_rl = [[ + ^ | + {1: }{s: dnikoof oof}| + {1: }{n: dnikoof oofoof}| + {1: }{n: dnikoof raboof}| + {1: }{n: dnikoof zaBoof}| + {1: }{n: dnikoof alaboof}| + {1: }{n: 好你}| + {1: }{n: 吗好你}| + {1: }{n: 吗好不你}| + {1: }{n: 吗好可你}| + {1: ~}|*9 + {2:-- }{5:match 1 of 9} | + ]] + screen:expect(pum_start_rl) + feed('fo') + screen:expect([[ + ^ of| + {1: }{s: dnikoof o}{ms:of}| + {1: }{n: dnikoof oofo}{mn:of}| + {1: }{n: dnikoof rabo}{mn:of}| + {1: }{n: dnikoof zaBo}{mn:of}| + {1: }{n: dnikoof alabo}{mn:of}| + {1: ~}|*13 + {2:-- }{5:match 1 of 9} | + ]]) + feed('<Esc>S<C-X><C-O>') + screen:expect(pum_start_rl) + feed('你') + screen:expect([[ + ^ 你| + {1: }{s: 好}{ms:你}| + {1: }{n: 吗好}{mn:你}| + {1: }{n: 吗好不}{mn:你}| + {1: }{n: 吗好可}{mn:你}| + {1: ~}|*14 + {2:-- }{5:match 1 of 9} | + ]]) + feed('吗') + screen:expect([[ + ^ 吗你| + {1: }{s: }{ms:吗}{s:好}{ms:你}| + {1: }{n: }{mn:吗}{n:好不}{mn:你}| + {1: }{n: }{mn:吗}{n:好可}{mn:你}| + {1: ~}|*15 + {2:-- }{5:match 1 of 9} | + ]]) + feed('<C-E><Esc>') + command('set norightleft') + + command('set completeopt-=fuzzy') + feed('S<C-X><C-O>') + screen:expect(pum_start) + feed('fo') + screen:expect([[ + fo^ | + {ms:fo}{s:o fookind }{1: }| + {mn:fo}{n:ofoo fookind }{1: }| + {mn:fo}{n:obar fookind }{1: }| + {mn:fo}{n:oBaz fookind }{1: }| + {mn:fo}{n:obala fookind }{1: }| + {1:~ }|*13 + {2:-- }{5:match 1 of 9} | + ]]) + feed('<C-E><Esc>') + + command('set rightleft') + feed('S<C-X><C-O>') + screen:expect(pum_start_rl) + feed('fo') + screen:expect([[ + ^ of| + {1: }{s: dnikoof o}{ms:of}| + {1: }{n: dnikoof oofo}{mn:of}| + {1: }{n: dnikoof rabo}{mn:of}| + {1: }{n: dnikoof zaBo}{mn:of}| + {1: }{n: dnikoof alabo}{mn:of}| + {1: ~}|*13 + {2:-- }{5:match 1 of 9} | + ]]) + feed('<C-E><Esc>') + command('set norightleft') + + feed('S<C-R>=Comp()<CR>f') + screen:expect([[ + f^ | + {ms:f}{s:oo }{1: }| + {mn:F}{n:oobar }{1: }| + {mn:f}{n:ooBaz }{1: }| + {1:~ }|*15 + {2:-- INSERT --} | + ]]) + feed('o<BS><C-R>=Comp()<CR>') + screen:expect_unchanged(true) + + feed('<Esc>') + command('set completeopt+=fuzzy,menu') + feed('S hello helio hero h<C-X><C-P>') + screen:expect([[ + hello helio hero h^ | + {1:~ }{n: }{mn:h}{n:ello }{1: }| + {1:~ }{n: }{mn:h}{n:elio }{1: }| + {1:~ }{s: }{ms:h}{s:ero }{1: }| + {1:~ }|*15 + {2:-- }{5:match 1 of 3} | + ]]) + + feed('<Esc>S hello helio hero h<C-X><C-P><C-P>') + screen:expect([[ + hello helio hero h^ | + {1:~ }{n: }{mn:h}{n:ello }{1: }| + {1:~ }{s: }{ms:h}{s:elio }{1: }| + {1:~ }{n: }{mn:h}{n:ero }{1: }| + {1:~ }|*15 + {2:-- }{5:match 2 of 3} | + ]]) + + feed('<C-E><Esc>') + end) + + -- oldtest: Test_pum_user_hl_group() + it('custom hl_group override', function() + exec([[ + func CompleteFunc( findstart, base ) + if a:findstart + return 0 + endif + return { + \ 'words': [ + \ { 'word': 'aword1', 'menu': 'extra text 1', 'kind': 'W', 'hl_group': 'StrikeFake' }, + \ { 'word': 'aword2', 'menu': 'extra text 2', 'kind': 'W', }, + \ { 'word': '你好', 'menu': 'extra text 3', 'kind': 'W', 'hl_group': 'StrikeFake' }, + \]} + endfunc + set completeopt=menu + set completefunc=CompleteFunc + + hi StrikeFake guifg=DarkRed + func HlMatch() + hi PmenuMatchSel guifg=Blue guibg=Grey gui=underline + hi PmenuMatch guifg=Blue guibg=Plum1 gui=underline + endfunc + ]]) + + feed('Saw<C-X><C-U>') + screen:expect([[ + aword1^ | + {ds:aword1 W extra text 1 }{1: }| + {n:aword2 W extra text 2 }{1: }| + {dn:你好 W extra text 3 }{1: }| + {1:~ }|*15 + {2:-- }{5:match 1 of 3} | + ]]) + feed('<C-E><Esc>') + + command('call HlMatch()') + + feed('Saw<C-X><C-U>') + screen:expect([[ + aword1^ | + {uds:aw}{ds:ord1 W extra text 1 }{1: }| + {umn:aw}{n:ord2 W extra text 2 }{1: }| + {dn:你好 W extra text 3 }{1: }| + {1:~ }|*15 + {2:-- }{5:match 1 of 3} | + ]]) + feed('<C-N>') + screen:expect([[ + aword2^ | + {udn:aw}{dn:ord1 W extra text 1 }{1: }| + {ums:aw}{s:ord2 W extra text 2 }{1: }| + {dn:你好 W extra text 3 }{1: }| + {1:~ }|*15 + {2:-- }{5:match 2 of 3} | + ]]) + feed('<C-E><Esc>') + end) + + -- oldtest: Test_pum_user_kind_hlgroup() + it('custom kind_hlgroup override', function() + exec([[ + func CompleteFunc( findstart, base ) + if a:findstart + return 0 + endif + return { + \ 'words': [ + \ { 'word': 'aword1', 'menu': 'extra text 1', 'kind': 'variable', 'kind_hlgroup': 'KindVar', 'hl_group': 'StrikeFake' }, + \ { 'word': 'aword2', 'menu': 'extra text 2', 'kind': 'function', 'kind_hlgroup': 'KindFunc' }, + \ { 'word': '你好', 'menu': 'extra text 3', 'kind': 'class', 'kind_hlgroup': 'KindClass' }, + \]} + endfunc + set completeopt=menu + set completefunc=CompleteFunc + + hi StrikeFake guifg=DarkRed + hi KindVar guifg=DarkYellow + hi KindFunc guifg=DarkBlue + hi KindClass guifg=DarkGreen + ]]) + + local attr_ids = screen:get_default_attr_ids() + attr_ids.kvs = { foreground = Screen.colors.DarkYellow, background = Screen.colors.Grey } + attr_ids.kfn = { foreground = Screen.colors.DarkBlue, background = Screen.colors.Plum1 } + attr_ids.kcn = { foreground = Screen.colors.DarkGreen, background = Screen.colors.Plum1 } + screen:set_default_attr_ids(attr_ids) + + feed('S<C-X><C-U>') + screen:expect([[ + aword1^ | + {ds:aword1 }{kvs:variable }{ds:extra text 1 }{1: }| + {n:aword2 }{kfn:function }{n:extra text 2 }{1: }| + {n:你好 }{kcn:class }{n:extra text 3 }{1: }| + {1:~ }|*15 + {2:-- }{5:match 1 of 3} | + ]]) + feed('<C-E><Esc>') + end) + + -- oldtest: Test_pum_completeitemalign() + it('completeitemalign option', function() + screen:try_resize(30, 15) + exec([[ + func Omni_test(findstart, base) + if a:findstart + return col(".") + endif + return { + \ 'words': [ + \ { 'word': 'foo', 'kind': 'S', 'menu': 'menu' }, + \ { 'word': 'bar', 'kind': 'T', 'menu': 'menu' }, + \ { 'word': '你好', 'kind': 'C', 'menu': '中文' }, + \]} + endfunc + + func Omni_long(findstart, base) + if a:findstart + return col(".") + endif + return { + \ 'words': [ + \ { 'word': 'loooong_foo', 'kind': 'S', 'menu': 'menu' }, + \ { 'word': 'loooong_bar', 'kind': 'T', 'menu': 'menu' }, + \]} + endfunc + set omnifunc=Omni_test + ]]) + -- T1 + command('set cia=abbr,kind,menu') + feed('S<C-X><C-O>') + screen:expect([[ + foo^ | + {s:foo S menu }{1: }| + {n:bar T menu }{1: }| + {n:你好 C 中文 }{1: }| + {1:~ }|*10 + {2:-- }{5:match 1 of 3} | + ]]) + feed('<C-E><ESC>') + -- T2 + command('set cia=abbr,menu,kind') + feed('S<C-X><C-O>') + screen:expect([[ + foo^ | + {s:foo menu S }{1: }| + {n:bar menu T }{1: }| + {n:你好 中文 C }{1: }| + {1:~ }|*10 + {2:-- }{5:match 1 of 3} | + ]]) + feed('<C-E><ESC>') + -- T3 + command('set cia=kind,abbr,menu') + feed('S<C-X><C-O>') + screen:expect([[ + foo^ | + {s:S foo menu }{1: }| + {n:T bar menu }{1: }| + {n:C 你好 中文 }{1: }| + {1:~ }|*10 + {2:-- }{5:match 1 of 3} | + ]]) + feed('<C-E><ESC>') + -- T4 + command('set cia=kind,menu,abbr') + feed('S<C-X><C-O>') + screen:expect([[ + foo^ | + {s:S menu foo }{1: }| + {n:T menu bar }{1: }| + {n:C 中文 你好 }{1: }| + {1:~ }|*10 + {2:-- }{5:match 1 of 3} | + ]]) + feed('<C-E><ESC>') + -- T5 + command('set cia=menu,abbr,kind') + feed('S<C-X><C-O>') + screen:expect([[ + foo^ | + {s:menu foo S }{1: }| + {n:menu bar T }{1: }| + {n:中文 你好 C }{1: }| + {1:~ }|*10 + {2:-- }{5:match 1 of 3} | + ]]) + feed('<C-E><ESC>') + -- T6 + command('set cia=menu,kind,abbr') + feed('S<C-X><C-O>') + screen:expect([[ + foo^ | + {s:menu S foo }{1: }| + {n:menu T bar }{1: }| + {n:中文 C 你好 }{1: }| + {1:~ }|*10 + {2:-- }{5:match 1 of 3} | + ]]) + feed('<C-E><ESC>') + -- T7 + command('set cia&') + feed('S<C-X><C-O>') + screen:expect([[ + foo^ | + {s:foo S menu }{1: }| + {n:bar T menu }{1: }| + {n:你好 C 中文 }{1: }| + {1:~ }|*10 + {2:-- }{5:match 1 of 3} | + ]]) + feed('<C-E><ESC>') + + -- Test_pum_completeitemalign_07 + command('set cia=menu,kind,abbr columns=12 cmdheight=2 omnifunc=Omni_long') + feed('S<C-X><C-O>') + screen:expect([[ + loooong_foo^ | + {s:menu S loooo}| + {n:menu T loooo}| + {1:~ }|*10 + | + {2:--} | + ]]) + feed('<C-E><ESC>') + end) end end diff --git a/test/functional/ui/screen.lua b/test/functional/ui/screen.lua index 4625ce8553..f1891b608e 100644 --- a/test/functional/ui/screen.lua +++ b/test/functional/ui/screen.lua @@ -255,6 +255,7 @@ end function Screen:set_default_attr_ids(attr_ids) self._default_attr_ids = attr_ids + self._attrs_overridden = true end function Screen:add_extra_attr_ids(extra_attr_ids) @@ -437,7 +438,7 @@ end --- @field mouse_enabled? boolean --- --- @field win_viewport? table<integer,table<string,integer>> ---- @field float_pos? {[1]:integer,[2]:integer} +--- @field float_pos? [integer,integer] --- @field hl_groups? table<string,integer> --- --- The following keys should be used to expect the state of various ext_ @@ -699,9 +700,9 @@ screen:redraw_debug() to show all intermediate screen states.]] end, expected) end -function Screen:expect_unchanged(intermediate, waittime_ms, ignore_attrs) +function Screen:expect_unchanged(intermediate, waittime_ms) -- Collect the current screen state. - local kwargs = self:get_snapshot(nil, ignore_attrs) + local kwargs = self:get_snapshot() if intermediate then kwargs.intermediate = true @@ -787,7 +788,9 @@ function Screen:_wait(check, flags) end local eof = run_session(self._session, flags.request_cb, notification_cb, nil, minimal_timeout) if not did_flush then - err = 'no flush received' + if eof then + err = 'no flush received' + end elseif not checked then err = check() if not err and flags.unchanged then @@ -800,6 +803,9 @@ function Screen:_wait(check, flags) did_minimal_timeout = true eof = run_session(self._session, flags.request_cb, notification_cb, nil, timeout - minimal_timeout) + if not did_flush then + err = 'no flush received' + end end local did_warn = false @@ -1536,13 +1542,14 @@ end -- Use snapshot_util({}) to generate a text-only (no attributes) test. -- -- @see Screen:redraw_debug() -function Screen:snapshot_util(attrs, ignore, request_cb) +function Screen:snapshot_util(request_cb) + -- TODO: simplify this later when existing tests have been updated self:sleep(250, request_cb) - self:print_snapshot(attrs, ignore) + self:print_snapshot() end -function Screen:redraw_debug(attrs, ignore, timeout) - self:print_snapshot(attrs, ignore) +function Screen:redraw_debug(timeout) + self:print_snapshot() local function notification_cb(method, args) assert(method == 'redraw') for _, update in ipairs(args) do @@ -1552,7 +1559,7 @@ function Screen:redraw_debug(attrs, ignore, timeout) end end self:_redraw(args) - self:print_snapshot(attrs, ignore) + self:print_snapshot() return true end if timeout == nil then @@ -1596,23 +1603,12 @@ end -- Returns the current screen state in the form of a screen:expect() -- keyword-args map. -function Screen:get_snapshot(attrs, ignore) - if ignore == nil then - ignore = self._default_attr_ignore - end +function Screen:get_snapshot() local attr_state = { ids = {}, - ignore = ignore, mutable = true, -- allow _row_repr to add missing highlights } - if attrs == nil then - attrs = self._default_attr_ids - elseif isempty(attrs) then - attrs = nil - attr_state.ids = nil - else - attr_state.modified = true - end + local attrs = self._default_attr_ids if attrs ~= nil then for i, a in pairs(attrs) do @@ -1708,9 +1704,10 @@ local function fmt_ext_state(name, state) end end -function Screen:_print_snapshot(attrs, ignore) - local kwargs, ext_state, attr_state = self:get_snapshot(attrs, ignore) +function Screen:_print_snapshot() + local kwargs, ext_state, attr_state = self:get_snapshot() local attrstr = '' + local modify_attrs = not self._attrs_overridden if attr_state.modified then local attrstrs = {} for i, a in pairs(attr_state.ids) do @@ -1721,16 +1718,20 @@ function Screen:_print_snapshot(attrs, ignore) dict = '{ ' .. self:_pprint_attrs(a) .. ' }' end local keyval = (type(i) == 'number') and '[' .. tostring(i) .. ']' or i - table.insert(attrstrs, ' ' .. keyval .. ' = ' .. dict .. ',') + if not (type(i) == 'number' and modify_attrs and i <= 30) then + table.insert(attrstrs, ' ' .. keyval .. ' = ' .. dict .. ',') + end + if modify_attrs then + self._default_attr_ids = attr_state.ids + end end - attrstr = (',\n attr_ids = {\n ' .. table.concat(attrstrs, '\n ') .. '\n },') - elseif isempty(attrs) then - attrstr = ',\n attr_ids = {},' + 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') end - local result = ('screen:expect({\n grid = [[\n %s\n ]]%s'):format( - kwargs.grid:gsub('\n', '\n '), - attrstr + local result = ('%sscreen:expect({\n grid = [[\n %s\n ]]'):format( + attrstr, + kwargs.grid:gsub('\n', '\n ') ) for _, k in ipairs(ext_keys) do if ext_state[k] ~= nil and not (k == 'win_viewport' and not self.options.ext_multigrid) then @@ -1742,8 +1743,8 @@ function Screen:_print_snapshot(attrs, ignore) return result end -function Screen:print_snapshot(attrs, ignore) - print('\n' .. self:_print_snapshot(attrs, ignore) .. '\n') +function Screen:print_snapshot() + print('\n' .. self:_print_snapshot() .. '\n') io.stdout:flush() end diff --git a/test/functional/ui/screen_basic_spec.lua b/test/functional/ui/screen_basic_spec.lua index 54580bf47c..85a653df36 100644 --- a/test/functional/ui/screen_basic_spec.lua +++ b/test/functional/ui/screen_basic_spec.lua @@ -4,6 +4,7 @@ local Screen = require('test.functional.ui.screen') local spawn, set_session, clear = n.spawn, n.set_session, n.clear local feed, command = n.feed, n.command +local exec = n.exec local insert = n.insert local eq = t.eq local fn, api = n.fn, n.api @@ -819,3 +820,39 @@ it("showcmd doesn't cause empty grid_line with redrawdebug=compositor #22593", f ]], } end) + +it("scrolling in narrow window doesn't draw over separator #29033", function() + clear() + local screen = Screen.new(60, 8) + screen:attach() + feed('100Oa<Esc>gg') + exec([[ + set number nowrap + vsplit + set scrollbind + wincmd l + set scrollbind + wincmd | + ]]) + screen:expect([[ + {8: }│{8: 1 }^a | + {8: }│{8: 2 }a | + {8: }│{8: 3 }a | + {8: }│{8: 4 }a | + {8: }│{8: 5 }a | + {8: }│{8: 6 }a | + {2:< }{3:[No Name] [+] }| + | + ]]) + feed('<C-F>') + screen:expect([[ + {8: }│{8: 5 }^a | + {8: }│{8: 6 }a | + {8: }│{8: 7 }a | + {8: }│{8: 8 }a | + {8: }│{8: 9 }a | + {8: }│{8: 10 }a | + {2:< }{3:[No Name] [+] }| + | + ]]) +end) diff --git a/test/functional/ui/scrollbind_spec.lua b/test/functional/ui/scrollbind_spec.lua new file mode 100644 index 0000000000..9e70b25efa --- /dev/null +++ b/test/functional/ui/scrollbind_spec.lua @@ -0,0 +1,442 @@ +local t = require('test.testutil') +local n = require('test.functional.testnvim')() +local clear = n.clear +local Screen = require('test.functional.ui.screen') + +before_each(clear) + +describe('Scrollbind', function() + local screen --- @type test.functional.ui.screen + + before_each(function() + screen = Screen.new(40, 12) + screen:attach() + end) + + it('works with one buffer with virtual lines', function() + n.exec_lua(function() + local lines = {} --- @type string[] + + for i = 1, 20 do + lines[i] = tostring(i * 2 - 1) + end + + local ns = vim.api.nvim_create_namespace('test') + + vim.api.nvim_buf_set_lines(0, 0, -1, false, lines) + vim.bo.buftype = 'nofile' + + for i in ipairs(lines) do + vim.api.nvim_buf_set_extmark(0, ns, i - 1, 0, { + virt_lines = { { { tostring(2 * i) .. ' v' } } }, + }) + end + + vim.wo.scrollbind = true + vim.cmd.vsplit() + vim.wo.scrollbind = true + end) + + n.feed('<C-d>') + + t.eq(5, n.api.nvim_get_option_value('scroll', {})) + + screen:expect({ + grid = [[ + 6 v │6 v | + 7 │7 | + 8 v │8 v | + 9 │9 | + 10 v │10 v | + ^11 │11 | + 12 v │12 v | + 13 │13 | + 14 v │14 v | + 15 │15 | + {3:[Scratch] }{2:[Scratch] }| + | + ]], + }) + + n.feed('<C-u>') + + local line1_grid = [[ + ^1 │1 | + 2 v │2 v | + 3 │3 | + 4 v │4 v | + 5 │5 | + 6 v │6 v | + 7 │7 | + 8 v │8 v | + 9 │9 | + 10 v │10 v | + {3:[Scratch] }{2:[Scratch] }| + | + ]] + + screen:expect({ grid = line1_grid }) + + n.api.nvim_set_option_value('scroll', 6, {}) + + n.feed('<C-d>') + + screen:expect({ + grid = [[ + 7 │7 | + 8 v │8 v | + 9 │9 | + 10 v │10 v | + 11 │11 | + 12 v │12 v | + ^13 │13 | + 14 v │14 v | + 15 │15 | + 16 v │16 v | + {3:[Scratch] }{2:[Scratch] }| + | + ]], + }) + + n.feed('<C-u>') + + screen:expect({ grid = line1_grid }) + end) + + it('works with two buffers with virtual lines on one side', function() + n.exec_lua(function() + local lines = {} --- @type string[] + + for i = 1, 20 do + lines[i] = tostring(i) + end + + local ns = vim.api.nvim_create_namespace('test') + + vim.api.nvim_buf_set_lines(0, 0, -1, false, lines) + vim.bo.buftype = 'nofile' + + vim.wo.scrollbind = true + vim.cmd.vnew() + + lines = {} --- @type string[] + + for i = 1, 20 do + lines[i] = tostring(i + (i > 3 and 4 or 0)) + end + vim.api.nvim_buf_set_lines(0, 0, -1, false, lines) + vim.bo.buftype = 'nofile' + + vim.api.nvim_buf_set_extmark(0, ns, 2, 0, { + virt_lines = { + { { '4 v' } }, + { { '5 v' } }, + { { '6 v' } }, + { { '7 v' } }, + }, + }) + + vim.wo.scrollbind = true + end) + + n.feed('<C-d>') + + t.eq(5, n.api.nvim_get_option_value('scroll', {})) + + screen:expect({ + grid = [[ + 6 v │6 | + 7 v │7 | + 8 │8 | + 9 │9 | + ^10 │10 | + 11 │11 | + 12 │12 | + 13 │13 | + 14 │14 | + 15 │15 | + {3:[Scratch] }{2:[Scratch] }| + | + ]], + }) + + n.feed('<C-u>') + + local line1_grid = [[ + ^1 │1 | + 2 │2 | + 3 │3 | + 4 v │4 | + 5 v │5 | + 6 v │6 | + 7 v │7 | + 8 │8 | + 9 │9 | + 10 │10 | + {3:[Scratch] }{2:[Scratch] }| + | + ]] + + screen:expect({ grid = line1_grid }) + + n.api.nvim_set_option_value('scroll', 6, {}) + + n.feed('<C-d>') + + screen:expect({ + grid = [[ + 7 v │7 | + 8 │8 | + 9 │9 | + 10 │10 | + ^11 │11 | + 12 │12 | + 13 │13 | + 14 │14 | + 15 │15 | + 16 │16 | + {3:[Scratch] }{2:[Scratch] }| + | + ]], + }) + + n.feed('<C-u>') + + screen:expect({ grid = line1_grid }) + + -- Note: not the same as n.feed('4<C-e>') + n.feed('<C-e>') + n.feed('<C-e>') + n.feed('<C-e>') + n.feed('<C-e>') + + screen:expect({ + grid = [[ + 5 v │5 | + 6 v │6 | + 7 v │7 | + ^8 │8 | + 9 │9 | + 10 │10 | + 11 │11 | + 12 │12 | + 13 │13 | + 14 │14 | + {3:[Scratch] }{2:[Scratch] }| + | + ]], + }) + + n.feed('<C-e>') + + screen:expect({ + grid = [[ + 6 v │6 | + 7 v │7 | + ^8 │8 | + 9 │9 | + 10 │10 | + 11 │11 | + 12 │12 | + 13 │13 | + 14 │14 | + 15 │15 | + {3:[Scratch] }{2:[Scratch] }| + | + ]], + }) + + n.feed('<C-y>') + n.feed('<C-y>') + + screen:expect({ + grid = [[ + 4 v │4 | + 5 v │5 | + 6 v │6 | + 7 v │7 | + ^8 │8 | + 9 │9 | + 10 │10 | + 11 │11 | + 12 │12 | + 13 │13 | + {3:[Scratch] }{2:[Scratch] }| + | + ]], + }) + end) + + it('works with buffers of different lengths', function() + n.exec_lua(function() + vim.api.nvim_buf_set_lines(0, 0, -1, false, { '1', '2', '3' }) + vim.bo.buftype = 'nofile' + + vim.wo.scrollbind = true + vim.cmd.vnew() + + local lines = {} --- @type string[] + + for i = 1, 50 do + lines[i] = tostring(i) + end + + vim.api.nvim_buf_set_lines(0, 0, -1, false, lines) + vim.bo.buftype = 'nofile' + vim.wo.scrollbind = true + end) + + n.feed('10<C-e>') + + screen:expect({ + grid = [[ + ^11 │3 | + 12 │{1:~ }| + 13 │{1:~ }| + 14 │{1:~ }| + 15 │{1:~ }| + 16 │{1:~ }| + 17 │{1:~ }| + 18 │{1:~ }| + 19 │{1:~ }| + 20 │{1:~ }| + {3:[Scratch] }{2:[Scratch] }| + | + ]], + }) + + n.feed('<C-y>') + + screen:expect({ + grid = [[ + 10 │3 | + ^11 │{1:~ }| + 12 │{1:~ }| + 13 │{1:~ }| + 14 │{1:~ }| + 15 │{1:~ }| + 16 │{1:~ }| + 17 │{1:~ }| + 18 │{1:~ }| + 19 │{1:~ }| + {3:[Scratch] }{2:[Scratch] }| + | + ]], + }) + end) + + it('works with buffers of different lengths and virtual lines', function() + n.exec_lua(function() + vim.api.nvim_buf_set_lines(0, 0, -1, false, { '1', '5', '6' }) + + local ns = vim.api.nvim_create_namespace('test') + vim.api.nvim_buf_set_extmark(0, ns, 0, 0, { + virt_lines = { + { { '2 v' } }, + { { '3 v' } }, + { { '4 v' } }, + }, + }) + + vim.bo.buftype = 'nofile' + + vim.wo.scrollbind = true + vim.cmd.vnew() + + local lines = {} --- @type string[] + + for i = 1, 50 do + lines[i] = tostring(i) + end + + vim.api.nvim_buf_set_lines(0, 0, -1, false, lines) + vim.bo.buftype = 'nofile' + vim.wo.scrollbind = true + end) + + n.feed('<C-e>') + n.feed('<C-e>') + screen:expect({ + grid = [[ + ^3 │3 v | + 4 │4 v | + 5 │5 | + 6 │6 | + 7 │{1:~ }| + 8 │{1:~ }| + 9 │{1:~ }| + 10 │{1:~ }| + 11 │{1:~ }| + 12 │{1:~ }| + {3:[Scratch] }{2:[Scratch] }| + | + ]], + }) + + n.feed('8<C-e>') + + screen:expect({ + grid = [[ + ^11 │6 | + 12 │{1:~ }| + 13 │{1:~ }| + 14 │{1:~ }| + 15 │{1:~ }| + 16 │{1:~ }| + 17 │{1:~ }| + 18 │{1:~ }| + 19 │{1:~ }| + 20 │{1:~ }| + {3:[Scratch] }{2:[Scratch] }| + | + ]], + }) + + n.feed('<C-y>') + n.feed('<C-y>') + n.feed('<C-y>') + n.feed('<C-y>') + n.feed('<C-y>') + + t.eq(n.exec_lua [[return vim.fn.line('w0', 1001)]], 6) + t.eq(n.exec_lua [[return vim.fn.line('w0', 1000)]], 3) + + screen:expect({ + grid = [[ + 6 │6 | + 7 │{1:~ }| + 8 │{1:~ }| + 9 │{1:~ }| + 10 │{1:~ }| + ^11 │{1:~ }| + 12 │{1:~ }| + 13 │{1:~ }| + 14 │{1:~ }| + 15 │{1:~ }| + {3:[Scratch] }{2:[Scratch] }| + | + ]], + }) + + n.feed('<C-y>') + n.feed('<C-y>') + n.feed('<C-y>') + + screen:expect({ + grid = [[ + 3 │3 v | + 4 │4 v | + 5 │5 | + 6 │6 | + 7 │{1:~ }| + 8 │{1:~ }| + 9 │{1:~ }| + 10 │{1:~ }| + ^11 │{1:~ }| + 12 │{1:~ }| + {3:[Scratch] }{2:[Scratch] }| + | + ]], + }) + end) +end) diff --git a/test/functional/ui/searchhl_spec.lua b/test/functional/ui/searchhl_spec.lua index 8bdf528412..eab265cbb1 100644 --- a/test/functional/ui/searchhl_spec.lua +++ b/test/functional/ui/searchhl_spec.lua @@ -197,7 +197,8 @@ describe('search highlighting', function() } end) - it('works for multiline match', function() + -- oldtest: Test_hlsearch_cursearch() + it('works for multiline match, no duplicate highlight', function() command([[call setline(1, ['one', 'foo', 'bar', 'baz', 'foo the foo and foo', 'bar'])]]) feed('gg/foo<CR>') screen:expect([[ @@ -281,6 +282,28 @@ describe('search highlighting', function() {2:hij}kl | /efg\nhij | ]]) + + -- check clearing CurSearch when using it for another match + feed('G?^abcd<CR>Y') + screen:expect([[ + --- | + {1:abcd}efg | + hijkl | + --- | + {2:^abcd}efg | + hijkl | + ?^abcd | + ]]) + feed('kkP') + screen:expect([[ + --- | + {1:abcd}efg | + {2:^abcd}efg | + hijkl | + --- | + {1:abcd}efg | + ?^abcd | + ]]) end) end) @@ -345,12 +368,19 @@ describe('search highlighting', function() bar baz foo bar foo baz]]) feed('/foo') + screen:set_default_attr_ids({ + [1] = { bold = true, foreground = Screen.colors.Blue }, + [2] = { background = Screen.colors.Yellow }, -- Search + [3] = { reverse = true }, + [4] = { bold = true, reverse = true }, + [5] = { foreground = Screen.colors.White, background = Screen.colors.DarkGreen }, + }) screen:expect([[ {3:foo} bar baz │{MATCH:%d+}: {2:foo}{MATCH:%s+}| bar baz {2:foo} │{MATCH:%d+}: {2:foo}{MATCH:%s+}| bar {2:foo} baz │{MATCH:%d+}: {2:foo}{MATCH:%s+}| {1:~ }│{MATCH:.*}|*2 - {5:[No Name] [+] }{3:term }| + {4:[No Name] [+] }{5:term }| /foo^ | ]]) end) diff --git a/test/functional/ui/sign_spec.lua b/test/functional/ui/sign_spec.lua index b353b3738a..30da79af47 100644 --- a/test/functional/ui/sign_spec.lua +++ b/test/functional/ui/sign_spec.lua @@ -4,6 +4,7 @@ local Screen = require('test.functional.ui.screen') local api, clear, eq = n.api, n.clear, t.eq local eval, exec, feed = n.eval, n.exec, n.feed +local exec_lua = n.exec_lua describe('Signs', function() local screen @@ -577,4 +578,45 @@ describe('Signs', function() ]]) eq({}, eval('sign_getdefined()')) end) + + it('no crash when unplacing signs beyond end of buffer', function() + exec([[ + sign define S1 text=S1 + sign define S2 text=S2 + sign place 1 line=8 name=S1 + sign place 2 line=9 name=S2 + ]]) + -- Now placed at end of buffer + local s1 = { + grid = [[ + S2^ | + {0:~ }|*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^ | + {0:~ }|*12 + | + ]], + }) + -- Unplacing the sign does not crash by decrementing tracked signs below zero + exec('sign unplace 1') + screen:expect(s1) + end) + + it('signcolumn width is set immediately after splitting window #30547', function() + local infos = exec_lua([[ + vim.o.number = true + vim.o.signcolumn = 'yes' + vim.cmd.wincmd('v') + return vim.fn.getwininfo() + ]]) + eq(6, infos[1].textoff) + eq(6, infos[2].textoff) + end) end) diff --git a/test/functional/ui/statuscolumn_spec.lua b/test/functional/ui/statuscolumn_spec.lua index faf94bccbe..b4d4c94a5e 100644 --- a/test/functional/ui/statuscolumn_spec.lua +++ b/test/functional/ui/statuscolumn_spec.lua @@ -91,49 +91,87 @@ describe('statuscolumn', function() {8:2 }aaaaa | | ]]) + -- Doesn't crash when clicking inside padded area without click_defs + command('set numberwidth=10') + api.nvim_input_mouse('left', 'press', '', 0, 0, 5) + assert_alive() end) it("works with 'number' and 'relativenumber'", function() - command([[set stc=%{&nu?v:lnum:''}%=%{&rnu?'\ '.v:relnum:''}│]]) - screen:expect([[ - {8:4 │}aaaaa | - {8:5 │}aaaaa | - {8:6 │}aaaaa | - {8:7 │}aaaaa | - {8:8 │}^aaaaa | - {8:9 │}aaaaa | - {8:10│}aaaaa | - {8:11│}aaaaa | - {8:12│}aaaaa | - {8:13│}aaaaa | - {8:14│}aaaaa | - {8:15│}aaaaa | - {8:16│}aaaaa | - | - ]]) - command([[set stc=%l%=%{&rnu?'\ ':''}%r│]]) + screen:expect([[ + {8: 4 }aaaaa | + {8: 5 }aaaaa | + {8: 6 }aaaaa | + {8: 7 }aaaaa | + {8: 8 }^aaaaa | + {8: 9 }aaaaa | + {8:10 }aaaaa | + {8:11 }aaaaa | + {8:12 }aaaaa | + {8:13 }aaaaa | + {8:14 }aaaaa | + {8:15 }aaaaa | + {8:16 }aaaaa | + | + ]]) + command([[set stc=%l\ ]]) screen:expect_unchanged() - command([[set stc=%{&nu?v:lnum:''}%=%{&rnu?'\ '.v:relnum:''}│]]) command('set relativenumber') screen:expect([[ - {8:4 4│}aaaaa | - {8:5 3│}aaaaa | - {8:6 2│}aaaaa | - {8:7 1│}aaaaa | - {8:8 0│}^aaaaa | - {8:9 1│}aaaaa | - {8:10 2│}aaaaa | - {8:11 3│}aaaaa | - {8:12 4│}aaaaa | - {8:13 5│}aaaaa | - {8:14 6│}aaaaa | - {8:15 7│}aaaaa | - {8:16 8│}aaaaa | - | - ]]) - command([[set stc=%l%=%{&rnu?'\ ':''}%r│]]) + {8: 4 }aaaaa | + {8: 3 }aaaaa | + {8: 2 }aaaaa | + {8: 1 }aaaaa | + {8:8 }^aaaaa | + {8: 1 }aaaaa | + {8: 2 }aaaaa | + {8: 3 }aaaaa | + {8: 4 }aaaaa | + {8: 5 }aaaaa | + {8: 6 }aaaaa | + {8: 7 }aaaaa | + {8: 8 }aaaaa | + | + ]]) + command('set stc=') + screen:expect_unchanged() + command([[set nonu stc=%l\ ]]) + screen:expect([[ + {8: 4 }aaaaa | + {8: 3 }aaaaa | + {8: 2 }aaaaa | + {8: 1 }aaaaa | + {8: 0 }^aaaaa | + {8: 1 }aaaaa | + {8: 2 }aaaaa | + {8: 3 }aaaaa | + {8: 4 }aaaaa | + {8: 5 }aaaaa | + {8: 6 }aaaaa | + {8: 7 }aaaaa | + {8: 8 }aaaaa | + | + ]]) + command('set nuw=1 stc=') screen:expect_unchanged() - command([[set stc=%{&nu?v:lnum:''}%=%{&rnu?'\ '.v:relnum:''}│]]) + -- Correct alignment with items before and after number column + command([[set nu stc=foo\ %l\ bar]]) + screen:expect([[ + {8:foo 4 bar}aaaaa | + {8:foo 3 bar}aaaaa | + {8:foo 2 bar}aaaaa | + {8:foo 1 bar}aaaaa | + {8:foo 8 bar}^aaaaa | + {8:foo 1 bar}aaaaa | + {8:foo 2 bar}aaaaa | + {8:foo 3 bar}aaaaa | + {8:foo 4 bar}aaaaa | + {8:foo 5 bar}aaaaa | + {8:foo 6 bar}aaaaa | + {8:foo 7 bar}aaaaa | + {8:foo 8 bar}aaaaa | + | + ]]) end) it("works with highlighted 'statuscolumn'", function() @@ -160,36 +198,36 @@ describe('statuscolumn', function() ]]) command('set relativenumber') screen:expect([[ - {1:4 }{8: 4│}aaaaa | - {1:5 3}{8:│}aaaaa | - {1:6 }{8: 2│}aaaaa | - {1:7 1}{8:│}aaaaa | - {1:8 }{8: 0│}^aaaaa | - {1:9 1}{8:│}aaaaa | - {1:10}{8: 2│}aaaaa | - {1:11 3}{8:│}aaaaa | - {1:12}{8: 4│}aaaaa | - {1:13 5}{8:│}aaaaa | - {1:14}{8: 6│}aaaaa | - {1:15 7}{8:│}aaaaa | - {1:16}{8: 8│}aaaaa | + {1:4 }{8: 4│}aaaaa | + {1:5 3}{8:│}aaaaa | + {1:6 }{8: 2│}aaaaa | + {1:7 1}{8:│}aaaaa | + {1:8 }{8: 0│}^aaaaa | + {1:9 1}{8:│}aaaaa | + {1:10 }{8: 2│}aaaaa | + {1:11 3}{8:│}aaaaa | + {1:12 }{8: 4│}aaaaa | + {1:13 5}{8:│}aaaaa | + {1:14 }{8: 6│}aaaaa | + {1:15 7}{8:│}aaaaa | + {1:16 }{8: 8│}aaaaa | | ]]) command('set nonumber') screen:expect([[ - {8:4│}aaaaa | - {1:3}{8:│}aaaaa | - {8:2│}aaaaa | - {1:1}{8:│}aaaaa | - {8:0│}^aaaaa | - {1:1}{8:│}aaaaa | - {8:2│}aaaaa | - {1:3}{8:│}aaaaa | - {8:4│}aaaaa | - {1:5}{8:│}aaaaa | - {8:6│}aaaaa | - {1:7}{8:│}aaaaa | - {8:8│}aaaaa | + {1: }{8:4│}aaaaa | + {1: 3}{8:│}aaaaa | + {1: }{8:2│}aaaaa | + {1: 1}{8:│}aaaaa | + {1: }{8:0│}^aaaaa | + {1: 1}{8:│}aaaaa | + {1: }{8:2│}aaaaa | + {1: 3}{8:│}aaaaa | + {1: }{8:4│}aaaaa | + {1: 5}{8:│}aaaaa | + {1: }{8:6│}aaaaa | + {1: 7}{8:│}aaaaa | + {1: }{8:8│}aaaaa | | ]]) end) @@ -305,36 +343,36 @@ describe('statuscolumn', function() -- v:relnum is the same value on wrapped lines command([[set stc=%C%=\ %{v:relnum}│%s\ ]]) screen:expect([[ - {2: }{1: 4│>>}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| - {2: }{1: 4│}{2: }{1: }aaaaaa | - {2: }{1: 3│}{0:>!}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| - {2: }{1: 3│}{2: }{1: }aaaaaa | - {2: }{1: 2│}{0:>!}{1:>> }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| - {2: }{1: 2│}{2: }{1: }aaaaaa | - {2: }{1: 1│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| - {2: }{1: 1│}{2: }{1: }aaaaaa | - {2:+}{4: 0│}{2: }{4: }{6:^+-- 1 line: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}| - {2: }{1: 1│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| - {2: }{1: 1│}{2: }{1: }aaaaaa | - {2: }{1: 2│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| - {2: }{1: 2│}{2: }{1: }aaaaaa | + {2: }{1: 4│>>}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: 4│}{2: }{1: }aaaaaaa | + {2: }{1: 3│}{0:>!}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: 3│}{2: }{1: }aaaaaaa | + {2: }{1: 2│}{0:>!}{1:>> }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: 2│}{2: }{1: }aaaaaaa | + {2: }{1: 1│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: 1│}{2: }{1: }aaaaaaa | + {2:+}{4: 0│}{2: }{4: }{6:^+-- 1 line: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}| + {2: }{1: 1│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: 1│}{2: }{1: }aaaaaaa | + {2: }{1: 2│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: 2│}{2: }{1: }aaaaaaa | | ]]) command([[set stc=%C%=\ %{v:virtnum?'':v:relnum}│%s\ ]]) screen:expect([[ - {2: }{1: 4│>>}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| - {2: }{1: │}{2: }{1: }aaaaaa | - {2: }{1: 3│}{0:>!}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| - {2: }{1: │}{2: }{1: }aaaaaa | - {2: }{1: 2│}{0:>!}{1:>> }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| - {2: }{1: │}{2: }{1: }aaaaaa | - {2: }{1: 1│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| - {2: }{1: │}{2: }{1: }aaaaaa | - {2:+}{4: 0│}{2: }{4: }{6:^+-- 1 line: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}| - {2: }{1: 1│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| - {2: }{1: │}{2: }{1: }aaaaaa | - {2: }{1: 2│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| - {2: }{1: │}{2: }{1: }aaaaaa | + {2: }{1: 4│>>}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │}{2: }{1: }aaaaaaa | + {2: }{1: 3│}{0:>!}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │}{2: }{1: }aaaaaaa | + {2: }{1: 2│}{0:>!}{1:>> }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │}{2: }{1: }aaaaaaa | + {2: }{1: 1│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │}{2: }{1: }aaaaaaa | + {2:+}{4: 0│}{2: }{4: }{6:^+-- 1 line: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}| + {2: }{1: 1│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │}{2: }{1: }aaaaaaa | + {2: }{1: 2│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │}{2: }{1: }aaaaaaa | | ]]) -- Up to 9 signs in a line @@ -347,75 +385,75 @@ 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([[ - {2: }{1: 4│>>}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| - {2: }{1: │}{2: }{1: }aaaaaaaaaaaaaaaaaaaa | - {2: }{1: 3│}{0:>!}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| - {2: }{1: │}{2: }{1: }aaaaaaaaaaaaaaaaaaaa | - {2: }{1: 2│>>}{0:>!}{1:>>}{0:>!}{1:>>}{0:>!}{1:>>}{0:>!}{1:>> }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| - {2: }{1: │}{2: }{1: }aaaaaaaaaaaaaaaaaaaa | - {2: }{1: 1│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| - {2: }{1: │}{2: }{1: }aaaaaaaaaaaaaaaaaaaa | - {2:+}{4: 0│}{2: }{4: }{6:^+-- 1 line: aaaaaaaaaaaaaaaaa}| - {2: }{1: 1│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| - {2: }{1: │}{2: }{1: }aaaaaaaaaaaaaaaaaaaa | - {2: }{1: 2│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| - {2: }{1: │}{2: }{1: }aaaaaaaaaaaaaaaaaaaa | + {2: }{1: 4│>>}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │}{2: }{1: }aaaaaaaaaaaaaaaaaaaaa | + {2: }{1: 3│}{0:>!}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │}{2: }{1: }aaaaaaaaaaaaaaaaaaaaa | + {2: }{1: 2│>>}{0:>!}{1:>>}{0:>!}{1:>>}{0:>!}{1:>>}{0:>!}{1:>> }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │}{2: }{1: }aaaaaaaaaaaaaaaaaaaaa | + {2: }{1: 1│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │}{2: }{1: }aaaaaaaaaaaaaaaaaaaaa | + {2:+}{4: 0│}{2: }{4: }{6:^+-- 1 line: aaaaaaaaaaaaaaaa}| + {2: }{1: 1│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │}{2: }{1: }aaaaaaaaaaaaaaaaaaaaa | + {2: }{1: 2│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }{1: │}{2: }{1: }aaaaaaaaaaaaaaaaaaaaa | | ]]) -- Also test fold and sign column when 'cpoptions' includes "n" command('set cpoptions+=n') feed('Hgjg0') screen:expect([[ - {2: }{4: 0│}{1:>>}{2: }{4: }{5:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}| - {2: }{5:^aaaaaaaaaaaaaaaaaaaa }| - {2: }{1: 3│}{0:>!}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| - {2: }aaaaaaaaaaaaaaaaaaaa | - {2: }{1: 2│>>}{0:>!}{1:>>}{0:>!}{1:>>}{0:>!}{1:>>}{0:>!}{1:>> }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| - {2: }aaaaaaaaaaaaaaaaaaaa | - {2: }{1: 1│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| - {2: }aaaaaaaaaaaaaaaaaaaa | - {2:+}{1: 4│}{2: }{1: }{3:+-- 1 line: aaaaaaaaaaaaaaaaa}| - {2: }{1: 1│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| - {2: }aaaaaaaaaaaaaaaaaaaa | - {2: }{1: 2│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| - {2: }aaaaaaaaaaaaaaaaaaaa | + {2: }{4: 0│}{1:>>}{2: }{4: }{5:aaaaaaaaaaaaaaaaaaaaaaaaaaaaa}| + {2: }{5:^aaaaaaaaaaaaaaaaaaaaa }| + {2: }{1: 3│}{0:>!}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }aaaaaaaaaaaaaaaaaaaaa | + {2: }{1: 2│>>}{0:>!}{1:>>}{0:>!}{1:>>}{0:>!}{1:>>}{0:>!}{1:>> }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }aaaaaaaaaaaaaaaaaaaaa | + {2: }{1: 1│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }aaaaaaaaaaaaaaaaaaaaa | + {2:+}{1: 4│}{2: }{1: }{3:+-- 1 line: aaaaaaaaaaaaaaaa}| + {2: }{1: 1│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }aaaaaaaaaaaaaaaaaaaaa | + {2: }{1: 2│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }aaaaaaaaaaaaaaaaaaaaa | | ]]) command('set breakindent') command('sign unplace 2') feed('J2gjg0') screen:expect([[ - {2: }{4: 0│}{1:>>}{2: }{4: }{5:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}| - {2: } {5:aaaaaaaaaaaaaaaaaaaa aaaaaaaaa}| - {2: } {5:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}| - {2: } {5:^aaaaaaaaaaa }| - {2: }{1: 1│>>}{0:>!}{1:>>}{0:>!}{1:>>}{0:>!}{1:>>}{0:>!}{1:>> }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| - {2: } aaaaaaaaaaaaaaaaaaaa | - {2: }{1: 2│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| - {2: } aaaaaaaaaaaaaaaaaaaa | - {2:+}{1: 3│}{2: }{1: }{3:+-- 1 line: aaaaaaaaaaaaaaaaa}| - {2: }{1: 4│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| - {2: } aaaaaaaaaaaaaaaaaaaa | - {2: }{1: 5│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| - {2: } aaaaaaaaaaaaaaaaaaaa | + {2: }{4: 0│}{1:>>}{2: }{4: }{5:aaaaaaaaaaaaaaaaaaaaaaaaaaaaa}| + {2: } {5:aaaaaaaaaaaaaaaaaaaaa aaaaaaa}| + {2: } {5:aaaaaaaaaaaaaaaaaaaaaaaaaaaaa}| + {2: } {5:^aaaaaaaaaaaaaa }| + {2: }{1: 1│>>}{0:>!}{1:>>}{0:>!}{1:>>}{0:>!}{1:>>}{0:>!}{1:>> }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: } aaaaaaaaaaaaaaaaaaaaa | + {2: }{1: 2│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: } aaaaaaaaaaaaaaaaaaaaa | + {2:+}{1: 3│}{2: }{1: }{3:+-- 1 line: aaaaaaaaaaaaaaaa}| + {2: }{1: 4│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: } aaaaaaaaaaaaaaaaaaaaa | + {2: }{1: 5│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: } aaaaaaaaaaaaaaaaaaaaa | | ]]) command('set nobreakindent') feed('$g0') screen:expect([[ - {2: }{4: 0│}{1:>>}{2: }{4: }{5:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}| - {2: }{5:aaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaaa}| + {2: }{4: 0│}{1:>>}{2: }{4: }{5:aaaaaaaaaaaaaaaaaaaaaaaaaaaaa}| + {2: }{5:aaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaa}| {2: }{5:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}| - {2: }{5:^aaa }| - {2: }{1: 1│>>}{0:>!}{1:>>}{0:>!}{1:>>}{0:>!}{1:>>}{0:>!}{1:>> }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| - {2: }aaaaaaaaaaaaaaaaaaaa | - {2: }{1: 2│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| - {2: }aaaaaaaaaaaaaaaaaaaa | - {2:+}{1: 3│}{2: }{1: }{3:+-- 1 line: aaaaaaaaaaaaaaaaa}| - {2: }{1: 4│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| - {2: }aaaaaaaaaaaaaaaaaaaa | - {2: }{1: 5│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| - {2: }aaaaaaaaaaaaaaaaaaaa | + {2: }{5:^aaaa }| + {2: }{1: 1│>>}{0:>!}{1:>>}{0:>!}{1:>>}{0:>!}{1:>>}{0:>!}{1:>> }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }aaaaaaaaaaaaaaaaaaaaa | + {2: }{1: 2│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }aaaaaaaaaaaaaaaaaaaaa | + {2:+}{1: 3│}{2: }{1: }{3:+-- 1 line: aaaaaaaaaaaaaaaa}| + {2: }{1: 4│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }aaaaaaaaaaaaaaaaaaaaa | + {2: }{1: 5│}{2: }{1: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {2: }aaaaaaaaaaaaaaaaaaaaa | | ]]) command('silent undo') @@ -427,7 +465,23 @@ describe('statuscolumn', function() virt_lines_above = true, virt_lines = {{{"virt_line above", ""}}} }) vim.api.nvim_buf_set_extmark(0, ns, 4, 0, { virt_lines = {{{"virt_line", ""}}} }) ]]) - command('set foldcolumn=0 signcolumn=no') + command('set foldcolumn=0 signcolumn=number stc=%l') + screen:expect([[ + {1:>>}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | + {1: 5}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | + {1: }virt_line | + {1: }virt_line above | + {1:>>}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | + {1: 7}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | + {4: 8}{6:^+-- 1 line: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}| + {1: 9}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | + {1:10}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | + {1:11}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | + {1:12}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | + {1:13}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | + {1:14}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | + | + ]]) command( [[set stc=%{v:virtnum<0?'virtual':(!v:virtnum?'buffer':'wrapped')}%=%{'\ '.v:virtnum.'\ '.v:lnum}]] ) @@ -533,8 +587,8 @@ 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: 9 }aaaaa | + {8: 𒀀 8}^aaaaa | + {8: }{7: }{8: 9}aaaaa | | ]]) end) @@ -598,9 +652,6 @@ 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 cmdline click doesn't register as statuscolumn click - api.nvim_input_mouse('right', 'press', '', 0, 13, 0) - eq('', eval('g:testvar')) end) it('clicks and highlights work with control characters', function() @@ -644,26 +695,26 @@ describe('statuscolumn', function() -- clicking an item does not drag mouse api.nvim_input_mouse('left', 'press', '', 0, 0, 0) screen:expect([[ - {0:8 }^aaaaa | + {0: 8}^aaaaa | {1: Echo } | ]]) api.nvim_input_mouse('left', 'press', '', 0, 1, 5) api.nvim_input_mouse('left', 'release', '', 0, 1, 5) screen:expect([[ - {0:8 }^aaaaa | + {0: 8}^aaaaa | 0 1 l 8 | ]]) command('echo') -- clicking outside to close the menu does not drag mouse api.nvim_input_mouse('left', 'press', '', 0, 0, 0) screen:expect([[ - {0:8 }^aaaaa | + {0: 8}^aaaaa | {1: Echo } | ]]) api.nvim_input_mouse('left', 'press', '', 0, 0, 10) api.nvim_input_mouse('left', 'release', '', 0, 0, 10) screen:expect([[ - {0:8 }^aaaaa | + {0: 8}^aaaaa | | ]]) end) @@ -749,9 +800,9 @@ describe('statuscolumn', function() it('works with cmdwin', function() feed(':set stc=%l<CR>q:k$') screen:expect([[ - {8:7 }aaaaa | - {8:8 }aaaaa | - {8:9 }aaaaa | + {8: 7}aaaaa | + {8: 8}aaaaa | + {8: 9}aaaaa | {8:10}aaaaa | {2:[No Name] [+] }| {1::}{8:1}set stc=%^l | @@ -899,16 +950,16 @@ describe('statuscolumn', function() command([[set spell stc=%l\ ]]) command('call setline(8, "This is a line that contains ᶏ multibyte character.")') screen:expect([[ - {8:8 }^This is a line that contains {31:ᶏ}| + {8: 8 }^This is a line that contains {31:ᶏ}| {8: } {31:multibyte} character. | - {8:9 }{31:aaaaa} | + {8: 9 }{31:aaaaa} | | ]]) end) it('line increase properly redraws buffer text with relativenumber #27709', function() screen:try_resize(33, 4) - command([[set rnu nuw=3 stc=%l\ ]]) + command([[set rnu nuw=3 stc=%{v:lnum}\ ]]) command('call setline(1, range(1, 99))') feed('Gyyp') screen:expect([[ diff --git a/test/functional/ui/statusline_spec.lua b/test/functional/ui/statusline_spec.lua index 3087a0cde1..937e709d66 100644 --- a/test/functional/ui/statusline_spec.lua +++ b/test/functional/ui/statusline_spec.lua @@ -63,6 +63,22 @@ for _, model in ipairs(mousemodels) do eq('0 3 r', eval('g:testvar')) api.nvim_input_mouse('right', 'press', '', 0, 6, 28) eq('0 4 r', eval('g:testvar')) + api.nvim_input_mouse('x1', 'press', '', 0, 6, 17) + eq('0 1 x1', eval('g:testvar')) + api.nvim_input_mouse('x1', 'press', '', 0, 6, 17) + eq('0 2 x1', eval('g:testvar')) + api.nvim_input_mouse('x1', 'press', '', 0, 6, 17) + eq('0 3 x1', eval('g:testvar')) + api.nvim_input_mouse('x1', 'press', '', 0, 6, 17) + eq('0 4 x1', eval('g:testvar')) + api.nvim_input_mouse('x2', 'press', '', 0, 6, 28) + eq('0 1 x2', eval('g:testvar')) + api.nvim_input_mouse('x2', 'press', '', 0, 6, 28) + eq('0 2 x2', eval('g:testvar')) + api.nvim_input_mouse('x2', 'press', '', 0, 6, 28) + eq('0 3 x2', eval('g:testvar')) + api.nvim_input_mouse('x2', 'press', '', 0, 6, 28) + eq('0 4 x2', eval('g:testvar')) end) it('works with control characters and highlight', function() diff --git a/test/functional/ui/title_spec.lua b/test/functional/ui/title_spec.lua index e86fdbe5a3..3189232957 100644 --- a/test/functional/ui/title_spec.lua +++ b/test/functional/ui/title_spec.lua @@ -22,7 +22,7 @@ describe('title', function() end) it('has correct default title with unnamed file', function() - local expected = '[No Name] - NVIM' + local expected = '[No Name] - Nvim' command('set title') screen:expect(function() eq(expected, screen.title) @@ -30,7 +30,7 @@ describe('title', function() end) it('has correct default title with named file', function() - local expected = (is_os('win') and 'myfile (C:\\mydir) - NVIM' or 'myfile (/mydir) - NVIM') + local expected = (is_os('win') and 'myfile (C:\\mydir) - Nvim' or 'myfile (/mydir) - Nvim') command('set title') command(is_os('win') and 'file C:\\mydir\\myfile' or 'file /mydir/myfile') screen:expect(function() @@ -41,7 +41,7 @@ describe('title', function() 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' - local expected = (is_os('win') and 'myfile1 (C:\\mydir) - NVIM' or 'myfile1 (/mydir) - NVIM') + local expected = (is_os('win') and 'myfile1 (C:\\mydir) - Nvim' or 'myfile1 (/mydir) - Nvim') local buf2 before_each(function() @@ -82,11 +82,11 @@ describe('title', function() end) end) - it('a Lua callback calling nvim_buf_call in a hidden buffer', function() + it('a Lua callback calling vim._with in a hidden buffer', function() exec_lua(string.format( [[ vim.schedule(function() - vim.api.nvim_buf_call(%d, function() end) + vim._with({buf = %d}, function() end) end) ]], buf2 diff --git a/test/functional/ui/wildmode_spec.lua b/test/functional/ui/wildmode_spec.lua index 0feec6bd03..4d01b7a779 100644 --- a/test/functional/ui/wildmode_spec.lua +++ b/test/functional/ui/wildmode_spec.lua @@ -16,30 +16,24 @@ describe("'wildmenu'", function() before_each(function() clear() screen = Screen.new(25, 5) - screen:set_default_attr_ids { - [1] = { foreground = Screen.colors.Blue, bold = true }, - [2] = { reverse = true }, - [3] = { bold = true, reverse = true }, - [5] = { bold = true }, - [31] = { foreground = Screen.colors.Grey0, background = Screen.colors.Yellow }, + screen:add_extra_attr_ids { + [100] = { background = Screen.colors.Yellow1, foreground = Screen.colors.Black }, } screen:attach() end) -- oldtest: Test_wildmenu_screendump() it('works', function() - screen:set_default_attr_ids({ - [0] = { bold = true, foreground = Screen.colors.Blue }, -- NonText - [1] = { foreground = Screen.colors.Black, background = Screen.colors.Yellow }, -- WildMenu - [2] = { bold = true, reverse = true }, -- StatusLine - }) + screen:add_extra_attr_ids { + [100] = { background = Screen.colors.Yellow1, foreground = Screen.colors.Black }, + } -- Test simple wildmenu feed(':sign <Tab>') screen:expect { grid = [[ | - {0:~ }|*2 - {1:define}{2: jump list > }| + {1:~ }|*2 + {100:define}{3: jump list > }| :sign define^ | ]], } @@ -48,8 +42,8 @@ describe("'wildmenu'", function() screen:expect { grid = [[ | - {0:~ }|*2 - {2:define }{1:jump}{2: list > }| + {1:~ }|*2 + {3:define }{100:jump}{3: list > }| :sign jump^ | ]], } @@ -58,8 +52,8 @@ describe("'wildmenu'", function() screen:expect { grid = [[ | - {0:~ }|*2 - {2:define jump }{1:list}{2: > }| + {1:~ }|*2 + {3:define jump }{100:list}{3: > }| :sign list^ | ]], } @@ -69,8 +63,8 @@ describe("'wildmenu'", function() screen:expect { grid = [[ | - {0:~ }|*2 - {2:define jump list > }| + {1:~ }|*2 + {3:define jump list > }| :sign ^ | ]], } @@ -80,7 +74,7 @@ describe("'wildmenu'", function() screen:expect { grid = [[ | - {0:~ }|*3 + {1:~ }|*3 :sign ^ | ]], } @@ -92,8 +86,8 @@ describe("'wildmenu'", function() screen:expect { grid = [[ | - {0:~ }|*2 - {1:define}{2: jump list > }| + {1:~ }|*2 + {100:define}{3: jump list > }| :sign define^ | ]], } @@ -104,7 +98,7 @@ describe("'wildmenu'", function() screen:expect { grid = [[ ^ | - {0:~ }|*3 + {1:~ }|*3 | ]], } @@ -115,7 +109,7 @@ describe("'wildmenu'", function() screen:expect([[ | {1:~ }|*2 - {31:define}{3: jump list > }| + {100:define}{3: jump list > }| :sign define^ | ]]) feed('<C-E>') @@ -131,7 +125,7 @@ describe("'wildmenu'", function() screen:expect([[ | {1:~ }|*2 - {31:define}{3: jump list > }| + {100:define}{3: jump list > }| :sign define^ | ]]) feed('<tab><C-Y>') @@ -148,7 +142,7 @@ describe("'wildmenu'", function() screen:expect([[ | {1:~ }|*2 - {31:define}{3: jump list > }| + {100:define}{3: jump list > }| :sign define^ | ]]) end) @@ -162,7 +156,7 @@ describe("'wildmenu'", function() screen:expect([[ | {1:~ }|*2 - {31:define}{3: jump list > }| + {100:define}{3: jump list > }| :sign define^ | ]]) feed('<space>') @@ -188,7 +182,7 @@ describe("'wildmenu'", function() screen:expect([[ | {1:~ }|*2 - {31:!}{3: # & < = > @ > }| + {100:!}{3: # & < = > @ > }| :!^ | ]]) end) @@ -199,9 +193,17 @@ describe("'wildmenu'", function() feed((':terminal "%s" REP 5000 !terminal_output!<cr>'):format(testprg('shell-test'))) feed('G') -- Follow :terminal output. feed([[:sign <Tab>]]) -- Invoke wildmenu. + screen:add_extra_attr_ids { + [100] = { foreground = Screen.colors.Black, background = Screen.colors.Yellow }, + [101] = { + bold = true, + foreground = Screen.colors.White, + background = Screen.colors.DarkGreen, + }, + } -- NB: in earlier versions terminal output was redrawn during cmdline mode. -- For now just assert that the screen remains unchanged. - screen:expect { any = '{31:define}{3: jump list > }|\n:sign define^ |' } + screen:expect { any = '{100:define}{101: jump list > }|\n:sign define^ |' } screen:expect_unchanged() -- cmdline CTRL-D display should also be preserved. @@ -232,7 +234,7 @@ describe("'wildmenu'", function() grid = [[ | {1:~ }|*2 - {31:define}{3: jump list > }| + {100:define}{3: jump list > }| :sign define^ | ]], } @@ -259,9 +261,17 @@ describe("'wildmenu'", function() feed([[<C-\><C-N>]]) feed([[:<Tab>]]) -- Invoke wildmenu. + screen:add_extra_attr_ids { + [100] = { foreground = Screen.colors.Black, background = Screen.colors.Yellow }, + [101] = { + bold = true, + foreground = Screen.colors.White, + background = Screen.colors.DarkGreen, + }, + } -- Check only the last 2 lines, because the shell output is -- system-dependent. - screen:expect { any = '{31:!}{3: # & < = > @ > }|\n:!^' } + screen:expect { any = '{100:!}{101: # & < = > @ > }|\n:!^' } -- Because this test verifies a _lack_ of activity, we must wait the full timeout. -- So make it reasonable. screen:expect_unchanged(false, 1000) @@ -290,7 +300,7 @@ describe("'wildmenu'", function() {3: }| :set wildm | wildmenu wildmode | - {31:wildmenu}{3: wildmode }| + {100:wildmenu}{3: wildmode }| :set wildmenu^ | ]]) feed('<Esc>') @@ -416,10 +426,8 @@ describe("'wildmenu'", function() end) it('works with c_CTRL_Z standard mapping', function() - screen:set_default_attr_ids { - [1] = { bold = true, foreground = Screen.colors.Blue1 }, - [2] = { foreground = Screen.colors.Grey0, background = Screen.colors.Yellow }, - [3] = { bold = true, reverse = true }, + screen:add_extra_attr_ids { + [100] = { background = Screen.colors.Yellow1, foreground = Screen.colors.Black }, } -- Wildcharm? where we are going we aint't no need no wildcharm. @@ -436,7 +444,7 @@ describe("'wildmenu'", function() grid = [[ | {1:~ }|*2 - {2:case}{3: clear cluster > }| + {100:case}{3: clear cluster > }| :syntax case^ | ]], } @@ -481,11 +489,9 @@ describe('command line completion', function() before_each(function() clear() screen = Screen.new(40, 5) - screen:set_default_attr_ids({ - [1] = { bold = true, foreground = Screen.colors.Blue1 }, - [2] = { foreground = Screen.colors.Grey0, background = Screen.colors.Yellow }, - [3] = { bold = true, reverse = true }, - }) + screen:add_extra_attr_ids { + [100] = { background = Screen.colors.Yellow1, foreground = Screen.colors.Black }, + } screen:attach() end) after_each(function() @@ -513,7 +519,7 @@ describe('command line completion', function() screen:expect([[ | {1:~ }|*2 - {2:XTEST_1}{3: XTEST_2 }| + {100:XTEST_1}{3: XTEST_2 }| :!echo $XTEST_1^ | ]]) end) @@ -529,7 +535,7 @@ describe('command line completion', function() screen:expect([[ | {1:~ }|*2 - {2:XTEST_1AaあB}{3: XTEST_2 }| + {100:XTEST_1AaあB}{3: XTEST_2 }| :!echo $XTEST_1AaあB^ | ]]) end) diff --git a/test/functional/vimscript/api_functions_spec.lua b/test/functional/vimscript/api_functions_spec.lua index b2865d2b4c..30d6c969ca 100644 --- a/test/functional/vimscript/api_functions_spec.lua +++ b/test/functional/vimscript/api_functions_spec.lua @@ -115,7 +115,7 @@ describe('eval-API', function() exec_lua, [[ local cmdwin_buf = vim.api.nvim_get_current_buf() - vim.api.nvim_buf_call(vim.api.nvim_create_buf(false, true), function() + vim._with({buf = vim.api.nvim_create_buf(false, true)}, function() vim.api.nvim_open_term(cmdwin_buf, {}) end) ]] diff --git a/test/functional/vimscript/changedtick_spec.lua b/test/functional/vimscript/changedtick_spec.lua index baea53a700..ef9d6b1a69 100644 --- a/test/functional/vimscript/changedtick_spec.lua +++ b/test/functional/vimscript/changedtick_spec.lua @@ -38,7 +38,7 @@ describe('b:changedtick', function() -- Somehow undo counts as two changes eq(5, changedtick()) end) - it('is present in b: dictionary', function() + it('is present in b: dict', function() eq(2, changedtick()) command('let d = b:') eq(2, api.nvim_get_var('d').changedtick) @@ -168,7 +168,7 @@ describe('b:changedtick', function() ) eq(2, changedtick()) end) - it('does not inherit VAR_FIXED when copying dictionary over', function() + it('does not inherit VAR_FIXED when copying dict over', function() eq(2, changedtick()) eq('', exec_capture('let d1 = copy(b:)|let d1.changedtick = 42')) eq('', exec_capture('let d2 = copy(b:)|unlet d2.changedtick')) diff --git a/test/functional/vimscript/ctx_functions_spec.lua b/test/functional/vimscript/ctx_functions_spec.lua index 5e9a803b5d..873e4f820d 100644 --- a/test/functional/vimscript/ctx_functions_spec.lua +++ b/test/functional/vimscript/ctx_functions_spec.lua @@ -295,7 +295,7 @@ describe('context functions', function() eq(outofbounds, pcall_err(call, 'ctxget', 0)) end) - it('returns context dictionary at index in context stack', function() + it('returns context dict at index in context stack', function() feed('i1<cr>2<cr>3<c-[>ddddddqahjklq') command('edit! ' .. fname1) feed('G') @@ -404,7 +404,7 @@ describe('context functions', function() eq(outofbounds, pcall_err(call, 'ctxset', { dummy = 1 }, 0)) end) - it('errors when context dictionary is invalid', function() + it('errors when context dict is invalid', function() call('ctxpush') eq( 'Vim:E474: Failed to convert list to msgpack string buffer', @@ -412,7 +412,7 @@ describe('context functions', function() ) end) - it('sets context dictionary at index in context stack', function() + it('sets context dict at index in context stack', function() api.nvim_set_var('one', 1) api.nvim_set_var('Two', 2) api.nvim_set_var('THREE', 3) diff --git a/test/functional/vimscript/fnamemodify_spec.lua b/test/functional/vimscript/fnamemodify_spec.lua index 51b1e8489a..f2cee9b83e 100644 --- a/test/functional/vimscript/fnamemodify_spec.lua +++ b/test/functional/vimscript/fnamemodify_spec.lua @@ -7,11 +7,10 @@ local fnamemodify = n.fn.fnamemodify local getcwd = n.fn.getcwd local command = n.command local write_file = t.write_file -local alter_slashes = n.alter_slashes local is_os = t.is_os local function eq_slashconvert(expected, got) - eq(alter_slashes(expected), alter_slashes(got)) + eq(t.fix_slashes(expected), t.fix_slashes(got)) end describe('fnamemodify()', function() diff --git a/test/functional/vimscript/input_spec.lua b/test/functional/vimscript/input_spec.lua index 552ae6d5cc..0b774404eb 100644 --- a/test/functional/vimscript/input_spec.lua +++ b/test/functional/vimscript/input_spec.lua @@ -108,7 +108,7 @@ describe('input()', function() {T:1}^ | ]]) end) - it('allows unequal numeric values when using {opts} dictionary', function() + it('allows unequal numeric values when using {opts} dict', function() command('echohl Test') api.nvim_set_var('opts', { prompt = 1, default = 2, cancelreturn = 3 }) feed([[:echo input(opts)<CR>]]) @@ -164,7 +164,7 @@ describe('input()', function() reset = true, } end) - it('allows omitting everything with dictionary argument', function() + it('allows omitting everything with dict argument', function() command('echohl Test') feed([[:call input({})<CR>]]) screen:expect([[ @@ -290,7 +290,7 @@ describe('inputdialog()', function() {T:1}^ | ]]) end) - it('allows unequal numeric values when using {opts} dictionary', function() + it('allows unequal numeric values when using {opts} dict', function() command('echohl Test') api.nvim_set_var('opts', { prompt = 1, default = 2, cancelreturn = 3 }) feed([[:echo input(opts)<CR>]]) @@ -346,7 +346,7 @@ describe('inputdialog()', function() reset = true, } end) - it('allows omitting everything with dictionary argument', function() + it('allows omitting everything with dict argument', function() command('echohl Test') feed(':echo inputdialog({})<CR>') screen:expect([[ diff --git a/test/functional/vimscript/json_functions_spec.lua b/test/functional/vimscript/json_functions_spec.lua index ae56e8873d..895e722e96 100644 --- a/test/functional/vimscript/json_functions_spec.lua +++ b/test/functional/vimscript/json_functions_spec.lua @@ -502,9 +502,9 @@ describe('json_decode() function', function() end it('parses strings with NUL properly', function() - sp_decode_eq({ _TYPE = 'string', _VAL = { '\n' } }, '"\\u0000"') - sp_decode_eq({ _TYPE = 'string', _VAL = { '\n', '\n' } }, '"\\u0000\\n\\u0000"') - sp_decode_eq({ _TYPE = 'string', _VAL = { '\n«\n' } }, '"\\u0000\\u00AB\\u0000"') + sp_decode_eq('\000', '"\\u0000"') + sp_decode_eq('\000\n\000', '"\\u0000\\n\\u0000"') + sp_decode_eq('\000«\000', '"\\u0000\\u00AB\\u0000"') end) it('parses dictionaries with duplicate keys to special maps', function() @@ -580,14 +580,8 @@ describe('json_decode() function', function() end) it('parses dictionaries with keys with NUL bytes to special maps', function() - sp_decode_eq( - { _TYPE = 'map', _VAL = { { { _TYPE = 'string', _VAL = { 'a\n', 'b' } }, 4 } } }, - '{"a\\u0000\\nb": 4}' - ) - sp_decode_eq( - { _TYPE = 'map', _VAL = { { { _TYPE = 'string', _VAL = { 'a\n', 'b', '' } }, 4 } } }, - '{"a\\u0000\\nb\\n": 4}' - ) + sp_decode_eq({ _TYPE = 'map', _VAL = { { 'a\000\nb', 4 } } }, '{"a\\u0000\\nb": 4}') + sp_decode_eq({ _TYPE = 'map', _VAL = { { 'a\000\nb\n', 4 } } }, '{"a\\u0000\\nb\\n": 4}') sp_decode_eq({ _TYPE = 'map', _VAL = { @@ -595,10 +589,7 @@ describe('json_decode() function', function() { 'a', 1 }, { 'c', 4 }, { 'd', 2 }, - { - { _TYPE = 'string', _VAL = { '\n' } }, - 4, - }, + { '\000', 4 }, }, }, '{"b": 3, "a": 1, "c": 4, "d": 2, "\\u0000": 4}') end) @@ -738,22 +729,11 @@ describe('json_encode() function', function() eq('{"\\u0000": 1}', eval('json_encode(todump)')) end) - it('can dump generic mapping with BIN special key and NUL', function() - command('let todump = {"_TYPE": v:msgpack_types.binary, "_VAL": ["\\n"]}') - command('let todump = {"_TYPE": v:msgpack_types.map, "_VAL": [[todump, 1]]}') - eq('{"\\u0000": 1}', eval('json_encode(todump)')) - end) - it('can dump STR special mapping with NUL and NL', function() command('let todump = {"_TYPE": v:msgpack_types.string, "_VAL": ["\\n", ""]}') eq('"\\u0000\\n"', eval('json_encode(todump)')) end) - it('can dump BIN special mapping with NUL and NL', function() - command('let todump = {"_TYPE": v:msgpack_types.binary, "_VAL": ["\\n", ""]}') - eq('"\\u0000\\n"', eval('json_encode(todump)')) - end) - it('cannot dump special ext mapping', function() command('let todump = {"_TYPE": v:msgpack_types.ext, "_VAL": [5, ["",""]]}') eq('Vim(call):E474: Unable to convert EXT string to JSON', exc_exec('call json_encode(todump)')) @@ -931,7 +911,7 @@ describe('json_encode() function', function() eq('[]', eval('json_encode(v:_null_list)')) end) - it('can dump NULL dictionary', function() + it('can dump NULL dict', function() eq('{}', eval('json_encode(v:_null_dict)')) end) diff --git a/test/functional/vimscript/map_functions_spec.lua b/test/functional/vimscript/map_functions_spec.lua index 44be5b3185..b69b7dbd57 100644 --- a/test/functional/vimscript/map_functions_spec.lua +++ b/test/functional/vimscript/map_functions_spec.lua @@ -36,7 +36,7 @@ describe('maparg()', function() lnum = 0, } - it('returns a dictionary', function() + it('returns a dict', function() command('nnoremap foo bar') eq('bar', fn.maparg('foo')) eq(foo_bar_map_table, fn.maparg('foo', 'n', false, true)) @@ -54,7 +54,7 @@ describe('maparg()', function() eq('', fn.maparg('not a mapping')) end) - it('returns an empty dictionary when no map is present and dict is requested', function() + it('returns an empty dict when no map is present and dict is requested', function() eq({}, fn.maparg('not a mapping', 'n', false, true)) end) diff --git a/test/functional/vimscript/msgpack_functions_spec.lua b/test/functional/vimscript/msgpack_functions_spec.lua index d59dceef31..d2011f9fec 100644 --- a/test/functional/vimscript/msgpack_functions_spec.lua +++ b/test/functional/vimscript/msgpack_functions_spec.lua @@ -371,13 +371,14 @@ describe('msgpack*() functions', function() eq(1, eval('dumped ==# dumped2')) end) - it('can restore and dump STR string with zero byte', function() + it('can restore and dump STR string contents with zero byte', function() command('let dumped = ["\\xA1\\n"]') command('let parsed = msgpackparse(dumped)') command('let dumped2 = msgpackdump(parsed)') - eq({ { _TYPE = {}, _VAL = { '\n' } } }, eval('parsed')) - eq(1, eval('parsed[0]._TYPE is v:msgpack_types.string')) - eq(1, eval('dumped ==# dumped2')) + eq({ '\000' }, eval('parsed')) + eq(eval('v:t_blob'), eval('type(parsed[0])')) + -- type is not preserved: prefer BIN for binary contents + eq(0, eval('dumped ==# dumped2')) end) it('can restore and dump BIN string with NL', function() @@ -428,26 +429,24 @@ describe('msgpackparse() function', function() parse_eq({ true }, { '\195' }) end) - it('restores FIXSTR as special dict', function() - parse_eq({ { _TYPE = {}, _VAL = { 'ab' } } }, { '\162ab' }) - eq(1, eval('g:parsed[0]._TYPE is v:msgpack_types.string')) + it('restores FIXSTR as string', function() + parse_eq({ 'ab' }, { '\162ab' }) end) it('restores BIN 8 as string', function() parse_eq({ 'ab' }, { '\196\002ab' }) end) - it('restores FIXEXT1 as special dictionary', function() + it('restores FIXEXT1 as special dict', function() parse_eq({ { _TYPE = {}, _VAL = { 0x10, { '', '' } } } }, { '\212\016', '' }) eq(1, eval('g:parsed[0]._TYPE is v:msgpack_types.ext')) end) - it('restores MAP with BIN key as special dictionary', function() - parse_eq({ { _TYPE = {}, _VAL = { { 'a', '' } } } }, { '\129\196\001a\196\n' }) - eq(1, eval('g:parsed[0]._TYPE is v:msgpack_types.map')) + it('restores MAP with BIN key as ordinary dict', function() + parse_eq({ { a = '' } }, { '\129\196\001a\196\n' }) end) - it('restores MAP with duplicate STR keys as special dictionary', function() + it('restores MAP with duplicate STR keys as special dict', function() command('let dumped = ["\\x82\\xA1a\\xC4\\n\\xA1a\\xC4\\n"]') -- FIXME Internal error bug, can't use parse_eq() here command('silent! let parsed = msgpackparse(dumped)') @@ -455,17 +454,17 @@ describe('msgpackparse() function', function() { _TYPE = {}, _VAL = { - { { _TYPE = {}, _VAL = { 'a' } }, '' }, - { { _TYPE = {}, _VAL = { 'a' } }, '' }, + { 'a', '' }, + { 'a', '' }, }, }, }, eval('parsed')) eq(1, eval('g:parsed[0]._TYPE is v:msgpack_types.map')) - eq(1, eval('g:parsed[0]._VAL[0][0]._TYPE is v:msgpack_types.string')) - eq(1, eval('g:parsed[0]._VAL[1][0]._TYPE is v:msgpack_types.string')) + eq(eval('v:t_string'), eval('type(g:parsed[0]._VAL[0][0])')) + eq(eval('v:t_string'), eval('type(g:parsed[0]._VAL[1][0])')) end) - it('restores MAP with MAP key as special dictionary', function() + it('restores MAP with MAP key as special dict', function() parse_eq({ { _TYPE = {}, _VAL = { { {}, '' } } } }, { '\129\128\196\n' }) eq(1, eval('g:parsed[0]._TYPE is v:msgpack_types.map')) end) @@ -511,7 +510,7 @@ describe('msgpackparse() function', function() ) end) - it('fails to parse a dictionary', function() + it('fails to parse a dict', function() eq( 'Vim(call):E899: Argument of msgpackparse() must be a List or Blob', exc_exec('call msgpackparse({})') @@ -765,7 +764,7 @@ describe('msgpackdump() function', function() ) end) - it('fails to dump a dictionary', function() + it('fails to dump a dict', function() eq('Vim(call):E686: Argument of msgpackdump() must be a List', exc_exec('call msgpackdump({})')) end) @@ -802,7 +801,7 @@ describe('msgpackdump() function', function() it('can dump NULL string', function() dump_eq({ '\196\n' }, '[$XXX_UNEXISTENT_VAR_XXX]') - dump_eq({ '\196\n' }, '[{"_TYPE": v:msgpack_types.binary, "_VAL": [$XXX_UNEXISTENT_VAR_XXX]}]') + dump_eq({ '\196\n' }, '[v:_null_blob]') dump_eq({ '\160' }, '[{"_TYPE": v:msgpack_types.string, "_VAL": [$XXX_UNEXISTENT_VAR_XXX]}]') end) @@ -814,7 +813,7 @@ describe('msgpackdump() function', function() eq({ '\144' }, eval('msgpackdump([v:_null_list])')) end) - it('can dump NULL dictionary', function() + it('can dump NULL dict', function() eq({ '\128' }, eval('msgpackdump([v:_null_dict])')) end) end) diff --git a/test/functional/vimscript/printf_spec.lua b/test/functional/vimscript/printf_spec.lua index 3c66e07618..1fd5c3c9b6 100644 --- a/test/functional/vimscript/printf_spec.lua +++ b/test/functional/vimscript/printf_spec.lua @@ -84,10 +84,13 @@ describe('printf()', function() end api.nvim_del_var('__result') end + check_printf('v:_null_string', true) check_printf('v:_null_list', true) check_printf('v:_null_dict', true) + check_printf('v:_null_blob', true) check_printf('[]') check_printf('{}') + check_printf('0z') check_printf('function("tr", ["a"])') end) end) diff --git a/test/functional/vimscript/string_spec.lua b/test/functional/vimscript/string_spec.lua index 32aa04c0d0..4df9104e1e 100644 --- a/test/functional/vimscript/string_spec.lua +++ b/test/functional/vimscript/string_spec.lua @@ -170,9 +170,9 @@ describe('string() function', function() ) end) - it('does not show errors when dumping partials referencing the same dictionary', function() + it('does not show errors when dumping partials referencing the same dict', function() command('let d = {}') - -- Regression for “eval/typval_encode: Dump empty dictionary before + -- Regression for “eval/typval_encode: Dump empty dict before -- checking for refcycle”, results in error. eq( "[function('tr', {}), function('tr', {})]", @@ -256,7 +256,7 @@ describe('string() function', function() end) describe('used to represent dictionaries', function() - it('dumps empty dictionary', function() + it('dumps empty dict', function() eq('{}', eval('string({})')) end) @@ -267,7 +267,7 @@ describe('string() function', function() eq("[{}, function('tr', {})]", eval('string([d, function("tr", d)])')) end) - it('dumps non-empty dictionary', function() + it('dumps non-empty dict', function() eq("{'t''est': 1}", fn.string({ ["t'est"] = 1 })) end) |