diff options
Diffstat (limited to 'test')
266 files changed, 21900 insertions, 9470 deletions
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 3ae11bd1a7..8db05ac839 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -19,7 +19,7 @@ if(LUA_HAS_FFI) ${TEST_OPTIONS} -P ${PROJECT_SOURCE_DIR}/cmake/RunTests.cmake USES_TERMINAL) - add_dependencies(unittest lua-dev-deps nvim) + add_dependencies(unittest lua_dev_deps nvim) else() message(WARNING "disabling unit tests: no Luajit FFI in ${LUA_PRG}") endif() @@ -35,7 +35,7 @@ add_custom_target(functionaltest -P ${PROJECT_SOURCE_DIR}/cmake/RunTests.cmake DEPENDS printenv-test printargs-test shell-test pwsh-test streams-test tty-test USES_TERMINAL) -add_dependencies(functionaltest lua-dev-deps nvim) +add_dependencies(functionaltest lua_dev_deps nvim) add_custom_target(benchmark COMMAND ${CMAKE_COMMAND} @@ -44,4 +44,4 @@ add_custom_target(benchmark -P ${PROJECT_SOURCE_DIR}/cmake/RunTests.cmake DEPENDS tty-test USES_TERMINAL) -add_dependencies(benchmark lua-dev-deps nvim) +add_dependencies(benchmark lua_dev_deps nvim) diff --git a/test/README.md b/test/README.md index 45b3322305..d1b053fca3 100644 --- a/test/README.md +++ b/test/README.md @@ -103,7 +103,7 @@ Debugging tests DBG 2022-06-15T18:37:45.227 T57.58016.0/c UI: stop INF 2022-06-15T18:37:45.227 T57.58016.0/c os_exit:595: Nvim exit: 0 DBG 2022-06-15T18:37:45.229 T57.58016.0 read_cb:118: closing Stream (0x7fd5d700ea18): EOF (end of file) - INF 2022-06-15T18:37:45.229 T57.58016.0 on_process_exit:400: exited: pid=58017 status=0 stoptime=0 + INF 2022-06-15T18:37:45.229 T57.58016.0 on_proc_exit:400: exited: pid=58017 status=0 stoptime=0 ``` - You can set `$GDB` to [run functional tests under gdbserver](https://github.com/neovim/neovim/pull/1527): @@ -263,7 +263,7 @@ by the semantic component they are testing. Lint ==== -`make lint` (and `make lualint`) runs [luacheck](https://github.com/mpeterv/luacheck) +`make lint` (and `make lintlua`) runs [luacheck](https://github.com/mpeterv/luacheck) on the test code. If a luacheck warning must be ignored, specify the warning code. Example: diff --git a/test/_meta.lua b/test/_meta.lua new file mode 100644 index 0000000000..b736bcc6d9 --- /dev/null +++ b/test/_meta.lua @@ -0,0 +1,10 @@ +--- @meta + +do -- Mark block as optional + ---Mark a test as placeholder. + --- + ---This will not fail or pass, it will simply be marked as "pending". + ---@param name string + ---@param block? fun() + function pending(name, block) end +end diff --git a/test/client/session.lua b/test/client/session.lua index cf3d8c4f25..f1f46c5efe 100644 --- a/test/client/session.lua +++ b/test/client/session.lua @@ -7,6 +7,7 @@ local MsgpackRpcStream = require('test.client.msgpack_rpc_stream') --- @field private _prepare uv.uv_prepare_t --- @field private _timer uv.uv_timer_t --- @field private _is_running boolean +--- @field exec_lua_setup boolean local Session = {} Session.__index = Session if package.loaded['jit'] then @@ -96,8 +97,7 @@ end --- @param method string --- @param ... any ---- @return boolean ---- @return table +--- @return boolean, table function Session:request(method, ...) local args = { ... } local err, result @@ -114,6 +114,7 @@ function Session:request(method, ...) return true, result end +--- Runs the event loop. function Session:run(request_cb, notification_cb, setup_cb, timeout) local function on_request(method, args, response) coroutine_exec(request_cb, method, args, function(status, result, flag) 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) diff --git a/test/old/testdir/crash/dialog_changed_uaf b/test/old/testdir/crash/dialog_changed_uaf Binary files differnew file mode 100644 index 0000000000..e37d18d6da --- /dev/null +++ b/test/old/testdir/crash/dialog_changed_uaf diff --git a/test/old/testdir/crash/double_free b/test/old/testdir/crash/double_free Binary files differnew file mode 100644 index 0000000000..895c4a04b6 --- /dev/null +++ b/test/old/testdir/crash/double_free diff --git a/test/old/testdir/crash/poc_uaf_check_argument_types b/test/old/testdir/crash/poc_uaf_check_argument_types Binary files differnew file mode 100644 index 0000000000..83a2e7b0a6 --- /dev/null +++ b/test/old/testdir/crash/poc_uaf_check_argument_types diff --git a/test/old/testdir/crash/poc_uaf_exec_instructions b/test/old/testdir/crash/poc_uaf_exec_instructions Binary files differnew file mode 100644 index 0000000000..49ae8577ff --- /dev/null +++ b/test/old/testdir/crash/poc_uaf_exec_instructions diff --git a/test/old/testdir/runtest.vim b/test/old/testdir/runtest.vim index 2d8ba60a7e..e05a78e9ca 100644 --- a/test/old/testdir/runtest.vim +++ b/test/old/testdir/runtest.vim @@ -13,6 +13,9 @@ " For csh: " setenv TEST_FILTER Test_channel " +" If the environment variable $TEST_SKIP_PAT is set then test functions +" matching this pattern will be skipped. It's the opposite of $TEST_FILTER. +" " While working on a test you can make $TEST_NO_RETRY non-empty to not retry: " export TEST_NO_RETRY=yes " @@ -45,8 +48,18 @@ " call add(v:errors, "this happened") +" Without the +eval feature we can't run these tests, bail out. +silent! while 0 + qa! +silent! endwhile + +" In the GUI we can always change the screen size. +if has('gui_running') + set columns=80 lines=25 +endif + " Check that the screen size is at least 24 x 80 characters. -if &lines < 24 || &columns < 80 +if &lines < 24 || &columns < 80 let error = 'Screen size too small! Tests require at least 24 lines with 80 characters, got ' .. &lines .. ' lines with ' .. &columns .. ' characters' echoerr error split test.log @@ -92,7 +105,12 @@ set encoding=utf-8 " REDIR_TEST_TO_NULL has a very permissive SwapExists autocommand which is for " the test_name.vim file itself. Replace it here with a more restrictive one, " so we still catch mistakes. -let s:test_script_fname = expand('%') +if has("win32") + " replace any '/' directory separators by '\\' + let s:test_script_fname = substitute(expand('%'), '/', '\\', 'g') +else + let s:test_script_fname = expand('%') +endif au! SwapExists * call HandleSwapExists() func HandleSwapExists() if exists('g:ignoreSwapExists') @@ -131,7 +149,7 @@ if has('win32') endif if has('mac') - " In MacOS, when starting a shell in a terminal, a bash deprecation warning + " In macOS, when starting a shell in a terminal, a bash deprecation warning " message is displayed. This breaks the terminal test. Disable the warning " message. let $BASH_SILENCE_DEPRECATION_WARNING = 1 @@ -431,13 +449,17 @@ func FinishTesting() if s:done == 0 if s:filtered > 0 - let message = "NO tests match $TEST_FILTER: '" .. $TEST_FILTER .. "'" + if $TEST_FILTER != '' + let message = "NO tests match $TEST_FILTER: '" .. $TEST_FILTER .. "'" + else + let message = "ALL tests match $TEST_SKIP_PAT: '" .. $TEST_SKIP_PAT .. "'" + endif else let message = 'NO tests executed' endif else if s:filtered > 0 - call add(s:messages, "Filtered " .. s:filtered .. " tests with $TEST_FILTER") + call add(s:messages, "Filtered " .. s:filtered .. " tests with $TEST_FILTER and $TEST_SKIP_PAT") endif let message = 'Executed ' . s:done . (s:done > 1 ? ' tests' : ' test') endif @@ -530,6 +552,12 @@ endif " Execute the tests in alphabetical order. for g:testfunc in sort(s:tests) + if $TEST_SKIP_PAT != '' && g:testfunc =~ $TEST_SKIP_PAT + call add(s:messages, g:testfunc .. ' matches $TEST_SKIP_PAT') + let s:filtered += 1 + continue + endif + " Silence, please! set belloff=all let prev_error = '' diff --git a/test/old/testdir/samples/test.zip b/test/old/testdir/samples/test.zip Binary files differnew file mode 100644 index 0000000000..6d34ac6992 --- /dev/null +++ b/test/old/testdir/samples/test.zip diff --git a/test/old/testdir/samples/testa.zip b/test/old/testdir/samples/testa.zip Binary files differnew file mode 100644 index 0000000000..10b0346e76 --- /dev/null +++ b/test/old/testdir/samples/testa.zip diff --git a/test/old/testdir/setup.vim b/test/old/testdir/setup.vim index 7313a0a162..e7b4bb1a88 100644 --- a/test/old/testdir/setup.vim +++ b/test/old/testdir/setup.vim @@ -1,7 +1,7 @@ if exists('s:did_load') " Align Nvim defaults to Vim. set backspace= - set commentstring=/*%s*/ + set commentstring=/*\ %s\ */ set complete=.,w,b,u,t,i set define=^\\s*#\\s*define set directory^=. @@ -13,6 +13,7 @@ if exists('s:did_load') set laststatus=1 set listchars=eol:$ set joinspaces + set jumpoptions= set mousemodel=extend set nohidden nosmarttab noautoindent noautoread noruler noshowcmd set nohlsearch noincsearch @@ -31,8 +32,8 @@ if exists('s:did_load') endif if g:testname !~ 'test_mapping.vim$' " Make "Q" switch to Ex mode. - " This does not work for all tests. - nnoremap Q gQ + " This does not work for all tests as Nvim only supports Vim Ex mode. + nnoremap Q gQ<Cmd>call<SID>ExStart()<CR> endif endif @@ -44,12 +45,28 @@ if exists('s:did_load') endif let s:did_load = 1 +func s:ExStart() + call feedkeys($"\<Cmd>call{expand('<SID>')}ExMayEnd()\<CR>") +endfunc + +func s:ExMayEnd() + " When :normal runs out of characters in Vim, the behavior is different in + " normal Ex mode vs. Vim Ex mode. + " - In normal Ex mode, "\n" is used. + " - In Vim Ex mode, Ctrl-C is used. + " Nvim only supports Vim Ex mode, so emulate the normal Ex mode behavior. + if state('m') == '' && mode(1) == 'cv' && getcharstr(1) == "\<C-C>" + call feedkeys("\n") + endif +endfunc + " Clear Nvim default user commands, mappings and menus. comclear mapclear mapclear! aunmenu * tlunmenu * +autocmd! nvim_popupmenu " Undo the 'grepprg' and 'grepformat' setting in _defaults.lua. set grepprg& grepformat& diff --git a/test/old/testdir/summarize.vim b/test/old/testdir/summarize.vim index da5856a2e7..d0d4e00b2c 100644 --- a/test/old/testdir/summarize.vim +++ b/test/old/testdir/summarize.vim @@ -33,7 +33,7 @@ if 1 silent %s/^SKIPPED \zs.*/\=Count(submatch(0), 'skipped')/egn silent %s/^\(\d\+\)\s\+FAILED:/\=Count(submatch(1), 'failed')/egn - call extend(output, ["Skipped:"]) + call extend(output, ["Skipped:"]) call extend(output, skipped_output) call extend(output, [ diff --git a/test/old/testdir/test_arglist.vim b/test/old/testdir/test_arglist.vim index ebda332562..952b121aed 100644 --- a/test/old/testdir/test_arglist.vim +++ b/test/old/testdir/test_arglist.vim @@ -360,6 +360,7 @@ func Test_argv() call assert_equal('', argv(1, 100)) call assert_equal([], argv(-1, 100)) call assert_equal('', argv(10, -1)) + %argdelete endfunc " Test for the :argedit command @@ -744,4 +745,26 @@ func Test_all_command() %bw! endfunc +" Test for deleting buffer when creating an arglist. This was accessing freed +" memory +func Test_crash_arglist_uaf() + "%argdelete + new one + au BufAdd XUAFlocal :bw + "call assert_fails(':arglocal XUAFlocal', 'E163:') + arglocal XUAFlocal + au! BufAdd + bw! XUAFlocal + + au BufAdd XUAFlocal2 :bw + new two + new three + arglocal + argadd XUAFlocal2 Xfoobar + bw! XUAFlocal2 + bw! two + + au! BufAdd +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/test_assert.vim b/test/old/testdir/test_assert.vim index fe093d3582..fa63af687d 100644 --- a/test/old/testdir/test_assert.vim +++ b/test/old/testdir/test_assert.vim @@ -48,10 +48,19 @@ func Test_assert_equal() call assert_match("Expected 'bar' but got 'foo'", v:errors[0]) call remove(v:errors, 0) + let s = 'αβγ' + call assert_equal(1, assert_equal('δεζ', s)) + call assert_match("Expected 'δεζ' but got 'αβγ'", v:errors[0]) + call remove(v:errors, 0) + call assert_equal('XxxxxxxxxxxxxxxxxxxxxxX', 'XyyyyyyyyyyyyyyyyyyyyyyyyyX') call assert_match("Expected 'X\\\\\\[x occurs 21 times]X' but got 'X\\\\\\[y occurs 25 times]X'", v:errors[0]) call remove(v:errors, 0) + call assert_equal('ΩωωωωωωωωωωωωωωωωωωωωωΩ', 'ΩψψψψψψψψψψψψψψψψψψψψψψψψψΩ') + call assert_match("Expected 'Ω\\\\\\[ω occurs 21 times]Ω' but got 'Ω\\\\\\[ψ occurs 25 times]Ω'", v:errors[0]) + call remove(v:errors, 0) + " special characters are escaped call assert_equal("\b\e\f\n\t\r\\\x01\x7f", 'x') call assert_match('Expected ''\\b\\e\\f\\n\\t\\r\\\\\\x01\\x7f'' but got ''x''', v:errors[0]) @@ -189,6 +198,22 @@ func Test_wrong_error_type() call assert_equal(type([]), type(verrors)) endfunc +func Test_compare_fail() + let s:v = {} + let s:x = {"a": s:v} + let s:v["b"] = s:x + let s:w = {"c": s:x, "d": ''} + try + call assert_equal(s:w, '') + catch + call assert_equal(0, assert_exception('E724:')) + " Nvim: expected value isn't shown as NULL + " call assert_match("Expected NULL but got ''", v:errors[0]) + call assert_match("Expected .* but got ''", v:errors[0]) + call remove(v:errors, 0) + endtry +endfunc + func Test_match() call assert_equal(0, assert_match('^f.*b.*r$', 'foobar')) diff --git a/test/old/testdir/test_autocmd.vim b/test/old/testdir/test_autocmd.vim index cdcd68f3d6..9d06ebb2be 100644 --- a/test/old/testdir/test_autocmd.vim +++ b/test/old/testdir/test_autocmd.vim @@ -15,6 +15,13 @@ func s:cleanup_buffers() abort endfor endfunc +func CleanUpTestAuGroup() + augroup testing + au! + augroup END + augroup! testing +endfunc + func Test_vim_did_enter() call assert_false(v:vim_did_enter) @@ -273,8 +280,8 @@ func Test_win_tab_autocmd() augroup testing au WinNew * call add(g:record, 'WinNew') au WinClosed * call add(g:record, 'WinClosed') - au WinEnter * call add(g:record, 'WinEnter') - au WinLeave * call add(g:record, 'WinLeave') + au WinEnter * call add(g:record, 'WinEnter') + au WinLeave * call add(g:record, 'WinLeave') au TabNew * call add(g:record, 'TabNew') au TabClosed * call add(g:record, 'TabClosed') au TabEnter * call add(g:record, 'TabEnter') @@ -2010,6 +2017,38 @@ func Test_Cmdline() au! CmdlineEnter au! CmdlineLeave let &shellslash = save_shellslash + + au! CursorMovedC : let g:pos += [getcmdpos()] + let g:pos = [] + call feedkeys(":foo bar baz\<C-W>\<C-W>\<C-W>\<Esc>", 'xt') + call assert_equal([2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 9, 5, 1], g:pos) + let g:pos = [] + call feedkeys(":hello\<C-B>\<Esc>", 'xt') + call assert_equal([2, 3, 4, 5, 6, 1], g:pos) + let g:pos = [] + call feedkeys(":hello\<C-U>\<Esc>", 'xt') + call assert_equal([2, 3, 4, 5, 6, 1], g:pos) + let g:pos = [] + call feedkeys(":hello\<Left>\<C-R>=''\<CR>\<Left>\<Right>\<Esc>", 'xt') + call assert_equal([2, 3, 4, 5, 6, 5, 4, 5], g:pos) + let g:pos = [] + call feedkeys(":12345678\<C-R>=setcmdpos(3)??''\<CR>\<Esc>", 'xt') + call assert_equal([2, 3, 4, 5, 6, 7, 8, 9, 3], g:pos) + let g:pos = [] + call feedkeys(":12345678\<C-R>=setcmdpos(3)??''\<CR>\<Left>\<Esc>", 'xt') + call assert_equal([2, 3, 4, 5, 6, 7, 8, 9, 3, 2], g:pos) + au! CursorMovedC + + " setcmdpos() is no-op inside an autocommand + au! CursorMovedC : let g:pos += [getcmdpos()] | call setcmdpos(1) + let g:pos = [] + call feedkeys(":hello\<Left>\<Left>\<Esc>", 'xt') + call assert_equal([2, 3, 4, 5, 6, 5, 4], g:pos) + au! CursorMovedC + + unlet g:entered + unlet g:left + unlet g:pos endfunc " Test for BufWritePre autocommand that deletes or unloads the buffer. @@ -3738,7 +3777,7 @@ endfunc func Test_autocmd_split_dummy() " Autocommand trying to split a window containing a dummy buffer. - auto BufReadPre * exe "sbuf " .. expand("<abuf>") + auto BufReadPre * exe "sbuf " .. expand("<abuf>") " Avoid the "W11" prompt au FileChangedShell * let v:fcs_choice = 'reload' func Xautocmd_changelist() @@ -4120,4 +4159,23 @@ func Test_BufEnter_botline() set hidden&vim endfunc +" This was using freed memory +func Test_autocmd_BufWinLeave_with_vsp() + new + let fname = 'XXXBufWinLeaveUAF.txt' + let dummy = 'XXXDummy.txt' + call writefile([], fname) + call writefile([], dummy) + defer delete(fname) + defer delete(dummy) + exe "e " fname + vsp + augroup testing + exe "au BufWinLeave " .. fname .. " :e " dummy .. "| vsp " .. fname + augroup END + bw + call CleanUpTestAuGroup() + exe "bw! " .. dummy +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/test_autoload.vim b/test/old/testdir/test_autoload.vim index e89fe3943b..156387a2d2 100644 --- a/test/old/testdir/test_autoload.vim +++ b/test/old/testdir/test_autoload.vim @@ -21,5 +21,4 @@ func Test_source_autoload() call assert_equal(1, g:loaded_sourced_vim) endfunc - " vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/test_blob.vim b/test/old/testdir/test_blob.vim index 25b3c00dfc..fbc080059e 100644 --- a/test/old/testdir/test_blob.vim +++ b/test/old/testdir/test_blob.vim @@ -75,6 +75,13 @@ func Test_blob_assign() VAR l = [0z12] VAR m = deepcopy(l) LET m[0] = 0z34 #" E742 or E741 should not occur. + + VAR blob1 = 0z10 + LET blob1 += v:_null_blob + call assert_equal(0z10, blob1) + LET blob1 = v:_null_blob + LET blob1 += 0z20 + call assert_equal(0z20, blob1) END call CheckLegacyAndVim9Success(lines) @@ -332,6 +339,17 @@ func Test_blob_for_loop() call assert_equal(5, i) END call CheckLegacyAndVim9Success(lines) + + " Test for skipping the loop var assignment in a for loop + let lines =<< trim END + VAR blob = 0z998877 + VAR c = 0 + for _ in blob + LET c += 1 + endfor + call assert_equal(3, c) + END + call CheckLegacyAndVim9Success(lines) endfunc func Test_blob_concatenate() @@ -850,7 +868,8 @@ func Test_indexof() call assert_equal(-1, indexof(v:_null_blob, "v:val == 0xde")) call assert_equal(-1, indexof(b, v:_null_string)) " Nvim doesn't have null functions - " call assert_equal(-1, indexof(b, test_null_function())) + " call assert_equal(-1, indexof(b, test_null_function())) + call assert_equal(-1, indexof(b, "")) let b = 0z01020102 call assert_equal(1, indexof(b, "v:val == 0x02", #{startidx: 0})) @@ -862,6 +881,7 @@ func Test_indexof() " failure cases call assert_fails('let i = indexof(b, "val == 0xde")', 'E121:') call assert_fails('let i = indexof(b, {})', 'E1256:') + call assert_fails('let i = indexof(b, " ")', 'E15:') endfunc " vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/test_blockedit.vim b/test/old/testdir/test_blockedit.vim index e0cfe11af0..c36ff74f29 100644 --- a/test/old/testdir/test_blockedit.vim +++ b/test/old/testdir/test_blockedit.vim @@ -53,7 +53,7 @@ func Test_blockinsert_autoindent() let expected =<< trim END vim9script var d = { - a: (): asdf => 0, + a: (): asdf => 0, b: (): asdf => 0, c: (): asdf => 0, } diff --git a/test/old/testdir/test_breakindent.vim b/test/old/testdir/test_breakindent.vim index 636ec6f285..3787747104 100644 --- a/test/old/testdir/test_breakindent.vim +++ b/test/old/testdir/test_breakindent.vim @@ -837,18 +837,73 @@ func Test_breakindent20_list() \ ] let lines = s:screen_lines2(1, 9, 20) call s:compare_lines(expect, lines) + + " check with TABs + call setline(1, ["\t1.\tCongress shall make no law", + \ "\t2.) Congress shall make no law", + \ "\t3.] Congress shall make no law"]) + setl tabstop=4 list listchars=tab:<-> + norm! 1gg + redraw! + let expect = [ + \ "<-->1.<>Congress ", + \ " shall make ", + \ " no law ", + \ "<-->2.) Congress ", + \ " shall make ", + \ " no law ", + \ "<-->3.] Congress ", + \ " shall make ", + \ " no law ", + \ ] + let lines = s:screen_lines2(1, 9, 20) + call s:compare_lines(expect, lines) + + setl tabstop=2 nolist + redraw! + let expect = [ + \ " 1. Congress ", + \ " shall make no ", + \ " law ", + \ " 2.) Congress ", + \ " shall make no ", + \ " law ", + \ " 3.] Congress ", + \ " shall make no ", + \ " law ", + \ ] + let lines = s:screen_lines2(1, 9, 20) + call s:compare_lines(expect, lines) + + setl tabstop& list listchars=space:_ + redraw! + let expect = [ + \ "^I1.^ICongress_ ", + \ " shall_make_no_", + \ " law ", + \ "^I2.)_Congress_ ", + \ " shall_make_no_", + \ " law ", + \ "^I3.]_Congress_ ", + \ " shall_make_no_", + \ " law ", + \ ] + let lines = s:screen_lines2(1, 9, 20) + call s:compare_lines(expect, lines) + " check formatlistpat indent with different list levels - let &l:flp = '^\s*\*\+\s\+' + let &l:flp = '^\s*\(\*\|•\)\+\s\+' + setl list&vim listchars&vim %delete _ call setline(1, ['* Congress shall make no law', - \ '*** Congress shall make no law', + \ '••• Congress shall make no law', \ '**** Congress shall make no law']) norm! 1gg redraw! let expect = [ \ "* Congress shall ", \ " make no law ", - \ "*** Congress shall ", + \ "••• Congress shall ", \ " make no law ", \ "**** Congress shall ", \ " make no law ", @@ -864,7 +919,7 @@ func Test_breakindent20_list() let expect = [ \ "* Congress shall ", \ "> make no law ", - \ "*** Congress shall ", + \ "••• Congress shall ", \ "> make no law ", \ "**** Congress shall ", \ "> make no law ", @@ -880,7 +935,7 @@ func Test_breakindent20_list() let expect = [ \ "* Congress shall ", \ "> make no law ", - \ "*** Congress shall ", + \ "••• Congress shall ", \ "> make no law ", \ "**** Congress shall ", \ "> make no law ", @@ -1205,4 +1260,15 @@ func Test_breakindent_min_with_signcol() call s:close_windows() endfunc +func Test_breakindent_with_double_width_wrap() + 50vnew + setlocal tabstop=8 breakindent nolist + call setline(1, "\t" .. repeat('a', winwidth(0) - 9) .. '口口口') + normal! $g0 + call assert_equal(2, winline()) + call assert_equal(9, wincol()) + + bwipe! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/test_buffer.vim b/test/old/testdir/test_buffer.vim index bc8e5eaf7b..69d22de2ce 100644 --- a/test/old/testdir/test_buffer.vim +++ b/test/old/testdir/test_buffer.vim @@ -126,6 +126,52 @@ func Test_buflist_browse() %bwipe! endfunc +" Test for :bnext and :bprev when called from help and non-help buffers. +func Test_bnext_bprev_help() + %bwipe! + + e XHelp1 | set bt=help + let b1 = bufnr() + e Xbuf1 + let b2 = bufnr() + + " There's only one buffer of each type. + b XHelp1 + bnext | call assert_equal(b1, bufnr()) + bprev | call assert_equal(b1, bufnr()) + b Xbuf1 + bnext | call assert_equal(b2, bufnr()) + bprev | call assert_equal(b2, bufnr()) + + " Add one more buffer of each type. + e XHelp2 | set bt=help + let b3 = bufnr() + e Xbuf2 + let b4 = bufnr() + + " Help buffer jumps to help buffer. + b XHelp1 + bnext | call assert_equal(b3, bufnr()) + bnext | call assert_equal(b1, bufnr()) + bprev | call assert_equal(b3, bufnr()) + bprev | call assert_equal(b1, bufnr()) + + " Regular buffer jumps to regular buffer. + b Xbuf1 + bnext | call assert_equal(b4, bufnr()) + bnext | call assert_equal(b2, bufnr()) + bprev | call assert_equal(b4, bufnr()) + bprev | call assert_equal(b2, bufnr()) + + " :brewind and :blast are not affected by the buffer type. + b Xbuf2 + brewind | call assert_equal(b1, bufnr()) + b XHelp1 + blast | call assert_equal(b4, bufnr()) + + %bwipe! +endfunc + " Test for :bdelete func Test_bdelete_cmd() %bwipe! diff --git a/test/old/testdir/test_cd.vim b/test/old/testdir/test_cd.vim index cffba99451..dd92fc6c38 100644 --- a/test/old/testdir/test_cd.vim +++ b/test/old/testdir/test_cd.vim @@ -58,22 +58,21 @@ func Test_cd_minus() call writefile(v:errors, 'Xresult') qall! [SCRIPT] - call writefile(lines, 'Xscript') + call writefile(lines, 'Xscript', 'D') if RunVim([], [], '--clean -S Xscript') call assert_equal([], readfile('Xresult')) endif - call delete('Xscript') call delete('Xresult') endfunc " Test for chdir() func Test_chdir_func() let topdir = getcwd() - call mkdir('Xdir/y/z', 'p') + call mkdir('Xchdir/y/z', 'pR') " Create a few tabpages and windows with different directories new - cd Xdir + cd Xchdir tabnew tcd y below new @@ -81,22 +80,22 @@ func Test_chdir_func() lcd z tabfirst - call assert_match('^\[global\] .*/Xdir$', trim(execute('verbose pwd'))) + call assert_match('^\[global\] .*/Xchdir$', trim(execute('verbose pwd'))) call chdir('..') call assert_equal('y', fnamemodify(getcwd(1, 2), ':t')) call assert_equal('z', fnamemodify(3->getcwd(2), ':t')) tabnext | wincmd t call assert_match('^\[tabpage\] .*/y$', trim(execute('verbose pwd'))) eval '..'->chdir() - call assert_equal('Xdir', fnamemodify(getcwd(1, 2), ':t')) - call assert_equal('Xdir', fnamemodify(getcwd(2, 2), ':t')) + call assert_equal('Xchdir', fnamemodify(getcwd(1, 2), ':t')) + call assert_equal('Xchdir', fnamemodify(getcwd(2, 2), ':t')) call assert_equal('z', fnamemodify(getcwd(3, 2), ':t')) call assert_equal('testdir', fnamemodify(getcwd(1, 1), ':t')) 3wincmd w call assert_match('^\[window\] .*/z$', trim(execute('verbose pwd'))) call chdir('..') - call assert_equal('Xdir', fnamemodify(getcwd(1, 2), ':t')) - call assert_equal('Xdir', fnamemodify(getcwd(2, 2), ':t')) + call assert_equal('Xchdir', fnamemodify(getcwd(1, 2), ':t')) + call assert_equal('Xchdir', fnamemodify(getcwd(2, 2), ':t')) call assert_equal('y', fnamemodify(getcwd(3, 2), ':t')) call assert_equal('testdir', fnamemodify(getcwd(1, 1), ':t')) @@ -110,20 +109,19 @@ func Test_chdir_func() only | tabonly call chdir(topdir) - call delete('Xdir', 'rf') endfunc " Test for changing to the previous directory '-' func Test_prev_dir() let topdir = getcwd() - call mkdir('Xdir/a/b/c', 'p') + call mkdir('Xprevdir/a/b/c', 'pR') " Create a few tabpages and windows with different directories new | only tabnew | new tabnew tabfirst - cd Xdir + cd Xprevdir tabnext | wincmd t tcd a wincmd w @@ -143,7 +141,7 @@ func Test_prev_dir() " Check the directory of all the windows tabfirst - call assert_equal('Xdir', fnamemodify(getcwd(), ':t')) + call assert_equal('Xprevdir', fnamemodify(getcwd(), ':t')) tabnext | wincmd t call assert_equal('a', fnamemodify(getcwd(), ':t')) wincmd w @@ -163,7 +161,7 @@ func Test_prev_dir() " Check the directory of all the windows tabfirst - call assert_equal('Xdir', fnamemodify(getcwd(), ':t')) + call assert_equal('Xprevdir', fnamemodify(getcwd(), ':t')) tabnext | wincmd t call assert_equal('a', fnamemodify(getcwd(), ':t')) wincmd w @@ -173,7 +171,6 @@ func Test_prev_dir() only | tabonly call chdir(topdir) - call delete('Xdir', 'rf') endfunc func Test_lcd_split() @@ -201,22 +198,26 @@ func Test_cd_from_non_existing_dir() endfunc func Test_cd_completion() - call mkdir('XComplDir1', 'p') - call mkdir('XComplDir2', 'p') - call writefile([], 'XComplFile') + call mkdir('XComplDir1', 'D') + call mkdir('XComplDir2', 'D') + call mkdir('sub/XComplDir3', 'pD') + call writefile([], 'XComplFile', 'D') for cmd in ['cd', 'chdir', 'lcd', 'lchdir', 'tcd', 'tchdir'] call feedkeys(':' .. cmd .. " XCompl\<C-A>\<C-B>\"\<CR>", 'tx') call assert_equal('"' .. cmd .. ' XComplDir1/ XComplDir2/', @:) endfor - call delete('XComplDir1', 'd') - call delete('XComplDir2', 'd') - call delete('XComplFile') + set cdpath+=sub + for cmd in ['cd', 'chdir', 'lcd', 'lchdir', 'tcd', 'tchdir'] + call feedkeys(':' .. cmd .. " XCompl\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"' .. cmd .. ' XComplDir1/ XComplDir2/ XComplDir3/', @:) + endfor + set cdpath& endfunc func Test_cd_unknown_dir() - call mkdir('Xa') + call mkdir('Xa', 'R') cd Xa call writefile(['text'], 'Xb.txt') edit Xa/Xb.txt @@ -229,7 +230,6 @@ func Test_cd_unknown_dir() bwipe! exe "bwipe! " .. first_buf - call delete('Xa', 'rf') endfunc func Test_getcwd_actual_dir() @@ -237,7 +237,7 @@ func Test_getcwd_actual_dir() CheckOption autochdir let startdir = getcwd() - call mkdir('Xactual') + call mkdir('Xactual', 'R') call test_autochdir() set autochdir edit Xactual/file.txt @@ -251,7 +251,6 @@ func Test_getcwd_actual_dir() set noautochdir bwipe! call chdir(startdir) - call delete('Xactual', 'rf') endfunc " vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/test_cmdline.vim b/test/old/testdir/test_cmdline.vim index 443539fbfd..9f25a42c38 100644 --- a/test/old/testdir/test_cmdline.vim +++ b/test/old/testdir/test_cmdline.vim @@ -645,7 +645,8 @@ func Test_getcompletion() unlet g:cmdline_compl_params " For others test if the name is recognized. - let names = ['buffer', 'environment', 'file_in_path', 'mapping', 'tag', 'tag_listfiles', 'user'] + let names = ['buffer', 'environment', 'file_in_path', 'dir_in_path', 'mapping', 'tag', + \ 'tag_listfiles', 'user'] if has('cmdline_hist') call add(names, 'history') endif @@ -1533,7 +1534,7 @@ endfunc set cpo& -func Test_getcmdtype() +func Test_getcmdtype_getcmdprompt() call feedkeys(":MyCmd a\<C-R>=Check_cmdline(':')\<CR>\<Esc>", "xt") let cmdtype = '' @@ -1557,6 +1558,31 @@ func Test_getcmdtype() cunmap <F6> call assert_equal('', getcmdline()) + + call assert_equal('', getcmdprompt()) + augroup test_CmdlineEnter + autocmd! + autocmd CmdlineEnter * let g:cmdprompt=getcmdprompt() + augroup END + call feedkeys(":call input('Answer?')\<CR>a\<CR>\<ESC>", "xt") + call assert_equal('Answer?', g:cmdprompt) + call assert_equal('', getcmdprompt()) + call feedkeys(":\<CR>\<ESC>", "xt") + call assert_equal('', g:cmdprompt) + call assert_equal('', getcmdprompt()) + + let str = "C" .. repeat("c", 1023) .. "xyz" + call feedkeys(":call input('" .. str .. "')\<CR>\<CR>\<ESC>", "xt") + call assert_equal(str, g:cmdprompt) + + call feedkeys(':call input("Msg1\nMessage2\nAns?")' .. "\<CR>b\<CR>\<ESC>", "xt") + call assert_equal('Ans?', g:cmdprompt) + call assert_equal('', getcmdprompt()) + + augroup test_CmdlineEnter + au! + augroup END + augroup! test_CmdlineEnter endfunc func Test_getcmdwintype() @@ -2905,6 +2931,55 @@ func Test_wildmenu_pum_odd_wildchar() call StopVimInTerminal(buf) endfunc +" Test that 'rightleft' should not affect cmdline completion popup menu. +func Test_wildmenu_pum_rightleft() + CheckFeature rightleft + CheckScreendump + + let lines =<< trim END + set wildoptions=pum + set rightleft + END + call writefile(lines, 'Xwildmenu_pum_rl', 'D') + let buf = RunVimInTerminal('-S Xwildmenu_pum_rl', #{rows: 10, cols: 50}) + + call term_sendkeys(buf, ":sign \<Tab>") + call VerifyScreenDump(buf, 'Test_wildmenu_pum_rl', {}) + + call StopVimInTerminal(buf) +endfunc + +" Test highlighting matched text in cmdline completion popup menu. +func Test_wildmenu_pum_hl_match() + CheckScreendump + + let lines =<< trim END + set wildoptions=pum,fuzzy + hi PmenuMatchSel ctermfg=6 ctermbg=7 + hi PmenuMatch ctermfg=4 ctermbg=225 + END + call writefile(lines, 'Xwildmenu_pum_hl', 'D') + let buf = RunVimInTerminal('-S Xwildmenu_pum_hl', #{rows: 10, cols: 50}) + + call term_sendkeys(buf, ":sign plc\<Tab>") + call VerifyScreenDump(buf, 'Test_wildmenu_pum_hl_match_1', {}) + call term_sendkeys(buf, "\<Tab>") + call VerifyScreenDump(buf, 'Test_wildmenu_pum_hl_match_2', {}) + call term_sendkeys(buf, "\<Tab>") + call VerifyScreenDump(buf, 'Test_wildmenu_pum_hl_match_3', {}) + call term_sendkeys(buf, "\<Esc>:set wildoptions-=fuzzy\<CR>") + call TermWait(buf) + call term_sendkeys(buf, ":sign un\<Tab>") + call VerifyScreenDump(buf, 'Test_wildmenu_pum_hl_match_4', {}) + call term_sendkeys(buf, "\<Tab>") + call VerifyScreenDump(buf, 'Test_wildmenu_pum_hl_match_5', {}) + call term_sendkeys(buf, "\<Tab>") + call VerifyScreenDump(buf, 'Test_wildmenu_pum_hl_match_6', {}) + call term_sendkeys(buf, "\<Esc>") + + call StopVimInTerminal(buf) +endfunc + " Test for completion after a :substitute command followed by a pipe (|) " character func Test_cmdline_complete_substitute() @@ -3736,7 +3811,7 @@ func Test_cmdline_complete_bang_cmd_argument() endfunc func Call_cmd_funcs() - return string([getcmdpos(), getcmdscreenpos(), getcmdcompltype()]) + return [getcmdpos(), getcmdscreenpos(), getcmdcompltype()] endfunc func Test_screenpos_and_completion() @@ -3744,13 +3819,24 @@ func Test_screenpos_and_completion() call assert_equal(0, getcmdscreenpos()) call assert_equal('', getcmdcompltype()) - cnoremap <expr> <F2> string([getcmdpos(), getcmdscreenpos(), getcmdcompltype()]) + cnoremap <expr> <F2> string(Call_cmd_funcs()) call feedkeys(":let a\<F2>\<C-B>\"\<CR>", "xt") call assert_equal("\"let a[6, 7, 'var']", @:) call feedkeys(":quit \<F2>\<C-B>\"\<CR>", "xt") call assert_equal("\"quit [6, 7, '']", @:) call feedkeys(":nosuchcommand \<F2>\<C-B>\"\<CR>", "xt") call assert_equal("\"nosuchcommand [15, 16, '']", @:) + + " Check that getcmdcompltype() doesn't interfere with cmdline completion. + let g:results = [] + cnoremap <F2> <Cmd>let g:results += [[getcmdline()] + Call_cmd_funcs()]<CR> + call feedkeys(":sign un\<Tab>\<F2>\<Tab>\<F2>\<Tab>\<F2>\<C-C>", "xt") + call assert_equal([ + \ ['sign undefine', 14, 15, 'sign'], + \ ['sign unplace', 13, 14, 'sign'], + \ ['sign un', 8, 9, 'sign']], g:results) + + unlet g:results cunmap <F2> endfunc @@ -3984,4 +4070,15 @@ func Test_term_option() let &cpo = _cpo endfunc +func Test_cd_bslsh_completion_windows() + CheckMSWindows + let save_shellslash = &shellslash + set noshellslash + call system('mkdir XXXa\_b') + defer delete('XXXa', 'rf') + call feedkeys(":cd XXXa\\_b\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"cd XXXa\_b\', @:) + let &shellslash = save_shellslash +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/test_codestyle.vim b/test/old/testdir/test_codestyle.vim index 01dd03f693..a0cebf76d1 100644 --- a/test/old/testdir/test_codestyle.vim +++ b/test/old/testdir/test_codestyle.vim @@ -6,6 +6,45 @@ func s:ReportError(fname, lnum, msg) endif endfunc +func Test_test_files() + for fname in glob('*.vim', 0, 1) + let g:ignoreSwapExists = 'e' + exe 'edit ' .. fname + + " some files intentionally have misplaced white space + if fname =~ 'test_cindent.vim' || fname =~ 'test_join.vim' + continue + endif + + " skip files that are known to have a space before a tab + if fname !~ 'test_comments.vim' + \ && fname !~ 'test_listchars.vim' + \ && fname !~ 'test_visual.vim' + call cursor(1, 1) + let skip = 'getline(".") =~ "codestyle: ignore"' + let lnum = search(fname =~ "test_regexp_latin" ? '[^á] \t' : ' \t', 'W', 0, 0, skip) + call s:ReportError('testdir/' .. fname, lnum, 'space before Tab') + endif + + " skip files that are known to have trailing white space + if fname !~ 'test_cmdline.vim' + \ && fname !~ 'test_let.vim' + \ && fname !~ 'test_tagjump.vim' + \ && fname !~ 'test_vim9_cmd.vim' + call cursor(1, 1) + let lnum = search( + \ fname =~ 'test_vim9_assign.vim' ? '[^=]\s$' + \ : fname =~ 'test_vim9_class.vim' ? '[^)]\s$' + \ : fname =~ 'test_vim9_script.vim' ? '[^,:3]\s$' + \ : fname =~ 'test_visual.vim' ? '[^/]\s$' + \ : '[^\\]\s$') + call s:ReportError('testdir/' .. fname, lnum, 'trailing white space') + endif + endfor + + bwipe! +endfunc + func Test_help_files() set nowrapscan diff --git a/test/old/testdir/test_cpoptions.vim b/test/old/testdir/test_cpoptions.vim index 915f418712..dd959c64a8 100644 --- a/test/old/testdir/test_cpoptions.vim +++ b/test/old/testdir/test_cpoptions.vim @@ -8,7 +8,7 @@ source view_util.vim " file name. func Test_cpo_a() let save_cpo = &cpo - call writefile(['one'], 'XfileCpoA') + call writefile(['one'], 'XfileCpoA', 'D') " Wipe out all the buffers, so that the alternate file is empty edit Xfoo | %bw set cpo-=a @@ -19,8 +19,7 @@ func Test_cpo_a() set cpo+=a read XfileCpoA call assert_equal('XfileCpoA', @#) - close! - call delete('XfileCpoA') + bw! let &cpo = save_cpo endfunc @@ -40,7 +39,7 @@ func Test_cpo_A() set cpo+=A write XcpoAfile2 call assert_equal('XcpoAfile2', @#) - close! + bw! call delete('XcpoAfile2') let &cpo = save_cpo endfunc @@ -82,7 +81,7 @@ func Test_cpo_B() call assert_equal('abd ', getline(1)) call feedkeys(":imap <buffer> x\<C-A>\<C-B>\"\<CR>", 'tx') call assert_equal('"imap <buffer> x\k', @:) - close! + bw! let &cpo = save_cpo endfunc @@ -97,28 +96,27 @@ func Test_cpo_c() set cpo-=c exe "normal gg/abab\<CR>" call assert_equal(5, searchcount().total) - close! + bw! let &cpo = save_cpo endfunc " Test for the 'C' flag in 'cpo' (line continuation) func Test_cpo_C() let save_cpo = &cpo - call writefile(['let l = [', '\ 1,', '\ 2]'], 'XfileCpoC') + call writefile(['let l = [', '\ 1,', '\ 2]'], 'XfileCpoC', 'D') set cpo-=C source XfileCpoC call assert_equal([1, 2], g:l) set cpo+=C call assert_fails('source XfileCpoC', ['E697:', 'E10:']) - call delete('XfileCpoC') let &cpo = save_cpo endfunc " Test for the 'd' flag in 'cpo' (tags relative to the current file) func Test_cpo_d() let save_cpo = &cpo - call mkdir('XdirCpoD') - call writefile(["one\tXfile1\t/^one$/"], 'tags') + call mkdir('XdirCpoD', 'R') + call writefile(["one\tXfile1\t/^one$/"], 'tags', 'D') call writefile(["two\tXfile2\t/^two$/"], 'XdirCpoD/tags') set tags=./tags set cpo-=d @@ -126,9 +124,8 @@ func Test_cpo_d() call assert_equal('two', taglist('.*')[0].name) set cpo+=d call assert_equal('one', taglist('.*')[0].name) + %bw! - call delete('tags') - call delete('XdirCpoD', 'rf') set tags& let &cpo = save_cpo endfunc @@ -146,7 +143,7 @@ func Test_cpo_D() exe "norm! 1gg0f\<c-k>!!" call assert_equal(1, col('.')) set cpo-=D - close! + bw! let &cpo = save_cpo endfunc @@ -186,7 +183,7 @@ func Test_cpo_E() call assert_beeps('exe "normal v\<C-A>"') call assert_beeps('exe "normal v\<C-X>"') set cpo-=E - close! + bw! endfunc " Test for the 'f' flag in 'cpo' (read in an empty buffer sets the file name) @@ -216,7 +213,7 @@ func Test_cpo_F() set cpo+=F write XfileCpoF call assert_equal('XfileCpoF', @%) - close! + bw! call delete('XfileCpoF') let &cpo = save_cpo endfunc @@ -233,7 +230,7 @@ func Test_cpo_g() " set cpo+=g " edit " call assert_equal(1, line('.')) - close! + bw! let &cpo = save_cpo endfunc @@ -250,7 +247,7 @@ func Test_cpo_H() " call setline(1, ' ') " normal! Ia " call assert_equal(' a ', getline(1)) - close! + bw! let &cpo = save_cpo endfunc @@ -269,7 +266,7 @@ func Test_cpo_I() %d exe "normal i one\<CR>\<Up>" call assert_equal('', getline(2)) - close! + bw! let &cpo = save_cpo endfunc @@ -300,7 +297,7 @@ func Test_cpo_J() normal ( call assert_equal(colnr, col('.')) endfor - close! + bw! let &cpo = save_cpo endfunc @@ -322,7 +319,7 @@ func Test_cpo_l() set cpo+=l exe 'normal gg/[\t]' .. "\<CR>" call assert_equal([4, 10], [col('.'), virtcol('.')]) - close! + bw! let &cpo = save_cpo endfunc @@ -343,7 +340,7 @@ func Test_cpo_L() call setline(1, 'abcdefghijklmnopqr') exe "normal 0gR\<Tab>" call assert_equal("\<Tab>ijklmnopqr", getline(1)) - close! + bw! let &cpo = save_cpo endfunc @@ -381,7 +378,7 @@ func Test_cpo_M() call cursor(2, 1) call assert_beeps('normal %') - close! + bw! let &cpo = save_cpo endfunc @@ -397,7 +394,7 @@ func Test_cpo_n() set cpo+=n redraw! call assert_equal('aaaa', Screenline(2)) - close! + bw! let &cpo = save_cpo endfunc @@ -414,7 +411,7 @@ func Test_cpo_o() exe "normal /one/+2\<CR>" normal n call assert_equal(5, line('.')) - close! + bw! let &cpo = save_cpo endfunc @@ -423,14 +420,13 @@ func Test_cpo_O() let save_cpo = &cpo new XfileCpoO call setline(1, 'one') - call writefile(['two'], 'XfileCpoO') + call writefile(['two'], 'XfileCpoO', 'D') set cpo-=O call assert_fails('write', 'E13:') set cpo+=O write call assert_equal(['one'], readfile('XfileCpoO')) - close! - call delete('XfileCpoO') + bw! let &cpo = save_cpo endfunc @@ -440,7 +436,7 @@ endfunc " name) func Test_cpo_P() let save_cpo = &cpo - call writefile([], 'XfileCpoP') + call writefile([], 'XfileCpoP', 'D') new call setline(1, 'one') set cpo+=F @@ -452,7 +448,6 @@ func Test_cpo_P() call assert_equal('XfileCpoP', @%) bwipe! - call delete('XfileCpoP') let &cpo = save_cpo endfunc @@ -469,7 +464,7 @@ func Test_cpo_q() set cpo+=q normal gg4J call assert_equal(4, col('.')) - close! + bw! let &cpo = save_cpo endfunc @@ -490,7 +485,7 @@ func Test_cpo_r() let @/ = 'three' normal 2G. call assert_equal('abc three four', getline(2)) - close! + bw! let &cpo = save_cpo endfunc @@ -510,7 +505,7 @@ func Test_cpo_R() 3mark r %!sort call assert_equal(0, line("'r")) - close! + bw! let &cpo = save_cpo endfunc @@ -537,8 +532,8 @@ func Test_cpo_S() wincmd p call assert_equal(0, &autoindent) wincmd t - close! - close! + bw! + bw! let &cpo = save_cpo endfunc @@ -557,7 +552,7 @@ func Test_cpo_u() exe "normal iabc\<C-G>udef\<C-G>ughi" normal uu call assert_equal('abcdefghi', getline(1)) - close! + bw! let &cpo = save_cpo endfunc @@ -582,7 +577,7 @@ func Test_cpo_w() call assert_equal('hereZZZare some words', getline('.')) norm! 1gg2elcWYYY call assert_equal('hereZZZare someYYYwords', getline('.')) - close! + bw! let &cpo = save_cpo endfunc @@ -619,7 +614,7 @@ func Test_cpo_X() normal ggRy normal 4. call assert_equal('yyyyxxxaaaaa', getline(1)) - close! + bw! let &cpo = save_cpo endfunc @@ -638,14 +633,14 @@ func Test_cpo_y() normal ggyy normal 2G. call assert_equal("two\n", @") - close! + bw! let &cpo = save_cpo endfunc " Test for the 'Z' flag in 'cpo' (write! resets 'readonly') func Test_cpo_Z() let save_cpo = &cpo - call writefile([], 'XfileCpoZ') + call writefile([], 'XfileCpoZ', 'D') new XfileCpoZ setlocal readonly set cpo-=Z @@ -655,8 +650,7 @@ func Test_cpo_Z() setlocal readonly write! call assert_equal(1, &readonly) - close! - call delete('XfileCpoZ') + bw! let &cpo = save_cpo endfunc @@ -710,7 +704,7 @@ func Test_cpo_percent() call assert_equal(15, col('.')) normal 22|% call assert_equal(27, col('.')) - close! + bw! let &cpo = save_cpo endfunc @@ -727,7 +721,7 @@ func Test_cpo_minus() call assert_beeps('normal 10k') call assert_equal(3, line('.')) call assert_fails(10, 'E16:') - close! + bw! let &cpo = save_cpo endfunc @@ -735,7 +729,7 @@ endfunc " flag) func Test_cpo_plus() let save_cpo = &cpo - call writefile([], 'XfileCpoPlus') + call writefile([], 'XfileCpoPlus', 'D') new XfileCpoPlus call setline(1, 'foo') write X1 @@ -743,8 +737,7 @@ func Test_cpo_plus() set cpo+=+ write X2 call assert_equal(0, &modified) - close! - call delete('XfileCpoPlus') + bw! call delete('X1') call delete('X2') let &cpo = save_cpo @@ -762,7 +755,7 @@ func Test_cpo_star() " set cpo+=* " *a " call assert_equal(1, x) - close! + bw! let &cpo = save_cpo endfunc @@ -783,7 +776,7 @@ func Test_cpo_gt() normal gg"Rye normal "Rye call assert_equal("\none\none", @r) - close! + bw! let &cpo = save_cpo endfunc @@ -816,7 +809,7 @@ func Test_cpo_semicolon() call assert_equal('bbb y', getline(4)) call assert_equal('ccc', getline(5)) call assert_equal('ddd yee y', getline(6)) - close! + bw! let &cpo = save_cpo endfunc @@ -842,7 +835,7 @@ func Test_cpo_hash() " call assert_equal(['', 'one', 'two', 'three'], getline(1, '$')) " normal gg2Ozero " call assert_equal(['zero', '', 'one', 'two', 'three'], getline(1, '$')) - close! + bw! let &cpo = save_cpo endfunc @@ -850,7 +843,7 @@ endfunc " loaded and ':preserve' is used. func Test_cpo_ampersand() throw 'Skipped: Nvim does not support cpoptions flag "&"' - call writefile(['one'], 'XfileCpoAmp') + call writefile(['one'], 'XfileCpoAmp', 'D') let after =<< trim [CODE] set cpo+=& preserve @@ -860,7 +853,6 @@ func Test_cpo_ampersand() call assert_equal(1, filereadable('.XfileCpoAmp.swp')) call delete('.XfileCpoAmp.swp') endif - call delete('XfileCpoAmp') endfunc " Test for the '\' flag in 'cpo' (backslash in a [] range in a search pattern) @@ -875,7 +867,7 @@ func Test_cpo_backslash() " set cpo+=\ " exe 'normal gg/[ \-]' .. "\<CR>n" " call assert_equal(2, col('.')) - close! + bw! let &cpo = save_cpo endfunc @@ -898,7 +890,7 @@ func Test_cpo_brace() " call assert_equal(2, line('.')) " normal G{ " call assert_equal(2, line('.')) - close! + bw! let &cpo = save_cpo endfunc @@ -927,7 +919,7 @@ func Test_cpo_dot() call delete('Xfoo') set cpo& - close! + bw! let &cpo = save_cpo endfunc diff --git a/test/old/testdir/test_crash.vim b/test/old/testdir/test_crash.vim index 49e712a901..9aef245026 100644 --- a/test/old/testdir/test_crash.vim +++ b/test/old/testdir/test_crash.vim @@ -113,6 +113,7 @@ endfunc func Test_crash1_2() CheckNotBSD CheckExecutable dash + let g:test_is_flaky = 1 " The following used to crash Vim let opts = #{cmd: 'sh'} @@ -149,22 +150,9 @@ func Test_crash1_2() \ ' ; echo "crash 4: [OK]" >> '.. result .. "\<cr>") call TermWait(buf, 150) - let file = 'crash/poc_ex_substitute' - let cmn_args = "%s -u NONE -i NONE -n -e -s -S %s -c ':qa!'" - let args = printf(cmn_args, vim, file) - " just make sure it runs, we don't care about the resulting echo - call term_sendkeys(buf, args .. "\<cr>") - " There is no output generated in Github CI for the asan clang build. - " so just skip generating the ouput. - " call term_sendkeys(buf, args .. - " \ ' && echo "crash 5: [OK]" >> '.. result .. "\<cr>") - call TermWait(buf, 150) - " clean up exe buf .. "bw!" - exe "sp " .. result - let expected = [ \ 'crash 1: [OK]', \ 'crash 2: [OK]', @@ -174,10 +162,51 @@ func Test_crash1_2() call assert_equal(expected, getline(1, '$')) bw! - call delete(result) endfunc +" This test just runs various scripts, that caused issues before. +" We are not really asserting anything here, it's just important +" that ASAN does not detect any issues. +func Test_crash1_3() + let vim = GetVimProg() + let buf = RunVimInTerminal('sh', #{cmd: 'sh'}) + + let file = 'crash/poc_ex_substitute' + let cmn_args = "%s -u NONE -i NONE -n -e -s -S %s -c ':qa!'\<cr>" + let args = printf(cmn_args, vim, file) + call term_sendkeys(buf, args) + call TermWait(buf, 150) + + let file = 'crash/poc_uaf_exec_instructions' + let cmn_args = "%s -u NONE -i NONE -n -e -s -S %s -c ':qa!'\<cr>" + let args = printf(cmn_args, vim, file) + call term_sendkeys(buf, args) + call TermWait(buf, 150) + + let file = 'crash/poc_uaf_check_argument_types' + let cmn_args = "%s -u NONE -i NONE -n -e -s -S %s -c ':qa!'\<cr>" + let args = printf(cmn_args, vim, file) + call term_sendkeys(buf, args) + call TermWait(buf, 150) + + let file = 'crash/double_free' + let cmn_args = "%s -u NONE -i NONE -n -e -s -S %s -c ':qa!'\<cr>" + let args = printf(cmn_args, vim, file) + call term_sendkeys(buf, args) + call TermWait(buf, 50) + + let file = 'crash/dialog_changed_uaf' + let cmn_args = "%s -u NONE -i NONE -n -e -s -S %s -c ':qa!'\<cr>" + let args = printf(cmn_args, vim, file) + call term_sendkeys(buf, args) + call TermWait(buf, 150) + + " clean up + exe buf .. "bw!" + bw! +endfunc + func Test_crash2() " The following used to crash Vim let opts = #{wait_for_ruler: 0, rows: 20} diff --git a/test/old/testdir/test_cursor_func.vim b/test/old/testdir/test_cursor_func.vim index 5d99963814..99af0ab359 100644 --- a/test/old/testdir/test_cursor_func.vim +++ b/test/old/testdir/test_cursor_func.vim @@ -279,6 +279,21 @@ func Test_screenpos_number() bwipe! endfunc +func Test_screenpos_edit_newfile() + new + 20vsp + setl nowrap + call setline(1, 'abcdefghijklmnopqrstuvwxyz') + call cursor(1, 10) + norm! 5zl + call assert_equal(#{col: 5, row: 1, endcol: 5, curscol: 5}, screenpos(win_getid(), 1, 10)) + enew! + call assert_equal(1, &l:wrap) + call assert_equal(#{col: 1, row: 1, endcol: 1, curscol: 1}, screenpos(win_getid(), 1, 1)) + + bwipe! +endfunc + " Save the visual start character position func SaveVisualStartCharPos() call add(g:VisualStartPos, getcharpos('v')) diff --git a/test/old/testdir/test_cursorline.vim b/test/old/testdir/test_cursorline.vim index 99a812b1de..2c375f20c0 100644 --- a/test/old/testdir/test_cursorline.vim +++ b/test/old/testdir/test_cursorline.vim @@ -262,14 +262,34 @@ func Test_cursorline_callback() call timer_start(300, 'Func') END - call writefile(lines, 'Xcul_timer') + call writefile(lines, 'Xcul_timer', 'D') let buf = RunVimInTerminal('-S Xcul_timer', #{rows: 8}) call TermWait(buf, 310) call VerifyScreenDump(buf, 'Test_cursorline_callback_1', {}) call StopVimInTerminal(buf) - call delete('Xcul_timer') +endfunc + +func Test_cursorline_screenline_resize() + CheckScreendump + + let lines =<< trim END + 50vnew + call setline(1, repeat('xyz ', 30)) + setlocal number cursorline cursorlineopt=screenline + normal! $ + END + call writefile(lines, 'Xcul_screenline_resize', 'D') + + let buf = RunVimInTerminal('-S Xcul_screenline_resize', #{rows: 8}) + call VerifyScreenDump(buf, 'Test_cursorline_screenline_resize_1', {}) + call term_sendkeys(buf, ":vertical resize -4\<CR>") + call VerifyScreenDump(buf, 'Test_cursorline_screenline_resize_2', {}) + call term_sendkeys(buf, ":set cpoptions+=n\<CR>") + call VerifyScreenDump(buf, 'Test_cursorline_screenline_resize_3', {}) + + call StopVimInTerminal(buf) endfunc func Test_cursorline_screenline_update() @@ -280,7 +300,7 @@ func Test_cursorline_screenline_update() set cursorline cursorlineopt=screenline inoremap <F2> <Cmd>call cursor(1, 1)<CR> END - call writefile(lines, 'Xcul_screenline') + call writefile(lines, 'Xcul_screenline', 'D') let buf = RunVimInTerminal('-S Xcul_screenline', #{rows: 8}) call term_sendkeys(buf, "A") @@ -290,7 +310,17 @@ func Test_cursorline_screenline_update() call term_sendkeys(buf, "\<Esc>") call StopVimInTerminal(buf) - call delete('Xcul_screenline') +endfunc + +func Test_cursorline_screenline_zero_width() + CheckOption foldcolumn + + set cursorline culopt=screenline winminwidth=1 foldcolumn=1 + " This used to crash Vim + 1vnew | redraw + + bwipe! + set cursorline& culopt& winminwidth& foldcolumn& endfunc func Test_cursorline_cursorbind_horizontal_scroll() diff --git a/test/old/testdir/test_debugger.vim b/test/old/testdir/test_debugger.vim index ad03443cb4..3a469e8a17 100644 --- a/test/old/testdir/test_debugger.vim +++ b/test/old/testdir/test_debugger.vim @@ -524,7 +524,7 @@ func Test_Backtrace_Through_Source() call RunDbgCmd( buf, 'down', [ 'frame is zero' ] ) - " step until we have another meaninfgul trace + " step until we have another meaningful trace call RunDbgCmd(buf, 'step', ['line 5: func File2Function()']) call RunDbgCmd(buf, 'step', ['line 9: call File2Function()']) call RunDbgCmd(buf, 'backtrace', [ @@ -611,7 +611,7 @@ func Test_Backtrace_Autocmd() \ ['cmd: doautocmd User TestGlobalFunction']) call RunDbgCmd(buf, 'step', ['cmd: call GlobalFunction() | echo "Done"']) - " At this point the ontly thing in the stack is the autocommand + " At this point the only thing in the stack is the autocommand call RunDbgCmd(buf, 'backtrace', [ \ '>backtrace', \ '->0 User Autocommands for "TestGlobalFunction"', @@ -741,7 +741,7 @@ func Test_Backtrace_Autocmd() call RunDbgCmd( buf, 'down', [ 'frame is zero' ] ) - " step until we have another meaninfgul trace + " step until we have another meaningful trace call RunDbgCmd(buf, 'step', ['line 5: func File2Function()']) call RunDbgCmd(buf, 'step', ['line 9: call File2Function()']) call RunDbgCmd(buf, 'backtrace', [ @@ -867,7 +867,7 @@ func Test_Backtrace_CmdLine() call CheckDbgOutput(buf, ['command line', \ 'cmd: call GlobalFunction()'], #{msec: 5000}) - " At this point the ontly thing in the stack is the cmdline + " At this point the only thing in the stack is the cmdline call RunDbgCmd(buf, 'backtrace', [ \ '>backtrace', \ '->0 command line', @@ -1285,7 +1285,7 @@ func Test_debug_backtrace_level() \ #{ match: 'pattern' } ) " Expression evaluation in the script frame (not the function frame) - " FIXME: Unexpected in this scope (a: should not be visibnle) + " FIXME: Unexpected in this scope (a: should not be visible) call RunDbgCmd(buf, 'echo a:arg', [ 'arg1' ] ) call RunDbgCmd(buf, 'echo s:file1_var', [ 'file1' ] ) call RunDbgCmd(buf, 'echo g:global_var', [ 'global' ] ) diff --git a/test/old/testdir/test_diffmode.vim b/test/old/testdir/test_diffmode.vim index 71483b7469..a448ed9b7f 100644 --- a/test/old/testdir/test_diffmode.vim +++ b/test/old/testdir/test_diffmode.vim @@ -276,7 +276,7 @@ func Test_diffget_diffput_empty_buffer() endfunc " :diffput and :diffget completes names of buffers which -" are in diff mode and which are different then current buffer. +" are in diff mode and which are different than current buffer. " No completion when the current window is not in diff mode. func Test_diffget_diffput_completion() e Xdiff1 | diffthis @@ -679,7 +679,7 @@ func Test_diffexpr() call assert_notequal(normattr, screenattr(3, 1)) diffoff! - " Try using an non-existing function for 'diffexpr'. + " Try using a non-existing function for 'diffexpr'. set diffexpr=NewDiffFunc() call assert_fails('windo diffthis', ['E117:', 'E97:']) diffoff! @@ -830,6 +830,15 @@ func WriteDiffFiles(buf, list1, list2) endif endfunc +func WriteDiffFiles3(buf, list1, list2, list3) + call writefile(a:list1, 'Xdifile1') + call writefile(a:list2, 'Xdifile2') + call writefile(a:list3, 'Xdifile3') + if a:buf + call term_sendkeys(a:buf, ":checktime\<CR>") + endif +endfunc + " Verify a screendump with both the internal and external diff. func VerifyBoth(buf, dumpfile, extra) " trailing : for leaving the cursor on the command line @@ -997,9 +1006,17 @@ func Test_diff_screen() call WriteDiffFiles(buf, ['a ', 'x', 'cd', 'ef', 'xx xx', 'foo', 'bar'], ['a', 'x', 'c d', ' ef', 'xx xx', 'foo', '', 'bar']) call VerifyInternal(buf, 'Test_diff_19', " diffopt+=iwhiteeol") - " Test 19: test diffopt+=iwhiteall + " Test 20: test diffopt+=iwhiteall call VerifyInternal(buf, 'Test_diff_20', " diffopt+=iwhiteall") + " Test 21: Delete all lines + call WriteDiffFiles(buf, [0], []) + call VerifyBoth(buf, "Test_diff_21", "") + + " Test 22: Add line to empty file + call WriteDiffFiles(buf, [], [0]) + call VerifyBoth(buf, "Test_diff_22", "") + " clean up call StopVimInTerminal(buf) call delete('Xdifile1') @@ -1332,12 +1349,12 @@ endfunc func Test_diff_and_scroll() " this was causing an ml_get error set ls=2 - for i in range(winheight(0) * 2) - call setline(i, i < winheight(0) - 10 ? i : i + 10) + for i in range(winheight(0) * 2) + call setline(i, i < winheight(0) - 10 ? i : i + 10) endfor vnew - for i in range(winheight(0)*2 + 10) - call setline(i, i < winheight(0) - 10 ? 0 : i) + for i in range(winheight(0)*2 + 10) + call setline(i, i < winheight(0) - 10 ? 0 : i) endfor diffthis wincmd p @@ -1791,4 +1808,203 @@ func Test_diff_eob_halfpage() %bwipe! endfunc +func Test_diff_overlapped_diff_blocks_will_be_merged() + CheckScreendump + + let lines =<< trim END + 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 + END + call writefile(lines, 'XdiffSetup', 'D') + + call WriteDiffFiles(0, [], []) + let buf = RunVimInTerminal('-d -S XdiffSetup Xdifile1 Xdifile2', {}) + call term_sendkeys(buf, ":set autoread\<CR>\<c-w>w:set autoread\<CR>\<c-w>w") + + call WriteDiffFiles(buf, ["a", "b"], ["x", "x"]) + call writefile(["a", "b"], "Xdiin1") + call writefile(["x", "x"], "Xdinew1") + call writefile(["1c1", "2c2"], "Xdiout1") + call term_sendkeys(buf, ":set diffexpr=DiffExprStub()\<CR>:") + call VerifyBoth(buf, "Test_diff_overlapped_2.01", "") + call term_sendkeys(buf, ":set diffexpr&\<CR>:") + + call WriteDiffFiles(buf, ["a", "b", "c"], ["x", "c"]) + call writefile(["a", "b", "c"], "Xdiin1") + call writefile(["x", "c"], "Xdinew1") + call writefile(["1c1", "2d1"], "Xdiout1") + call term_sendkeys(buf, ":set diffexpr=DiffExprStub()\<CR>:") + call VerifyBoth(buf, "Test_diff_overlapped_2.02", "") + call term_sendkeys(buf, ":set diffexpr&\<CR>:") + + call WriteDiffFiles(buf, ["a", "c"], ["x", "x", "c"]) + call writefile(["a", "c"], "Xdiin1") + call writefile(["x", "x", "c"], "Xdinew1") + call writefile(["1c1", "1a2"], "Xdiout1") + call term_sendkeys(buf, ":set diffexpr=DiffExprStub()\<CR>:") + call VerifyBoth(buf, "Test_diff_overlapped_2.03", "") + call term_sendkeys(buf, ":set diffexpr&\<CR>:") + + call StopVimInTerminal(buf) + wincmd c + + call WriteDiffFiles3(0, [], [], []) + let buf = RunVimInTerminal('-d -S XdiffSetup Xdifile1 Xdifile2 Xdifile3', {}) + call term_sendkeys(buf, ":set autoread\<CR>\<c-w>w:set autoread\<CR>\<c-w>w:set autoread\<CR>\<c-w>w") + + call WriteDiffFiles3(buf, ["a", "b", "c"], ["a", "x", "c"], ["y", "b", "c"]) + call VerifyBoth(buf, "Test_diff_overlapped_3.01", "") + + call WriteDiffFiles3(buf, ["a", "b", "c"], ["a", "x", "c"], ["a", "y", "c"]) + call VerifyBoth(buf, "Test_diff_overlapped_3.02", "") + + call WriteDiffFiles3(buf, ["a", "b", "c"], ["a", "x", "c"], ["a", "b", "y"]) + call VerifyBoth(buf, "Test_diff_overlapped_3.03", "") + + call WriteDiffFiles3(buf, ["a", "b", "c"], ["a", "x", "c"], ["y", "y", "c"]) + call VerifyBoth(buf, "Test_diff_overlapped_3.04", "") + + call WriteDiffFiles3(buf, ["a", "b", "c"], ["a", "x", "c"], ["a", "y", "y"]) + call VerifyBoth(buf, "Test_diff_overlapped_3.05", "") + + call WriteDiffFiles3(buf, ["a", "b", "c"], ["a", "x", "c"], ["y", "y", "y"]) + call VerifyBoth(buf, "Test_diff_overlapped_3.06", "") + + call WriteDiffFiles3(buf, ["a", "b", "c"], ["a", "x", "x"], ["y", "y", "c"]) + call VerifyBoth(buf, "Test_diff_overlapped_3.07", "") + + call WriteDiffFiles3(buf, ["a", "b", "c"], ["x", "x", "c"], ["a", "y", "y"]) + call VerifyBoth(buf, "Test_diff_overlapped_3.08", "") + + call WriteDiffFiles3(buf, ["a", "b", "c", "d", "e"], ["a", "x", "c", "x", "e"], ["y", "y", "y", "d", "e"]) + call VerifyBoth(buf, "Test_diff_overlapped_3.09", "") + + call WriteDiffFiles3(buf, ["a", "b", "c", "d", "e"], ["a", "x", "c", "x", "e"], ["y", "y", "y", "y", "e"]) + call VerifyBoth(buf, "Test_diff_overlapped_3.10", "") + + call WriteDiffFiles3(buf, ["a", "b", "c", "d", "e"], ["a", "x", "c", "x", "e"], ["y", "y", "y", "y", "y"]) + call VerifyBoth(buf, "Test_diff_overlapped_3.11", "") + + call WriteDiffFiles3(buf, ["a", "b", "c", "d", "e"], ["a", "x", "c", "x", "e"], ["a", "y", "y", "d", "e"]) + call VerifyBoth(buf, "Test_diff_overlapped_3.12", "") + + call WriteDiffFiles3(buf, ["a", "b", "c", "d", "e"], ["a", "x", "c", "x", "e"], ["a", "y", "y", "y", "e"]) + call VerifyBoth(buf, "Test_diff_overlapped_3.13", "") + + call WriteDiffFiles3(buf, ["a", "b", "c", "d", "e"], ["a", "x", "c", "x", "e"], ["a", "y", "y", "y", "y"]) + call VerifyBoth(buf, "Test_diff_overlapped_3.14", "") + + call WriteDiffFiles3(buf, ["a", "b", "c", "d", "e"], ["a", "x", "c", "x", "e"], ["a", "b", "y", "d", "e"]) + call VerifyBoth(buf, "Test_diff_overlapped_3.15", "") + + call WriteDiffFiles3(buf, ["a", "b", "c", "d", "e"], ["a", "x", "c", "x", "e"], ["a", "b", "y", "y", "e"]) + call VerifyBoth(buf, "Test_diff_overlapped_3.16", "") + + call WriteDiffFiles3(buf, ["a", "b", "c", "d", "e"], ["a", "x", "c", "x", "e"], ["a", "b", "y", "y", "y"]) + call VerifyBoth(buf, "Test_diff_overlapped_3.17", "") + + call WriteDiffFiles3(buf, ["a", "b"], ["x", "b"], ["y", "y"]) + call writefile(["a", "b"], "Xdiin1") + call writefile(["x", "b"], "Xdinew1") + call writefile(["1c1"], "Xdiout1") + call writefile(["a", "b"], "Xdiin2") + call writefile(["y", "y"], "Xdinew2") + call writefile(["1c1", "2c2"], "Xdiout2") + call term_sendkeys(buf, ":set diffexpr=DiffExprStub()\<CR>:") + call VerifyInternal(buf, "Test_diff_overlapped_3.18", "") + call term_sendkeys(buf, ":set diffexpr&\<CR>:") + + call WriteDiffFiles3(buf, ["a", "b", "c", "d"], ["x", "b", "x", "d"], ["y", "y", "c", "d"]) + call writefile(["a", "b", "c", "d"], "Xdiin1") + call writefile(["x", "b", "x", "d"], "Xdinew1") + call writefile(["1c1", "3c3"], "Xdiout1") + call writefile(["a", "b", "c", "d"], "Xdiin2") + call writefile(["y", "y", "c", "d"], "Xdinew2") + call writefile(["1c1", "2c2"], "Xdiout2") + call term_sendkeys(buf, ":set diffexpr=DiffExprStub()\<CR>:") + call VerifyInternal(buf, "Test_diff_overlapped_3.19", "") + call term_sendkeys(buf, ":set diffexpr&\<CR>:") + + call WriteDiffFiles3(buf, ["a", "b", "c", "d"], ["x", "b", "x", "d"], ["y", "y", "y", "d"]) + call writefile(["a", "b", "c", "d"], "Xdiin1") + call writefile(["x", "b", "x", "d"], "Xdinew1") + call writefile(["1c1", "3c3"], "Xdiout1") + call writefile(["a", "b", "c", "d"], "Xdiin2") + call writefile(["y", "y", "y", "d"], "Xdinew2") + call writefile(["1c1", "2,3c2,3"], "Xdiout2") + call term_sendkeys(buf, ":set diffexpr=DiffExprStub()\<CR>:") + call VerifyInternal(buf, "Test_diff_overlapped_3.20", "") + call term_sendkeys(buf, ":set diffexpr&\<CR>:") + + call WriteDiffFiles3(buf, ["a", "b", "c", "d"], ["x", "b", "x", "d"], ["y", "y", "y", "y"]) + call writefile(["a", "b", "c", "d"], "Xdiin1") + call writefile(["x", "b", "x", "d"], "Xdinew1") + call writefile(["1c1", "3c3"], "Xdiout1") + call writefile(["a", "b", "c", "d"], "Xdiin2") + call writefile(["y", "y", "y", "y"], "Xdinew2") + call writefile(["1c1", "2,4c2,4"], "Xdiout2") + call term_sendkeys(buf, ":set diffexpr=DiffExprStub()\<CR>:") + call VerifyInternal(buf, "Test_diff_overlapped_3.21", "") + call term_sendkeys(buf, ":set diffexpr&\<CR>:") + + call WriteDiffFiles3(buf, ["a", "b", "c"], ["a", "x", "c"], ["b", "c"]) + call VerifyBoth(buf, "Test_diff_overlapped_3.22", "") + + call WriteDiffFiles3(buf, ["a", "b", "c"], ["a", "x", "c"], ["c"]) + call VerifyBoth(buf, "Test_diff_overlapped_3.23", "") + + call WriteDiffFiles3(buf, ["a", "b", "c"], ["a", "x", "c"], []) + call VerifyBoth(buf, "Test_diff_overlapped_3.24", "") + + call WriteDiffFiles3(buf, ["a", "b", "c"], ["a", "x", "c"], ["a", "c"]) + call VerifyBoth(buf, "Test_diff_overlapped_3.25", "") + + call WriteDiffFiles3(buf, ["a", "b", "c"], ["a", "x", "c"], ["a"]) + call VerifyBoth(buf, "Test_diff_overlapped_3.26", "") + + call WriteDiffFiles3(buf, ["a", "b", "c"], ["a", "x", "c"], ["b"]) + call VerifyBoth(buf, "Test_diff_overlapped_3.27", "") + + call WriteDiffFiles3(buf, ["a", "b", "c", "d", "e"], ["a", "x", "c", "x", "e"], ["d", "e"]) + call VerifyBoth(buf, "Test_diff_overlapped_3.28", "") + + call WriteDiffFiles3(buf, ["a", "b", "c", "d", "e"], ["a", "x", "c", "x", "e"], ["e"]) + call VerifyBoth(buf, "Test_diff_overlapped_3.29", "") + + call WriteDiffFiles3(buf, ["a", "b", "c", "d", "e"], ["a", "x", "c", "x", "e"], ["a", "d", "e"]) + call VerifyBoth(buf, "Test_diff_overlapped_3.30", "") + + call WriteDiffFiles3(buf, ["a", "b", "c", "d", "e"], ["a", "x", "c", "x", "e"], ["a", "e"]) + call VerifyBoth(buf, "Test_diff_overlapped_3.31", "") + + call WriteDiffFiles3(buf, ["a", "b", "c", "d", "e"], ["a", "x", "c", "x", "e"], ["a"]) + call VerifyBoth(buf, "Test_diff_overlapped_3.32", "") + + call WriteDiffFiles3(buf, ["a", "b", "c", "d", "e"], ["a", "x", "c", "x", "e"], ["a", "b", "d", "e"]) + call VerifyBoth(buf, "Test_diff_overlapped_3.33", "") + + call WriteDiffFiles3(buf, ["a", "b", "c", "d", "e"], ["a", "x", "c", "x", "e"], ["a", "b", "e"]) + call VerifyBoth(buf, "Test_diff_overlapped_3.34", "") + + call WriteDiffFiles3(buf, ["a", "b", "c", "d", "e"], ["a", "x", "c", "x", "e"], ["a", "b"]) + call VerifyBoth(buf, "Test_diff_overlapped_3.35", "") + + call WriteDiffFiles3(buf, ["a", "b", "c"], ["a", "x", "c"], ["a", "y", "b", "c"]) + call VerifyBoth(buf, "Test_diff_overlapped_3.36", "") + + call WriteDiffFiles3(buf, ["a", "b", "c"], ["a", "x", "c"], ["a", "b", "y", "c"]) + call VerifyBoth(buf, "Test_diff_overlapped_3.37", "") + + call StopVimInTerminal(buf) +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/test_edit.vim b/test/old/testdir/test_edit.vim index 57ff63f26d..037282bf1a 100644 --- a/test/old/testdir/test_edit.vim +++ b/test/old/testdir/test_edit.vim @@ -1973,6 +1973,11 @@ func Test_edit_insert_reg() let @r = 'sample' call feedkeys("a\<C-R>=SaveFirstLine()\<CR>", "xt") call assert_equal('"', g:Line) + + " Test for inserting an null and an empty list + call feedkeys("a\<C-R>=test_null_list()\<CR>", "xt") + call feedkeys("a\<C-R>=[]\<CR>", "xt") + call assert_equal(['r'], getbufline('', 1, '$')) call test_override('ALL', 0) close! endfunc @@ -2112,7 +2117,7 @@ func Test_edit_overlong_file_name() file %%%%%%%%%%%%%%%%%%%%%%%%%% file %%%%%% set readonly - set ls=2 + set ls=2 redraw! set noreadonly ls& diff --git a/test/old/testdir/test_ex_mode.vim b/test/old/testdir/test_ex_mode.vim index 42f08868a0..32b01fef9e 100644 --- a/test/old/testdir/test_ex_mode.vim +++ b/test/old/testdir/test_ex_mode.vim @@ -69,7 +69,7 @@ func Test_Ex_substitute() CheckRunVimInTerminal let buf = RunVimInTerminal('', {'rows': 6}) - call term_sendkeys(buf, ":call setline(1, ['foo foo', 'foo foo', 'foo foo'])\<CR>") + call term_sendkeys(buf, ":call setline(1, repeat(['foo foo'], 4))\<CR>") call term_sendkeys(buf, ":set number\<CR>") call term_sendkeys(buf, "gQ") call WaitForAssert({-> assert_match(':', term_getline(buf, 6))}, 1000) @@ -91,8 +91,14 @@ func Test_Ex_substitute() " Pressing enter in ex mode should print the current line call term_sendkeys(buf, "\<CR>") - call WaitForAssert({-> assert_match(' 3 foo foo', - \ term_getline(buf, 5))}, 1000) + call WaitForAssert({-> assert_match(' 3 foo foo', term_getline(buf, 5))}, 1000) + call WaitForAssert({-> assert_match(':', term_getline(buf, 6))}, 1000) + + " The printed line should overwrite the colon + call term_sendkeys(buf, "\<CR>") + call WaitForAssert({-> assert_match(' 3 foo foo', term_getline(buf, 4))}, 1000) + call WaitForAssert({-> assert_match(' 4 foo foo', term_getline(buf, 5))}, 1000) + call WaitForAssert({-> assert_match(':', term_getline(buf, 6))}, 1000) call term_sendkeys(buf, ":vi\<CR>") call WaitForAssert({-> assert_match('foo bar', term_getline(buf, 1))}, 1000) @@ -282,4 +288,48 @@ func Test_ex_mode_large_indent() endfunc +" Testing implicit print command +func Test_implicit_print() + new + call setline(1, ['one', 'two', 'three']) + call feedkeys('Q:let a=execute(":1,2")', 'xt') + call feedkeys('Q:let b=execute(":3")', 'xt') + call assert_equal('one two', a->split('\n')->join(' ')) + call assert_equal('three', b->split('\n')->join(' ')) + bw! +endfunc + +" Test inserting text after the trailing bar +func Test_insert_after_trailing_bar() + new + call feedkeys("Qi|\nfoo\n.\na|bar\nbar\n.\nc|baz\n.", "xt") + call assert_equal(['', 'foo', 'bar', 'baz'], getline(1, '$')) + bwipe! +endfunc + +" Test global insert of a newline without terminating period +func Test_global_insert_newline() + new + call setline(1, ['foo']) + call feedkeys("Qg/foo/i\\\n", "xt") + call assert_equal(['', 'foo'], getline(1, '$')) + bwipe! +endfunc + +" An empty command followed by a newline shouldn't cause E749 in Ex mode. +func Test_ex_empty_command_newline() + let g:var = 0 + call feedkeys("gQexecute \"\\nlet g:var = 1\"\r", 'xt') + call assert_equal(1, g:var) + call feedkeys("gQexecute \" \\nlet g:var = 2\"\r", 'xt') + call assert_equal(2, g:var) + call feedkeys("gQexecute \"\\t \\nlet g:var = 3\"\r", 'xt') + call assert_equal(3, g:var) + call feedkeys("gQexecute \"\\\"?!\\nlet g:var = 4\"\r", 'xt') + call assert_equal(4, g:var) + call feedkeys("gQexecute \" \\\"?!\\nlet g:var = 5\"\r", 'xt') + call assert_equal(5, g:var) + unlet g:var +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/test_excmd.vim b/test/old/testdir/test_excmd.vim index 4a780078fd..50e5080f60 100644 --- a/test/old/testdir/test_excmd.vim +++ b/test/old/testdir/test_excmd.vim @@ -665,7 +665,7 @@ func Sandbox_tests() if has('clientserver') call assert_fails('let s=remote_expr("gvim", "2+2")', 'E48:') if !has('win32') - " remote_foreground() doesn't thrown an error message on MS-Windows + " remote_foreground() doesn't throw an error message on MS-Windows call assert_fails('call remote_foreground("gvim")', 'E48:') endif call assert_fails('let s=remote_peek("gvim")', 'E48:') @@ -718,15 +718,20 @@ func Test_not_break_expression_register() endfunc func Test_address_line_overflow() - throw 'Skipped: v:sizeoflong is N/A' " use legacy/excmd_spec.lua instead - - if v:sizeoflong < 8 + if !has('nvim') && v:sizeoflong < 8 throw 'Skipped: only works with 64 bit long ints' endif new - call setline(1, 'text') + call setline(1, range(100)) call assert_fails('|.44444444444444444444444', 'E1247:') call assert_fails('|.9223372036854775806', 'E1247:') + call assert_fails('.44444444444444444444444d', 'E1247:') + call assert_equal(range(100)->map('string(v:val)'), getline(1, '$')) + + $ + yank 77777777777777777777 + call assert_equal("99\n", @") + bwipe! endfunc diff --git a/test/old/testdir/test_expand_func.vim b/test/old/testdir/test_expand_func.vim index 454d76f0aa..12750baf67 100644 --- a/test/old/testdir/test_expand_func.vim +++ b/test/old/testdir/test_expand_func.vim @@ -7,15 +7,15 @@ let s:slnum = str2nr(expand('<slnum>')) let s:sflnum = str2nr(expand('<sflnum>')) func s:expand_sfile() - return expand('<sfile>') + return expand('<sfile>') endfunc func s:expand_slnum() - return str2nr(expand('<slnum>')) + return str2nr(expand('<slnum>')) endfunc func s:expand_sflnum() - return str2nr(expand('<sflnum>')) + return str2nr(expand('<sflnum>')) endfunc " This test depends on the location in the test file, put it first. diff --git a/test/old/testdir/test_filecopy.vim b/test/old/testdir/test_filecopy.vim new file mode 100644 index 0000000000..b526dce7b8 --- /dev/null +++ b/test/old/testdir/test_filecopy.vim @@ -0,0 +1,72 @@ +" Test filecopy() + +source check.vim +source shared.vim + +func Test_copy_file_to_file() + call writefile(['foo'], 'Xcopy1') + + call assert_true(filecopy('Xcopy1', 'Xcopy2')) + + call assert_equal(['foo'], readfile('Xcopy2')) + + " When the destination file already exists, it should not be overwritten. + call writefile(['foo'], 'Xcopy1') + call writefile(['bar'], 'Xcopy2', 'D') + call assert_false(filecopy('Xcopy1', 'Xcopy2')) + call assert_equal(['bar'], readfile('Xcopy2')) + + call delete('Xcopy2') + call delete('Xcopy1') +endfunc + +func Test_copy_symbolic_link() + CheckUnix + + call writefile(['text'], 'Xtestfile', 'D') + silent !ln -s -f Xtestfile Xtestlink + + call assert_true(filecopy('Xtestlink', 'Xtestlink2')) + call assert_equal('link', getftype('Xtestlink2')) + call assert_equal(['text'], readfile('Xtestlink2')) + + " When the destination file already exists, it should not be overwritten. + call assert_false(filecopy('Xtestlink', 'Xtestlink2')) + + call delete('Xtestlink2') + call delete('Xtestlink') + call delete('Xtestfile') +endfunc + +func Test_copy_dir_to_dir() + call mkdir('Xcopydir1') + call writefile(['foo'], 'Xcopydir1/Xfilecopy') + call mkdir('Xcopydir2') + + " Directory copy is not supported + call assert_false(filecopy('Xcopydir1', 'Xcopydir2')) + + call delete('Xcopydir2', 'rf') + call delete('Xcopydir1', 'rf') +endfunc + +func Test_copy_fails() + CheckUnix + + call writefile(['foo'], 'Xfilecopy', 'D') + + " Can't copy into a non-existing directory. + call assert_false(filecopy('Xfilecopy', 'Xdoesnotexist/Xfilecopy')) + + " Can't copy a non-existing file. + call assert_false(filecopy('Xdoesnotexist', 'Xfilecopy2')) + call assert_equal('', glob('Xfilecopy2')) + + " Can't copy to en empty file name. + call assert_false(filecopy('Xfilecopy', '')) + + call assert_fails('call filecopy("Xfilecopy", [])', 'E1174:') + call assert_fails('call filecopy(0z, "Xfilecopy")', 'E1174:') +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/test_filetype.vim b/test/old/testdir/test_filetype.vim index 34fe93fd55..d99656be47 100644 --- a/test/old/testdir/test_filetype.vim +++ b/test/old/testdir/test_filetype.vim @@ -96,6 +96,7 @@ func s:GetFilenameChecks() abort \ 'aml': ['file.aml'], \ 'ampl': ['file.run'], \ 'ant': ['build.xml'], + \ 'antlr4': ['parser.g4'], \ 'apache': ['.htaccess', '/etc/httpd/file.conf', '/etc/apache2/sites-2/file.com', '/etc/apache2/some.config', '/etc/apache2/conf.file/conf', '/etc/apache2/mods-some/file', '/etc/apache2/sites-some/file', '/etc/httpd/conf.d/file.config', '/etc/apache2/conf.file/file', '/etc/apache2/file.conf', '/etc/apache2/file.conf-file', '/etc/apache2/mods-file/file', '/etc/apache2/sites-file/file', '/etc/apache2/sites-file/file.com', '/etc/httpd/conf.d/file.conf', '/etc/httpd/conf.d/file.conf-file', 'access.conf', 'access.conf-file', 'any/etc/apache2/conf.file/file', 'any/etc/apache2/file.conf', 'any/etc/apache2/file.conf-file', 'any/etc/apache2/mods-file/file', 'any/etc/apache2/sites-file/file', 'any/etc/apache2/sites-file/file.com', 'any/etc/httpd/conf.d/file.conf', 'any/etc/httpd/conf.d/file.conf-file', 'any/etc/httpd/file.conf', 'apache.conf', 'apache.conf-file', 'apache2.conf', 'apache2.conf-file', 'httpd.conf', 'httpd.conf-file', 'srm.conf', 'srm.conf-file', '/etc/httpd/mods-some/file', '/etc/httpd/sites-some/file', '/etc/httpd/conf.file/conf'], \ 'apachestyle': ['/etc/proftpd/file.config,/etc/proftpd/conf.file/file', '/etc/proftpd/conf.file/file', '/etc/proftpd/file.conf', '/etc/proftpd/file.conf-file', 'any/etc/proftpd/conf.file/file', 'any/etc/proftpd/file.conf', 'any/etc/proftpd/file.conf-file', 'proftpd.conf', 'proftpd.conf-file'], \ 'applescript': ['file.scpt'], @@ -107,6 +108,7 @@ func s:GetFilenameChecks() abort \ 'asn': ['file.asn', 'file.asn1'], \ 'asterisk': ['asterisk/file.conf', 'asterisk/file.conf-file', 'some-asterisk/file.conf', 'some-asterisk/file.conf-file'], \ 'astro': ['file.astro'], + \ 'asy': ['file.asy'], \ 'atlas': ['file.atl', 'file.as'], \ 'authzed': ['schema.zed'], \ 'autohotkey': ['file.ahk'], @@ -122,7 +124,7 @@ func s:GetFilenameChecks() abort \ 'beancount': ['file.beancount'], \ 'bib': ['file.bib'], \ 'bicep': ['file.bicep', 'file.bicepparam'], - \ 'bindzone': ['named.root', '/bind/db.file', '/named/db.file', 'any/bind/db.file', 'any/named/db.file'], + \ 'bindzone': ['named.root', '/bind/db.file', '/named/db.file', 'any/bind/db.file', 'any/named/db.file', 'foobar.zone'], \ 'bitbake': ['file.bb', 'file.bbappend', 'file.bbclass', 'build/conf/local.conf', 'meta/conf/layer.conf', 'build/conf/bbappend.conf', 'meta-layer/conf/distro/foo.conf'], \ 'blade': ['file.blade.php'], \ 'blank': ['file.bl'], @@ -143,6 +145,7 @@ func s:GetFilenameChecks() abort \ 'cdl': ['file.cdl'], \ 'cdrdaoconf': ['/etc/cdrdao.conf', '/etc/defaults/cdrdao', '/etc/default/cdrdao', '.cdrdao', 'any/etc/cdrdao.conf', 'any/etc/default/cdrdao', 'any/etc/defaults/cdrdao'], \ 'cdrtoc': ['file.toc'], + \ 'cedar': ['file.cedar'], \ 'cf': ['file.cfm', 'file.cfi', 'file.cfc'], \ 'cfengine': ['cfengine.conf'], \ 'cfg': ['file.hgrc', 'filehgrc', 'hgrc', 'some-hgrc'], @@ -161,7 +164,7 @@ func s:GetFilenameChecks() abort \ 'cmakecache': ['CMakeCache.txt'], \ 'cmod': ['file.cmod'], \ 'cmusrc': ['any/.cmus/autosave', 'any/.cmus/rc', 'any/.cmus/command-history', 'any/.cmus/file.theme', 'any/cmus/rc', 'any/cmus/file.theme', '/.cmus/autosave', '/.cmus/command-history', '/.cmus/file.theme', '/.cmus/rc', '/cmus/file.theme', '/cmus/rc'], - \ 'cobol': ['file.cbl', 'file.cob', 'file.lib'], + \ 'cobol': ['file.cbl', 'file.cob'], \ 'coco': ['file.atg'], \ 'conaryrecipe': ['file.recipe'], \ 'conf': ['auto.master', 'file.conf', 'texdoc.cnf', '.x11vncrc', '.chktexrc', '.ripgreprc', 'ripgreprc', 'file.ctags', '.mbsyncrc'], @@ -221,13 +224,14 @@ func s:GetFilenameChecks() abort \ '.coveragerc', '.pypirc', '.gitlint', '.oelint.cfg', 'pylintrc', '.pylintrc', \ '/home/user/.config/bpython/config', '/home/user/.config/mypy/config', '.wakatime.cfg', '.replyrc', \ 'psprint.conf', 'sofficerc', 'any/.config/lxqt/globalkeyshortcuts.conf', 'any/.config/screengrab/screengrab.conf', - \ 'any/.local/share/flatpak/repo/config', '.notmuch-config'], + \ 'any/.local/share/flatpak/repo/config', '.notmuch-config', '.notmuch-config.myprofile', + \ '~/.config/notmuch/myprofile/config'] + s:WhenConfigHome('$XDG_CONFIG_HOME/notmuch/myprofile/config'), \ 'dot': ['file.dot', 'file.gv'], - \ 'dracula': ['file.drac', 'file.drc', 'filelvs', 'filelpe', 'drac.file', 'lpe', 'lvs', 'some-lpe', 'some-lvs'], + \ 'dracula': ['file.drac', 'file.drc', 'file.lvs', 'file.lpe', 'drac.file'], \ 'dtd': ['file.dtd'], \ 'dtrace': ['/usr/lib/dtrace/io.d'], \ 'dts': ['file.dts', 'file.dtsi', 'file.dtso', 'file.its', 'file.keymap'], - \ 'dune': ['jbuild', 'dune', 'dune-project', 'dune-workspace'], + \ 'dune': ['jbuild', 'dune', 'dune-project', 'dune-workspace', 'dune-file'], \ 'dylan': ['file.dylan'], \ 'dylanintr': ['file.intr'], \ 'dylanlid': ['file.lid'], @@ -257,6 +261,7 @@ func s:GetFilenameChecks() abort \ 'factor': ['file.factor'], \ 'falcon': ['file.fal'], \ 'fan': ['file.fan', 'file.fwt'], + \ 'faust': ['file.dsp', 'file.lib'], \ 'fennel': ['file.fnl'], \ 'fetchmail': ['.fetchmailrc'], \ 'fgl': ['file.4gl', 'file.4gh', 'file.m4gl'], @@ -286,17 +291,18 @@ func s:GetFilenameChecks() abort \ 'gitattributes': ['file.git/info/attributes', '.gitattributes', '/.config/git/attributes', '/etc/gitattributes', '/usr/local/etc/gitattributes', 'some.git/info/attributes'] + s:WhenConfigHome('$XDG_CONFIG_HOME/git/attributes'), \ 'gitcommit': ['COMMIT_EDITMSG', 'MERGE_MSG', 'TAG_EDITMSG', 'NOTES_EDITMSG', 'EDIT_DESCRIPTION'], \ 'gitconfig': ['file.git/config', 'file.git/config.worktree', 'file.git/worktrees/x/config.worktree', '.gitconfig', '.gitmodules', 'file.git/modules//config', '/.config/git/config', '/etc/gitconfig', '/usr/local/etc/gitconfig', '/etc/gitconfig.d/file', 'any/etc/gitconfig.d/file', '/.gitconfig.d/file', 'any/.config/git/config', 'any/.gitconfig.d/file', 'some.git/config', 'some.git/modules/any/config'] + s:WhenConfigHome('$XDG_CONFIG_HOME/git/config'), - \ 'gitignore': ['file.git/info/exclude', '.gitignore', '/.config/git/ignore', 'some.git/info/exclude'] + s:WhenConfigHome('$XDG_CONFIG_HOME/git/ignore'), + \ 'gitignore': ['file.git/info/exclude', '.gitignore', '/.config/git/ignore', 'some.git/info/exclude'] + s:WhenConfigHome('$XDG_CONFIG_HOME/git/ignore') + ['.prettierignore'], \ 'gitolite': ['gitolite.conf', '/gitolite-admin/conf/file', 'any/gitolite-admin/conf/file'], \ 'gitrebase': ['git-rebase-todo'], \ 'gitsendemail': ['.gitsendemail.msg.xxxxxx'], \ 'gkrellmrc': ['gkrellmrc', 'gkrellmrc_x'], \ 'gleam': ['file.gleam'], - \ 'glsl': ['file.glsl'], + \ 'glsl': ['file.glsl', 'file.vert', 'file.tesc', 'file.tese', 'file.geom', 'file.frag', 'file.comp', 'file.rgen', 'file.rmiss', 'file.rchit', 'file.rahit', 'file.rint', 'file.rcall'], \ 'gn': ['file.gn', 'file.gni'], \ 'gnash': ['gnashrc', '.gnashrc', 'gnashpluginrc', '.gnashpluginrc'], \ 'gnuplot': ['file.gpi', '.gnuplot', 'file.gnuplot', '.gnuplot_history'], \ 'go': ['file.go'], + \ 'goaccess': ['goaccess.conf'], \ 'gomod': ['go.mod'], \ 'gosum': ['go.sum', 'go.work.sum'], \ 'gowork': ['go.work'], @@ -334,6 +340,7 @@ func s:GetFilenameChecks() abort \ 'hostconf': ['/etc/host.conf', 'any/etc/host.conf'], \ 'hostsaccess': ['/etc/hosts.allow', '/etc/hosts.deny', 'any/etc/hosts.allow', 'any/etc/hosts.deny'], \ 'html': ['file.html', 'file.htm', 'file.cshtml', 'file.component.html'], + \ 'http': ['file.http'], \ 'htmlm4': ['file.html.m4'], \ 'httest': ['file.htt', 'file.htb'], \ 'hurl': ['file.hurl'], @@ -356,16 +363,17 @@ func s:GetFilenameChecks() abort \ 'janet': ['file.janet'], \ 'java': ['file.java', 'file.jav'], \ 'javacc': ['file.jj', 'file.jjt'], - \ 'javascript': ['file.js', 'file.jsm', 'file.javascript', 'file.es', 'file.mjs', 'file.cjs', '.node_repl_history'], + \ 'javascript': ['file.js', 'file.jsm', 'file.javascript', 'file.es', 'file.mjs', 'file.cjs', '.node_repl_history', '.bun_repl_history', 'deno_history.txt'], \ 'javascript.glimmer': ['file.gjs'], \ 'javascriptreact': ['file.jsx'], \ 'jess': ['file.clp'], \ 'jgraph': ['file.jgr'], + \ 'jinja': ['file.jinja'], \ 'jj': ['file.jjdescription'], \ 'jq': ['file.jq'], \ 'jovial': ['file.jov', 'file.j73', 'file.jovial'], \ 'jproperties': ['file.properties', 'file.properties_xx', 'file.properties_xx_xx', 'some.properties_xx_xx_file', 'org.eclipse.xyz.prefs'], - \ 'json': ['file.json', 'file.jsonp', 'file.json-patch', 'file.geojson', 'file.webmanifest', 'Pipfile.lock', 'file.ipynb', 'file.jupyterlab-settings', '.prettierrc', '.firebaserc', '.stylelintrc', 'file.slnf', 'file.sublime-project', 'file.sublime-settings', 'file.sublime-workspace', 'file.bd', 'file.bda', 'file.xci', 'flake.lock'], + \ 'json': ['file.json', 'file.jsonp', 'file.json-patch', 'file.geojson', 'file.webmanifest', 'Pipfile.lock', 'file.ipynb', 'file.jupyterlab-settings', '.prettierrc', '.firebaserc', '.stylelintrc', '.lintstagedrc', 'file.slnf', 'file.sublime-project', 'file.sublime-settings', 'file.sublime-workspace', 'file.bd', 'file.bda', 'file.xci', 'flake.lock', 'pack.mcmeta', 'deno.lock'], \ 'json5': ['file.json5'], \ 'jsonc': ['file.jsonc', '.babelrc', '.eslintrc', '.jsfmtrc', '.jshintrc', '.jscsrc', '.vsconfig', '.hintrc', '.swrc', 'jsconfig.json', 'tsconfig.json', 'tsconfig.test.json', 'tsconfig-test.json', '.luaurc'], \ 'jsonl': ['file.jsonl'], @@ -384,6 +392,7 @@ func s:GetFilenameChecks() abort \ 'lace': ['file.ace', 'file.ACE'], \ 'latte': ['file.latte', 'file.lte'], \ 'ld': ['file.ld', 'any/usr/lib/aarch64-xilinx-linux/ldscripts/aarch64elf32b.x'], + \ 'ldapconf': ['ldap.conf', '.ldaprc', 'ldaprc'], \ 'ldif': ['file.ldif'], \ 'lean': ['file.lean'], \ 'ledger': ['file.ldg', 'file.ledger', 'file.journal'], @@ -421,18 +430,19 @@ func s:GetFilenameChecks() abort \ 'mail': ['snd.123', '.letter', '.letter.123', '.followup', '.article', '.article.123', 'pico.123', 'mutt-xx-xxx', 'muttng-xx-xxx', 'ae123.txt', 'file.eml', 'reportbug-file'], \ 'mailaliases': ['/etc/mail/aliases', '/etc/aliases', 'any/etc/aliases', 'any/etc/mail/aliases'], \ 'mailcap': ['.mailcap', 'mailcap'], - \ 'make': ['file.mk', 'file.mak', 'file.dsp', 'makefile', 'Makefile', 'makefile-file', 'Makefile-file', 'some-makefile', 'some-Makefile', 'Kbuild'], + \ 'make': ['file.mk', 'file.mak', 'makefile', 'Makefile', 'makefile-file', 'Makefile-file', 'some-makefile', 'some-Makefile', 'Kbuild'], \ 'mallard': ['file.page'], "\ 'man': ['file.man'], \ 'manconf': ['/etc/man.conf', 'man.config', 'any/etc/man.conf'], \ 'map': ['file.map'], \ 'maple': ['file.mv', 'file.mpl', 'file.mws'], \ 'markdown': ['file.markdown', 'file.mdown', 'file.mkd', 'file.mkdn', 'file.mdwn', 'file.md'], - \ 'mason': ['file.mason', 'file.mhtml', 'file.comp'], + \ 'mason': ['file.mason', 'file.mhtml'], \ 'master': ['file.mas', 'file.master'], \ 'matlab': ['file.m'], \ 'maxima': ['file.demo', 'file.dmt', 'file.dm1', 'file.dm2', 'file.dm3', \ 'file.wxm', 'maxima-init.mac'], + \ 'mediawiki': ['file.mw', 'file.wiki'], \ 'mel': ['file.mel'], \ 'mermaid': ['file.mmd', 'file.mmdc', 'file.mermaid'], \ 'meson': ['meson.build', 'meson.options', 'meson_options.txt'], @@ -467,7 +477,7 @@ func s:GetFilenameChecks() abort \ 'mgp': ['file.mgp'], \ 'mib': ['file.mib', 'file.my'], \ 'mix': ['file.mix', 'file.mixal'], - \ 'mma': ['file.nb'], + \ 'mma': ['file.nb', 'file.wl'], \ 'mmp': ['file.mmp'], \ 'modconf': ['/etc/modules.conf', '/etc/modules', '/etc/conf.modules', '/etc/modprobe.file', 'any/etc/conf.modules', 'any/etc/modprobe.file', 'any/etc/modules', 'any/etc/modules.conf'], \ 'modula3': ['file.m3', 'file.mg', 'file.i3', 'file.ig', 'file.lm3'], @@ -523,7 +533,7 @@ func s:GetFilenameChecks() abort \ 'odin': ['file.odin'], \ 'omnimark': ['file.xom', 'file.xin'], \ 'ondir': ['.ondirrc'], - \ 'opam': ['opam', 'file.opam', 'file.opam.template'], + \ 'opam': ['opam', 'file.opam', 'file.opam.template', 'opam.locked', 'file.opam.locked'], \ 'openroad': ['file.or'], \ 'openscad': ['file.scad'], \ 'openvpn': ['file.ovpn', '/etc/openvpn/client/client.conf', '/usr/share/openvpn/examples/server.conf'], @@ -593,6 +603,8 @@ func s:GetFilenameChecks() abort \ 'radiance': ['file.rad', 'file.mat'], \ 'raku': ['file.pm6', 'file.p6', 'file.t6', 'file.pod6', 'file.raku', 'file.rakumod', 'file.rakudoc', 'file.rakutest'], \ 'raml': ['file.raml'], + \ 'rapid': ['file.sysx', 'file.Sysx', 'file.SysX', 'file.SYSx', 'file.SYSX', 'file.modx', 'file.Modx', 'file.ModX', 'file.MODx', 'file.MODX'], + \ 'rasi': ['file.rasi'], \ 'ratpoison': ['.ratpoisonrc', 'ratpoisonrc'], \ 'rbs': ['file.rbs'], \ 'rc': ['file.rc', 'file.rch'], @@ -624,6 +636,7 @@ func s:GetFilenameChecks() abort \ 'rtf': ['file.rtf'], \ 'ruby': ['.irbrc', 'irbrc', '.irb_history', 'irb_history', 'file.rb', 'file.rbw', 'file.gemspec', 'file.ru', 'Gemfile', 'file.builder', 'file.rxml', 'file.rjs', 'file.rant', 'file.rake', 'rakefile', 'Rakefile', 'rantfile', 'Rantfile', 'rakefile-file', 'Rakefile-file', 'Puppetfile', 'Vagrantfile'], \ 'rust': ['file.rs'], + \ 'salt': ['file.sls'], \ 'samba': ['smb.conf'], \ 'sas': ['file.sas'], \ 'sass': ['file.sass'], @@ -645,7 +658,8 @@ func s:GetFilenameChecks() abort \ 'sh': ['.bashrc', '.bash_profile', '.bash-profile', '.bash_logout', '.bash-logout', '.bash_aliases', '.bash-aliases', '.bash_history', '.bash-history', \ '/tmp/bash-fc-3Ozjlw', '/tmp/bash-fc.3Ozjlw', 'PKGBUILD', 'APKBUILD', 'file.bash', '/usr/share/doc/bash-completion/filter.sh', \ '/etc/udev/cdsymlinks.conf', 'any/etc/udev/cdsymlinks.conf', 'file.bats', '.ash_history', 'any/etc/neofetch/config.conf', '.xprofile', - \ 'user-dirs.defaults', 'user-dirs.dirs', 'makepkg.conf', '.makepkg.conf', 'file.mdd', 'file.cygport'], + \ 'user-dirs.defaults', 'user-dirs.dirs', 'makepkg.conf', '.makepkg.conf', 'file.mdd', 'file.cygport', '.env', '.envrc', 'devscripts.conf', + \ '.devscripts'], \ 'sieve': ['file.siv', 'file.sieve'], \ 'sil': ['file.sil'], \ 'simula': ['file.sim'], @@ -669,6 +683,7 @@ func s:GetFilenameChecks() abort \ 'smith': ['file.smt', 'file.smith'], \ 'smithy': ['file.smithy'], \ 'sml': ['file.sml'], + \ 'snakemake': ['file.smk', 'Snakefile'], \ 'snobol4': ['file.sno', 'file.spt'], \ 'solidity': ['file.sol'], \ 'solution': ['file.sln'], @@ -700,7 +715,7 @@ func s:GetFilenameChecks() abort \ 'svg': ['file.svg'], \ 'svn': ['svn-commitfile.tmp', 'svn-commit-file.tmp', 'svn-commit.tmp'], \ 'swayconfig': ['/home/user/.sway/config', '/home/user/.config/sway/config', '/etc/sway/config', '/etc/xdg/sway/config'], - \ 'swift': ['file.swift'], + \ 'swift': ['file.swift', 'file.swiftinterface'], \ 'swiftgyb': ['file.swift.gyb'], \ 'swig': ['file.swg', 'file.swig'], \ 'sysctl': ['/etc/sysctl.conf', '/etc/sysctl.d/file.conf', 'any/etc/sysctl.conf', 'any/etc/sysctl.d/file.conf'], @@ -758,7 +773,7 @@ func s:GetFilenameChecks() abort \ 'teraterm': ['file.ttl'], \ 'terminfo': ['file.ti'], \ 'terraform-vars': ['file.tfvars'], - \ 'tex': ['file.latex', 'file.sty', 'file.dtx', 'file.ltx', 'file.bbl', 'any/.texlive/texmf-config/tex/latex/file/file.cfg', 'file.pgf', 'file.nlo', 'file.nls', 'file.thm', 'file.eps_tex', 'file.pygtex', 'file.pygstyle', 'file.clo', 'file.aux', 'file.brf', 'file.ind', 'file.lof', 'file.loe', 'file.nav', 'file.vrb', 'file.ins', 'file.tikz', 'file.bbx', 'file.cbx', 'file.beamer'], + \ 'tex': ['file.latex', 'file.sty', 'file.dtx', 'file.ltx', 'file.bbl', 'any/.texlive/texmf-config/tex/latex/file/file.cfg', 'file.pgf', 'file.nlo', 'file.nls', 'file.thm', 'file.eps_tex', 'file.pygtex', 'file.pygstyle', 'file.clo', 'file.aux', 'file.brf', 'file.ind', 'file.lof', 'file.loe', 'file.nav', 'file.vrb', 'file.ins', 'file.tikz', 'file.bbx', 'file.cbx', 'file.beamer', 'file.pdf_tex'], \ 'texinfo': ['file.texinfo', 'file.texi', 'file.txi'], \ 'texmf': ['texmf.cnf'], \ 'text': ['file.text', 'file.txt', 'README', 'LICENSE', 'COPYING', 'AUTHORS', '/usr/share/doc/bash-completion/AUTHORS', '/etc/apt/apt.conf.d/README', '/etc/Muttrc.d/README'], @@ -853,6 +868,8 @@ func s:GetFilenameChecks() abort \ 'z8a': ['file.z8a'], \ 'zathurarc': ['zathurarc'], \ 'zig': ['file.zig', 'build.zig.zon'], + \ 'ziggy': ['file.ziggy'], + \ 'ziggy_schema': ['file.ziggy-schema'], \ 'zimbu': ['file.zu'], \ 'zimbutempl': ['file.zut'], \ 'zserio': ['file.zs'], @@ -990,6 +1007,7 @@ func s:GetScriptChecks() abort \ ['#!/path/regina']], \ 'janet': [['#!/path/janet']], \ 'dart': [['#!/path/dart']], + \ 'vim': [['#!/path/vim']], \ } endfunc @@ -1038,7 +1056,8 @@ func Test_emptybuf_ftdetect() call assert_equal('', &filetype) filetype detect call assert_equal('sh', &filetype) - close! + " close the swapfile + bw! endfunc " Test for ':filetype indent on' and ':filetype indent off' commands @@ -1136,15 +1155,14 @@ func Test_cfg_file() unlet g:filetype_cfg " RAPID cfg - let ext = 'cfg' for i in ['EIO', 'MMC', 'MOC', 'PROC', 'SIO', 'SYS'] - call writefile([i .. ':CFG'], 'cfgfile.' .. ext) - execute "split cfgfile." .. ext - call assert_equal('rapid', &filetype) - bwipe! - call delete('cfgfile.' .. ext) - " check different case of file extension - let ext = substitute(ext, '\(\l\)', '\u\1', '') + for ext in ['cfg', 'Cfg', 'CFG'] + call writefile([i .. ':CFG'], 'cfgfile.' .. ext) + execute "split cfgfile." .. ext + call assert_equal('rapid', &filetype) + bwipe! + call delete('cfgfile.' .. ext) + endfor endfor " clean up @@ -1511,6 +1529,41 @@ func Test_git_file() filetype off endfunc +func Test_haredoc_file() + filetype on + call assert_true(mkdir('foo/bar', 'pR')) + + call writefile([], 'README', 'D') + split README + call assert_notequal('haredoc', &filetype) + bwipe! + + let g:filetype_haredoc = 1 + split README + call assert_notequal('haredoc', &filetype) + bwipe! + + call writefile([], 'foo/quux.ha') + split README + call assert_equal('haredoc', &filetype) + bwipe! + call delete('foo/quux.ha') + + call writefile([], 'foo/bar/baz.ha', 'D') + split README + call assert_notequal('haredoc', &filetype) + bwipe! + + let g:haredoc_search_depth = 2 + split README + call assert_equal('haredoc', &filetype) + bwipe! + unlet g:filetype_haredoc + unlet g:haredoc_search_depth + + filetype off +endfunc + func Test_hook_file() filetype on @@ -1527,6 +1580,73 @@ func Test_hook_file() filetype off endfunc +func Test_html_file() + filetype on + + " HTML Angular + let content = ['@for (item of items; track item.name) {', ' <li> {{ item.name }}</li>', '} @empty {', ' <li> There are no items.</li>', '}'] + call writefile(content, 'Xfile.html', 'D') + split Xfile.html + call assert_equal('htmlangular', &filetype) + bwipe! + + " Django Template + let content = ['{% if foobar %}', + \ ' <ul>', + \ ' {% for question in list %}', + \ ' <li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>', + \ ' {% endfor %}', + \ ' </ul>', + \ '{% else %}', + \ ' <p>No polls are available.</p>', + \ '{% endif %}'] + call writefile(content, 'Xfile.html', 'D') + split Xfile.html + call assert_equal('htmldjango', &filetype) + bwipe! + + " Super html layout + let content = ['<extend template="base.shtml">', + \ '<title id="title" var="$page.title"></title>', + \ '<head id="head"></head>', + \ '<div id="content">', + \ '</div>'] + call writefile(content, 'Xfile.shtml', 'D') + split Xfile.shtml + call assert_equal('superhtml', &filetype) + bwipe! + + " Super html template + let content = ['<!DOCTYPE html>', + \ '<html>', + \ ' <head id="head">', + \ ' <title id="title">', + \ ' <super>', + \ ' suffix', + \ ' </title>', + \ ' <super>', + \ ' </head>', + \ ' <body>', + \ ' <div id="content">', + \ ' <super>', + \ ' </div>', + \ ' </body>', + \ '</html>'] + call writefile(content, 'Xfile.shtml', 'D') + split Xfile.shtml + call assert_equal('superhtml', &filetype) + bwipe! + + " regular HTML + let content = ['<!DOCTYPE html>', '<html>', ' <head>Foobar</head>', ' <body>Content', ' </body>', '</html>'] + call writefile(content, 'Xfile.html', 'D') + split Xfile.html + call assert_equal('html', &filetype) + bwipe! + + filetype off +endfunc + func Test_m_file() filetype on @@ -2361,6 +2481,32 @@ func Test_typ_file() filetype off endfunc +func Test_dsp_file() + filetype on + + " Microsoft Developer Studio Project file + + call writefile(['# Microsoft Developer Studio Project File'], 'Xfile.dsp', 'D') + split Xfile.dsp + call assert_equal('make', &filetype) + bwipe! + + let g:filetype_dsp = 'make' + split test.dsp + call assert_equal('make', &filetype) + bwipe! + unlet g:filetype_dsp + + " Faust + + call writefile(['this is a fallback'], 'Xfile.dsp') + split Xfile.dsp + call assert_equal('faust', &filetype) + bwipe! + + filetype off +endfunc + func Test_vba_file() filetype on @@ -2469,4 +2615,86 @@ func Test_uci_file() filetype off endfunc +func Test_pro_file() + filetype on + + "Prolog + call writefile([':-module(test/1,'], 'Xfile.pro', 'D') + split Xfile.pro + call assert_equal('prolog', &filetype) + bwipe! + + call writefile(['% comment'], 'Xfile.pro', 'D') + split Xfile.pro + call assert_equal('prolog', &filetype) + bwipe! + + call writefile(['/* multiline comment'], 'Xfile.pro', 'D') + split Xfile.pro + call assert_equal('prolog', &filetype) + bwipe! + + call writefile(['rule(test, 1.7).'], 'Xfile.pro', 'D') + split Xfile.pro + call assert_equal('prolog', &filetype) + bwipe! + + " IDL + call writefile(['x = findgen(100)/10'], 'Xfile.pro', 'D') + split Xfile.pro + call assert_equal('idlang', &filetype) + + filetype off +endfunc + + +func Test_pl_file() + filetype on + + "Prolog + call writefile([':-module(test/1,'], 'Xfile.pl', 'D') + split Xfile.pl + call assert_equal('prolog', &filetype) + bwipe! + + call writefile(['% comment'], 'Xfile.pl', 'D') + split Xfile.pl + call assert_equal('prolog', &filetype) + bwipe! + + call writefile(['/* multiline comment'], 'Xfile.pl', 'D') + split Xfile.pl + call assert_equal('prolog', &filetype) + bwipe! + + call writefile(['rule(test, 1.7).'], 'Xfile.pl', 'D') + split Xfile.pl + call assert_equal('prolog', &filetype) + bwipe! + + " Perl + call writefile(['%data = (1, 2, 3);'], 'Xfile.pl', 'D') + split Xfile.pl + call assert_equal('perl', &filetype) + + filetype off +endfunc + +func Test_make_file() + filetype on + + " Microsoft Makefile + call writefile(['# Makefile for Windows', '!if "$(VIMDLL)" == "yes"'], 'XMakefile.mak', 'D') + split XMakefile.mak + call assert_equal(1, get(b:, 'make_microsoft', 0)) + bwipe! + + call writefile(['# get the list of tests', 'include testdir/Make_all.mak'], 'XMakefile.mak', 'D') + split XMakefile.mak + call assert_equal(0, get(b:, 'make_microsoft', 0)) + bwipe! + + filetype off +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/test_findfile.vim b/test/old/testdir/test_findfile.vim index 0f4b30aec2..06d781ed69 100644 --- a/test/old/testdir/test_findfile.vim +++ b/test/old/testdir/test_findfile.vim @@ -1,14 +1,14 @@ " Test findfile() and finddir() -let s:files = [ 'Xdir1/foo', - \ 'Xdir1/bar', - \ 'Xdir1/Xdir2/foo', - \ 'Xdir1/Xdir2/foobar', - \ 'Xdir1/Xdir2/Xdir3/bar', - \ 'Xdir1/Xdir2/Xdir3/barfoo' ] +let s:files = [ 'Xfinddir1/foo', + \ 'Xfinddir1/bar', + \ 'Xfinddir1/Xdir2/foo', + \ 'Xfinddir1/Xdir2/foobar', + \ 'Xfinddir1/Xdir2/Xdir3/bar', + \ 'Xfinddir1/Xdir2/Xdir3/barfoo' ] func CreateFiles() - call mkdir('Xdir1/Xdir2/Xdir3/Xdir2', 'p') + call mkdir('Xfinddir1/Xdir2/Xdir3/Xdir2', 'p') for f in s:files call writefile([], f) endfor @@ -16,15 +16,15 @@ endfunc func CleanFiles() " Safer to delete each file even if it's more verbose - " than doing a recursive delete('Xdir1', 'rf'). + " than doing a recursive delete('Xfinddir1', 'rf'). for f in s:files call delete(f) endfor - call delete('Xdir1/Xdir2/Xdir3/Xdir2', 'd') - call delete('Xdir1/Xdir2/Xdir3', 'd') - call delete('Xdir1/Xdir2', 'd') - call delete('Xdir1', 'd') + call delete('Xfinddir1/Xdir2/Xdir3/Xdir2', 'd') + call delete('Xfinddir1/Xdir2/Xdir3', 'd') + call delete('Xfinddir1/Xdir2', 'd') + call delete('Xfinddir1', 'd') endfunc " Test findfile({name} [, {path} [, {count}]]) @@ -34,7 +34,7 @@ func Test_findfile() let save_dir = getcwd() set shellslash call CreateFiles() - cd Xdir1 + cd Xfinddir1 e Xdir2/foo " With ,, in path, findfile() searches in current directory. @@ -83,34 +83,68 @@ func Test_findfile() " Test upwards search. cd Xdir2/Xdir3 call assert_equal('bar', findfile('bar', ';')) - call assert_match('.*/Xdir1/Xdir2/foo', findfile('foo', ';')) - call assert_match('.*/Xdir1/Xdir2/foo', findfile('foo', ';', 1)) - call assert_match('.*/Xdir1/foo', findfile('foo', ';', 2)) - call assert_match('.*/Xdir1/foo', findfile('foo', ';', 2)) - call assert_match('.*/Xdir1/Xdir2/foo', findfile('foo', 'Xdir2;', 1)) + call assert_match('.*/Xfinddir1/Xdir2/foo', findfile('foo', ';')) + call assert_match('.*/Xfinddir1/Xdir2/foo', findfile('foo', ';', 1)) + call assert_match('.*/Xfinddir1/foo', findfile('foo', ';', 2)) + call assert_match('.*/Xfinddir1/foo', findfile('foo', ';', 2)) + call assert_match('.*/Xfinddir1/Xdir2/foo', findfile('foo', 'Xdir2;', 1)) call assert_equal('', findfile('foo', 'Xdir2;', 2)) " List l should have at least 2 values (possibly more if foo file - " happens to be found upwards above Xdir1). + " happens to be found upwards above Xfinddir1). let l = findfile('foo', ';', -1) - call assert_match('.*/Xdir1/Xdir2/foo', l[0]) - call assert_match('.*/Xdir1/foo', l[1]) + call assert_match('.*/Xfinddir1/Xdir2/foo', l[0]) + call assert_match('.*/Xfinddir1/foo', l[1]) " Test upwards search with stop-directory. cd Xdir2 - let l = findfile('bar', ';' . save_dir . '/Xdir1/Xdir2/', -1) + let l = findfile('bar', ';' . save_dir . '/Xfinddir1/Xdir2/Xdir3/', -1) call assert_equal(1, len(l)) - call assert_match('.*/Xdir1/Xdir2/Xdir3/bar', l[0]) + call assert_match('.*/Xfinddir1/Xdir2/Xdir3/bar', l[0]) + let l = findfile('bar', ';' . save_dir . '/Xfinddir1/Xdir2/Xdir3', -1) + call assert_equal(1, len(l)) + call assert_match('.*/Xfinddir1/Xdir2/Xdir3/bar', l[0]) + let l = findfile('bar', ';../', -1) + call assert_equal(1, len(l)) + call assert_match('.*/Xfinddir1/Xdir2/Xdir3/bar', l[0]) + let l = findfile('bar', ';..', -1) + call assert_equal(1, len(l)) + call assert_match('.*/Xfinddir1/Xdir2/Xdir3/bar', l[0]) - let l = findfile('bar', ';' . save_dir . '/Xdir1/', -1) + let l = findfile('bar', ';' . save_dir . '/Xfinddir1/Xdir2/', -1) + call assert_equal(1, len(l)) + call assert_match('.*/Xfinddir1/Xdir2/Xdir3/bar', l[0]) + let l = findfile('bar', ';' . save_dir . '/Xfinddir1/Xdir2', -1) + call assert_equal(1, len(l)) + call assert_match('.*/Xfinddir1/Xdir2/Xdir3/bar', l[0]) + let l = findfile('bar', ';../../', -1) + call assert_equal(1, len(l)) + call assert_match('.*/Xfinddir1/Xdir2/Xdir3/bar', l[0]) + let l = findfile('bar', ';../..', -1) + call assert_equal(1, len(l)) + call assert_match('.*/Xfinddir1/Xdir2/Xdir3/bar', l[0]) + + let l = findfile('bar', ';' . save_dir . '/Xfinddir1/', -1) + call assert_equal(2, len(l)) + call assert_match('.*/Xfinddir1/Xdir2/Xdir3/bar', l[0]) + call assert_match('.*/Xfinddir1/bar', l[1]) + let l = findfile('bar', ';' . save_dir . '/Xfinddir1', -1) + call assert_equal(2, len(l)) + call assert_match('.*/Xfinddir1/Xdir2/Xdir3/bar', l[0]) + call assert_match('.*/Xfinddir1/bar', l[1]) + let l = findfile('bar', ';../../../', -1) + call assert_equal(2, len(l)) + call assert_match('.*/Xfinddir1/Xdir2/Xdir3/bar', l[0]) + call assert_match('.*/Xfinddir1/bar', l[1]) + let l = findfile('bar', ';../../..', -1) call assert_equal(2, len(l)) - call assert_match('.*/Xdir1/Xdir2/Xdir3/bar', l[0]) - call assert_match('.*/Xdir1/bar', l[1]) + call assert_match('.*/Xfinddir1/Xdir2/Xdir3/bar', l[0]) + call assert_match('.*/Xfinddir1/bar', l[1]) " Test combined downwards and upwards search from Xdir2/. cd ../.. call assert_equal('Xdir3/bar', findfile('bar', '**;', 1)) - call assert_match('.*/Xdir1/bar', findfile('bar', '**;', 2)) + call assert_match('.*/Xfinddir1/bar', findfile('bar', '**;', 2)) bwipe! call chdir(save_dir) @@ -135,7 +169,7 @@ func Test_finddir() set path=,, set shellslash call CreateFiles() - cd Xdir1 + cd Xfinddir1 call assert_equal('Xdir2', finddir('Xdir2')) call assert_equal('', 'Xdir3'->finddir()) @@ -158,17 +192,17 @@ func Test_finddir() " Test upwards dir search. cd Xdir2/Xdir3 - call assert_match('.*/Xdir1', finddir('Xdir1', ';')) + call assert_match('.*/Xfinddir1', finddir('Xfinddir1', ';')) " Test upwards search with stop-directory. - call assert_match('.*/Xdir1', finddir('Xdir1', ';' . save_dir . '/')) - call assert_equal('', finddir('Xdir1', ';' . save_dir . '/Xdir1/')) + call assert_match('.*/Xfinddir1', finddir('Xfinddir1', ';' . save_dir . '/')) + call assert_equal('', finddir('Xfinddir1', ';' . save_dir . '/Xfinddir1/')) " Test combined downwards and upwards dir search from Xdir2/. cd .. - call assert_match('.*/Xdir1', finddir('Xdir1', '**;', 1)) + call assert_match('.*/Xfinddir1', finddir('Xfinddir1', '**;', 1)) call assert_equal('Xdir3/Xdir2', finddir('Xdir2', '**;', 1)) - call assert_match('.*/Xdir1/Xdir2', finddir('Xdir2', '**;', 2)) + call assert_match('.*/Xfinddir1/Xdir2', finddir('Xdir2', '**;', 2)) call assert_equal('Xdir3', finddir('Xdir3', '**;', 1)) call chdir(save_dir) @@ -192,7 +226,7 @@ func Test_find_cmd() let save_dir = getcwd() set path=.,./**/* call CreateFiles() - cd Xdir1 + cd Xfinddir1 " Test for :find find foo diff --git a/test/old/testdir/test_flatten.vim b/test/old/testdir/test_flatten.vim index aa91060313..035ca18f73 100644 --- a/test/old/testdir/test_flatten.vim +++ b/test/old/testdir/test_flatten.vim @@ -53,7 +53,7 @@ func Test_flatten() call test_garbagecollect_now() call assert_equal([1, 2, 3], l:list) - " Tests for checking circular reference list can be flatten. + " Tests for checking circular reference list can be flattened. let l:x = [1] let l:y = [x] let l:z = flatten(l:y) diff --git a/test/old/testdir/test_fnamemodify.vim b/test/old/testdir/test_fnamemodify.vim index 258a2093bd..f0bc2503b5 100644 --- a/test/old/testdir/test_fnamemodify.vim +++ b/test/old/testdir/test_fnamemodify.vim @@ -12,6 +12,10 @@ func Test_fnamemodify() call assert_equal('r', fnamemodify('.', ':p:h')[-1:]) call assert_equal('t', fnamemodify('test.out', ':p')[-1:]) call assert_equal($HOME .. "/foo" , fnamemodify('~/foo', ':p')) + call assert_equal(fnamemodify('.', ':p:h:h:h') .. '/', fnamemodify($HOME .. '/../', ':p')) + call assert_equal(fnamemodify('.', ':p:h:h:h') .. '/', fnamemodify($HOME .. '/..', ':p')) + call assert_equal(fnamemodify('.', ':p:h:h') .. '/', fnamemodify('../', ':p')) + call assert_equal(fnamemodify('.', ':p:h:h') .. '/', fnamemodify('..', ':p')) call assert_equal('test.out', fnamemodify('test.out', ':.')) call assert_equal('a', fnamemodify('../testdir/a', ':.')) call assert_equal('~/testdir/test.out', fnamemodify('test.out', ':~')) diff --git a/test/old/testdir/test_fold.vim b/test/old/testdir/test_fold.vim index eae6952e72..6569e032f6 100644 --- a/test/old/testdir/test_fold.vim +++ b/test/old/testdir/test_fold.vim @@ -8,7 +8,73 @@ func PrepIndent(arg) return [a:arg] + repeat(["\t".a:arg], 5) endfu -func Test_address_fold() +func Test_address_fold_new_default_commentstring() + " Test with the new commentstring defaults, that includes padding after v9.1.464 + new + call setline(1, ['int FuncName() {/* {{{ */', 1, 2, 3, 4, 5, '}/* }}} */', + \ 'after fold 1', 'after fold 2', 'after fold 3']) + setl fen fdm=marker + " The next commands should all copy the same part of the buffer, + " regardless of the addressing type, since the part to be copied + " is folded away + :1y + call assert_equal(['int FuncName() {/* {{{ */', '1', '2', '3', '4', '5', '}/* }}} */'], getreg(0,1,1)) + :.y + call assert_equal(['int FuncName() {/* {{{ */', '1', '2', '3', '4', '5', '}/* }}} */'], getreg(0,1,1)) + :.+y + call assert_equal(['int FuncName() {/* {{{ */', '1', '2', '3', '4', '5', '}/* }}} */'], getreg(0,1,1)) + :.,.y + call assert_equal(['int FuncName() {/* {{{ */', '1', '2', '3', '4', '5', '}/* }}} */'], getreg(0,1,1)) + :sil .1,.y + call assert_equal(['int FuncName() {/* {{{ */', '1', '2', '3', '4', '5', '}/* }}} */'], getreg(0,1,1)) + " use silent to make E493 go away + :sil .+,.y + call assert_equal(['int FuncName() {/* {{{ */', '1', '2', '3', '4', '5', '}/* }}} */'], getreg(0,1,1)) + :,y + call assert_equal(['int FuncName() {/* {{{ */', '1', '2', '3', '4', '5', '}/* }}} */'], getreg(0,1,1)) + :,+y + call assert_equal(['int FuncName() {/* {{{ */', '1', '2', '3', '4', '5', '}/* }}} */','after fold 1'], getreg(0,1,1)) + " using .+3 as second address should c opy the whole folded line + the next 3 + " lines + :.,+3y + call assert_equal(['int FuncName() {/* {{{ */', '1', '2', '3', '4', '5', '}/* }}} */', + \ 'after fold 1', 'after fold 2' , 'after fold 3'], getreg(0,1,1)) + :sil .,-2y + call assert_equal(['int FuncName() {/* {{{ */', '1', '2', '3', '4', '5', '}/* }}} */'], getreg(0,1,1)) + + " now test again with folding disabled + set nofoldenable + :1y + call assert_equal(['int FuncName() {/* {{{ */'], getreg(0,1,1)) + :.y + call assert_equal(['int FuncName() {/* {{{ */'], getreg(0,1,1)) + :.+y + call assert_equal(['1'], getreg(0,1,1) ) + :.,.y + call assert_equal(['int FuncName() {/* {{{ */'], getreg(0,1,1)) + " use silent to make E493 go away + :sil .1,.y + call assert_equal(['int FuncName() {/* {{{ */', '1'], getreg(0,1,1)) + " use silent to make E493 go away + :sil .+,.y + call assert_equal(['int FuncName() {/* {{{ */', '1'], getreg(0,1,1)) + :,y + call assert_equal(['int FuncName() {/* {{{ */'], getreg(0,1,1)) + :,+y + call assert_equal(['int FuncName() {/* {{{ */', '1'], getreg(0,1,1)) + " using .+3 as second address should c opy the whole folded line + the next 3 + " lines + :.,+3y + call assert_equal(['int FuncName() {/* {{{ */', '1', '2', '3'], getreg(0,1,1)) + :7 + :sil .,-2y + call assert_equal(['4', '5', '}/* }}} */'], getreg(0,1,1)) + + quit! +endfunc + +func Test_address_fold_old_default_commentstring() + " Test with the old commentstring defaults, before v9.1.464 new call setline(1, ['int FuncName() {/*{{{*/', 1, 2, 3, 4, 5, '}/*}}}*/', \ 'after fold 1', 'after fold 2', 'after fold 3']) @@ -320,6 +386,32 @@ func Test_foldexpr_no_interrupt_addsub() set foldmethod& foldexpr& endfunc +" Fold function defined in another script +func Test_foldexpr_compiled() + throw 'Skipped: Vim9 script is N/A' + new + let lines =<< trim END + vim9script + def FoldFunc(): number + return v:lnum + enddef + + set foldmethod=expr + set foldexpr=s:FoldFunc() + END + call writefile(lines, 'XfoldExpr', 'D') + source XfoldExpr + + call setline(1, ['one', 'two', 'three']) + redraw + call assert_equal(1, foldlevel(1)) + call assert_equal(2, foldlevel(2)) + call assert_equal(3, foldlevel(3)) + + bwipe! + set foldmethod& foldexpr& +endfunc + func Check_foldlevels(expected) call assert_equal(a:expected, map(range(1, line('$')), 'foldlevel(v:val)')) endfunc @@ -433,7 +525,7 @@ func Test_move_folds_around_manual() %foldopen! 13m7 call Check_foldlevels([1, 2, 2, 2, 1, 2, 2, 1, 1, 1, 2, 2, 2, 1, 0]) - + bw! endfunc @@ -682,7 +774,7 @@ func Test_fold_create_marker_in_C() let content =<< trim [CODE] /* * comment - * + * * */ int f(int* p) { @@ -696,7 +788,7 @@ func Test_fold_create_marker_in_C() call append(0, content) call cursor(c + 1, 1) norm! zfG - call assert_equal(content[c] . (c < 4 ? '{{{' : '/*{{{*/'), getline(c + 1)) + call assert_equal(content[c] . (c < 4 ? '{{{' : '/* {{{ */'), getline(c + 1)) endfor set fdm& fdl& @@ -1403,6 +1495,63 @@ func Test_foldtext_scriptlocal_func() delfunc s:FoldText endfunc +" Test for setting 'foldtext' from the modeline and executing the expression +" in a sandbox +func Test_foldtext_in_modeline() + func ModelineFoldText() + call feedkeys('aFoo', 'xt') + return "folded text" + endfunc + let lines =<< trim END + func T() + let i = 1 + endfunc + " vim: foldenable foldtext=ModelineFoldText() + END + call writefile(lines, 'Xmodelinefoldtext', 'D') + + set modeline modelineexpr + split Xmodelinefoldtext + + call cursor(1, 1) + normal! zf3j + call assert_equal('folded text', foldtextresult(1)) + call assert_equal(lines, getbufline('', 1, '$')) + + bw! + set modeline& modelineexpr& + delfunc ModelineFoldText +endfunc + +" Test for setting 'foldexpr' from the modeline and executing the expression +" in a sandbox +func Test_foldexpr_in_modeline() + func ModelineFoldExpr() + call feedkeys('aFoo', 'xt') + return strlen(matchstr(getline(v:lnum),'^\s*')) + endfunc + let lines =<< trim END + aaa + bbb + ccc + ccc + bbb + aaa + " vim: foldenable foldmethod=expr foldexpr=ModelineFoldExpr() + END + call writefile(lines, 'Xmodelinefoldexpr', 'D') + + set modeline modelineexpr + split Xmodelinefoldexpr + + call assert_equal(2, foldlevel(3)) + call assert_equal(lines, getbufline('', 1, '$')) + + bw! + set modeline& modelineexpr& + delfunc ModelineFoldExpr +endfunc + " Make sure a fold containing a nested fold is split correctly when using " foldmethod=indent func Test_fold_split() @@ -1420,6 +1569,8 @@ func Test_fold_split() call assert_equal([0, 1, 1, 2, 2], range(1, 5)->map('foldlevel(v:val)')) call append(2, 'line 2.5') call assert_equal([0, 1, 0, 1, 2, 2], range(1, 6)->map('foldlevel(v:val)')) + 3d + call assert_equal([0, 1, 1, 2, 2], range(1, 5)->map('foldlevel(v:val)')) bw! endfunc @@ -1591,4 +1742,39 @@ func Test_foldexpr_end_fold() bwipe! endfunc +" Test moving cursor down to or beyond start of folded end of buffer. +func Test_cursor_down_fold_eob() + call setline(1, range(1, 4)) + norm Gzf2kj + call assert_equal(2, line('.')) + norm zojzc + call assert_equal(3, line('.')) + norm j + call assert_equal(3, line('.')) + norm k2j + call assert_equal(4, line('.')) + bwipe! +endfunc + +" issue: #15455 +func Test_cursor_fold_marker_undo() + new + call setline(1, ['{{{', '', 'This is a Line', '', 'This is a Line', '', '}}}']) + let &ul=&ul + setl foldmethod=marker + call cursor(2, 1) + norm! zo1vjdu + call assert_equal(1, foldlevel('.')) + bwipe! + new + call setline(1, ['', '{{{', '', 'This is a Line', '', 'This is a Line', '', '}}}']) + let &ul=&ul + setl foldmethod=marker + call cursor(3, 1) + norm! zo + norm! vjdu + call assert_equal(1, foldlevel('.')) + bwipe! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/test_functions.vim b/test/old/testdir/test_functions.vim index 18d0d9aa5d..ffe7f3fb39 100644 --- a/test/old/testdir/test_functions.vim +++ b/test/old/testdir/test_functions.vim @@ -810,6 +810,10 @@ func Test_mode() call feedkeys("gQ\<Insert>\<F2>vi\<CR>", 'xt') call assert_equal("c-cvr", g:current_modes) + " Commandline mode in Visual mode should return "c-c", never "v-v". + call feedkeys("v\<Cmd>call input('')\<CR>\<F2>\<CR>\<Esc>", 'xt') + call assert_equal("c-c", g:current_modes) + " Executing commands in Vim Ex mode should return "cv", never "cvr", " as Cmdline editing has already ended. call feedkeys("gQcall Save_mode()\<CR>vi\<CR>", 'xt') @@ -2143,54 +2147,6 @@ func Test_balloon_show() endif endfunc -func Test_shellescape() - let save_shell = &shell - set shell=bash - call assert_equal("'text'", shellescape('text')) - call assert_equal("'te\"xt'", 'te"xt'->shellescape()) - call assert_equal("'te'\\''xt'", shellescape("te'xt")) - - call assert_equal("'te%xt'", shellescape("te%xt")) - call assert_equal("'te\\%xt'", shellescape("te%xt", 1)) - call assert_equal("'te#xt'", shellescape("te#xt")) - call assert_equal("'te\\#xt'", shellescape("te#xt", 1)) - call assert_equal("'te!xt'", shellescape("te!xt")) - call assert_equal("'te\\!xt'", shellescape("te!xt", 1)) - - call assert_equal("'te\nxt'", shellescape("te\nxt")) - call assert_equal("'te\\\nxt'", shellescape("te\nxt", 1)) - set shell=tcsh - call assert_equal("'te\\!xt'", shellescape("te!xt")) - call assert_equal("'te\\\\!xt'", shellescape("te!xt", 1)) - call assert_equal("'te\\\nxt'", shellescape("te\nxt")) - call assert_equal("'te\\\\\nxt'", shellescape("te\nxt", 1)) - - set shell=fish - call assert_equal("'text'", shellescape('text')) - call assert_equal("'te\"xt'", shellescape('te"xt')) - call assert_equal("'te'\\''xt'", shellescape("te'xt")) - - call assert_equal("'te%xt'", shellescape("te%xt")) - call assert_equal("'te\\%xt'", shellescape("te%xt", 1)) - call assert_equal("'te#xt'", shellescape("te#xt")) - call assert_equal("'te\\#xt'", shellescape("te#xt", 1)) - call assert_equal("'te!xt'", shellescape("te!xt")) - call assert_equal("'te\\!xt'", shellescape("te!xt", 1)) - - call assert_equal("'te\\\\xt'", shellescape("te\\xt")) - call assert_equal("'te\\\\xt'", shellescape("te\\xt", 1)) - call assert_equal("'te\\\\'\\''xt'", shellescape("te\\'xt")) - call assert_equal("'te\\\\'\\''xt'", shellescape("te\\'xt", 1)) - call assert_equal("'te\\\\!xt'", shellescape("te\\!xt")) - call assert_equal("'te\\\\\\!xt'", shellescape("te\\!xt", 1)) - call assert_equal("'te\\\\%xt'", shellescape("te\\%xt")) - call assert_equal("'te\\\\\\%xt'", shellescape("te\\%xt", 1)) - call assert_equal("'te\\\\#xt'", shellescape("te\\#xt")) - call assert_equal("'te\\\\\\#xt'", shellescape("te\\#xt", 1)) - - let &shell = save_shell -endfunc - func Test_setbufvar_options() " This tests that aucmd_prepbuf() and aucmd_restbuf() properly restore the " window layout and cursor position. @@ -3496,6 +3452,56 @@ func Test_glob() call assert_fails("call glob('*', 0, {})", 'E728:') endfunc +func Test_glob2() + call mkdir('[XglobDir]', 'R') + call mkdir('abc[glob]def', 'R') + + call writefile(['glob'], '[XglobDir]/Xglob') + call writefile(['glob'], 'abc[glob]def/Xglob') + if has("unix") + call assert_equal([], (glob('[XglobDir]/*', 0, 1))) + call assert_equal([], (glob('abc[glob]def/*', 0, 1))) + call assert_equal(['[XglobDir]/Xglob'], (glob('\[XglobDir]/*', 0, 1))) + call assert_equal(['abc[glob]def/Xglob'], (glob('abc\[glob]def/*', 0, 1))) + elseif has("win32") + let _sl=&shellslash + call assert_equal([], (glob('[XglobDir]\*', 0, 1))) + call assert_equal([], (glob('abc[glob]def\*', 0, 1))) + call assert_equal([], (glob('\[XglobDir]\*', 0, 1))) + call assert_equal([], (glob('abc\[glob]def\*', 0, 1))) + set noshellslash + call assert_equal(['[XglobDir]\Xglob'], (glob('[[]XglobDir]/*', 0, 1))) + call assert_equal(['abc[glob]def\Xglob'], (glob('abc[[]glob]def/*', 0, 1))) + set shellslash + call assert_equal(['[XglobDir]/Xglob'], (glob('[[]XglobDir]/*', 0, 1))) + call assert_equal(['abc[glob]def/Xglob'], (glob('abc[[]glob]def/*', 0, 1))) + let &shellslash=_sl + endif +endfunc + +func Test_glob_symlinks() + call writefile([], 'Xglob1') + + if has("win32") + silent !mklink XglobBad DoesNotExist + if v:shell_error + throw 'Skipped: cannot create symlinks' + endif + silent !mklink XglobOk Xglob1 + else + silent !ln -s DoesNotExist XglobBad + silent !ln -s Xglob1 XglobOk + endif + + " The broken symlink is excluded when alllinks is false. + call assert_equal(['Xglob1', 'XglobBad', 'XglobOk'], sort(glob('Xglob*', 0, 1, 1))) + call assert_equal(['Xglob1', 'XglobOk'], sort(glob('Xglob*', 0, 1, 0))) + + call delete('Xglob1') + call delete('XglobBad') + call delete('XglobOk') +endfunc + " Test for browse() func Test_browse() CheckFeature browse @@ -3557,6 +3563,42 @@ func Test_builtin_check() unlet bar endfunc +" Test for isabsolutepath() +func Test_isabsolutepath() + call assert_false(isabsolutepath('')) + call assert_false(isabsolutepath('.')) + call assert_false(isabsolutepath('../Foo')) + call assert_false(isabsolutepath('Foo/')) + if has('win32') + call assert_true(isabsolutepath('A:\')) + call assert_true(isabsolutepath('A:\Foo')) + call assert_true(isabsolutepath('A:/Foo')) + call assert_false(isabsolutepath('A:Foo')) + call assert_false(isabsolutepath('\Windows')) + call assert_true(isabsolutepath('\\Server2\Share\Test\Foo.txt')) + else + call assert_true(isabsolutepath('/')) + call assert_true(isabsolutepath('/usr/share/')) + endif +endfunc + +" Test for exepath() +func Test_exepath() + if has('win32') + call assert_notequal(exepath('cmd'), '') + + let oldNoDefaultCurrentDirectoryInExePath = $NoDefaultCurrentDirectoryInExePath + call writefile(['@echo off', 'echo Evil'], 'vim-test-evil.bat') + let $NoDefaultCurrentDirectoryInExePath = '' + call assert_notequal(exepath("vim-test-evil.bat"), '') + let $NoDefaultCurrentDirectoryInExePath = '1' + call assert_equal(exepath("vim-test-evil.bat"), '') + let $NoDefaultCurrentDirectoryInExePath = oldNoDefaultCurrentDirectoryInExePath + call delete('vim-test-evil.bat') + else + call assert_notequal(exepath('sh'), '') + endif +endfunc " Test for virtcol() func Test_virtcol() @@ -3621,7 +3663,7 @@ func Test_string_reverse() call assert_equal('', reverse(v:_null_string)) for [s1, s2] in [['', ''], ['a', 'a'], ['ab', 'ba'], ['abc', 'cba'], \ ['abcd', 'dcba'], ['«-«-»-»', '»-»-«-«'], - \ ['🇦', '🇦'], ['🇦🇧', '🇧🇦'], ['🇦🇧🇨', '🇨🇧🇦'], + \ ['🇦', '🇦'], ['🇦🇧', '🇦🇧'], ['🇦🇧🇨', '🇨🇦🇧'], \ ['🇦«🇧-🇨»🇩', '🇩»🇨-🇧«🇦']] call assert_equal(s2, reverse(s1)) endfor @@ -3729,6 +3771,8 @@ func Test_slice() call assert_equal('', 'ὰ̳β̳́γ̳̂δ̳̃ε̳̄ζ̳̅'->slice(1, -6)) END call CheckLegacyAndVim9Success(lines) + + call assert_equal(0, slice(v:true, 1)) endfunc " vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/test_getvar.vim b/test/old/testdir/test_getvar.vim index 1e29f49fb4..56f737ab9c 100644 --- a/test/old/testdir/test_getvar.vim +++ b/test/old/testdir/test_getvar.vim @@ -142,21 +142,31 @@ func Test_get_func() let l:F = function('tr') call assert_equal('tr', get(l:F, 'name')) call assert_equal(l:F, get(l:F, 'func')) + call assert_equal({'required': 3, 'optional': 0, 'varargs': v:false}, + \ get(l:F, 'arity')) let Fb_func = function('s:FooBar') call assert_match('<SNR>\d\+_FooBar', get(Fb_func, 'name')) + call assert_equal({'required': 0, 'optional': 0, 'varargs': v:false}, + \ get(Fb_func, 'arity')) let Fb_ref = funcref('s:FooBar') call assert_match('<SNR>\d\+_FooBar', get(Fb_ref, 'name')) + call assert_equal({'required': 0, 'optional': 0, 'varargs': v:false}, + \ get(Fb_ref, 'arity')) call assert_equal({'func has': 'no dict'}, get(l:F, 'dict', {'func has': 'no dict'})) call assert_equal(0, get(l:F, 'dict')) call assert_equal([], get(l:F, 'args')) + " Nvim doesn't have null functions " let NF = test_null_function() " call assert_equal('', get(NF, 'name')) " call assert_equal(NF, get(NF, 'func')) " call assert_equal(0, get(NF, 'dict')) " call assert_equal([], get(NF, 'args')) + " call assert_equal({'required': 0, 'optional': 0, 'varargs': v:false}, get(NF, 'arity')) endfunc " get({partial}, {what} [, {default}]) - in test_partial.vim + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/test_goto.vim b/test/old/testdir/test_goto.vim index c5492ff97b..b6a6695d17 100644 --- a/test/old/testdir/test_goto.vim +++ b/test/old/testdir/test_goto.vim @@ -17,7 +17,7 @@ endfunc func Test_gD() let lines =<< trim [CODE] int x; - + int func(void) { return x; @@ -30,7 +30,7 @@ endfunc func Test_gD_too() let lines =<< trim [CODE] Filename x; - + int Filename int func() { Filename x; @@ -44,7 +44,7 @@ func Test_gD_comment() let lines =<< trim [CODE] /* int x; */ int x; - + int func(void) { return x; @@ -58,7 +58,7 @@ func Test_gD_inline_comment() let lines =<< trim [CODE] int y /* , x */; int x; - + int func(void) { return x; @@ -72,7 +72,7 @@ func Test_gD_string() let lines =<< trim [CODE] char *s[] = "x"; int x = 1; - + int func(void) { return x; @@ -85,7 +85,7 @@ endfunc func Test_gD_string_same_line() let lines =<< trim [CODE] char *s[] = "x", int x = 1; - + int func(void) { return x; @@ -99,7 +99,7 @@ func Test_gD_char() let lines =<< trim [CODE] char c = 'x'; int x = 1; - + int func(void) { return x; @@ -112,7 +112,7 @@ endfunc func Test_gd() let lines =<< trim [CODE] int x; - + int func(int x) { return x; @@ -146,7 +146,7 @@ func Test_gd_not_local() { return x; } - + int func2(int x) { return x; @@ -173,9 +173,9 @@ func Test_gd_missing_braces() def func1(a) a + 1 end - + a = 1 - + def func2() return a end @@ -252,11 +252,11 @@ func Test_gd_inline_comment_body() int func(void) { int y /* , x */; - + for (/* int x = 0 */; y < 2; y++); - + int x = 0; - + return x; } [CODE] @@ -292,7 +292,7 @@ func Test_gd_string() { char *s = "x"; int x = 1; - + return x; } [CODE] @@ -305,7 +305,7 @@ func Test_gd_string_only() int func(void) { char *s = "x"; - + return x; } [CODE] @@ -321,14 +321,16 @@ func Test_set_options_keep_col() let pos = getcurpos() normal j set invhlsearch spell spelllang=en,cjk spelloptions=camel textwidth=80 - set cursorline cursorcolumn cursorlineopt=line colorcolumn=+1 + set cursorline cursorcolumn cursorlineopt=line colorcolumn=+1 winfixbuf + set comments=:# commentstring=#%s define=function set background=dark set background=light normal k call assert_equal(pos, getcurpos()) bwipe! set hlsearch& spell& spelllang& spelloptions& textwidth& - set cursorline& cursorcolumn& cursorlineopt& colorcolumn& + set cursorline& cursorcolumn& cursorlineopt& colorcolumn& winfixbuf& + set comments& commentstring& define& set background& endfunc @@ -347,7 +349,7 @@ func Test_gd_local_block() char *b = "NULL"; return b; } - + return 0; } [CODE] diff --git a/test/old/testdir/test_increment.vim b/test/old/testdir/test_increment.vim index 433b2b4471..5c61f25103 100644 --- a/test/old/testdir/test_increment.vim +++ b/test/old/testdir/test_increment.vim @@ -705,7 +705,7 @@ endfunc " Text: " 1 23 " 4 56 -" +" " Expected: " 1) f2 Ctrl-V jl <ctrl-a>, repeat twice afterwards with . " 1 26 @@ -841,6 +841,44 @@ func Test_increment_unsigned() set nrformats-=unsigned endfunc +" Try incrementing/decrementing a number when nrformats contains blank +func Test_increment_blank() + set nrformats+=blank + + " Signed + call setline(1, '0') + exec "norm! gg0\<C-X>" + call assert_equal('-1', getline(1)) + + call setline(1, '3') + exec "norm! gg010\<C-X>" + call assert_equal('-7', getline(1)) + + call setline(1, '-0') + exec "norm! gg0\<C-X>" + call assert_equal("-1", getline(1)) + + " Unsigned + " NOTE: 18446744073709551615 == 2^64 - 1 + call setline(1, 'a-18446744073709551615') + exec "norm! gg0\<C-A>" + call assert_equal('a-18446744073709551615', getline(1)) + + call setline(1, 'a-18446744073709551615') + exec "norm! gg0\<C-A>" + call assert_equal('a-18446744073709551615', getline(1)) + + call setline(1, 'a-18446744073709551614') + exec "norm! gg08\<C-A>" + call assert_equal('a-18446744073709551615', getline(1)) + + call setline(1, 'a-1') + exec "norm! gg0\<C-A>" + call assert_equal('a-2', getline(1)) + + set nrformats-=blank +endfunc + func Test_in_decrement_large_number() " NOTE: 18446744073709551616 == 2^64 call setline(1, '18446744073709551616') diff --git a/test/old/testdir/test_indent.vim b/test/old/testdir/test_indent.vim index 3b5b643177..dcacc11663 100644 --- a/test/old/testdir/test_indent.vim +++ b/test/old/testdir/test_indent.vim @@ -176,7 +176,7 @@ func Test_modeline_indent_expr() endfunc func Test_indent_func_with_gq() - + function GetTeXIndent() " Sample indent expression for TeX files let lnum = prevnonblank(v:lnum - 1) @@ -187,7 +187,7 @@ func Test_indent_func_with_gq() let line = getline(lnum) let ind = indent(lnum) " Add a 'shiftwidth' after beginning of environments. - if line =~ '\\begin{center}' + if line =~ '\\begin{center}' let ind = ind + shiftwidth() endif return ind @@ -249,7 +249,7 @@ func Test_indent_func_with_gq() bwipe! delmark ab - delfunction GetTeXIndent + delfunction GetTeXIndent endfu func Test_formatting_keeps_first_line_indent() diff --git a/test/old/testdir/test_ins_complete.vim b/test/old/testdir/test_ins_complete.vim index 3f67a06999..48319f5017 100644 --- a/test/old/testdir/test_ins_complete.vim +++ b/test/old/testdir/test_ins_complete.vim @@ -700,14 +700,14 @@ func Test_pum_with_preview_win() CheckScreendump let lines =<< trim END - funct Omni_test(findstart, base) - if a:findstart - return col(".") - 1 - endif - return [#{word: "one", info: "1info"}, #{word: "two", info: "2info"}, #{word: "three", info: "3info"}] - endfunc - set omnifunc=Omni_test - set completeopt+=longest + funct Omni_test(findstart, base) + if a:findstart + return col(".") - 1 + endif + return [#{word: "one", info: "1info"}, #{word: "two", info: "2info"}, #{word: "three", info: "3info"}] + endfunc + set omnifunc=Omni_test + set completeopt+=longest END call writefile(lines, 'Xpreviewscript') @@ -884,6 +884,74 @@ func Test_complete_with_longest() bwipe! endfunc +" Test for buffer-local value of 'completeopt' +func Test_completeopt_buffer_local() + set completeopt=menu + new + call setline(1, ['foofoo', 'foobar', 'foobaz', '']) + call assert_equal('', &l:completeopt) + call assert_equal('menu', &completeopt) + call assert_equal('menu', &g:completeopt) + + setlocal bufhidden=hide + enew + call setline(1, ['foofoo', 'foobar', 'foobaz', '']) + call assert_equal('', &l:completeopt) + call assert_equal('menu', &completeopt) + call assert_equal('menu', &g:completeopt) + + setlocal completeopt+=fuzzy,noinsert + call assert_equal('menu,fuzzy,noinsert', &l:completeopt) + call assert_equal('menu,fuzzy,noinsert', &completeopt) + call assert_equal('menu', &g:completeopt) + call feedkeys("Gccf\<C-X>\<C-N>bz\<C-Y>", 'tnix') + call assert_equal('foobaz', getline('.')) + + setlocal completeopt= + call assert_equal('', &l:completeopt) + call assert_equal('menu', &completeopt) + call assert_equal('menu', &g:completeopt) + call feedkeys("Gccf\<C-X>\<C-N>\<C-Y>", 'tnix') + call assert_equal('foofoo', getline('.')) + + setlocal completeopt+=longest + call assert_equal('menu,longest', &l:completeopt) + call assert_equal('menu,longest', &completeopt) + call assert_equal('menu', &g:completeopt) + call feedkeys("Gccf\<C-X>\<C-N>\<C-X>\<C-Z>", 'tnix') + call assert_equal('foo', getline('.')) + + setlocal bufhidden=hide + buffer # + call assert_equal('', &l:completeopt) + call assert_equal('menu', &completeopt) + call assert_equal('menu', &g:completeopt) + call feedkeys("Gccf\<C-X>\<C-N>\<C-Y>", 'tnix') + call assert_equal('foofoo', getline('.')) + + setlocal completeopt+=fuzzy,noinsert + call assert_equal('menu,fuzzy,noinsert', &l:completeopt) + call assert_equal('menu,fuzzy,noinsert', &completeopt) + call assert_equal('menu', &g:completeopt) + call feedkeys("Gccf\<C-X>\<C-N>bz\<C-Y>", 'tnix') + call assert_equal('foobaz', getline('.')) + + buffer # + call assert_equal('menu,longest', &l:completeopt) + call assert_equal('menu,longest', &completeopt) + call assert_equal('menu', &g:completeopt) + call feedkeys("Gccf\<C-X>\<C-N>\<C-X>\<C-Z>", 'tnix') + call assert_equal('foo', getline('.')) + + setlocal bufhidden=wipe + buffer! # + bwipe! + call assert_equal('', &l:completeopt) + call assert_equal('menu', &completeopt) + call assert_equal('menu', &g:completeopt) + + set completeopt& +endfunc " Test for completing words following a completed word in a line func Test_complete_wrapscan() @@ -1623,6 +1691,23 @@ func Test_completefunc_callback() bw! delfunc s:CompleteFunc3 + " In Vim9 script s: can be omitted + let lines =<< trim END + vim9script + var CompleteFunc4Args = [] + def CompleteFunc4(findstart: bool, base: string): any + add(CompleteFunc4Args, [findstart, base]) + return findstart ? 0 : [] + enddef + set completefunc=CompleteFunc4 + new + setline(1, 'script1') + feedkeys("A\<C-X>\<C-U>\<Esc>", 'x') + assert_equal([[1, ''], [0, 'script1']], CompleteFunc4Args) + bw! + END + call CheckScriptSuccess(lines) + " invalid return value let &completefunc = {a -> 'abc'} call feedkeys("A\<C-X>\<C-U>\<Esc>", 'x') @@ -2314,7 +2399,7 @@ endfunc func Test_ins_complete_end_of_line() " this was reading past the end of the line - new + new norm 8oý sil! norm o @@ -2512,4 +2597,111 @@ func Test_completefunc_first_call_complete_add() bwipe! endfunc +func Test_complete_fuzzy_match() + func OnPumChange() + let g:item = get(v:event, 'completed_item', {}) + let g:word = get(g:item, 'word', v:null) + endfunction + + augroup AAAAA_Group + au! + autocmd CompleteChanged * :call OnPumChange() + augroup END + + func Omni_test(findstart, base) + if a:findstart + return col(".") + endif + return [#{word: "foo"}, #{word: "foobar"}, #{word: "fooBaz"}, #{word: "foobala"}] + endfunc + + new + set omnifunc=Omni_test + set completeopt+=noinsert,fuzzy + call feedkeys("Gi\<C-x>\<C-o>", 'tx') + call assert_equal('foo', g:word) + call feedkeys("S\<C-x>\<C-o>fb", 'tx') + call assert_equal('fooBaz', g:word) + call feedkeys("S\<C-x>\<C-o>fa", 'tx') + call assert_equal('foobar', g:word) + " select next + call feedkeys("S\<C-x>\<C-o>fb\<C-n>", 'tx') + call assert_equal('foobar', g:word) + " can cyclically select next + call feedkeys("S\<C-x>\<C-o>fb\<C-n>\<C-n>\<C-n>", 'tx') + call assert_equal(v:null, g:word) + " select prev + call feedkeys("S\<C-x>\<C-o>fb\<C-p>", 'tx') + call assert_equal(v:null, g:word) + " can cyclically select prev + call feedkeys("S\<C-x>\<C-o>fb\<C-p>\<C-p>\<C-p>\<C-p>", 'tx') + call assert_equal('fooBaz', g:word) + + func Comp() + call complete(col('.'), ["fooBaz", "foobar", "foobala"]) + return '' + endfunc + call feedkeys("i\<C-R>=Comp()\<CR>", 'tx') + call assert_equal('fooBaz', g:word) + + " respect noselect + set completeopt+=noselect + call feedkeys("S\<C-x>\<C-o>fb", 'tx') + call assert_equal(v:null, g:word) + call feedkeys("S\<C-x>\<C-o>fb\<C-n>", 'tx') + call assert_equal('fooBaz', g:word) + + " avoid breaking default completion behavior + set completeopt=fuzzy,menu + call setline(1, ['hello help hero h']) + " Use "!" flag of feedkeys() so that ex_normal_busy is not set and + " ins_compl_check_keys() is not skipped. + " Add a "0" after the <Esc> to avoid waiting for an escape sequence. + call feedkeys("A\<C-X>\<C-N>\<Esc>0", 'tx!') + call assert_equal('hello help hero hello', getline('.')) + set completeopt+=noinsert + call setline(1, ['hello help hero h']) + call feedkeys("A\<C-X>\<C-N>\<Esc>0", 'tx!') + call assert_equal('hello help hero h', getline('.')) + + " issue #15526 + set completeopt=fuzzy,menuone,menu,noselect + call setline(1, ['Text', 'ToText', '']) + call cursor(2, 1) + call feedkeys("STe\<C-X>\<C-N>x\<CR>\<Esc>0", 'tx!') + call assert_equal('Tex', getline('.')) + + " clean up + set omnifunc= + bw! + set complete& completeopt& + autocmd! AAAAA_Group + augroup! AAAAA_Group + delfunc OnPumChange + delfunc Omni_test + delfunc Comp + unlet g:item + unlet g:word +endfunc + +" Check that tie breaking is stable for completeopt+=fuzzy (which should +" behave the same on different platforms). +func Test_complete_fuzzy_match_tie() + new + set completeopt+=fuzzy,noselect + call setline(1, ['aaabbccc', 'aaabbCCC', 'aaabbcccc', 'aaabbCCCC', '']) + + call feedkeys("Gcc\<C-X>\<C-N>ab\<C-N>\<C-Y>", 'tx') + call assert_equal('aaabbccc', getline('.')) + call feedkeys("Gcc\<C-X>\<C-N>ab\<C-N>\<C-N>\<C-Y>", 'tx') + call assert_equal('aaabbCCC', getline('.')) + call feedkeys("Gcc\<C-X>\<C-N>ab\<C-N>\<C-N>\<C-N>\<C-Y>", 'tx') + call assert_equal('aaabbcccc', getline('.')) + call feedkeys("Gcc\<C-X>\<C-N>ab\<C-N>\<C-N>\<C-N>\<C-N>\<C-Y>", 'tx') + call assert_equal('aaabbCCCC', getline('.')) + + bwipe! + set completeopt& +endfunc + " vim: shiftwidth=2 sts=2 expandtab nofoldenable diff --git a/test/old/testdir/test_jumplist.vim b/test/old/testdir/test_jumplist.vim index b4dcdad9d6..1feadead60 100644 --- a/test/old/testdir/test_jumplist.vim +++ b/test/old/testdir/test_jumplist.vim @@ -19,7 +19,7 @@ func Test_getjumplist() for i in range(1, 100) call add(lines, "Line " . i) endfor - call writefile(lines, "Xtest") + call writefile(lines, "Xtest", 'D') " Jump around and create a jump list edit Xtest @@ -61,33 +61,65 @@ func Test_getjumplist() clearjumps call test_garbagecollect_now() call assert_equal(4, l[1]) - - call delete("Xtest") endfunc -func Test_jumplist_invalid() +func Test_jumplist_wipe_buf() new clearjumps - " put some randome text - put ='a' - let prev = bufnr('%') + " Put some random text and fill the jump list. + call setline(1, ['foo', 'bar', 'baz']) + normal G + normal gg setl nomodified bufhidden=wipe e XXJumpListBuffer - let bnr = bufnr('%') - " 1) empty jumplist - let expected = [[ - \ {'lnum': 2, 'bufnr': prev, 'col': 0, 'coladd': 0}], 1] - call assert_equal(expected, getjumplist()) + " The jump list is empty as the buffer was wiped out. + call assert_equal([[], 0], getjumplist()) let jumps = execute(':jumps') call assert_equal('>', jumps[-1:]) - " now jump back - exe ":norm! \<c-o>" - let expected = [[ - \ {'lnum': 2, 'bufnr': prev, 'col': 0, 'coladd': 0}, - \ {'lnum': 1, 'bufnr': bnr, 'col': 0, 'coladd': 0}], 0] - call assert_equal(expected, getjumplist()) - let jumps = execute(':jumps') - call assert_match('> 0 2 0 -invalid-', jumps) + + " Put some random text and fill the jump list. + call setline(1, ['foo', 'bar', 'baz']) + setl bufhidden=hide + + " References to wiped buffer are deleted with multiple tabpages. + let [w1, t1] = [win_getid(), tabpagenr()] + clearjumps + normal G + normal gg + enew + + split XXJumpListBuffer + let [w2, t2] = [win_getid(), tabpagenr()] + clearjumps + normal G + normal gg + enew + + tabnew XXJumpListBuffer + let [w3, t3] = [win_getid(), tabpagenr()] + clearjumps + normal G + normal gg + enew + + split XXJumpListBuffer + let [w4, t4] = [win_getid(), tabpagenr()] + clearjumps + normal G + normal gg + enew + + for [w, t] in [[w1, t1], [w2, t2], [w3, t3], [w4, t4]] + call assert_equal(2, len(getjumplist(w, t)[0])) + endfor + + bwipe! XXJumpListBuffer + + for [w, t] in [[w1, t1], [w2, t2], [w3, t3], [w4, t4]] + call assert_equal(0, len(getjumplist(w, t)[0])) + endfor + + %bwipe! endfunc " Test for '' mark in an empty buffer diff --git a/test/old/testdir/test_lambda.vim b/test/old/testdir/test_lambda.vim index 810b41b389..0340a7260b 100644 --- a/test/old/testdir/test_lambda.vim +++ b/test/old/testdir/test_lambda.vim @@ -217,7 +217,9 @@ endfunc func Test_lambda_combination() call assert_equal(2, {x -> {x -> x}}(1)(2)) call assert_equal(10, {y -> {x -> x(y)(10)}({y -> y})}({z -> z})) - call assert_equal(5.0, {x -> {y -> x / y}}(10)(2.0)) + if has('float') + call assert_equal(5.0, {x -> {y -> x / y}}(10)(2.0)) + endif call assert_equal(6, {x -> {y -> {z -> x + y + z}}}(1)(2)(3)) call assert_equal(6, {x -> {f -> f(x)}}(3)({x -> x * 2})) diff --git a/test/old/testdir/test_let.vim b/test/old/testdir/test_let.vim index d37af45aaa..44852c1d38 100644 --- a/test/old/testdir/test_let.vim +++ b/test/old/testdir/test_let.vim @@ -521,12 +521,12 @@ END END call assert_equal(['vim', '', 'end', ' END', 'END '], var3) - let var1 =<< trim END - Line1 - Line2 - Line3 - END - END + let var1 =<< trim END + Line1 + Line2 + Line3 + END + END call assert_equal(['Line1', ' Line2', "\tLine3", ' END'], var1) let var1 =<< trim !!! @@ -563,6 +563,14 @@ END END call assert_equal(['something', 'endfunc'], var1) + " not concatenate lines + let var1 =<< END +some + \thing + \ else +END + call assert_equal(['some', ' \thing', ' \ else'], var1) + " ignore "python << xx" let var1 =<<END something diff --git a/test/old/testdir/test_listdict.vim b/test/old/testdir/test_listdict.vim index 0adc3286f9..678734dafb 100644 --- a/test/old/testdir/test_listdict.vim +++ b/test/old/testdir/test_listdict.vim @@ -57,6 +57,9 @@ func Test_list_slice() assert_equal([1, 2], l[-3 : -1]) END call CheckDefAndScriptSuccess(lines) + + call assert_fails('let l[[]] = 1', 'E730: Using a List as a String') + call assert_fails('let l[1 : []] = [1]', 'E730: Using a List as a String') endfunc " List identity @@ -175,6 +178,19 @@ func Test_list_assign() END call CheckScriptFailure(['vim9script'] + lines, 'E688:') call CheckDefExecFailure(lines, 'E1093: Expected 2 items but got 1') + + let lines =<< trim END + VAR l = [2] + LET l += v:_null_list + call assert_equal([2], l) + LET l = v:_null_list + LET l += [1] + call assert_equal([1], l) + END + call CheckLegacyAndVim9Success(lines) + + let d = {'abc': [1, 2, 3]} + call assert_fails('let d.abc[0:0z10] = [10, 20]', 'E976: Using a Blob as a String') endfunc " test for range assign @@ -195,6 +211,26 @@ func Test_list_range_assign() call CheckDefAndScriptFailure(lines, 'E1012:', 2) endfunc +func Test_list_items() + let r = [] + let l = ['a', 'b', 'c'] + for [idx, val] in items(l) + call extend(r, [[idx, val]]) + endfor + call assert_equal([[0, 'a'], [1, 'b'], [2, 'c']], r) + + call assert_fails('call items(3)', 'E1225:') +endfunc + +func Test_string_items() + let r = [] + let s = 'ábツ' + for [idx, val] in items(s) + call extend(r, [[idx, val]]) + endfor + call assert_equal([[0, 'á'], [1, 'b'], [2, 'ツ']], r) +endfunc + " Test removing items in list func Test_list_func_remove() let lines =<< trim END @@ -420,6 +456,9 @@ func Test_dict_assign() n.key = 3 END call CheckDefFailure(lines, 'E1141:') + + let d = {'abc': {}} + call assert_fails("let d.abc[0z10] = 10", 'E976: Using a Blob as a String') endfunc " Function in script-local List or Dict @@ -1055,6 +1094,19 @@ func Test_listdict_compare() call assert_fails('echo {} =~ {}', 'E736:') endfunc +func Test_recursive_listdict_compare() + let l1 = [0, 1] + let l1[0] = l1 + let l2 = [0, 1] + let l2[0] = l2 + call assert_true(l1 == l2) + let d1 = {0: 0, 1: 1} + let d1[0] = d1 + let d2 = {0: 0, 1: 1} + let d2[0] = d2 + call assert_true(d1 == d2) +endfunc + " compare complex recursively linked list and dict func Test_listdict_compare_complex() let lines =<< trim END @@ -1416,6 +1468,8 @@ func Test_indexof() call assert_equal(-1, indexof(l, v:_null_string)) " Nvim doesn't have null functions " call assert_equal(-1, indexof(l, test_null_function())) + call assert_equal(-1, indexof(l, "")) + call assert_fails('let i = indexof(l, " ")', 'E15:') " failure cases call assert_fails('let i = indexof(l, "v:val == ''cyan''")', 'E735:') @@ -1447,4 +1501,53 @@ func Test_extendnew_leak() for i in range(100) | silent! call extendnew({}, {}, {}) | endfor endfunc +" Test for comparing deeply nested List/Dict values +func Test_deep_nested_listdict_compare() + let lines =<< trim END + func GetNestedList(sz) + let l = [] + let x = l + for i in range(a:sz) + let y = [1] + call add(x, y) + let x = y + endfor + return l + endfunc + + VAR l1 = GetNestedList(1000) + VAR l2 = GetNestedList(999) + call assert_false(l1 == l2) + + #" after 1000 nested items, the lists are considered to be equal + VAR l3 = GetNestedList(1001) + VAR l4 = GetNestedList(1002) + call assert_true(l3 == l4) + END + call CheckLegacyAndVim9Success(lines) + + let lines =<< trim END + func GetNestedDict(sz) + let d = {} + let x = d + for i in range(a:sz) + let y = {} + let x['a'] = y + let x = y + endfor + return d + endfunc + + VAR d1 = GetNestedDict(1000) + VAR d2 = GetNestedDict(999) + call assert_false(d1 == d2) + + #" after 1000 nested items, the Dicts are considered to be equal + VAR d3 = GetNestedDict(1001) + VAR d4 = GetNestedDict(1002) + call assert_true(d3 == d4) + END + call CheckLegacyAndVim9Success(lines) +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/test_listlbr_utf8.vim b/test/old/testdir/test_listlbr_utf8.vim index 313ff30cc4..693f2015fc 100644 --- a/test/old/testdir/test_listlbr_utf8.vim +++ b/test/old/testdir/test_listlbr_utf8.vim @@ -280,6 +280,9 @@ func Test_chinese_char_on_wrap_column() call s:compare_lines(expect, lines) call assert_equal(len(expect), winline()) call assert_equal(strwidth(trim(expect[-1], ' ', 2)), wincol()) + norm! g0 + call assert_equal(len(expect), winline()) + call assert_equal(1, wincol()) call s:close_windows() endfunc @@ -315,6 +318,9 @@ func Test_chinese_char_on_wrap_column_sbr() call s:compare_lines(expect, lines) call assert_equal(len(expect), winline()) call assert_equal(strwidth(trim(expect[-1], ' ', 2)), wincol()) + norm! g0 + call assert_equal(len(expect), winline()) + call assert_equal(4, wincol()) call s:close_windows() endfunc diff --git a/test/old/testdir/test_map_functions.vim b/test/old/testdir/test_map_functions.vim index 0898242154..8f7c8bae76 100644 --- a/test/old/testdir/test_map_functions.vim +++ b/test/old/testdir/test_map_functions.vim @@ -20,7 +20,7 @@ func Test_maparg() call assert_equal({'silent': 0, 'noremap': 0, 'script': 0, 'lhs': 'foo<C-V>', \ 'lhsraw': "foo\x80\xfc\x04V", 'lhsrawalt': "foo\x16", \ 'mode': ' ', 'nowait': 0, 'expr': 0, 'sid': sid, 'scriptversion': 1, - \ 'lnum': lnum + 1, + \ 'lnum': lnum + 1, \ 'rhs': 'is<F4>foo', 'buffer': 0, 'abbr': 0, 'mode_bits': 0x47}, \ maparg('foo<C-V>', '', 0, 1)) call assert_equal({'silent': 1, 'noremap': 1, 'script': 1, 'lhs': 'bar', diff --git a/test/old/testdir/test_mapping.vim b/test/old/testdir/test_mapping.vim index 2a4d068dea..e4e446c55c 100644 --- a/test/old/testdir/test_mapping.vim +++ b/test/old/testdir/test_mapping.vim @@ -1672,6 +1672,49 @@ func Test_unmap_simplifiable() unmap <C-I> endfunc +" Test that the first byte of rhs is not remapped if rhs starts with lhs. +func Test_map_rhs_starts_with_lhs() + new + func MapExpr() + return "\<C-R>\<C-P>" + endfunc + + for expr in [v:false, v:true] + if expr + imap <buffer><expr> <C-R> MapExpr() + else + imap <buffer> <C-R> <C-R><C-P> + endif + + for restore in [v:false, v:true] + if restore + let saved = maparg('<C-R>', 'i', v:false, v:true) + iunmap <buffer> <C-R> + call mapset(saved) + endif + + let @a = 'foo' + call assert_nobeep('call feedkeys("S\<C-R>a", "tx")') + call assert_equal('foo', getline('.')) + + let @a = 'bar' + call assert_nobeep('call feedkeys("S\<*C-R>a", "tx")') + call assert_equal('bar', getline('.')) + endfor + endfor + + " When two mappings are used for <C-I> and <Tab>, remapping should work. + imap <buffer> <C-I> <Tab>bar + imap <buffer> <Tab> foo + call feedkeys("S\<Tab>", 'xt') + call assert_equal('foo', getline('.')) + call feedkeys("S\<*C-I>", 'xt') + call assert_equal('foobar', getline('.')) + + delfunc MapExpr + bwipe! +endfunc + func Test_expr_map_escape_special() nnoremap … <Cmd>let g:got_ellipsis += 1<CR> func Func() diff --git a/test/old/testdir/test_matchparen.vim b/test/old/testdir/test_matchparen.vim index ab425b046a..7d80e43046 100644 --- a/test/old/testdir/test_matchparen.vim +++ b/test/old/testdir/test_matchparen.vim @@ -108,5 +108,35 @@ func Test_matchparen_pum_clear() call StopVimInTerminal(buf) endfunc +" Test that matchparen works with multibyte chars in 'matchpairs' +func Test_matchparen_mbyte() + CheckScreendump + + let lines =<< trim END + source $VIMRUNTIME/plugin/matchparen.vim + call setline(1, ['aaaaaaaa(', 'bbbb)cc']) + set matchpairs+=(:) + END + + call writefile(lines, 'XmatchparenMbyte', 'D') + let buf = RunVimInTerminal('-S XmatchparenMbyte', #{rows: 10}) + call VerifyScreenDump(buf, 'Test_matchparen_mbyte_1', {}) + call term_sendkeys(buf, "$") + call VerifyScreenDump(buf, 'Test_matchparen_mbyte_2', {}) + call term_sendkeys(buf, "j") + call VerifyScreenDump(buf, 'Test_matchparen_mbyte_3', {}) + call term_sendkeys(buf, "2h") + call VerifyScreenDump(buf, 'Test_matchparen_mbyte_4', {}) + call term_sendkeys(buf, "0") + call VerifyScreenDump(buf, 'Test_matchparen_mbyte_5', {}) + call term_sendkeys(buf, "kA") + call VerifyScreenDump(buf, 'Test_matchparen_mbyte_6', {}) + call term_sendkeys(buf, "\<Down>") + call VerifyScreenDump(buf, 'Test_matchparen_mbyte_7', {}) + call term_sendkeys(buf, "\<C-W>") + call VerifyScreenDump(buf, 'Test_matchparen_mbyte_8', {}) + + call StopVimInTerminal(buf) +endfunc " vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/test_menu.vim b/test/old/testdir/test_menu.vim index d1c1180ce1..88d74c8a1a 100644 --- a/test/old/testdir/test_menu.vim +++ b/test/old/testdir/test_menu.vim @@ -482,13 +482,48 @@ func Test_popup_menu() unmenu PopUp endfunc +func Test_popup_menu_truncated() + CheckNotGui + + set mouse=a mousemodel=popup + aunmenu PopUp + for i in range(2 * &lines) + exe $'menu PopUp.{i} <Cmd>let g:res = {i}<CR>' + endfor + + func LeftClickExpr(row, col) + call Ntest_setmouse(a:row, a:col) + return "\<LeftMouse>" + endfunc + + " Clicking at the bottom should place popup menu above click position. + " <RightRelease> should not select an item immediately. + let g:res = -1 + call Ntest_setmouse(&lines, 1) + nnoremap <expr><F2> LeftClickExpr(4, 1) + call feedkeys("\<RightMouse>\<RightRelease>\<F2>", 'tx') + call assert_equal(3, g:res) + + " Clicking at the top should place popup menu below click position. + let g:res = -1 + call Ntest_setmouse(1, 1) + nnoremap <expr><F2> LeftClickExpr(5, 1) + call feedkeys("\<RightMouse>\<RightRelease>\<F2>", 'tx') + call assert_equal(3, g:res) + + nunmap <F2> + delfunc LeftClickExpr + unlet g:res + aunmenu PopUp + set mouse& mousemodel& +endfunc + " Test for MenuPopup autocommand func Test_autocmd_MenuPopup() CheckNotGui - set mouse=a - set mousemodel=popup - aunmenu * + set mouse=a mousemodel=popup + aunmenu PopUp autocmd MenuPopup * exe printf( \ 'anoremenu PopUp.Foo <Cmd>let g:res = ["%s", "%s"]<CR>', \ expand('<afile>'), expand('<amatch>')) diff --git a/test/old/testdir/test_method.vim b/test/old/testdir/test_method.vim index 88dbbd7bf4..ca1ca7d573 100644 --- a/test/old/testdir/test_method.vim +++ b/test/old/testdir/test_method.vim @@ -20,9 +20,8 @@ func Test_list_method() call assert_equal(2, l->get(1)) call assert_equal(1, l->index(2)) call assert_equal([0, 1, 2, 3], [1, 2, 3]->insert(0)) - call assert_fails('eval l->items()', 'E715:') call assert_equal('1 2 3', l->join()) - call assert_fails('eval l->keys()', 'E715:') + call assert_fails('eval l->keys()', 'E1206:') call assert_equal(3, l->len()) call assert_equal([2, 3, 4], [1, 2, 3]->map('v:val + 1')) call assert_equal(3, l->max()) @@ -34,7 +33,7 @@ func Test_list_method() call assert_equal('[1, 2, 3]', l->string()) call assert_equal(v:t_list, l->type()) call assert_equal([1, 2, 3], [1, 1, 2, 3, 3]->uniq()) - call assert_fails('eval l->values()', 'E715:') + call assert_fails('eval l->values()', 'E1206:') call assert_fails('echo []->len', 'E107:') endfunc @@ -79,6 +78,7 @@ func Test_string_method() eval "a\rb\ec"->strtrans()->assert_equal('a^Mb^[c') eval "aあb"->strwidth()->assert_equal(4) eval 'abc'->substitute('b', 'x', '')->assert_equal('axc') + call assert_fails('eval 123->items()', 'E1225:') eval 'abc'->printf('the %s arg')->assert_equal('the abc arg') endfunc @@ -128,12 +128,19 @@ endfunc func Test_method_syntax() eval [1, 2, 3] ->sort( ) - eval [1, 2, 3] + eval [1, 2, 3] \ ->sort( \ ) call assert_fails('eval [1, 2, 3]-> sort()', 'E15:') call assert_fails('eval [1, 2, 3]->sort ()', 'E274:') call assert_fails('eval [1, 2, 3]-> sort ()', 'E15:') + + " Test for using a method name containing a curly brace name + let s = 'len' + call assert_equal(4, "xxxx"->str{s}()) + + " Test for using a method in an interpolated string + call assert_equal('4', $'{"xxxx"->strlen()}') endfunc func Test_method_lambda() diff --git a/test/old/testdir/test_normal.vim b/test/old/testdir/test_normal.vim index a2ef07193d..46fddd6c1a 100644 --- a/test/old/testdir/test_normal.vim +++ b/test/old/testdir/test_normal.vim @@ -403,17 +403,17 @@ func Test_normal08_fold() " First fold norm! V4jzf " check that folds have been created - call assert_equal(['50/*{{{*/', '51', '52', '53', '54/*}}}*/'], getline(50,54)) + call assert_equal(['50/* {{{ */', '51', '52', '53', '54/* }}} */'], getline(50,54)) " Second fold 46 norm! V10jzf " check that folds have been created - call assert_equal('46/*{{{*/', getline(46)) - call assert_equal('60/*}}}*/', getline(60)) + call assert_equal('46/* {{{ */', getline(46)) + call assert_equal('60/* }}} */', getline(60)) norm! k call assert_equal('45', getline('.')) norm! j - call assert_equal('46/*{{{*/', getline('.')) + call assert_equal('46/* {{{ */', getline('.')) norm! j call assert_equal('61', getline('.')) norm! k @@ -422,12 +422,12 @@ func Test_normal08_fold() norm! k call assert_equal('45', getline('.')) norm! j - call assert_equal('46/*{{{*/', getline('.')) + call assert_equal('46/* {{{ */', getline('.')) norm! j call assert_equal('47', getline('.')) norm! k norm! zcVzO - call assert_equal('46/*{{{*/', getline('.')) + call assert_equal('46/* {{{ */', getline('.')) norm! j call assert_equal('47', getline('.')) norm! j @@ -435,7 +435,7 @@ func Test_normal08_fold() norm! j call assert_equal('49', getline('.')) norm! j - call assert_equal('50/*{{{*/', getline('.')) + call assert_equal('50/* {{{ */', getline('.')) norm! j call assert_equal('51', getline('.')) " delete folds @@ -1387,14 +1387,14 @@ func Test_normal18_z_fold() " First fold norm! 4zF " check that folds have been created - call assert_equal(['50/*{{{*/', '51', '52', '53/*}}}*/'], getline(50,53)) + call assert_equal(['50/* {{{ */', '51', '52', '53/* }}} */'], getline(50,53)) " Test for zd 51 norm! 2zF call assert_equal(2, foldlevel('.')) norm! kzd - call assert_equal(['50', '51/*{{{*/', '52/*}}}*/', '53'], getline(50,53)) + call assert_equal(['50', '51/* {{{ */', '52/* }}} */', '53'], getline(50,53)) norm! j call assert_equal(1, foldlevel('.')) @@ -1413,7 +1413,7 @@ func Test_normal18_z_fold() norm! 2zF 90 norm! 4zF - call assert_equal(['85/*{{{*/', '86/*{{{*/', '87/*}}}*/', '88/*}}}*/', '89', '90/*{{{*/', '91', '92', '93/*}}}*/'], getline(85,93)) + call assert_equal(['85/* {{{ */', '86/* {{{ */', '87/* }}} */', '88/* }}} */', '89', '90/* {{{ */', '91', '92', '93/* }}} */'], getline(85,93)) norm! zE call assert_equal(['85', '86', '87', '88', '89', '90', '91', '92', '93'], getline(85,93)) @@ -1425,9 +1425,9 @@ func Test_normal18_z_fold() norm! k call assert_equal('49', getline('.')) norm! j - call assert_equal('50/*{{{*/', getline('.')) + call assert_equal('50/* {{{ */', getline('.')) norm! j - call assert_equal('51/*}}}*/', getline('.')) + call assert_equal('51/* }}} */', getline('.')) norm! j call assert_equal('52', getline('.')) call assert_equal(0, &foldenable) @@ -1437,7 +1437,7 @@ func Test_normal18_z_fold() norm! zN call assert_equal('49', getline('.')) norm! j - call assert_equal('50/*{{{*/', getline('.')) + call assert_equal('50/* {{{ */', getline('.')) norm! j call assert_equal('52', getline('.')) call assert_equal(1, &foldenable) @@ -1458,9 +1458,9 @@ func Test_normal18_z_fold() norm! k call assert_equal('49', getline('.')) norm! j - call assert_equal('50/*{{{*/', getline('.')) + call assert_equal('50/* {{{ */', getline('.')) norm! j - call assert_equal('51/*}}}*/', getline('.')) + call assert_equal('51/* }}} */', getline('.')) norm! j call assert_equal('52', getline('.')) 50 @@ -1468,7 +1468,7 @@ func Test_normal18_z_fold() norm! k call assert_equal('49', getline('.')) norm! j - call assert_equal('50/*{{{*/', getline('.')) + call assert_equal('50/* {{{ */', getline('.')) norm! j call assert_equal('52', getline('.')) @@ -1477,14 +1477,14 @@ func Test_normal18_z_fold() norm! k call assert_equal('48', getline('.')) norm! j - call assert_equal('49/*{{{*/', getline('.')) + call assert_equal('49/* {{{ */', getline('.')) norm! j call assert_equal('55', getline('.')) 49 norm! za - call assert_equal('49/*{{{*/', getline('.')) + call assert_equal('49/* {{{ */', getline('.')) norm! j - call assert_equal('50/*{{{*/', getline('.')) + call assert_equal('50/* {{{ */', getline('.')) norm! j call assert_equal('52', getline('.')) set nofoldenable @@ -1498,11 +1498,11 @@ func Test_normal18_z_fold() norm! 2k call assert_equal('48', getline('.')) norm! j - call assert_equal('49/*{{{*/', getline('.')) + call assert_equal('49/* {{{ */', getline('.')) norm! j - call assert_equal('50/*{{{*/', getline('.')) + call assert_equal('50/* {{{ */', getline('.')) norm! j - call assert_equal('51/*}}}*/', getline('.')) + call assert_equal('51/* }}} */', getline('.')) norm! j call assert_equal('52', getline('.')) @@ -1514,15 +1514,15 @@ func Test_normal18_z_fold() norm! 2k call assert_equal('48', getline('.')) norm! j - call assert_equal('49/*{{{*/', getline('.')) + call assert_equal('49/* {{{ */', getline('.')) norm! j - call assert_equal('50/*{{{*/', getline('.')) + call assert_equal('50/* {{{ */', getline('.')) norm! j - call assert_equal('51/*}}}*/', getline('.')) + call assert_equal('51/* }}} */', getline('.')) norm! j call assert_equal('52', getline('.')) - " zA on a opened fold when foldenable is not set + " zA on an opened fold when foldenable is not set 50 set nofoldenable norm! zA @@ -1530,7 +1530,7 @@ func Test_normal18_z_fold() norm! k call assert_equal('48', getline('.')) norm! j - call assert_equal('49/*{{{*/', getline('.')) + call assert_equal('49/* {{{ */', getline('.')) norm! j call assert_equal('55', getline('.')) @@ -1550,7 +1550,7 @@ func Test_normal18_z_fold() norm! k call assert_equal('48', getline('.')) norm! j - call assert_equal('49/*{{{*/', getline('.')) + call assert_equal('49/* {{{ */', getline('.')) norm! j call assert_equal('55', getline('.')) set nofoldenable @@ -1559,7 +1559,7 @@ func Test_normal18_z_fold() norm! k call assert_equal('48', getline('.')) norm! j - call assert_equal('49/*{{{*/', getline('.')) + call assert_equal('49/* {{{ */', getline('.')) norm! j call assert_equal('55', getline('.')) @@ -1569,7 +1569,7 @@ func Test_normal18_z_fold() norm! zCk call assert_equal('48', getline('.')) norm! j - call assert_equal('49/*{{{*/', getline('.')) + call assert_equal('49/* {{{ */', getline('.')) norm! j call assert_equal('55', getline('.')) @@ -1580,7 +1580,7 @@ func Test_normal18_z_fold() norm! zx call assert_equal(1, &foldenable) norm! j - call assert_equal('49/*{{{*/', getline('.')) + call assert_equal('49/* {{{ */', getline('.')) norm! j call assert_equal('55', getline('.')) @@ -1592,17 +1592,17 @@ func Test_normal18_z_fold() norm! 3k call assert_equal('48', getline('.')) norm! j - call assert_equal('49/*{{{*/', getline('.')) + call assert_equal('49/* {{{ */', getline('.')) norm! j - call assert_equal('50/*{{{*/', getline('.')) + call assert_equal('50/* {{{ */', getline('.')) norm! j - call assert_equal('51/*}}}*/', getline('.')) + call assert_equal('51/* }}} */', getline('.')) norm! j call assert_equal('52', getline('.')) norm! j call assert_equal('53', getline('.')) norm! j - call assert_equal('54/*}}}*/', getline('.')) + call assert_equal('54/* }}} */', getline('.')) norm! j call assert_equal('55', getline('.')) @@ -1614,15 +1614,15 @@ func Test_normal18_z_fold() call assert_equal(1, &foldenable) call assert_equal('48', getline('.')) norm! j - call assert_equal('49/*{{{*/', getline('.')) + call assert_equal('49/* {{{ */', getline('.')) norm! j - call assert_equal('50/*{{{*/', getline('.')) + call assert_equal('50/* {{{ */', getline('.')) norm! j call assert_equal('52', getline('.')) norm! j call assert_equal('53', getline('.')) norm! j - call assert_equal('54/*}}}*/', getline('.')) + call assert_equal('54/* }}} */', getline('.')) norm! j call assert_equal('55', getline('.')) @@ -1635,7 +1635,7 @@ func Test_normal18_z_fold() norm! k call assert_equal('48', getline('.')) norm! j - call assert_equal('49/*{{{*/', getline('.')) + call assert_equal('49/* {{{ */', getline('.')) norm! j call assert_equal('55', getline('.')) @@ -1652,7 +1652,7 @@ func Test_normal18_z_fold() norm! k call assert_equal('48', getline('.')) norm! j - call assert_equal('49/*{{{*/', getline('.')) + call assert_equal('49/* {{{ */', getline('.')) norm! j call assert_equal('55', getline('.')) @@ -1671,7 +1671,7 @@ func Test_normal18_z_fold() call assert_equal(0, &foldlevel) call assert_equal('48', getline('.')) norm! j - call assert_equal('49/*{{{*/', getline('.')) + call assert_equal('49/* {{{ */', getline('.')) norm! j call assert_equal('55', getline('.')) @@ -1689,11 +1689,11 @@ func Test_normal18_z_fold() call assert_equal(2, &foldlevel) call assert_equal('48', getline('.')) norm! j - call assert_equal('49/*{{{*/', getline('.')) + call assert_equal('49/* {{{ */', getline('.')) norm! j - call assert_equal('50/*{{{*/', getline('.')) + call assert_equal('50/* {{{ */', getline('.')) norm! j - call assert_equal('51/*}}}*/', getline('.')) + call assert_equal('51/* }}} */', getline('.')) norm! j call assert_equal('52', getline('.')) @@ -1709,24 +1709,24 @@ func Test_normal18_z_fold() call assert_equal(2, &foldlevel) call assert_equal('48', getline('.')) norm! j - call assert_equal('49/*{{{*/', getline('.')) + call assert_equal('49/* {{{ */', getline('.')) norm! j - call assert_equal('50/*{{{*/', getline('.')) + call assert_equal('50/* {{{ */', getline('.')) norm! j - call assert_equal('51/*}}}*/', getline('.')) + call assert_equal('51/* }}} */', getline('.')) norm! j call assert_equal('52', getline('.')) - call append(50, ['a /*{{{*/', 'b /*}}}*/']) + call append(50, ['a /* {{{ */', 'b /* }}} */']) 48 call assert_equal('48', getline('.')) norm! j - call assert_equal('49/*{{{*/', getline('.')) + call assert_equal('49/* {{{ */', getline('.')) norm! j - call assert_equal('50/*{{{*/', getline('.')) + call assert_equal('50/* {{{ */', getline('.')) norm! j - call assert_equal('a /*{{{*/', getline('.')) + call assert_equal('a /* {{{ */', getline('.')) norm! j - call assert_equal('51/*}}}*/', getline('.')) + call assert_equal('51/* }}} */', getline('.')) norm! j call assert_equal('52', getline('.')) 48 @@ -1735,15 +1735,15 @@ func Test_normal18_z_fold() call assert_equal(3, &foldlevel) call assert_equal('48', getline('.')) norm! j - call assert_equal('49/*{{{*/', getline('.')) + call assert_equal('49/* {{{ */', getline('.')) norm! j - call assert_equal('50/*{{{*/', getline('.')) + call assert_equal('50/* {{{ */', getline('.')) norm! j - call assert_equal('a /*{{{*/', getline('.')) + call assert_equal('a /* {{{ */', getline('.')) norm! j - call assert_equal('b /*}}}*/', getline('.')) + call assert_equal('b /* }}} */', getline('.')) norm! j - call assert_equal('51/*}}}*/', getline('.')) + call assert_equal('51/* }}} */', getline('.')) norm! j call assert_equal('52', getline('.')) @@ -1878,7 +1878,7 @@ func Test_normal23_K() let not_gnu_man = has('mac') || has('bsd') if not_gnu_man - " In MacOS and BSD, the option for specifying a pager is different + " In macOS and BSD, the option for specifying a pager is different set keywordprg=man\ -P\ cat else set keywordprg=man\ --pager=cat @@ -2727,7 +2727,7 @@ func Test_normal33_g_cmd2() call assert_equal('foo first line', getline(1)) set virtualedit& - " Test for aboring a g command using CTRL-\ CTRL-G + " Test for aborting a g command using CTRL-\ CTRL-G exe "normal! g\<C-\>\<C-G>" call assert_equal('foo first line', getline('.')) @@ -3897,9 +3897,9 @@ func Test_normal_count_after_operator() bw! endfunc -func Test_normal_gj_on_extra_wide_char() +func Test_normal_gj_on_6_cell_wide_unprintable_char() new | 25vsp - let text='1 foooooooo ar e inszwe1 foooooooo inszwei' . + let text='1 foooooooo ar e inszwe1 foooooooo inszwei' . \ ' i drei vier fünf sechs sieben acht un zehn elf zwöfl' . \ ' dreizehn v ierzehn fünfzehn' put =text @@ -4267,12 +4267,16 @@ func Test_page_cursor_topbot() call assert_equal(18, line('.')) exe "norm! \<C-B>\<C-F>" call assert_equal(9, line('.')) + " Not when already at the start of the buffer. + exe "norm! ggj\<C-B>" + call assert_equal(2, line('.')) bwipe! endfunc " Test for Ctrl-D with long line func Test_halfpage_longline() 10new + 40vsplit call setline(1, ['long'->repeat(1000), 'short']) exe "norm! \<C-D>" call assert_equal(2, line('.')) @@ -4280,7 +4284,7 @@ func Test_halfpage_longline() endfunc " Test for Ctrl-E with long line and very narrow window, -" used to cause an inifite loop +" used to cause an infinite loop func Test_scroll_longline_no_loop() 4vnew setl smoothscroll number showbreak=> scrolloff=2 @@ -4288,4 +4292,17 @@ func Test_scroll_longline_no_loop() exe "normal! \<C-E>" bwipe! endfunc + +" Test for go command +func Test_normal_go() + new + call setline(1, ['one two three four']) + call cursor(1, 5) + norm! dvgo + call assert_equal('wo three four', getline(1)) + norm! ... + call assert_equal('three four', getline(1)) + + bwipe! +endfunc " vim: shiftwidth=2 sts=2 expandtab nofoldenable diff --git a/test/old/testdir/test_options.vim b/test/old/testdir/test_options.vim index 7786f82af2..d0ae33605b 100644 --- a/test/old/testdir/test_options.vim +++ b/test/old/testdir/test_options.vim @@ -558,6 +558,9 @@ func Test_set_completion_string_values() " call assert_equal('sync', getcompletion('set swapsync=', 'cmdline')[1]) call assert_equal('usetab', getcompletion('set switchbuf=', 'cmdline')[1]) call assert_equal('ignore', getcompletion('set tagcase=', 'cmdline')[1]) + if exists('+tabclose') + call assert_equal('left uselast', join(sort(getcompletion('set tabclose=', 'cmdline'))), ' ') + endif if exists('+termwintype') call assert_equal('conpty', getcompletion('set termwintype=', 'cmdline')[1]) endif @@ -1364,6 +1367,31 @@ func Test_local_scrolloff() set siso& endfunc +func Test_writedelay() + CheckFunction reltimefloat + + new + call setline(1, 'empty') + " Nvim: 'writedelay' is applied per screen line. + " Create 7 vertical splits first. + vs | vs | vs | vs | vs | vs + redraw + set writedelay=10 + let start = reltime() + " call setline(1, repeat('x', 70)) + " Nvim: enable 'writedelay' per screen line. + " In each of the 7 vertical splits, 10 screen lines need to be drawn. + set redrawdebug+=line + call setline(1, repeat(['x'], 10)) + redraw + let elapsed = reltimefloat(reltime(start)) + set writedelay=0 + " With 'writedelay' set should take at least 30 * 10 msec + call assert_inrange(30 * 0.01, 999.0, elapsed) + + bwipe! +endfunc + func Test_visualbell() set belloff= set visualbell @@ -1377,9 +1405,10 @@ func Test_write() new call setline(1, ['L1']) set nowrite - call assert_fails('write Xfile', 'E142:') + call assert_fails('write Xwrfile', 'E142:') set write - close! + " close swapfile + bw! endfunc " Test for 'buftype' option @@ -1400,35 +1429,6 @@ func Test_buftype() bwipe! endfunc -" Test for the 'shell' option -func Test_shell() - throw 'Skipped: Nvim does not have :shell' - CheckUnix - let save_shell = &shell - set shell= - let caught_e91 = 0 - try - shell - catch /E91:/ - let caught_e91 = 1 - endtry - call assert_equal(1, caught_e91) - let &shell = save_shell -endfunc - -" Test for the 'shellquote' option -func Test_shellquote() - CheckUnix - set shellquote=# - set verbose=20 - redir => v - silent! !echo Hello - redir END - set verbose& - set shellquote& - call assert_match(': "#echo Hello#"', v) -endfunc - " Test for the 'rightleftcmd' option func Test_rightleftcmd() CheckFeature rightleft @@ -1761,7 +1761,7 @@ func Test_cmdheight() set cmdheight& endfunc -" To specify a control character as a option value, '^' can be used +" To specify a control character as an option value, '^' can be used func Test_opt_control_char() set wildchar=^v call assert_equal("\<C-V>", nr2char(&wildchar)) diff --git a/test/old/testdir/test_packadd.vim b/test/old/testdir/test_packadd.vim index 730cb3278b..47cf520bb9 100644 --- a/test/old/testdir/test_packadd.vim +++ b/test/old/testdir/test_packadd.vim @@ -258,6 +258,19 @@ func Test_packloadall() call assert_equal(4321, g:plugin_bar_number) endfunc +func Test_start_autoload() + " plugin foo with an autoload directory + let autodir = &packpath .. '/pack/mine/start/foo/autoload' + call mkdir(autodir, 'p') + let fname = autodir .. '/foobar.vim' + call writefile(['func foobar#test()', + \ ' return 1666', + \ 'endfunc'], fname) + + call assert_equal(1666, foobar#test()) + call delete(fname) +endfunc + func Test_helptags() let docdir1 = &packpath . '/pack/mine/start/foo/doc' let docdir2 = &packpath . '/pack/mine/start/bar/doc' diff --git a/test/old/testdir/test_partial.vim b/test/old/testdir/test_partial.vim index 8b62e4a0e5..b5933cdd6d 100644 --- a/test/old/testdir/test_partial.vim +++ b/test/old/testdir/test_partial.vim @@ -284,6 +284,11 @@ func Test_auto_partial_rebind() endfunc func Test_get_partial_items() + func s:Qux(x, y, z=3, w=1, ...) + endfunc + func s:Qux1(x, y) + endfunc + let dict = {'name': 'hello'} let args = ["foo", "bar"] let Func = function('MyDictFunc') @@ -304,6 +309,23 @@ func Test_get_partial_items() let dict = {'partial has': 'no dict'} call assert_equal(dict, get(P, 'dict', dict)) call assert_equal(0, get(l:P, 'dict')) + + call assert_equal({'required': 2, 'optional': 2, 'varargs': v:true}, + \ get(funcref('s:Qux', []), 'arity')) + call assert_equal({'required': 1, 'optional': 2, 'varargs': v:true}, + \ get(funcref('s:Qux', [1]), 'arity')) + call assert_equal({'required': 0, 'optional': 2, 'varargs': v:true}, + \ get(funcref('s:Qux', [1, 2]), 'arity')) + call assert_equal({'required': 0, 'optional': 1, 'varargs': v:true}, + \ get(funcref('s:Qux', [1, 2, 3]), 'arity')) + call assert_equal({'required': 0, 'optional': 0, 'varargs': v:true}, + \ get(funcref('s:Qux', [1, 2, 3, 4]), 'arity')) + " More args than expected is not an error + call assert_equal({'required': 0, 'optional': 0, 'varargs': v:false}, + \ get(funcref('s:Qux1', [1, 2, 3, 4]), 'arity')) + + delfunc s:Qux + delfunc s:Qux1 endfunc func Test_compare_partials() @@ -381,4 +403,18 @@ func Test_compare_partials() call assert_false(F1 is N1) endfunc +func Test_partial_method() + func Foo(x, y, z) + return x + y + z + endfunc + let d = {"Fn": function('Foo', [10, 20])} + call assert_fails('echo 30->d.Fn()', 'E1265: Cannot use a partial here') + delfunc Foo +endfunc + +func Test_non_callable_type_as_method() + let d = {"Fn": 10} + call assert_fails('echo 30->d.Fn()', 'E1085: Not a callable type: d.Fn') +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/test_popup.vim b/test/old/testdir/test_popup.vim index 56c881b958..472882fb87 100644 --- a/test/old/testdir/test_popup.vim +++ b/test/old/testdir/test_popup.vim @@ -16,7 +16,7 @@ func ListMonths() if !empty(entered) let mth = filter(mth, 'v:val=~"^".entered') endif - call complete(1, mth) + call complete(1, mth) return '' endfunc @@ -74,7 +74,7 @@ func Test_popup_complete() call feedkeys("aJu\<f5>\<c-p>l\<c-y>", 'tx') call assert_equal(["Jul"], getline(1,2)) %d - + " any-non printable, non-white character: Add this character and " reduce number of matches call feedkeys("aJu\<f5>\<c-p>l\<c-n>\<c-y>", 'tx') @@ -96,7 +96,7 @@ func Test_popup_complete() call feedkeys("aJ\<f5>".repeat("\<c-n>",3)."\<c-l>\<esc>", 'tx') call assert_equal(["J"], getline(1,2)) %d - + " <c-l> - Insert one character from the current match call feedkeys("aJ\<f5>".repeat("\<c-n>",4)."\<c-l>\<esc>", 'tx') call assert_equal(["January"], getline(1,2)) @@ -857,7 +857,7 @@ func Test_popup_position() call term_sendkeys(buf, "jI123456789_\<Esc>") call term_sendkeys(buf, "GA\<C-N>") call VerifyScreenDump(buf, 'Test_popup_position_04', {'rows': 10}) - + call term_sendkeys(buf, "\<Esc>u") call StopVimInTerminal(buf) call delete('Xtest') @@ -910,6 +910,13 @@ func Test_popup_command_dump() call term_sendkeys(buf, "\<Esc>") + if has('rightleft') + call term_sendkeys(buf, ":set rightleft\<CR>") + call term_sendkeys(buf, "/X\<CR>:popup PopUp\<CR>") + call VerifyScreenDump(buf, 'Test_popup_command_rl', {}) + call term_sendkeys(buf, "\<Esc>:set norightleft\<CR>") + endif + " Set a timer to change a menu entry while it's displayed. The text should " not change but the command does. Making the screendump also verifies that " "changed" shows up, which means the timer triggered. @@ -932,6 +939,37 @@ func Test_popup_command_dump() call StopVimInTerminal(buf) endfunc +" Test position of right-click menu when clicking near window edge. +func Test_mouse_popup_position() + CheckFeature menu + CheckScreendump + + let script =<< trim END + set mousemodel=popup_setpos + source $VIMRUNTIME/menu.vim + call setline(1, join(range(20))) + func Trigger(col) + call test_setmouse(1, a:col) + call feedkeys("\<RightMouse>", 't') + endfunc + END + call writefile(script, 'XmousePopupPosition', 'D') + let buf = RunVimInTerminal('-S XmousePopupPosition', #{rows: 20, cols: 50}) + + call term_sendkeys(buf, ":call Trigger(45)\<CR>") + call VerifyScreenDump(buf, 'Test_mouse_popup_position_01', {}) + call term_sendkeys(buf, "\<Esc>") + + if has('rightleft') + call term_sendkeys(buf, ":set rightleft\<CR>") + call term_sendkeys(buf, ":call Trigger(50 + 1 - 45)\<CR>") + call VerifyScreenDump(buf, 'Test_mouse_popup_position_02', {}) + call term_sendkeys(buf, "\<Esc>:set norightleft\<CR>") + endif + + call StopVimInTerminal(buf) +endfunc + func Test_popup_complete_backwards() new call setline(1, ['Post', 'Port', 'Po']) @@ -1178,6 +1216,8 @@ func Test_CompleteChanged() set completeopt=menu,menuone call feedkeys("i\<C-X>\<C-O>\<BS>\<BS>\<BS>f", 'tx') call assert_equal('five', g:word) + call feedkeys("i\<C-X>\<C-O>\<BS>\<BS>\<BS>f\<BS>", 'tx') + call assert_equal('one', g:word) autocmd! AAAAA_Group set complete& completeopt& @@ -1346,4 +1386,275 @@ func Test_pum_highlights_custom() call StopVimInTerminal(buf) endfunc +" Test match relate highlight group in pmenu +func Test_pum_highlights_match() + CheckScreendump + let lines =<< trim END + 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 ctermfg=6 ctermbg=7 + hi PmenuMatch ctermfg=4 ctermbg=225 + END + call writefile(lines, 'Xscript', 'D') + let buf = RunVimInTerminal('-S Xscript', {}) + call TermWait(buf) + call term_sendkeys(buf, "i\<C-X>\<C-O>") + call TermWait(buf, 50) + call term_sendkeys(buf, "fo") + call TermWait(buf, 50) + call VerifyScreenDump(buf, 'Test_pum_highlights_03', {}) + call term_sendkeys(buf, "\<Esc>S\<C-X>\<C-O>") + call TermWait(buf, 50) + call term_sendkeys(buf, "你") + call TermWait(buf, 50) + call VerifyScreenDump(buf, 'Test_pum_highlights_04', {}) + call term_sendkeys(buf, "吗") + call TermWait(buf, 50) + call VerifyScreenDump(buf, 'Test_pum_highlights_05', {}) + call term_sendkeys(buf, "\<C-E>\<Esc>") + + if has('rightleft') + call term_sendkeys(buf, ":set rightleft\<CR>") + call TermWait(buf, 50) + call term_sendkeys(buf, "S\<C-X>\<C-O>") + call TermWait(buf, 50) + call term_sendkeys(buf, "fo") + call TermWait(buf, 50) + call VerifyScreenDump(buf, 'Test_pum_highlights_06', {}) + call term_sendkeys(buf, "\<Esc>S\<C-X>\<C-O>") + call TermWait(buf, 50) + call term_sendkeys(buf, "你") + call VerifyScreenDump(buf, 'Test_pum_highlights_06a', {}) + call term_sendkeys(buf, "吗") + call VerifyScreenDump(buf, 'Test_pum_highlights_06b', {}) + call term_sendkeys(buf, "\<C-E>\<Esc>") + call term_sendkeys(buf, ":set norightleft\<CR>") + call TermWait(buf) + endif + + call term_sendkeys(buf, ":set completeopt-=fuzzy\<CR>") + call TermWait(buf) + call term_sendkeys(buf, "S\<C-X>\<C-O>") + call TermWait(buf, 50) + call term_sendkeys(buf, "fo") + call TermWait(buf, 50) + call VerifyScreenDump(buf, 'Test_pum_highlights_07', {}) + call term_sendkeys(buf, "\<C-E>\<Esc>") + + if has('rightleft') + call term_sendkeys(buf, ":set rightleft\<CR>") + call TermWait(buf, 50) + call term_sendkeys(buf, "S\<C-X>\<C-O>") + call TermWait(buf, 50) + call term_sendkeys(buf, "fo") + call TermWait(buf, 50) + call VerifyScreenDump(buf, 'Test_pum_highlights_08', {}) + call term_sendkeys(buf, "\<C-E>\<Esc>") + call term_sendkeys(buf, ":set norightleft\<CR>") + endif + + call term_sendkeys(buf, "S\<C-R>=Comp()\<CR>f") + call VerifyScreenDump(buf, 'Test_pum_highlights_09', {}) + call term_sendkeys(buf, "o\<BS>\<C-R>=Comp()\<CR>") + call VerifyScreenDump(buf, 'Test_pum_highlights_09', {}) + + " issue #15095 wrong select + call term_sendkeys(buf, "\<ESC>:set completeopt=fuzzy,menu\<CR>") + call TermWait(buf) + call term_sendkeys(buf, "S hello helio hero h\<C-X>\<C-P>") + call TermWait(buf, 50) + call VerifyScreenDump(buf, 'Test_pum_highlights_10', {}) + + call term_sendkeys(buf, "\<ESC>S hello helio hero h\<C-X>\<C-P>\<C-P>") + call TermWait(buf, 50) + call VerifyScreenDump(buf, 'Test_pum_highlights_11', {}) + + call term_sendkeys(buf, "\<C-E>\<Esc>") + call TermWait(buf) + + call StopVimInTerminal(buf) +endfunc + +func Test_pum_user_hl_group() + CheckScreendump + let lines =<< trim END + 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 ctermfg=9 + func HlMatch() + hi PmenuMatchSel ctermfg=6 ctermbg=7 cterm=underline + hi PmenuMatch ctermfg=4 ctermbg=225 cterm=underline + endfunc + END + call writefile(lines, 'Xscript', 'D') + let buf = RunVimInTerminal('-S Xscript', {}) + + call TermWait(buf) + call term_sendkeys(buf, "Saw\<C-X>\<C-U>") + call VerifyScreenDump(buf, 'Test_pum_highlights_12', {}) + call term_sendkeys(buf, "\<C-E>\<Esc>") + + call TermWait(buf) + call term_sendkeys(buf, ":call HlMatch()\<CR>") + + call TermWait(buf) + call term_sendkeys(buf, "Saw\<C-X>\<C-U>") + call VerifyScreenDump(buf, 'Test_pum_highlights_13', {}) + call term_sendkeys(buf, "\<C-N>") + call VerifyScreenDump(buf, 'Test_pum_highlights_14', {}) + call term_sendkeys(buf, "\<C-E>\<Esc>") + + call StopVimInTerminal(buf) +endfunc + +func Test_pum_user_kind_hlgroup() + CheckScreendump + let lines =<< trim END + 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 ctermfg=9 + hi KindVar ctermfg=yellow + hi KindFunc ctermfg=blue + hi KindClass ctermfg=green + END + call writefile(lines, 'Xscript', 'D') + let buf = RunVimInTerminal('-S Xscript', {}) + + call TermWait(buf) + call term_sendkeys(buf, "S\<C-X>\<C-U>") + call VerifyScreenDump(buf, 'Test_pum_highlights_16', {}) + call term_sendkeys(buf, "\<C-E>\<Esc>") + + call StopVimInTerminal(buf) +endfunc + +func Test_pum_completeitemalign() + CheckScreendump + let lines =<< trim END + 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 + command! -nargs=0 T1 set cia=abbr,kind,menu + command! -nargs=0 T2 set cia=abbr,menu,kind + command! -nargs=0 T3 set cia=kind,abbr,menu + command! -nargs=0 T4 set cia=kind,menu,abbr + command! -nargs=0 T5 set cia=menu,abbr,kind + command! -nargs=0 T6 set cia=menu,kind,abbr + command! -nargs=0 T7 set cia& + END + call writefile(lines, 'Xscript', 'D') + let buf = RunVimInTerminal('-S Xscript', {}) + call TermWait(buf) + + " T1 is default + call term_sendkeys(buf, ":T1\<CR>S\<C-X>\<C-O>") + call VerifyScreenDump(buf, 'Test_pum_completeitemalign_01', {}) + call term_sendkeys(buf, "\<C-E>\<Esc>") + + " T2 + call term_sendkeys(buf, ":T2\<CR>S\<C-X>\<C-O>") + call VerifyScreenDump(buf, 'Test_pum_completeitemalign_02', {}) + call term_sendkeys(buf, "\<C-E>\<Esc>") + + " T3 + call term_sendkeys(buf, ":T3\<CR>S\<C-X>\<C-O>") + call VerifyScreenDump(buf, 'Test_pum_completeitemalign_03', {}) + call term_sendkeys(buf, "\<C-E>\<Esc>") + + " T4 + call term_sendkeys(buf, ":T4\<CR>S\<C-X>\<C-O>") + call VerifyScreenDump(buf, 'Test_pum_completeitemalign_04', {}) + call term_sendkeys(buf, "\<C-E>\<Esc>") + + " T5 + call term_sendkeys(buf, ":T5\<CR>S\<C-X>\<C-O>") + call VerifyScreenDump(buf, 'Test_pum_completeitemalign_05', {}) + call term_sendkeys(buf, "\<C-E>\<Esc>") + + " T6 + call term_sendkeys(buf, ":T6\<CR>S\<C-X>\<C-O>") + call VerifyScreenDump(buf, 'Test_pum_completeitemalign_06', {}) + call term_sendkeys(buf, "\<C-E>\<Esc>") + + call term_sendkeys(buf, ":set columns=12 cmdheight=2 omnifunc=Omni_long\<CR>S\<C-X>\<C-O>") + call VerifyScreenDump(buf, 'Test_pum_completeitemalign_07', {}) + call term_sendkeys(buf, "\<C-E>\<Esc>:T7\<CR>") + call StopVimInTerminal(buf) +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/test_put.vim b/test/old/testdir/test_put.vim index 73b58dbe33..6b332faaeb 100644 --- a/test/old/testdir/test_put.vim +++ b/test/old/testdir/test_put.vim @@ -168,12 +168,6 @@ func Test_very_large_count() endfunc func Test_very_large_count_64bit() - throw 'Skipped: v:sizeoflong is N/A' " use legacy/put_spec.lua instead - - if v:sizeoflong < 8 - throw 'Skipped: only works with 64 bit long ints' - endif - new let @" = repeat('x', 100) call assert_fails('norm 999999999p', 'E1240:') @@ -190,12 +184,6 @@ func Test_very_large_count_block() endfunc func Test_very_large_count_block_64bit() - throw 'Skipped: v:sizeoflong is N/A' " use legacy/put_spec.lua instead - - if v:sizeoflong < 8 - throw 'Skipped: only works with 64 bit long ints' - endif - new call setline(1, repeat('x', 100)) exe "norm \<C-V>$y" diff --git a/test/old/testdir/test_quickfix.vim b/test/old/testdir/test_quickfix.vim index a708cabc26..753875963b 100644 --- a/test/old/testdir/test_quickfix.vim +++ b/test/old/testdir/test_quickfix.vim @@ -1020,52 +1020,50 @@ endfunc " More tests for 'errorformat' func Test_efm1() - if !has('unix') - " The 'errorformat' setting is different on non-Unix systems. - " This test works only on Unix-like systems. - return - endif + " The 'errorformat' setting is different on non-Unix systems. + " This test works only on Unix-like systems. + CheckUnix + + let l =<< trim [DATA] + "Xtestfile", line 4.12: 1506-045 (S) Undeclared identifier fd_set. + "Xtestfile", line 6 col 19; this is an error + gcc -c -DHAVE_CONFIsing-prototypes -I/usr/X11R6/include version.c + Xtestfile:9: parse error before `asd' + make: *** [src/vim/testdir/Makefile:100: test_quickfix] Error 1 + in file "Xtestfile" linenr 10: there is an error + + 2 returned + "Xtestfile", line 11 col 1; this is an error + "Xtestfile", line 12 col 2; this is another error + "Xtestfile", line 14:10; this is an error in column 10 + =Xtestfile=, line 15:10; this is another error, but in vcol 10 this time + "Xtestfile", linenr 16: yet another problem + Error in "Xtestfile" at line 17: + x should be a dot + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 17 + ^ + Error in "Xtestfile" at line 18: + x should be a dot + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 18 + .............^ + Error in "Xtestfile" at line 19: + x should be a dot + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 19 + --------------^ + Error in "Xtestfile" at line 20: + x should be a dot + xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 20 + ^ - let l =<< trim [DATA] - "Xtestfile", line 4.12: 1506-045 (S) Undeclared identifier fd_set. - "Xtestfile", line 6 col 19; this is an error - gcc -c -DHAVE_CONFIsing-prototypes -I/usr/X11R6/include version.c - Xtestfile:9: parse error before `asd' - make: *** [src/vim/testdir/Makefile:100: test_quickfix] Error 1 - in file "Xtestfile" linenr 10: there is an error - - 2 returned - "Xtestfile", line 11 col 1; this is an error - "Xtestfile", line 12 col 2; this is another error - "Xtestfile", line 14:10; this is an error in column 10 - =Xtestfile=, line 15:10; this is another error, but in vcol 10 this time - "Xtestfile", linenr 16: yet another problem - Error in "Xtestfile" at line 17: - x should be a dot - xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 17 - ^ - Error in "Xtestfile" at line 18: - x should be a dot - xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 18 - .............^ - Error in "Xtestfile" at line 19: - x should be a dot - xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 19 - --------------^ - Error in "Xtestfile" at line 20: - x should be a dot - xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 20 - ^ - - Does anyone know what is the problem and how to correction it? - "Xtestfile", line 21 col 9: What is the title of the quickfix window? - "Xtestfile", line 22 col 9: What is the title of the quickfix window? - [DATA] - - call writefile(l, 'Xerrorfile1') - call writefile(l[:-2], 'Xerrorfile2') - - let m =<< [DATA] + Does anyone know what is the problem and how to correction it? + "Xtestfile", line 21 col 9: What is the title of the quickfix window? + "Xtestfile", line 22 col 9: What is the title of the quickfix window? + [DATA] + + call writefile(l, 'Xerrorfile1') + call writefile(l[:-2], 'Xerrorfile2') + + let m =<< [DATA] xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 2 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 3 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 4 @@ -1088,55 +1086,55 @@ func Test_efm1() xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 21 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx line 22 [DATA] - call writefile(m, 'Xtestfile') + call writefile(m, 'Xtestfile') - let save_efm = &efm - set efm+==%f=\\,\ line\ %l%*\\D%v%*[^\ ]\ %m - set efm^=%AError\ in\ \"%f\"\ at\ line\ %l:,%Z%p^,%C%m + let save_efm = &efm + set efm+==%f=\\,\ line\ %l%*\\D%v%*[^\ ]\ %m + set efm^=%AError\ in\ \"%f\"\ at\ line\ %l:,%Z%p^,%C%m - exe 'cf Xerrorfile2' - clast - copen - call assert_equal(':cf Xerrorfile2', w:quickfix_title) - wincmd p + exe 'cf Xerrorfile2' + clast + copen + call assert_equal(':cf Xerrorfile2', w:quickfix_title) + wincmd p - exe 'cf Xerrorfile1' - call assert_equal([4, 12], [line('.'), col('.')]) - cn - call assert_equal([6, 19], [line('.'), col('.')]) - cn - call assert_equal([9, 2], [line('.'), col('.')]) - cn - call assert_equal([10, 2], [line('.'), col('.')]) - cn - call assert_equal([11, 1], [line('.'), col('.')]) - cn - call assert_equal([12, 2], [line('.'), col('.')]) - cn - call assert_equal([14, 10], [line('.'), col('.')]) - cn - call assert_equal([15, 3, 10], [line('.'), col('.'), virtcol('.')]) - cn - call assert_equal([16, 2], [line('.'), col('.')]) - cn - call assert_equal([17, 6], [line('.'), col('.')]) - cn - call assert_equal([18, 7], [line('.'), col('.')]) - cn - call assert_equal([19, 8], [line('.'), col('.')]) - cn - call assert_equal([20, 9], [line('.'), col('.')]) - clast - cprev - cprev - wincmd w - call assert_equal(':cf Xerrorfile1', w:quickfix_title) - wincmd p + exe 'cf Xerrorfile1' + call assert_equal([4, 12], [line('.'), col('.')]) + cn + call assert_equal([6, 19], [line('.'), col('.')]) + cn + call assert_equal([9, 2], [line('.'), col('.')]) + cn + call assert_equal([10, 2], [line('.'), col('.')]) + cn + call assert_equal([11, 1], [line('.'), col('.')]) + cn + call assert_equal([12, 2], [line('.'), col('.')]) + cn + call assert_equal([14, 10], [line('.'), col('.')]) + cn + call assert_equal([15, 3, 10], [line('.'), col('.'), virtcol('.')]) + cn + call assert_equal([16, 2], [line('.'), col('.')]) + cn + call assert_equal([17, 6], [line('.'), col('.')]) + cn + call assert_equal([18, 7], [line('.'), col('.')]) + cn + call assert_equal([19, 8], [line('.'), col('.')]) + cn + call assert_equal([20, 9], [line('.'), col('.')]) + clast + cprev + cprev + wincmd w + call assert_equal(':cf Xerrorfile1', w:quickfix_title) + wincmd p - let &efm = save_efm - call delete('Xerrorfile1') - call delete('Xerrorfile2') - call delete('Xtestfile') + let &efm = save_efm + call delete('Xerrorfile1') + call delete('Xerrorfile2') + call delete('Xtestfile') endfunc " Test for quickfix directory stack support @@ -1410,7 +1408,7 @@ func Test_efm2() failUnlessEqual raise self.failureException, \\ W:AssertionError: 34 != 33 - + -------------------------------------------------------------- Ran 27 tests in 0.063s [DATA] @@ -2210,10 +2208,8 @@ func s:test_xgrep(cchar) endfunc func Test_grep() - if !has('unix') - " The grepprg may not be set on non-Unix systems - return - endif + " The grepprg may not be set on non-Unix systems + CheckUnix call s:test_xgrep('c') call s:test_xgrep('l') @@ -2351,254 +2347,254 @@ endfunc " Quickfix/Location list set/get properties tests func Xproperty_tests(cchar) - call s:setup_commands(a:cchar) + call s:setup_commands(a:cchar) - " Error cases - call assert_fails('call g:Xgetlist(99)', 'E715:') - call assert_fails('call g:Xsetlist(99)', 'E714:') - call assert_fails('call g:Xsetlist([], "a", [])', 'E715:') + " Error cases + call assert_fails('call g:Xgetlist(99)', 'E715:') + call assert_fails('call g:Xsetlist(99)', 'E714:') + call assert_fails('call g:Xsetlist([], "a", [])', 'E715:') - " Set and get the title - call g:Xsetlist([]) - Xopen - wincmd p - call g:Xsetlist([{'filename':'foo', 'lnum':27}]) - let s = g:Xsetlist([], 'a', {'title' : 'Sample'}) - call assert_equal(0, s) - let d = g:Xgetlist({"title":1}) - call assert_equal('Sample', d.title) - " Try setting title to a non-string value - call assert_equal(-1, g:Xsetlist([], 'a', {'title' : ['Test']})) - call assert_equal('Sample', g:Xgetlist({"title":1}).title) + " Set and get the title + call g:Xsetlist([]) + Xopen + wincmd p + call g:Xsetlist([{'filename':'foo', 'lnum':27}]) + let s = g:Xsetlist([], 'a', {'title' : 'Sample'}) + call assert_equal(0, s) + let d = g:Xgetlist({"title":1}) + call assert_equal('Sample', d.title) + " Try setting title to a non-string value + call assert_equal(-1, g:Xsetlist([], 'a', {'title' : ['Test']})) + call assert_equal('Sample', g:Xgetlist({"title":1}).title) - Xopen - call assert_equal('Sample', w:quickfix_title) - Xclose - - " Tests for action argument - silent! Xolder 999 - let qfnr = g:Xgetlist({'all':1}).nr - call g:Xsetlist([], 'r', {'title' : 'N1'}) - call assert_equal('N1', g:Xgetlist({'all':1}).title) - call g:Xsetlist([], ' ', {'title' : 'N2'}) - call assert_equal(qfnr + 1, g:Xgetlist({'all':1}).nr) - - let res = g:Xgetlist({'nr': 0}) - call assert_equal(qfnr + 1, res.nr) - call assert_equal(['nr'], keys(res)) - - call g:Xsetlist([], ' ', {'title' : 'N3'}) - call assert_equal('N2', g:Xgetlist({'nr':2, 'title':1}).title) - - " Changing the title of an earlier quickfix list - call g:Xsetlist([], 'r', {'title' : 'NewTitle', 'nr' : 2}) - call assert_equal('NewTitle', g:Xgetlist({'nr':2, 'title':1}).title) - - " Changing the title of an invalid quickfix list - call assert_equal(-1, g:Xsetlist([], ' ', - \ {'title' : 'SomeTitle', 'nr' : 99})) - call assert_equal(-1, g:Xsetlist([], ' ', - \ {'title' : 'SomeTitle', 'nr' : 'abc'})) - - if a:cchar == 'c' - copen - call assert_equal({'winid':win_getid()}, getqflist({'winid':1})) - cclose - endif + Xopen + call assert_equal('Sample', w:quickfix_title) + Xclose - " Invalid arguments - call assert_fails('call g:Xgetlist([])', 'E715') - call assert_fails('call g:Xsetlist([], "a", [])', 'E715') - let s = g:Xsetlist([], 'a', {'abc':1}) - call assert_equal(-1, s) + " Tests for action argument + silent! Xolder 999 + let qfnr = g:Xgetlist({'all':1}).nr + call g:Xsetlist([], 'r', {'title' : 'N1'}) + call assert_equal('N1', g:Xgetlist({'all':1}).title) + call g:Xsetlist([], ' ', {'title' : 'N2'}) + call assert_equal(qfnr + 1, g:Xgetlist({'all':1}).nr) - call assert_equal({}, g:Xgetlist({'abc':1})) - call assert_equal('', g:Xgetlist({'nr':99, 'title':1}).title) - call assert_equal('', g:Xgetlist({'nr':[], 'title':1}).title) + let res = g:Xgetlist({'nr': 0}) + call assert_equal(qfnr + 1, res.nr) + call assert_equal(['nr'], keys(res)) - if a:cchar == 'l' - call assert_equal({}, getloclist(99, {'title': 1})) - endif + call g:Xsetlist([], ' ', {'title' : 'N3'}) + call assert_equal('N2', g:Xgetlist({'nr':2, 'title':1}).title) - " Context related tests - let s = g:Xsetlist([], 'a', {'context':[1,2,3]}) - call assert_equal(0, s) - call test_garbagecollect_now() - let d = g:Xgetlist({'context':1}) - call assert_equal([1,2,3], d.context) - call g:Xsetlist([], 'a', {'context':{'color':'green'}}) - let d = g:Xgetlist({'context':1}) - call assert_equal({'color':'green'}, d.context) - call g:Xsetlist([], 'a', {'context':"Context info"}) - let d = g:Xgetlist({'context':1}) - call assert_equal("Context info", d.context) - call g:Xsetlist([], 'a', {'context':246}) - let d = g:Xgetlist({'context':1}) - call assert_equal(246, d.context) - " set other Vim data types as context - call g:Xsetlist([], 'a', {'context' : v:_null_blob}) - if has('channel') - call g:Xsetlist([], 'a', {'context' : test_null_channel()}) - endif - if has('job') - call g:Xsetlist([], 'a', {'context' : test_null_job()}) - endif - " Nvim doesn't have null functions - " call g:Xsetlist([], 'a', {'context' : test_null_function()}) - " Nvim doesn't have null partials - " call g:Xsetlist([], 'a', {'context' : test_null_partial()}) - call g:Xsetlist([], 'a', {'context' : ''}) - call test_garbagecollect_now() - if a:cchar == 'l' - " Test for copying context across two different location lists - new | only - let w1_id = win_getid() - let l = [1] - call setloclist(0, [], 'a', {'context':l}) - new - let w2_id = win_getid() - call add(l, 2) - call assert_equal([1, 2], getloclist(w1_id, {'context':1}).context) - call assert_equal([1, 2], getloclist(w2_id, {'context':1}).context) - unlet! l - call assert_equal([1, 2], getloclist(w2_id, {'context':1}).context) - only - call setloclist(0, [], 'f') - call assert_equal('', getloclist(0, {'context':1}).context) - endif + " Changing the title of an earlier quickfix list + call g:Xsetlist([], 'r', {'title' : 'NewTitle', 'nr' : 2}) + call assert_equal('NewTitle', g:Xgetlist({'nr':2, 'title':1}).title) - " Test for changing the context of previous quickfix lists - call g:Xsetlist([], 'f') - Xexpr "One" - Xexpr "Two" - Xexpr "Three" - call g:Xsetlist([], 'r', {'context' : [1], 'nr' : 1}) - call g:Xsetlist([], 'a', {'context' : [2], 'nr' : 2}) - " Also, check for setting the context using quickfix list number zero. - call g:Xsetlist([], 'r', {'context' : [3], 'nr' : 0}) - call test_garbagecollect_now() - let l = g:Xgetlist({'nr' : 1, 'context' : 1}) - call assert_equal([1], l.context) - let l = g:Xgetlist({'nr' : 2, 'context' : 1}) - call assert_equal([2], l.context) - let l = g:Xgetlist({'nr' : 3, 'context' : 1}) - call assert_equal([3], l.context) - - " Test for changing the context through reference and for garbage - " collection of quickfix context - let l = ["red"] - call g:Xsetlist([], ' ', {'context' : l}) - call add(l, "blue") - let x = g:Xgetlist({'context' : 1}) - call add(x.context, "green") - call assert_equal(["red", "blue", "green"], l) - call assert_equal(["red", "blue", "green"], x.context) - unlet l - call test_garbagecollect_now() - let m = g:Xgetlist({'context' : 1}) - call assert_equal(["red", "blue", "green"], m.context) - - " Test for setting/getting items - Xexpr "" - let qfprev = g:Xgetlist({'nr':0}) - let s = g:Xsetlist([], ' ', {'title':'Green', - \ 'items' : [{'filename':'F1', 'lnum':10}]}) - call assert_equal(0, s) - let qfcur = g:Xgetlist({'nr':0}) - call assert_true(qfcur.nr == qfprev.nr + 1) - let l = g:Xgetlist({'items':1}) - call assert_equal('F1', bufname(l.items[0].bufnr)) - call assert_equal(10, l.items[0].lnum) - call g:Xsetlist([], 'a', {'items' : [{'filename':'F2', 'lnum':20}, - \ {'filename':'F2', 'lnum':30}]}) - let l = g:Xgetlist({'items':1}) - call assert_equal('F2', bufname(l.items[2].bufnr)) - call assert_equal(30, l.items[2].lnum) - call g:Xsetlist([], 'r', {'items' : [{'filename':'F3', 'lnum':40}]}) - let l = g:Xgetlist({'items':1}) - call assert_equal('F3', bufname(l.items[0].bufnr)) - call assert_equal(40, l.items[0].lnum) - call g:Xsetlist([], 'r', {'items' : []}) - let l = g:Xgetlist({'items':1}) - call assert_equal(0, len(l.items)) - - call g:Xsetlist([], 'r', {'title' : 'TestTitle'}) - call g:Xsetlist([], 'r', {'items' : [{'filename' : 'F1', 'lnum' : 10, 'text' : 'L10'}]}) - call g:Xsetlist([], 'r', {'items' : [{'filename' : 'F1', 'lnum' : 10, 'text' : 'L10'}]}) - call assert_equal('TestTitle', g:Xgetlist({'title' : 1}).title) - - " Test for getting id of window associated with a location list window - if a:cchar == 'l' - only - call assert_equal(0, g:Xgetlist({'all' : 1}).filewinid) - let wid = win_getid() - Xopen - call assert_equal(wid, g:Xgetlist({'filewinid' : 1}).filewinid) - wincmd w - call assert_equal(0, g:Xgetlist({'filewinid' : 1}).filewinid) - only - endif + " Changing the title of an invalid quickfix list + call assert_equal(-1, g:Xsetlist([], ' ', + \ {'title' : 'SomeTitle', 'nr' : 99})) + call assert_equal(-1, g:Xsetlist([], ' ', + \ {'title' : 'SomeTitle', 'nr' : 'abc'})) - " The following used to crash Vim with address sanitizer - call g:Xsetlist([], 'f') - call g:Xsetlist([], 'a', {'items' : [{'filename':'F1', 'lnum':10}]}) - call assert_equal(10, g:Xgetlist({'items':1}).items[0].lnum) + if a:cchar == 'c' + copen + call assert_equal({'winid':win_getid()}, getqflist({'winid':1})) + cclose + endif - " Try setting the items using a string - call assert_equal(-1, g:Xsetlist([], ' ', {'items' : 'Test'})) + " Invalid arguments + call assert_fails('call g:Xgetlist([])', 'E715') + call assert_fails('call g:Xsetlist([], "a", [])', 'E715') + let s = g:Xsetlist([], 'a', {'abc':1}) + call assert_equal(-1, s) - " Save and restore the quickfix stack - call g:Xsetlist([], 'f') - call assert_equal(0, g:Xgetlist({'nr':'$'}).nr) - Xexpr "File1:10:Line1" - Xexpr "File2:20:Line2" - Xexpr "File3:30:Line3" - let last_qf = g:Xgetlist({'nr':'$'}).nr - call assert_equal(3, last_qf) - let qstack = [] - for i in range(1, last_qf) - let qstack = add(qstack, g:Xgetlist({'nr':i, 'all':1})) - endfor - call g:Xsetlist([], 'f') - for i in range(len(qstack)) - call g:Xsetlist([], ' ', qstack[i]) - endfor - call assert_equal(3, g:Xgetlist({'nr':'$'}).nr) - call assert_equal(10, g:Xgetlist({'nr':1, 'items':1}).items[0].lnum) - call assert_equal(20, g:Xgetlist({'nr':2, 'items':1}).items[0].lnum) - call assert_equal(30, g:Xgetlist({'nr':3, 'items':1}).items[0].lnum) - call g:Xsetlist([], 'f') + call assert_equal({}, g:Xgetlist({'abc':1})) + call assert_equal('', g:Xgetlist({'nr':99, 'title':1}).title) + call assert_equal('', g:Xgetlist({'nr':[], 'title':1}).title) - " Swap two quickfix lists - Xexpr "File1:10:Line10" - Xexpr "File2:20:Line20" - Xexpr "File3:30:Line30" - call g:Xsetlist([], 'r', {'nr':1,'title':'Colors','context':['Colors']}) - call g:Xsetlist([], 'r', {'nr':2,'title':'Fruits','context':['Fruits']}) - let l1=g:Xgetlist({'nr':1,'all':1}) - let l2=g:Xgetlist({'nr':2,'all':1}) - let save_id = l1.id - let l1.id=l2.id - let l2.id=save_id - call g:Xsetlist([], 'r', l1) - call g:Xsetlist([], 'r', l2) - let newl1=g:Xgetlist({'nr':1,'all':1}) - let newl2=g:Xgetlist({'nr':2,'all':1}) - call assert_equal('Fruits', newl1.title) - call assert_equal(['Fruits'], newl1.context) - call assert_equal('Line20', newl1.items[0].text) - call assert_equal('Colors', newl2.title) - call assert_equal(['Colors'], newl2.context) - call assert_equal('Line10', newl2.items[0].text) - call g:Xsetlist([], 'f') + if a:cchar == 'l' + call assert_equal({}, getloclist(99, {'title': 1})) + endif - " Cannot specify both a non-empty list argument and a dict argument - call assert_fails("call g:Xsetlist([{}], ' ', {})", 'E475:') + " Context related tests + let s = g:Xsetlist([], 'a', {'context':[1,2,3]}) + call assert_equal(0, s) + call test_garbagecollect_now() + let d = g:Xgetlist({'context':1}) + call assert_equal([1,2,3], d.context) + call g:Xsetlist([], 'a', {'context':{'color':'green'}}) + let d = g:Xgetlist({'context':1}) + call assert_equal({'color':'green'}, d.context) + call g:Xsetlist([], 'a', {'context':"Context info"}) + let d = g:Xgetlist({'context':1}) + call assert_equal("Context info", d.context) + call g:Xsetlist([], 'a', {'context':246}) + let d = g:Xgetlist({'context':1}) + call assert_equal(246, d.context) + " set other Vim data types as context + call g:Xsetlist([], 'a', {'context' : v:_null_blob}) + if has('channel') + call g:Xsetlist([], 'a', {'context' : test_null_channel()}) + endif + if has('job') + call g:Xsetlist([], 'a', {'context' : test_null_job()}) + endif + " Nvim doesn't have null functions + " call g:Xsetlist([], 'a', {'context' : test_null_function()}) + " Nvim doesn't have null partials + " call g:Xsetlist([], 'a', {'context' : test_null_partial()}) + call g:Xsetlist([], 'a', {'context' : ''}) + call test_garbagecollect_now() + if a:cchar == 'l' + " Test for copying context across two different location lists + new | only + let w1_id = win_getid() + let l = [1] + call setloclist(0, [], 'a', {'context':l}) + new + let w2_id = win_getid() + call add(l, 2) + call assert_equal([1, 2], getloclist(w1_id, {'context':1}).context) + call assert_equal([1, 2], getloclist(w2_id, {'context':1}).context) + unlet! l + call assert_equal([1, 2], getloclist(w2_id, {'context':1}).context) + only + call setloclist(0, [], 'f') + call assert_equal('', getloclist(0, {'context':1}).context) + endif + + " Test for changing the context of previous quickfix lists + call g:Xsetlist([], 'f') + Xexpr "One" + Xexpr "Two" + Xexpr "Three" + call g:Xsetlist([], 'r', {'context' : [1], 'nr' : 1}) + call g:Xsetlist([], 'a', {'context' : [2], 'nr' : 2}) + " Also, check for setting the context using quickfix list number zero. + call g:Xsetlist([], 'r', {'context' : [3], 'nr' : 0}) + call test_garbagecollect_now() + let l = g:Xgetlist({'nr' : 1, 'context' : 1}) + call assert_equal([1], l.context) + let l = g:Xgetlist({'nr' : 2, 'context' : 1}) + call assert_equal([2], l.context) + let l = g:Xgetlist({'nr' : 3, 'context' : 1}) + call assert_equal([3], l.context) + + " Test for changing the context through reference and for garbage + " collection of quickfix context + let l = ["red"] + call g:Xsetlist([], ' ', {'context' : l}) + call add(l, "blue") + let x = g:Xgetlist({'context' : 1}) + call add(x.context, "green") + call assert_equal(["red", "blue", "green"], l) + call assert_equal(["red", "blue", "green"], x.context) + unlet l + call test_garbagecollect_now() + let m = g:Xgetlist({'context' : 1}) + call assert_equal(["red", "blue", "green"], m.context) + + " Test for setting/getting items + Xexpr "" + let qfprev = g:Xgetlist({'nr':0}) + let s = g:Xsetlist([], ' ', {'title':'Green', + \ 'items' : [{'filename':'F1', 'lnum':10}]}) + call assert_equal(0, s) + let qfcur = g:Xgetlist({'nr':0}) + call assert_true(qfcur.nr == qfprev.nr + 1) + let l = g:Xgetlist({'items':1}) + call assert_equal('F1', bufname(l.items[0].bufnr)) + call assert_equal(10, l.items[0].lnum) + call g:Xsetlist([], 'a', {'items' : [{'filename':'F2', 'lnum':20}, + \ {'filename':'F2', 'lnum':30}]}) + let l = g:Xgetlist({'items':1}) + call assert_equal('F2', bufname(l.items[2].bufnr)) + call assert_equal(30, l.items[2].lnum) + call g:Xsetlist([], 'r', {'items' : [{'filename':'F3', 'lnum':40}]}) + let l = g:Xgetlist({'items':1}) + call assert_equal('F3', bufname(l.items[0].bufnr)) + call assert_equal(40, l.items[0].lnum) + call g:Xsetlist([], 'r', {'items' : []}) + let l = g:Xgetlist({'items':1}) + call assert_equal(0, len(l.items)) + + call g:Xsetlist([], 'r', {'title' : 'TestTitle'}) + call g:Xsetlist([], 'r', {'items' : [{'filename' : 'F1', 'lnum' : 10, 'text' : 'L10'}]}) + call g:Xsetlist([], 'r', {'items' : [{'filename' : 'F1', 'lnum' : 10, 'text' : 'L10'}]}) + call assert_equal('TestTitle', g:Xgetlist({'title' : 1}).title) + + " Test for getting id of window associated with a location list window + if a:cchar == 'l' + only + call assert_equal(0, g:Xgetlist({'all' : 1}).filewinid) + let wid = win_getid() + Xopen + call assert_equal(wid, g:Xgetlist({'filewinid' : 1}).filewinid) + wincmd w + call assert_equal(0, g:Xgetlist({'filewinid' : 1}).filewinid) + only + endif + + " The following used to crash Vim with address sanitizer + call g:Xsetlist([], 'f') + call g:Xsetlist([], 'a', {'items' : [{'filename':'F1', 'lnum':10}]}) + call assert_equal(10, g:Xgetlist({'items':1}).items[0].lnum) + + " Try setting the items using a string + call assert_equal(-1, g:Xsetlist([], ' ', {'items' : 'Test'})) + + " Save and restore the quickfix stack + call g:Xsetlist([], 'f') + call assert_equal(0, g:Xgetlist({'nr':'$'}).nr) + Xexpr "File1:10:Line1" + Xexpr "File2:20:Line2" + Xexpr "File3:30:Line3" + let last_qf = g:Xgetlist({'nr':'$'}).nr + call assert_equal(3, last_qf) + let qstack = [] + for i in range(1, last_qf) + let qstack = add(qstack, g:Xgetlist({'nr':i, 'all':1})) + endfor + call g:Xsetlist([], 'f') + for i in range(len(qstack)) + call g:Xsetlist([], ' ', qstack[i]) + endfor + call assert_equal(3, g:Xgetlist({'nr':'$'}).nr) + call assert_equal(10, g:Xgetlist({'nr':1, 'items':1}).items[0].lnum) + call assert_equal(20, g:Xgetlist({'nr':2, 'items':1}).items[0].lnum) + call assert_equal(30, g:Xgetlist({'nr':3, 'items':1}).items[0].lnum) + call g:Xsetlist([], 'f') + + " Swap two quickfix lists + Xexpr "File1:10:Line10" + Xexpr "File2:20:Line20" + Xexpr "File3:30:Line30" + call g:Xsetlist([], 'r', {'nr':1,'title':'Colors','context':['Colors']}) + call g:Xsetlist([], 'r', {'nr':2,'title':'Fruits','context':['Fruits']}) + let l1=g:Xgetlist({'nr':1,'all':1}) + let l2=g:Xgetlist({'nr':2,'all':1}) + let save_id = l1.id + let l1.id=l2.id + let l2.id=save_id + call g:Xsetlist([], 'r', l1) + call g:Xsetlist([], 'r', l2) + let newl1=g:Xgetlist({'nr':1,'all':1}) + let newl2=g:Xgetlist({'nr':2,'all':1}) + call assert_equal('Fruits', newl1.title) + call assert_equal(['Fruits'], newl1.context) + call assert_equal('Line20', newl1.items[0].text) + call assert_equal('Colors', newl2.title) + call assert_equal(['Colors'], newl2.context) + call assert_equal('Line10', newl2.items[0].text) + call g:Xsetlist([], 'f') + + " Cannot specify both a non-empty list argument and a dict argument + call assert_fails("call g:Xsetlist([{}], ' ', {})", 'E475:') endfunc func Test_qf_property() - call Xproperty_tests('c') - call Xproperty_tests('l') + call Xproperty_tests('c') + call Xproperty_tests('l') endfunc " Test for setting the current index in the location/quickfix list @@ -3132,7 +3128,7 @@ func Test_vimgrep_existing_swapfile() call assert_match('.Xapple.swo', swapname('')) call delete('Xapple') - call delete('Xapple.swp') + call delete('.Xapple.swp') augroup grep au! SwapExists augroup END @@ -3212,8 +3208,11 @@ func Test_bufoverflow() cgetexpr ['Compiler: ' . repeat('a', 1015), 'File1:10:Hello World'] set efm=%DEntering\ directory\ %f,%f:%l:%m - cgetexpr ['Entering directory ' . repeat('a', 1006), - \ 'File1:10:Hello World'] + let lines =<< trim eval END + Entering directory $"{repeat('a', 1006)}" + File1:10:Hello World + END + cgetexpr lines set efm&vim endfunc @@ -3585,15 +3584,15 @@ endfunc " Open multiple help windows using ":lhelpgrep " This test used to crash Vim func Test_Multi_LL_Help() - new | only - lhelpgrep window - lopen - e# - lhelpgrep buffer - call assert_equal(3, winnr('$')) - call assert_true(len(getloclist(1)) != 0) - call assert_true(len(getloclist(2)) != 0) - new | only + new | only + lhelpgrep window + lopen + e# + lhelpgrep buffer + call assert_equal(3, winnr('$')) + call assert_true(len(getloclist(1)) != 0) + call assert_true(len(getloclist(2)) != 0) + new | only endfunc " Tests for adding new quickfix lists using setqflist() @@ -4072,11 +4071,23 @@ func Test_ll_window_ctx() enew | only endfunc +" Similar to the problem above, but for user data. +func Test_ll_window_user_data() + call setloclist(0, [#{bufnr: bufnr(), user_data: {}}]) + lopen + wincmd t + close + call test_garbagecollect_now() + call feedkeys("\<CR>", 'tx') + call test_garbagecollect_now() + %bwipe! +endfunc + " The following test used to crash vim func Test_lfile_crash() sp Xtest au QuickFixCmdPre * bw - call assert_fails('lfile', 'E40') + call assert_fails('lfile', 'E40:') au! QuickFixCmdPre endfunc @@ -4129,6 +4140,7 @@ endfunc " The following test used to crash Vim func Test_lvimgrep_crash() + " this leaves a swapfile .test_quickfix.vim.swp around, why? sv Xtest augroup QF_Test au! @@ -4191,8 +4203,8 @@ endfunc " :vimgrep/:lvimgrep commands are running. func Test_vimgrep_autocmd() call setqflist([], 'f') - call writefile(['stars'], 'Xtest1.txt') - call writefile(['stars'], 'Xtest2.txt') + call writefile(['stars'], 'Xtest1.txt', 'D') + call writefile(['stars'], 'Xtest2.txt', 'D') " Test 1: " When searching for a pattern using :vimgrep, if the quickfix list is @@ -4222,9 +4234,9 @@ func Test_vimgrep_autocmd() autocmd BufRead Xtest2.txt call setloclist(g:save_winid, [], 'f') call assert_fails('lvimgrep stars Xtest*.txt', 'E926:') au! BufRead Xtest2.txt + " cleanup the swap files + bw! Xtest2.txt Xtest1.txt - call delete('Xtest1.txt') - call delete('Xtest2.txt') call setqflist([], 'f') endfunc @@ -4353,11 +4365,9 @@ endfunc " Test for shortening/simplifying the file name when opening the " quickfix window or when displaying the quickfix list func Test_shorten_fname() - if !has('unix') - return - endif + CheckUnix %bwipe - " Create a quickfix list with a absolute path filename + " Create a quickfix list with an absolute path filename let fname = getcwd() . '/test_quickfix.vim' call setqflist([], ' ', {'lines':[fname . ":20:Line20"], 'efm':'%f:%l:%m'}) call assert_equal(fname, bufname('test_quickfix.vim')) @@ -4366,7 +4376,7 @@ func Test_shorten_fname() call assert_equal('test_quickfix.vim', bufname('test_quickfix.vim')) cclose %bwipe - " Create a quickfix list with a absolute path filename + " Create a quickfix list with an absolute path filename call setqflist([], ' ', {'lines':[fname . ":20:Line20"], 'efm':'%f:%l:%m'}) call assert_equal(fname, bufname('test_quickfix.vim')) " Displaying the quickfix list should simplify the file path @@ -5926,6 +5936,16 @@ func Test_qfbuf_update() call Xqfbuf_update('l') endfunc +func Test_vimgrep_noswapfile() + set noswapfile + call writefile(['one', 'two', 'three'], 'Xgreppie') + vimgrep two Xgreppie + call assert_equal('two', getline('.')) + + call delete('Xgreppie') + set swapfile +endfunc + " Test for the :vimgrep 'f' flag (fuzzy match) func Xvimgrep_fuzzy_match(cchar) call s:setup_commands(a:cchar) @@ -6439,4 +6459,41 @@ func Test_cbuffer_range() call XbufferTests_range('l') endfunc +" Test for displaying fname pass from setqflist when the name +" are hard links to prevent seemly duplicate entries. +func Xtest_hardlink_fname(cchar) + call s:setup_commands(a:cchar) + %bwipe + " Create a sample source file + let lines =<< trim END + void sample() {} + int main() { sample(); return 0; } + END + call writefile(lines, 'test_qf_hardlink1.c', 'D') + defer delete('test_qf_hardlink1.c') + defer delete('test_qf_hardlink2.c') + call system('ln test_qf_hardlink1.c test_qf_hardlink2.c') + if v:shell_error + throw 'Skipped: ln throws error on this platform' + endif + call g:Xsetlist([], 'f') + " Make a qflist that contains the file and it's hard link + " like how LSP plugins set response into qflist + call g:Xsetlist([{'filename' : 'test_qf_hardlink1.c', 'lnum' : 1}, + \ {'filename' : 'test_qf_hardlink2.c', 'lnum' : 1}], ' ') + Xopen + " Ensure that two entries are displayed with different name + " so that they aren't seen as duplication. + call assert_equal(['test_qf_hardlink1.c|1| ', + \ 'test_qf_hardlink2.c|1| '], getline(1, '$')) + Xclose +endfunc + +func Test_hardlink_fname() + CheckUnix + CheckExecutable ln + call Xtest_hardlink_fname('c') + call Xtest_hardlink_fname('l') +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/test_regexp_latin.vim b/test/old/testdir/test_regexp_latin.vim index 2cfa81e078..99a82e8386 100644 --- a/test/old/testdir/test_regexp_latin.vim +++ b/test/old/testdir/test_regexp_latin.vim @@ -28,61 +28,50 @@ func s:equivalence_test() endfunc func Test_equivalence_re1() - throw 'skipped: Nvim does not support enc=latin1' set re=1 call s:equivalence_test() + set re=0 endfunc func Test_equivalence_re2() - throw 'skipped: Nvim does not support enc=latin1' set re=2 call s:equivalence_test() + set re=0 endfunc -func Test_range_with_newline() +func Test_recursive_substitute() new - call setline(1, "a") - call assert_equal(0, search("[ -*\\n- ]")) - call assert_equal(0, search("[ -*\\t-\\n]")) + s/^/\=execute("s#^##gn") + " check we are now not in the sandbox + call setwinvar(1, 'myvar', 1) bwipe! endfunc -func Test_pattern_compile_speed() - CheckOption spellcapcheck - CheckFunction reltimefloat - - let start = reltime() - " this used to be very slow, not it should be about a second - set spc=\\v(((((Nxxxxxxx&&xxxx){179})+)+)+){179} - call assert_inrange(0.01, 10.0, reltimefloat(reltime(start))) - set spc= -endfunc - -func Test_get_equi_class() +func Test_nested_backrefs() + " Check example in change.txt. new - " Incomplete equivalence class caused invalid memory access - s/^/[[= - call assert_equal(1, search(getline(1))) - s/.*/[[. - call assert_equal(1, search(getline(1))) -endfunc + for re in range(0, 2) + exe 'set re=' . re + call setline(1, 'aa ab x') + 1s/\(\(a[a-d] \)*\)\(x\)/-\1- -\2- -\3-/ + call assert_equal('-aa ab - -ab - -x-', getline(1)) -func Test_rex_init() - set noincsearch - set re=1 - new - setlocal iskeyword=a-z - call setline(1, ['abc', 'ABC']) - call assert_equal(1, search('[[:keyword:]]')) - new - setlocal iskeyword=A-Z - call setline(1, ['abc', 'ABC']) - call assert_equal(2, search('[[:keyword:]]')) - bwipe! + call assert_equal('-aa ab - -ab - -x-', substitute('aa ab x', '\(\(a[a-d] \)*\)\(x\)', '-\1- -\2- -\3-', '')) + endfor bwipe! set re=0 endfunc +func Test_eow_with_optional() + let expected = ['abc def', 'abc', 'def', '', '', '', '', '', '', ''] + for re in range(0, 2) + exe 'set re=' . re + let actual = matchlist('abc def', '\(abc\>\)\?\s*\(def\)') + call assert_equal(expected, actual) + endfor + set re=0 +endfunc + func Test_backref() new call setline(1, ['one', 'two', 'three', 'four', 'five']) @@ -144,6 +133,50 @@ func Test_out_of_memory() call assert_fails('call search("\\v((n||<)+);")', 'E363:') endfunc +func Test_get_equi_class() + new + " Incomplete equivalence class caused invalid memory access + s/^/[[= + call assert_equal(1, search(getline(1))) + s/.*/[[. + call assert_equal(1, search(getline(1))) +endfunc + +func Test_rex_init() + set noincsearch + set re=1 + new + setlocal iskeyword=a-z + call setline(1, ['abc', 'ABC']) + call assert_equal(1, search('[[:keyword:]]')) + new + setlocal iskeyword=A-Z + call setline(1, ['abc', 'ABC']) + call assert_equal(2, search('[[:keyword:]]')) + bwipe! + bwipe! + set re=0 +endfunc + +func Test_range_with_newline() + new + call setline(1, "a") + call assert_equal(0, search("[ -*\\n- ]")) + call assert_equal(0, search("[ -*\\t-\\n]")) + bwipe! +endfunc + +func Test_pattern_compile_speed() + CheckOption spellcapcheck + CheckFunction reltimefloat + + let start = reltime() + " this used to be very slow, not it should be about a second + set spc=\\v(((((Nxxxxxxx&&xxxx){179})+)+)+){179} + call assert_inrange(0.01, 10.0, reltimefloat(reltime(start))) + set spc= +endfunc + " Tests for regexp patterns without multi-byte support. func Test_regexp_single_line_pat() " tl is a List of Lists with: @@ -937,8 +970,17 @@ func Test_regexp_error() call assert_fails("call matchlist('x x', '\\%#=1 \\ze*')", 'E888:') call assert_fails("call matchlist('x x', '\\%#=2 \\zs*')", 'E888:') call assert_fails("call matchlist('x x', '\\%#=2 \\ze*')", 'E888:') - call assert_fails('exe "normal /\\%#=1\\%[x\\%[x]]\<CR>"', 'E369:') call assert_fails("call matchstr('abcd', '\\%o841\\%o142')", 'E678:') + call assert_fails("call matchstr('abcd', '\\%#=2\\%2147483647c')", 'E951:') + call assert_fails("call matchstr('abcd', '\\%#=2\\%2147483647l')", 'E951:') + call assert_fails("call matchstr('abcd', '\\%#=2\\%2147483647v')", 'E951:') + call assert_fails('exe "normal /\\%#=1\\%[x\\%[x]]\<CR>"', 'E369:') + call assert_fails('exe "normal /\\%#=2\\%2147483647l\<CR>"', 'E951:') + call assert_fails('exe "normal /\\%#=2\\%2147483647c\<CR>"', 'E951:') + call assert_fails('exe "normal /\\%#=2\\%102261126v\<CR>"', 'E951:') + call assert_fails('exe "normal /\\%#=2\\%2147483646l\<CR>"', 'E486:') + call assert_fails('exe "normal /\\%#=2\\%2147483646c\<CR>"', 'E486:') + call assert_fails('exe "normal /\\%#=2\\%102261125v\<CR>"', 'E486:') call assert_equal('', matchstr('abcd', '\%o181\%o142')) endfunc @@ -1105,4 +1147,32 @@ func Test_recursive_substitute_expr() delfunc Repl endfunc +" def Test_compare_columns() +" # this was using a line below the last line +" enew +" setline(1, ['', '']) +" prop_type_add('name', {highlight: 'ErrorMsg'}) +" prop_add(1, 1, {length: 1, type: 'name'}) +" search('\%#=1\%>.l\n.*\%<2v', 'nW') +" search('\%#=2\%>.l\n.*\%<2v', 'nW') +" bwipe! +" prop_type_delete('name') +" enddef + +func Test_compare_column_matchstr() + " do some search in text to set the line number, it should be ignored in + " matchstr(). + enew + call setline(1, ['one', 'two', 'three']) + :3 + :/ee + bwipe! + set re=1 + call assert_equal('aaa', matchstr('aaaaaaaaaaaaaaaaaaaa', '.*\%<5v')) + set re=2 + call assert_equal('aaa', matchstr('aaaaaaaaaaaaaaaaaaaa', '.*\%<5v')) + set re=0 +endfunc + + " vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/test_regexp_utf8.vim b/test/old/testdir/test_regexp_utf8.vim index 728a88fa0f..505e99919c 100644 --- a/test/old/testdir/test_regexp_utf8.vim +++ b/test/old/testdir/test_regexp_utf8.vim @@ -1,5 +1,7 @@ " Tests for regexp in utf8 encoding +source shared.vim + func s:equivalence_test() let str = "AÀÁÂÃÄÅĀĂĄǍǞǠǺȂȦȺḀẠẢẤẦẨẪẬẮẰẲẴẶ BƁɃḂḄḆ CÇĆĈĊČƇȻḈꞒ DĎĐƊḊḌḎḐḒ EÈÉÊËĒĔĖĘĚȄȆȨɆḔḖḘḚḜẸẺẼẾỀỂỄỆ FƑḞꞘ GĜĞĠĢƓǤǦǴḠꞠ HĤĦȞḢḤḦḨḪⱧ IÌÍÎÏĨĪĬĮİƗǏȈȊḬḮỈỊ JĴɈ KĶƘǨḰḲḴⱩꝀ LĹĻĽĿŁȽḶḸḺḼⱠ MḾṀṂ NÑŃŅŇǸṄṆṈṊꞤ OÒÓÔÕÖØŌŎŐƟƠǑǪǬǾȌȎȪȬȮȰṌṎṐṒỌỎỐỒỔỖỘỚỜỞỠỢ PƤṔṖⱣ QɊ RŔŖŘȐȒɌṘṚṜṞⱤꞦ SŚŜŞŠȘṠṢṤṦṨⱾꞨ TŢŤŦƬƮȚȾṪṬṮṰ UÙÚÛÜŨŪŬŮŰƯǕǙǛǓǗȔȖɄṲṴṶṸṺỤỦỨỪỬỮỰ VƲṼṾ WŴẀẂẄẆẈ XẊẌ YÝŶŸƳȲɎẎỲỴỶỸ ZŹŻŽƵẐẒẔⱫ aàáâãäåāăąǎǟǡǻȃȧᶏḁẚạảấầẩẫậắằẳẵặⱥ bƀɓᵬᶀḃḅḇ cçćĉċčƈȼḉꞓꞔ dďđɗᵭᶁᶑḋḍḏḑḓ eèéêëēĕėęěȅȇȩɇᶒḕḗḙḛḝẹẻẽếềểễệ fƒᵮᶂḟꞙ gĝğġģǥǧǵɠᶃḡꞡ hĥħȟḣḥḧḩḫẖⱨꞕ iìíîïĩīĭįǐȉȋɨᶖḭḯỉị jĵǰɉ kķƙǩᶄḱḳḵⱪꝁ lĺļľŀłƚḷḹḻḽⱡ mᵯḿṁṃ nñńņňʼnǹᵰᶇṅṇṉṋꞥ oòóôõöøōŏőơǒǫǭǿȍȏȫȭȯȱɵṍṏṑṓọỏốồổỗộớờởỡợ pƥᵱᵽᶈṕṗ qɋʠ rŕŗřȑȓɍɽᵲᵳᶉṛṝṟꞧ sśŝşšșȿᵴᶊṡṣṥṧṩꞩ tţťŧƫƭțʈᵵṫṭṯṱẗⱦ uùúûüũūŭůűųǚǖưǔǘǜȕȗʉᵾᶙṳṵṷṹṻụủứừửữự vʋᶌṽṿ wŵẁẃẅẇẉẘ xẋẍ yýÿŷƴȳɏẏẙỳỵỷỹ zźżžƶᵶᶎẑẓẕⱬ" let groups = split(str) @@ -188,38 +190,6 @@ func Test_classes_re2() set re=0 endfunc -func Test_recursive_substitute() - new - s/^/\=execute("s#^##gn") - " check we are now not in the sandbox - call setwinvar(1, 'myvar', 1) - bwipe! -endfunc - -func Test_nested_backrefs() - " Check example in change.txt. - new - for re in range(0, 2) - exe 'set re=' . re - call setline(1, 'aa ab x') - 1s/\(\(a[a-d] \)*\)\(x\)/-\1- -\2- -\3-/ - call assert_equal('-aa ab - -ab - -x-', getline(1)) - - call assert_equal('-aa ab - -ab - -x-', substitute('aa ab x', '\(\(a[a-d] \)*\)\(x\)', '-\1- -\2- -\3-', '')) - endfor - bwipe! - set re=0 -endfunc - -func Test_eow_with_optional() - let expected = ['abc def', 'abc', 'def', '', '', '', '', '', '', ''] - for re in range(0, 2) - exe 'set re=' . re - let actual = matchlist('abc def', '\(abc\>\)\?\s*\(def\)') - call assert_equal(expected, actual) - endfor -endfunc - func Test_reversed_range() for re in range(0, 2) exe 'set re=' . re @@ -591,6 +561,19 @@ func Test_match_invalid_byte() call delete('Xinvalid') endfunc +func Test_match_illegal_byte() + let lines =<< trim END + silent! buffer ÿ\c + next ÿ + 0scriptnames + source + END + call writefile(lines, 'Xregexp') + call system(GetVimCommand() .. ' -X -Z -e -s -S Xregexp -c qa!') + + call delete('Xregexp') +endfunc + func Test_match_too_complicated() set regexpengine=1 exe "noswapfile vsplit \xeb\xdb\x99" @@ -611,4 +594,36 @@ func Test_combining_chars_in_collection() bw! endfunc +func Test_search_multibyte_match_ascii() + new + " Match single 'ſ' and 's' + call setline(1, 'das abc heraus abc ſich abc ſind') + for i in range(0, 2) + exe "set re="..i + let ic_match = matchbufline('%', '\c\%u17f', 1, '$')->mapnew({idx, val -> val.text}) + let noic_match = matchbufline('%', '\C\%u17f', 1, '$')->mapnew({idx, val -> val.text}) + call assert_equal(['s', 's', 'ſ','ſ'], ic_match, "Ignorecase Regex-engine: " .. &re) + call assert_equal(['ſ','ſ'], noic_match, "No-Ignorecase Regex-engine: " .. &re) + endfor + " Match several 'ſſ' and 'ss' + call setline(1, 'das abc herauss abc ſſich abc ſind') + for i in range(0, 2) + exe "set re="..i + let ic_match = matchbufline('%', '\c\%u17f\%u17f', 1, '$')->mapnew({idx, val -> val.text}) + let noic_match = matchbufline('%', '\C\%u17f\%u17f', 1, '$')->mapnew({idx, val -> val.text}) + let ic_match2 = matchbufline('%', '\c\%u17f\+', 1, '$')->mapnew({idx, val -> val.text}) + let noic_match2 = matchbufline('%', '\C\%u17f\+', 1, '$')->mapnew({idx, val -> val.text}) + let ic_match3 = matchbufline('%', '\c[\u17f]\+', 1, '$')->mapnew({idx, val -> val.text}) + let noic_match3 = matchbufline('%', '\C[\u17f]\+', 1, '$')->mapnew({idx, val -> val.text}) + + call assert_equal(['ss', 'ſſ'], ic_match, "Ignorecase Regex-engine: " .. &re) + call assert_equal(['ſſ'], noic_match, "No-Ignorecase Regex-engine: " .. &re) + call assert_equal(['s', 'ss', 'ſſ', 'ſ'], ic_match2, "Ignorecase Regex-engine: " .. &re) + call assert_equal(['ſſ','ſ'], noic_match2, "No-Ignorecase Regex-engine: " .. &re) + call assert_equal(['s', 'ss', 'ſſ', 'ſ'], ic_match3, "Ignorecase Collection Regex-engine: " .. &re) + call assert_equal(['ſſ','ſ'], noic_match3, "No-Ignorecase Collection Regex-engine: " .. &re) + endfor + bw! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/test_ruby.vim b/test/old/testdir/test_ruby.vim index 4929496086..d4a3dc3301 100644 --- a/test/old/testdir/test_ruby.vim +++ b/test/old/testdir/test_ruby.vim @@ -282,7 +282,7 @@ func Test_ruby_Vim_buffer_get() call assert_match('Xfoo1$', rubyeval('Vim::Buffer[1].name')) call assert_match('Xfoo2$', rubyeval('Vim::Buffer[2].name')) call assert_fails('ruby print Vim::Buffer[3].name', - \ "NoMethodError: undefined method `name' for nil:NilClass") + \ "NoMethodError: undefined method `name' for nil") %bwipe endfunc @@ -337,6 +337,14 @@ func Test_ruby_Vim_evaluate() call assert_equal('FalseClass',rubyeval('Vim::evaluate("v:false").class')) endfunc +func Test_ruby_Vim_blob() + throw 'skipped: TODO: ' + call assert_equal('0z', rubyeval('Vim::blob("")')) + call assert_equal('0z31326162', rubyeval('Vim::blob("12ab")')) + call assert_equal('0z00010203', rubyeval('Vim::blob("\x00\x01\x02\x03")')) + call assert_equal('0z8081FEFF', rubyeval('Vim::blob("\x80\x81\xfe\xff")')) +endfunc + func Test_ruby_Vim_evaluate_list() call setline(line('$'), ['2 line 2']) ruby Vim.command("normal /^2\n") diff --git a/test/old/testdir/test_scriptnames.vim b/test/old/testdir/test_scriptnames.vim index 69e5e526fd..13b09a601a 100644 --- a/test/old/testdir/test_scriptnames.vim +++ b/test/old/testdir/test_scriptnames.vim @@ -1,7 +1,7 @@ " Test for the :scriptnames command func Test_scriptnames() - call writefile(['let did_load_script = 123'], 'Xscripting') + call writefile(['let did_load_script = 123'], 'Xscripting', 'D') source Xscripting call assert_equal(123, g:did_load_script) @@ -22,7 +22,6 @@ func Test_scriptnames() call assert_equal('Xscripting', expand('%:t')) bwipe - call delete('Xscripting') let msgs = execute('messages') scriptnames @@ -47,7 +46,7 @@ func Test_getscriptinfo() " def Xscript_def_func2() " enddef END - call writefile(lines, 'X22script91') + call writefile(lines, 'X22script91', 'D') source X22script91 let l = getscriptinfo() call assert_match('X22script91$', l[-1].name) @@ -103,8 +102,6 @@ func Test_getscriptinfo() let max_sid = max(map(getscriptinfo(), { k, v -> v.sid })) call assert_equal([], getscriptinfo({'sid': max_sid + 1})) - - call delete('X22script91') endfunc " vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/test_scroll_opt.vim b/test/old/testdir/test_scroll_opt.vim index 76b4089bd1..55f9a32718 100644 --- a/test/old/testdir/test_scroll_opt.vim +++ b/test/old/testdir/test_scroll_opt.vim @@ -302,6 +302,36 @@ func Test_smoothscroll_diff_mode() call StopVimInTerminal(buf) endfunc +func Test_smoothscroll_diff_change_line() + CheckScreendump + + let lines =<< trim END + 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 + END + call writefile(lines, 'XSmoothDiffChangeLine', 'D') + let buf = RunVimInTerminal('-S XSmoothDiffChangeLine', #{rows: 20, columns: 55}) + + call VerifyScreenDump(buf, 'Test_smooth_diff_change_line_1', {}) + call term_sendkeys(buf, "Abar") + call VerifyScreenDump(buf, 'Test_smooth_diff_change_line_2', {}) + call term_sendkeys(buf, "\<Esc>") + call VerifyScreenDump(buf, 'Test_smooth_diff_change_line_3', {}) + call term_sendkeys(buf, "yyp") + call VerifyScreenDump(buf, 'Test_smooth_diff_change_line_4', {}) + + call StopVimInTerminal(buf) +endfunc + func Test_smoothscroll_wrap_scrolloff_zero() CheckScreendump @@ -369,18 +399,18 @@ func Test_smoothscroll_wrap_long_line() call term_sendkeys(buf, ":set scrolloff=1\<CR>") call term_sendkeys(buf, "10|\<C-E>") call VerifyScreenDump(buf, 'Test_smooth_long_6', {}) - + " 'scrolloff' set to 1, scrolling down, cursor moves screen line up call term_sendkeys(buf, "\<C-E>") call term_sendkeys(buf, "gjgj") call term_sendkeys(buf, "\<C-Y>") call VerifyScreenDump(buf, 'Test_smooth_long_7', {}) - + " 'scrolloff' set to 2, scrolling up, cursor moves screen line down call term_sendkeys(buf, ":set scrolloff=2\<CR>") call term_sendkeys(buf, "10|\<C-E>") call VerifyScreenDump(buf, 'Test_smooth_long_8', {}) - + " 'scrolloff' set to 2, scrolling down, cursor moves screen line up call term_sendkeys(buf, "\<C-E>") call term_sendkeys(buf, "gj") @@ -421,7 +451,7 @@ func Test_smoothscroll_wrap_long_line() call term_sendkeys(buf, "3Gzt") call term_sendkeys(buf, "\<C-E>j") call VerifyScreenDump(buf, 'Test_smooth_long_16', {}) - + call StopVimInTerminal(buf) endfunc @@ -436,7 +466,7 @@ func Test_smoothscroll_one_long_line() call writefile(lines, 'XSmoothOneLong', 'D') let buf = RunVimInTerminal('-S XSmoothOneLong', #{rows: 6, cols: 40}) call VerifyScreenDump(buf, 'Test_smooth_one_long_1', {}) - + call term_sendkeys(buf, "\<C-E>") call VerifyScreenDump(buf, 'Test_smooth_one_long_2', {}) @@ -458,7 +488,7 @@ func Test_smoothscroll_long_line_showbreak() call writefile(lines, 'XSmoothLongShowbreak', 'D') let buf = RunVimInTerminal('-S XSmoothLongShowbreak', #{rows: 6, cols: 40}) call VerifyScreenDump(buf, 'Test_smooth_long_showbreak_1', {}) - + call term_sendkeys(buf, "\<C-E>") call VerifyScreenDump(buf, 'Test_smooth_long_showbreak_2', {}) @@ -648,7 +678,7 @@ func Test_smoothscroll_cursor_scrolloff() call NewWindow(10, 20) setl smoothscroll wrap setl scrolloff=3 - + " 120 chars are 6 screen lines call setline(1, "abcdefghijklmnopqrstABCDEFGHIJKLMNOPQRSTabcdefghijklmnopqrstABCDEFGHIJKLMNOPQRSTabcdefghijklmnopqrstABCDEFGHIJKLMNOPQRST") call setline(2, "below") @@ -1165,7 +1195,6 @@ func Test_smooth_long_scrolloff() END call writefile(lines, 'XSmoothLongScrolloff', 'D') let buf = RunVimInTerminal('-u NONE -S XSmoothLongScrolloff', #{rows: 8, cols: 40}) - "FIXME: empty screen due to reset_skipcol()/curs_columns() shenanigans call term_sendkeys(buf, ":norm j721|\<CR>") call VerifyScreenDump(buf, 'Test_smooth_long_scrolloff_1', {}) @@ -1185,7 +1214,6 @@ func Test_smooth_long_scrolloff() call VerifyScreenDump(buf, 'Test_smooth_long_scrolloff_6', {}) call term_sendkeys(buf, "gk") - "FIXME: empty screen due to reset_skipcol()/curs_columns() shenanigans call VerifyScreenDump(buf, 'Test_smooth_long_scrolloff_7', {}) call StopVimInTerminal(buf) diff --git a/test/old/testdir/test_search.vim b/test/old/testdir/test_search.vim index 018ee7ad5a..b62c2b2e5f 100644 --- a/test/old/testdir/test_search.vim +++ b/test/old/testdir/test_search.vim @@ -674,7 +674,7 @@ func Test_search_cmdline8() endif " Prepare buffer text let lines = ['abb vim vim vi', 'vimvivim'] - call writefile(lines, 'Xsearch.txt') + call writefile(lines, 'Xsearch.txt', 'D') let buf = term_start([GetVimProg(), '--clean', '-c', 'set noswapfile', 'Xsearch.txt'], {'term_rows': 3}) call WaitForAssert({-> assert_equal(lines, [term_getline(buf, 1), term_getline(buf, 2)])}) @@ -693,9 +693,8 @@ func Test_search_cmdline8() call assert_notequal(a1, a2) call assert_equal(a0, a2) call assert_equal(a1, a3) - " clean up - call delete('Xsearch.txt') + " clean up bwipe! endfunc @@ -810,7 +809,7 @@ func Test_search_cmdline_incsearch_highlight_attr() " Prepare buffer text let lines = ['abb vim vim vi', 'vimvivim'] - call writefile(lines, 'Xsearch.txt') + call writefile(lines, 'Xsearch.txt', 'D') let buf = term_start([GetVimProg(), '--clean', '-c', 'set noswapfile', 'Xsearch.txt'], {'term_rows': 3}) call WaitForAssert({-> assert_equal(lines, [term_getline(buf, 1), term_getline(buf, 2)])}) @@ -879,9 +878,7 @@ func Test_search_cmdline_incsearch_highlight_attr() let attr_line2 = [a0,a0,a0,a0,a0,a0,a0,a0] call assert_equal(attr_line1, map(term_scrape(buf, 1)[:len(attr_line1)-1], 'v:val.attr')) call assert_equal(attr_line2, map(term_scrape(buf, 2)[:len(attr_line2)-1], 'v:val.attr')) - call delete('Xsearch.txt') - call delete('Xsearch.txt') bwipe! endfunc @@ -908,7 +905,7 @@ func Test_incsearch_scrolling() \ 'call setline(1, [dots, dots, dots, "", "target", dots, dots])', \ 'normal gg', \ 'redraw', - \ ], 'Xscript') + \ ], 'Xscript', 'D') let buf = RunVimInTerminal('-S Xscript', {'rows': 9, 'cols': 70}) " Need to send one key at a time to force a redraw call term_sendkeys(buf, '/') @@ -924,7 +921,6 @@ func Test_incsearch_scrolling() call term_sendkeys(buf, "\<Esc>") call StopVimInTerminal(buf) - call delete('Xscript') endfunc func Test_incsearch_search_dump() @@ -937,7 +933,7 @@ func Test_incsearch_search_dump() \ ' call setline(n, "foo " . n)', \ 'endfor', \ '3', - \ ], 'Xis_search_script') + \ ], 'Xis_search_script', 'D') let buf = RunVimInTerminal('-S Xis_search_script', {'rows': 9, 'cols': 70}) " Give Vim a chance to redraw to get rid of the spaces in line 2 caused by " the 'ambiwidth' check. @@ -954,7 +950,6 @@ func Test_incsearch_search_dump() call term_sendkeys(buf, "\<Esc>") call StopVimInTerminal(buf) - call delete('Xis_search_script') endfunc func Test_hlsearch_dump() @@ -966,7 +961,7 @@ func Test_hlsearch_dump() \ 'call setline(1, ["xxx", "xxx", "xxx"])', \ '/.*', \ '2', - \ ], 'Xhlsearch_script') + \ ], 'Xhlsearch_script', 'D') let buf = RunVimInTerminal('-S Xhlsearch_script', {'rows': 6, 'cols': 50}) call VerifyScreenDump(buf, 'Test_hlsearch_1', {}) @@ -974,7 +969,6 @@ func Test_hlsearch_dump() call VerifyScreenDump(buf, 'Test_hlsearch_2', {}) call StopVimInTerminal(buf) - call delete('Xhlsearch_script') endfunc func Test_hlsearch_and_visual() @@ -987,14 +981,13 @@ func Test_hlsearch_and_visual() \ 'hi Search cterm=bold', \ '/yyy', \ 'call cursor(1, 6)', - \ ], 'Xhlvisual_script') + \ ], 'Xhlvisual_script', 'D') let buf = RunVimInTerminal('-S Xhlvisual_script', {'rows': 6, 'cols': 40}) call term_sendkeys(buf, "vjj") call VerifyScreenDump(buf, 'Test_hlsearch_visual_1', {}) call term_sendkeys(buf, "\<Esc>") call StopVimInTerminal(buf) - call delete('Xhlvisual_script') endfunc func Test_hlsearch_block_visual_match() @@ -1004,7 +997,7 @@ func Test_hlsearch_block_visual_match() set hlsearch call setline(1, ['aa', 'bbbb', 'cccccc']) END - call writefile(lines, 'Xhlsearch_block') + call writefile(lines, 'Xhlsearch_block', 'D') let buf = RunVimInTerminal('-S Xhlsearch_block', {'rows': 9, 'cols': 60}) call term_sendkeys(buf, "G\<C-V>$kk\<Esc>") @@ -1014,7 +1007,6 @@ func Test_hlsearch_block_visual_match() call VerifyScreenDump(buf, 'Test_hlsearch_block_visual_match', {}) call StopVimInTerminal(buf) - call delete('Xhlsearch_block') endfunc func Test_incsearch_substitute() @@ -1062,7 +1054,7 @@ func Test_hlsearch_cursearch() hi Search ctermbg=yellow hi CurSearch ctermbg=blue END - call writefile(lines, 'Xhlsearch_cursearch') + call writefile(lines, 'Xhlsearch_cursearch', 'D') let buf = RunVimInTerminal('-S Xhlsearch_cursearch', {'rows': 9, 'cols': 60}) call term_sendkeys(buf, "gg/foo\<CR>") @@ -1094,8 +1086,12 @@ func Test_hlsearch_cursearch() call term_sendkeys(buf, "h\<C-L>") call VerifyScreenDump(buf, 'Test_hlsearch_cursearch_multiple_line_5', {}) + " check clearing CurSearch when using it for another match + call term_sendkeys(buf, "G?^abcd\<CR>Y") + call term_sendkeys(buf, "kkP") + call VerifyScreenDump(buf, 'Test_hlsearch_cursearch_changed_1', {}) + call StopVimInTerminal(buf) - call delete('Xhlsearch_cursearch') endfunc " Similar to Test_incsearch_substitute() but with a screendump halfway. @@ -1110,7 +1106,7 @@ func Test_incsearch_substitute_dump() \ 'endfor', \ 'call setline(11, "bar 11")', \ '3', - \ ], 'Xis_subst_script') + \ ], 'Xis_subst_script', 'D') let buf = RunVimInTerminal('-S Xis_subst_script', {'rows': 9, 'cols': 70}) " Give Vim a chance to redraw to get rid of the spaces in line 2 caused by " the 'ambiwidth' check. @@ -1205,7 +1201,6 @@ func Test_incsearch_substitute_dump() call term_sendkeys(buf, "<Esc>") call StopVimInTerminal(buf) - call delete('Xis_subst_script') endfunc func Test_incsearch_highlighting() @@ -1215,7 +1210,7 @@ func Test_incsearch_highlighting() call writefile([ \ 'set incsearch hlsearch', \ 'call setline(1, "hello/there")', - \ ], 'Xis_subst_hl_script') + \ ], 'Xis_subst_hl_script', 'D') let buf = RunVimInTerminal('-S Xis_subst_hl_script', {'rows': 4, 'cols': 20}) " Give Vim a chance to redraw to get rid of the spaces in line 2 caused by " the 'ambiwidth' check. @@ -1228,7 +1223,6 @@ func Test_incsearch_highlighting() call term_sendkeys(buf, "<Esc>") call StopVimInTerminal(buf) - call delete('Xis_subst_hl_script') endfunc func Test_incsearch_with_change() @@ -1240,7 +1234,7 @@ func Test_incsearch_with_change() \ 'set incsearch hlsearch scrolloff=0', \ 'call setline(1, ["one", "two ------ X", "three"])', \ 'call timer_start(200, { _ -> setline(2, "x")})', - \ ], 'Xis_change_script') + \ ], 'Xis_change_script', 'D') let buf = RunVimInTerminal('-S Xis_change_script', {'rows': 9, 'cols': 70}) " Give Vim a chance to redraw to get rid of the spaces in line 2 caused by " the 'ambiwidth' check. @@ -1252,7 +1246,6 @@ func Test_incsearch_with_change() call term_sendkeys(buf, "\<Esc>") call StopVimInTerminal(buf) - call delete('Xis_change_script') endfunc " Similar to Test_incsearch_substitute_dump() for :sort @@ -1263,7 +1256,7 @@ func Test_incsearch_sort_dump() call writefile([ \ 'set incsearch hlsearch scrolloff=0', \ 'call setline(1, ["another one 2", "that one 3", "the one 1"])', - \ ], 'Xis_sort_script') + \ ], 'Xis_sort_script', 'D') let buf = RunVimInTerminal('-S Xis_sort_script', {'rows': 9, 'cols': 70}) " Give Vim a chance to redraw to get rid of the spaces in line 2 caused by " the 'ambiwidth' check. @@ -1278,7 +1271,6 @@ func Test_incsearch_sort_dump() call term_sendkeys(buf, "\<Esc>") call StopVimInTerminal(buf) - call delete('Xis_sort_script') endfunc " Similar to Test_incsearch_substitute_dump() for :vimgrep famiry @@ -1289,7 +1281,7 @@ func Test_incsearch_vimgrep_dump() call writefile([ \ 'set incsearch hlsearch scrolloff=0', \ 'call setline(1, ["another one 2", "that one 3", "the one 1"])', - \ ], 'Xis_vimgrep_script') + \ ], 'Xis_vimgrep_script', 'D') let buf = RunVimInTerminal('-S Xis_vimgrep_script', {'rows': 9, 'cols': 70}) " Give Vim a chance to redraw to get rid of the spaces in line 2 caused by " the 'ambiwidth' check. @@ -1317,7 +1309,6 @@ func Test_incsearch_vimgrep_dump() call term_sendkeys(buf, "\<Esc>") call StopVimInTerminal(buf) - call delete('Xis_vimgrep_script') endfunc func Test_keep_last_search_pattern() @@ -1460,11 +1451,9 @@ endfunc func Test_no_last_substitute_pat() " Use viminfo to set the last search pattern to a string and make the last " substitute pattern the most recent used and make it empty (NULL). - call writefile(['~MSle0/bar', '~MSle0~&'], 'Xviminfo') + call writefile(['~MSle0/bar', '~MSle0~&'], 'Xviminfo', 'D') rviminfo! Xviminfo call assert_fails('normal n', 'E35:') - - call delete('Xviminfo') endfunc func Test_search_Ctrl_L_combining() @@ -1671,12 +1660,42 @@ func Test_search_with_no_last_pat() call writefile(v:errors, 'Xresult') qall! [SCRIPT] - call writefile(lines, 'Xscript') + call writefile(lines, 'Xscript', 'D') + + if RunVim([], [], '--clean -S Xscript') + call assert_equal([], readfile('Xresult')) + endif + call delete('Xresult') +endfunc + +" Test for using the last substitute pattern without last search pattern. +func Test_search_with_last_substitute_pat() + let lines =<< trim [SCRIPT] + new + set shortmess+=S + call setline(1, repeat(['foofoo'], 3)) + %s/foo/bar/ + call assert_equal(repeat(['barfoo'], 3), getline(1, '$')) + + call cursor(1, 1) + call assert_equal("/foo", execute('call feedkeys("/\r", "tx")', '')->trim()) + call assert_equal([0, 1, 4, 0], getpos('.')) + + if has('rightleft') + set rightleft rightleftcmd=search + call cursor(1, 1) + call assert_equal("oof/", execute('call feedkeys("/\r", "tx")', '')->trim()) + call assert_equal([0, 1, 4, 0], getpos('.')) + endif + + call writefile(v:errors, 'Xresult') + qall! + [SCRIPT] + call writefile(lines, 'Xscript', 'D') if RunVim([], [], '--clean -S Xscript') call assert_equal([], readfile('Xresult')) endif - call delete('Xscript') call delete('Xresult') endfunc @@ -1694,11 +1713,10 @@ func Test_search_tilde_pat() call writefile(v:errors, 'Xresult') qall! [SCRIPT] - call writefile(lines, 'Xscript') + call writefile(lines, 'Xscript', 'D') if RunVim([], [], '--clean -S Xscript') call assert_equal([], readfile('Xresult')) endif - call delete('Xscript') call delete('Xresult') endfunc @@ -1966,7 +1984,7 @@ func Test_incsearch_highlighting_newline() set incsearch nohls call setline(1, ['test', 'xxx']) [CODE] - call writefile(commands, 'Xincsearch_nl') + call writefile(commands, 'Xincsearch_nl', 'D') let buf = RunVimInTerminal('-S Xincsearch_nl', {'rows': 5, 'cols': 10}) call term_sendkeys(buf, '/test') call VerifyScreenDump(buf, 'Test_incsearch_newline1', {}) @@ -1982,7 +2000,6 @@ func Test_incsearch_highlighting_newline() call StopVimInTerminal(buf) " clean up - call delete('Xincsearch_nl') call test_override("char_avail", 0) bw endfunc @@ -2123,11 +2140,10 @@ func Test_search_with_invalid_range() 5/ c END - call writefile(lines, 'Xrangesearch') + call writefile(lines, 'Xrangesearch', 'D') source Xrangesearch bwipe! - call delete('Xrangesearch') endfunc diff --git a/test/old/testdir/test_search_stat.vim b/test/old/testdir/test_search_stat.vim index a2523fc6c7..b57b7ba7b0 100644 --- a/test/old/testdir/test_search_stat.vim +++ b/test/old/testdir/test_search_stat.vim @@ -308,7 +308,7 @@ func Test_searchcount_in_statusline() set hlsearch set laststatus=2 statusline+=%{TestSearchCount()} END - call writefile(lines, 'Xsearchstatusline') + call writefile(lines, 'Xsearchstatusline', 'D') let buf = RunVimInTerminal('-S Xsearchstatusline', #{rows: 10}) call TermWait(buf) call term_sendkeys(buf, "/something") @@ -316,7 +316,6 @@ func Test_searchcount_in_statusline() call term_sendkeys(buf, "\<Esc>") call StopVimInTerminal(buf) - call delete('Xsearchstatusline') endfunc func Test_search_stat_foldopen() @@ -330,7 +329,7 @@ func Test_search_stat_foldopen() call cursor(1,1) norm n END - call writefile(lines, 'Xsearchstat1') + call writefile(lines, 'Xsearchstat1', 'D') let buf = RunVimInTerminal('-S Xsearchstat1', #{rows: 10}) call VerifyScreenDump(buf, 'Test_searchstat_3', {}) @@ -342,7 +341,6 @@ func Test_search_stat_foldopen() call VerifyScreenDump(buf, 'Test_searchstat_3', {}) call StopVimInTerminal(buf) - call delete('Xsearchstat1') endfunc func! Test_search_stat_screendump() @@ -359,7 +357,7 @@ func! Test_search_stat_screendump() call cursor(1,1) norm n END - call writefile(lines, 'Xsearchstat') + call writefile(lines, 'Xsearchstat', 'D') let buf = RunVimInTerminal('-S Xsearchstat', #{rows: 10}) call VerifyScreenDump(buf, 'Test_searchstat_1', {}) @@ -368,7 +366,6 @@ func! Test_search_stat_screendump() call VerifyScreenDump(buf, 'Test_searchstat_2', {}) call StopVimInTerminal(buf) - call delete('Xsearchstat') endfunc func Test_search_stat_then_gd() @@ -379,7 +376,7 @@ func Test_search_stat_then_gd() set shortmess-=S set hlsearch END - call writefile(lines, 'Xsearchstatgd') + call writefile(lines, 'Xsearchstatgd', 'D') let buf = RunVimInTerminal('-S Xsearchstatgd', #{rows: 10}) call term_sendkeys(buf, "/dog\<CR>") @@ -389,7 +386,6 @@ func Test_search_stat_then_gd() call VerifyScreenDump(buf, 'Test_searchstatgd_2', {}) call StopVimInTerminal(buf) - call delete('Xsearchstatgd') endfunc func Test_search_stat_and_incsearch() diff --git a/test/old/testdir/test_selectmode.vim b/test/old/testdir/test_selectmode.vim index 59a1deba65..34ccdae7d8 100644 --- a/test/old/testdir/test_selectmode.vim +++ b/test/old/testdir/test_selectmode.vim @@ -323,4 +323,20 @@ func Test_ins_ctrl_o_in_insert_mode_resets_selectmode() bwipe! endfunc +" Test that an :lmap mapping for a printable keypad key is applied when typing +" it in Select mode. +func Test_selectmode_keypad_lmap() + new + lnoremap <buffer> <kPoint> ??? + lnoremap <buffer> <kEnter> !!! + setlocal iminsert=1 + call setline(1, 'abcdef') + call feedkeys("gH\<kPoint>\<Esc>", 'tx') + call assert_equal(['???'], getline(1, '$')) + call feedkeys("gH\<kEnter>\<Esc>", 'tx') + call assert_equal(['!!!'], getline(1, '$')) + + bwipe! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/test_shell.vim b/test/old/testdir/test_shell.vim index c50161a8ed..9499462a70 100644 --- a/test/old/testdir/test_shell.vim +++ b/test/old/testdir/test_shell.vim @@ -119,6 +119,10 @@ func Test_shellescape() call assert_equal("'te\\#xt'", shellescape("te#xt", 1)) call assert_equal("'te!xt'", shellescape("te!xt")) call assert_equal("'te\\!xt'", shellescape("te!xt", 1)) + call assert_equal("'te<cword>xt'", shellescape("te<cword>xt")) + call assert_equal("'te\\<cword>xt'", shellescape("te<cword>xt", 1)) + call assert_equal("'te<cword>%xt'", shellescape("te<cword>%xt")) + call assert_equal("'te\\<cword>\\%xt'", shellescape("te<cword>%xt", 1)) call assert_equal("'te\nxt'", shellescape("te\nxt")) call assert_equal("'te\\\nxt'", shellescape("te\nxt", 1)) @@ -128,6 +132,29 @@ func Test_shellescape() call assert_equal("'te\\\nxt'", shellescape("te\nxt")) call assert_equal("'te\\\\\nxt'", shellescape("te\nxt", 1)) + set shell=fish + call assert_equal("'text'", shellescape('text')) + call assert_equal("'te\"xt'", shellescape('te"xt')) + call assert_equal("'te'\\''xt'", shellescape("te'xt")) + + call assert_equal("'te%xt'", shellescape("te%xt")) + call assert_equal("'te\\%xt'", shellescape("te%xt", 1)) + call assert_equal("'te#xt'", shellescape("te#xt")) + call assert_equal("'te\\#xt'", shellescape("te#xt", 1)) + call assert_equal("'te!xt'", shellescape("te!xt")) + call assert_equal("'te\\!xt'", shellescape("te!xt", 1)) + + call assert_equal("'te\\\\xt'", shellescape("te\\xt")) + call assert_equal("'te\\\\xt'", shellescape("te\\xt", 1)) + call assert_equal("'te\\\\'\\''xt'", shellescape("te\\'xt")) + call assert_equal("'te\\\\'\\''xt'", shellescape("te\\'xt", 1)) + call assert_equal("'te\\\\!xt'", shellescape("te\\!xt")) + call assert_equal("'te\\\\\\!xt'", shellescape("te\\!xt", 1)) + call assert_equal("'te\\\\%xt'", shellescape("te\\%xt")) + call assert_equal("'te\\\\\\%xt'", shellescape("te\\%xt", 1)) + call assert_equal("'te\\\\#xt'", shellescape("te\\#xt")) + call assert_equal("'te\\\\\\#xt'", shellescape("te\\#xt", 1)) + let &shell = save_shell endfunc @@ -161,7 +188,7 @@ func Test_shellxquote() let save_sxq = &shellxquote let save_sxe = &shellxescape - call writefile(['#!/bin/sh', 'echo "Cmd: [$*]" > Xlog'], 'Xtestshell') + call writefile(['#!/bin/sh', 'echo "Cmd: [$*]" > Xlog'], 'Xtestshell', 'D') call setfperm('Xtestshell', "r-x------") set shell=./Xtestshell @@ -185,7 +212,6 @@ func Test_shellxquote() let &shell = save_shell let &shellxquote = save_sxq let &shellxescape = save_sxe - call delete('Xtestshell') call delete('Xlog') endfunc diff --git a/test/old/testdir/test_signals.vim b/test/old/testdir/test_signals.vim index 667448a7c2..4d4c7e405f 100644 --- a/test/old/testdir/test_signals.vim +++ b/test/old/testdir/test_signals.vim @@ -86,26 +86,72 @@ func Test_signal_INT() throw 'Skipped: INT signal not supported' endif - " Skip the rest of the test when running with valgrind as signal INT is not - " received somehow by Vim when running with valgrind. - let cmd = GetVimCommand() - if cmd =~ 'valgrind' - throw 'Skipped: cannot test signal INT with valgrind' - endif - let buf = RunVimInTerminal('', {'rows': 6}) let pid_vim = term_getjob(buf)->job_info().process " Check that an endless loop in Vim is interrupted by signal INT. + call term_sendkeys(buf, ":call setline(1, 'running')\n") call term_sendkeys(buf, ":while 1 | endwhile\n") call WaitForAssert({-> assert_equal(':while 1 | endwhile', term_getline(buf, 6))}) exe 'silent !kill -s INT ' .. pid_vim + sleep 50m call term_sendkeys(buf, ":call setline(1, 'INTERRUPTED')\n") call WaitForAssert({-> assert_equal('INTERRUPTED', term_getline(buf, 1))}) call StopVimInTerminal(buf) endfunc +" Test signal TSTP. Handler sets got_tstp. +func Test_signal_TSTP() + CheckRunVimInTerminal + if !HasSignal('TSTP') + throw 'Skipped: TSTP signal not supported' + endif + + " If test fails once, it can leave temporary files and trying to rerun + " the test would then fail again if they are not deleted first. + call delete('.Xsig_TERM.swp') + call delete('XsetupAucmd') + call delete('XautoOut1') + call delete('XautoOut2') + let lines =<< trim END + au VimSuspend * call writefile(["VimSuspend triggered"], "XautoOut1", "as") + au VimResume * call writefile(["VimResume triggered"], "XautoOut2", "as") + END + call writefile(lines, 'XsetupAucmd', 'D') + + let buf = RunVimInTerminal('-S XsetupAucmd Xsig_TERM', {'rows': 6}) + let pid_vim = term_getjob(buf)->job_info().process + + call term_sendkeys(buf, ":call setline(1, 'foo')\n") + call WaitForAssert({-> assert_equal('foo', term_getline(buf, 1))}) + + call assert_false(filereadable('Xsig_TERM')) + + " After TSTP the file is not saved (same function as ^Z) + exe 'silent !kill -s TSTP ' .. pid_vim + call WaitForAssert({-> assert_true(filereadable('.Xsig_TERM.swp'))}) + sleep 100m + + " We resume after the suspend. Sleep a bit for the signal to take effect, + " also when running under valgrind. + exe 'silent !kill -s CONT ' .. pid_vim + call WaitForAssert({-> assert_true(filereadable('XautoOut2'))}) + sleep 10m + + call StopVimInTerminal(buf) + + let result = readfile('XautoOut1') + call assert_equal(["VimSuspend triggered"], result) + let result = readfile('XautoOut2') + call assert_equal(["VimResume triggered"], result) + + %bwipe! + call delete('.Xsig_TERM.swp') + call delete('XautoOut1') + call delete('XautoOut2') +endfunc + " Test a deadly signal. " " There are several deadly signals: SISEGV, SIBUS, SIGTERM... @@ -120,10 +166,6 @@ func Test_deadly_signal_TERM() throw 'Skipped: TERM signal not supported' endif CheckRunVimInTerminal - let cmd = GetVimCommand() - if cmd =~ 'valgrind' - throw 'Skipped: cannot test signal TERM with valgrind' - endif " If test fails once, it can leave temporary files and trying to rerun " the test would then fail again if they are not deleted first. @@ -134,7 +176,7 @@ func Test_deadly_signal_TERM() au VimLeave * call writefile(["VimLeave triggered"], "XautoOut", "as") au VimLeavePre * call writefile(["VimLeavePre triggered"], "XautoOut", "as") END - call writefile(lines, 'XsetupAucmd') + call writefile(lines, 'XsetupAucmd', 'D') let buf = RunVimInTerminal('-S XsetupAucmd Xsig_TERM', {'rows': 6}) let pid_vim = term_getjob(buf)->job_info().process @@ -158,7 +200,6 @@ func Test_deadly_signal_TERM() %bwipe! call delete('.Xsig_TERM.swp') - call delete('XsetupAucmd') call delete('XautoOut') endfunc diff --git a/test/old/testdir/test_signs.vim b/test/old/testdir/test_signs.vim index d7baa7e870..4ac8831239 100644 --- a/test/old/testdir/test_signs.vim +++ b/test/old/testdir/test_signs.vim @@ -89,8 +89,9 @@ func Test_sign() " Place a sign without specifying the filename or buffer sign place 77 line=9 name=Sign2 let a=execute('sign place') + " Nvim: sign line clamped to buffer length call assert_equal("\n--- Signs ---\nSigns for [NULL]:\n" . - \ " line=9 id=77 name=Sign2 priority=10\n", a) + \ " line=4 id=77 name=Sign2 priority=10\n", a) sign unplace * " Check :jump with file=... @@ -245,7 +246,7 @@ func Test_sign_completion() call assert_equal('"sign define jump list place undefine unplace', @:) call feedkeys(":sign define Sign \<C-A>\<C-B>\"\<CR>", 'tx') - call assert_equal('"sign define Sign culhl= icon= linehl= numhl= text= texthl=', @:) + call assert_equal('"sign define Sign culhl= icon= linehl= numhl= priority= text= texthl=', @:) for hl in ['culhl', 'linehl', 'numhl', 'texthl'] call feedkeys(":sign define Sign "..hl.."=Spell\<C-A>\<C-B>\"\<CR>", 'tx') @@ -253,8 +254,8 @@ func Test_sign_completion() \ 'SpellLocal SpellRare', @:) endfor - call writefile(repeat(["Sun is shining"], 30), "XsignOne") - call writefile(repeat(["Sky is blue"], 30), "XsignTwo") + call writefile(repeat(["Sun is shining"], 30), "XsignOne", 'D') + call writefile(repeat(["Sky is blue"], 30), "XsignTwo", 'D') call feedkeys(":sign define Sign icon=Xsig\<C-A>\<C-B>\"\<CR>", 'tx') call assert_equal('"sign define Sign icon=XsignOne XsignTwo', @:) @@ -331,8 +332,6 @@ func Test_sign_completion() sign undefine Sign1 sign undefine Sign2 enew - call delete('XsignOne') - call delete('XsignTwo') endfunc func Test_sign_invalid_commands() @@ -475,7 +474,7 @@ func Test_sign_funcs() call assert_fails('call sign_getdefined({})', 'E731:') " Tests for sign_place() - call writefile(repeat(["Sun is shining"], 30), "Xsign") + call writefile(repeat(["Sun is shining"], 30), "Xsign", 'D') edit Xsign call assert_equal(10, sign_place(10, '', 'sign1', 'Xsign', @@ -581,7 +580,6 @@ func Test_sign_funcs() \ 'priority' : 10}]}], \ sign_getplaced('%', {'lnum' : 22})) - call delete("Xsign") call sign_unplace('*') call sign_undefine() enew | only @@ -594,7 +592,7 @@ func Test_sign_group() call sign_unplace('*') call sign_undefine() - call writefile(repeat(["Sun is shining"], 30), "Xsign") + call writefile(repeat(["Sun is shining"], 30), "Xsign", 'D') let attr = {'text' : '=>', 'linehl' : 'Search', 'texthl' : 'Error'} call assert_equal(0, sign_define("sign1", attr)) @@ -799,10 +797,11 @@ func Test_sign_group() set buftype=nofile sign place 25 line=76 name=sign1 priority=99 file=foo let a = execute('sign place') + " Nvim: sign line clamped to buffer length call assert_equal("\n--- Signs ---\nSigns for Xsign:\n" . \ " line=10 id=5 name=sign1 priority=10\n" . \ "Signs for foo:\n" . - \ " line=76 id=25 name=sign1 priority=99\n", a) + \ " line=1 id=25 name=sign1 priority=99\n", a) close bwipe foo @@ -834,7 +833,6 @@ func Test_sign_group() " Error cases call assert_fails("sign place 3 group= name=sign1 buffer=" . bnum, 'E474:') - call delete("Xsign") call sign_unplace('*') call sign_undefine() enew | only @@ -877,8 +875,8 @@ func Test_sign_unplace() call sign_undefine() " Create two files and define signs - call writefile(repeat(["Sun is shining"], 30), "Xsign1") - call writefile(repeat(["It is beautiful"], 30), "Xsign2") + call writefile(repeat(["Sun is shining"], 30), "Xsign1", 'D') + call writefile(repeat(["It is beautiful"], 30), "Xsign2", 'D') let attr = {'text' : '=>', 'linehl' : 'Search', 'texthl' : 'Error'} call sign_define("sign1", attr) @@ -1187,8 +1185,6 @@ func Test_sign_unplace() call sign_unplace('*') call sign_undefine() enew | only - call delete("Xsign1") - call delete("Xsign2") endfunc " Tests for auto-generating the sign identifier. @@ -1200,7 +1196,7 @@ func Test_aaa_sign_id_autogen() let attr = {'text' : '=>', 'linehl' : 'Search', 'texthl' : 'Error'} call assert_equal(0, sign_define("sign1", attr)) - call writefile(repeat(["Sun is shining"], 30), "Xsign") + call writefile(repeat(["Sun is shining"], 30), "Xsign", 'D') edit Xsign call assert_equal(1, sign_place(0, '', 'sign1', 'Xsign', @@ -1222,7 +1218,6 @@ func Test_aaa_sign_id_autogen() call assert_equal(10, \ sign_getplaced('Xsign', {'id' : 1})[0].signs[0].lnum) - call delete("Xsign") call sign_unplace('*') call sign_undefine() enew | only @@ -1238,9 +1233,28 @@ func Test_sign_priority() call sign_define("sign1", attr) call sign_define("sign2", attr) call sign_define("sign3", attr) + let attr = {'text' : '=>', 'linehl' : 'Search', 'texthl' : 'Search', 'priority': 60} + call sign_define("sign4", attr) + + " Test for :sign list + let a = execute('sign list') + call assert_equal("\nsign sign1 text==> linehl=Search texthl=Search\n" . + \ "sign sign2 text==> linehl=Search texthl=Search\n" . + \ "sign sign3 text==> linehl=Search texthl=Search\n" . + \ "sign sign4 text==> priority=60 linehl=Search texthl=Search", a) + + " Test for sign_getdefined() + let s = sign_getdefined() + call assert_equal([ + \ {'name': 'sign1', 'texthl': 'Search', 'linehl': 'Search', 'text': '=>'}, + \ {'name': 'sign2', 'texthl': 'Search', 'linehl': 'Search', 'text': '=>'}, + \ {'name': 'sign3', 'texthl': 'Search', 'linehl': 'Search', 'text': '=>'}, + \ {'name': 'sign4', 'priority': 60, 'texthl': 'Search', 'linehl': 'Search', + \ 'text': '=>'}], + \ s) " Place three signs with different priority in the same line - call writefile(repeat(["Sun is shining"], 30), "Xsign") + call writefile(repeat(["Sun is shining"], 30), "Xsign", 'D') edit Xsign call sign_place(1, 'g1', 'sign1', 'Xsign', @@ -1576,15 +1590,33 @@ func Test_sign_priority() \ " line=10 id=5 group=g1 name=sign1 priority=20\n", a) call sign_unplace('*') + + " Test for sign with default priority. + call sign_place(1, 'g1', 'sign4', 'Xsign', {'lnum' : 3}) + sign place 2 line=5 name=sign4 group=g1 file=Xsign + + let s = sign_getplaced('Xsign', {'group' : '*'}) + call assert_equal([ + \ {'id' : 1, 'name' : 'sign4', 'lnum' : 3, 'group' : 'g1', + \ 'priority' : 60}, + \ {'id' : 2, 'name' : 'sign4', 'lnum' : 5, 'group' : 'g1', + \ 'priority' : 60}], + \ s[0].signs) + + let a = execute('sign place group=g1') + call assert_equal("\n--- Signs ---\nSigns for Xsign:\n" . + \ " line=3 id=1 group=g1 name=sign4 priority=60\n" . + \ " line=5 id=2 group=g1 name=sign4 priority=60\n", a) + + call sign_unplace('*') call sign_undefine() enew | only - call delete("Xsign") endfunc " Tests for memory allocation failures in sign functions func Test_sign_memfailures() CheckFunction test_alloc_fail - call writefile(repeat(["Sun is shining"], 30), "Xsign") + call writefile(repeat(["Sun is shining"], 30), "Xsign", 'D') edit Xsign call test_alloc_fail(GetAllocId('sign_getdefined'), 0, 0) @@ -1621,7 +1653,6 @@ func Test_sign_memfailures() call sign_unplace('*') call sign_undefine() enew | only - call delete("Xsign") endfunc " Test for auto-adjusting the line number of a placed sign. @@ -1777,11 +1808,12 @@ func Test_sign_cursor_position() let lines =<< trim END call setline(1, [repeat('x', 75), 'mmmm', 'yyyy']) call cursor(2,1) - sign define s1 texthl=Search text==> + sign define s1 texthl=Search text==> + sign define s2 linehl=Pmenu redraw - sign place 10 line=2 name=s1 + sign place 10 line=2 name=s1 END - call writefile(lines, 'XtestSigncolumn') + call writefile(lines, 'XtestSigncolumn', 'D') let buf = RunVimInTerminal('-S XtestSigncolumn', {'rows': 6}) call VerifyScreenDump(buf, 'Test_sign_cursor_1', {}) @@ -1789,15 +1821,18 @@ func Test_sign_cursor_position() call term_sendkeys(buf, ":sign define s1 text=-)\<CR>") call VerifyScreenDump(buf, 'Test_sign_cursor_2', {}) + " Also place a line HL sign + call term_sendkeys(buf, ":sign place 11 line=2 name=s2\<CR>") + call VerifyScreenDump(buf, 'Test_sign_cursor_3', {}) + " update cursor position calculation call term_sendkeys(buf, "lh") + call term_sendkeys(buf, ":sign unplace 11\<CR>") call term_sendkeys(buf, ":sign unplace 10\<CR>") - call VerifyScreenDump(buf, 'Test_sign_cursor_3', {}) - + call VerifyScreenDump(buf, 'Test_sign_cursor_4', {}) " clean up call StopVimInTerminal(buf) - call delete('XtestSigncolumn') endfunc " Return the 'len' characters in screen starting from (row,col) @@ -1916,7 +1951,7 @@ endfunc " Test for managing multiple signs using the sign functions func Test_sign_funcs_multi() - call writefile(repeat(["Sun is shining"], 30), "Xsign") + call writefile(repeat(["Sun is shining"], 30), "Xsign", 'D') edit Xsign let bnum = bufnr('') @@ -2029,5 +2064,13 @@ func Test_sign_funcs_multi() call sign_unplace('*') call sign_undefine() enew! - call delete("Xsign") endfunc + +func Test_sign_null_list() + eval v:_null_list->sign_define() + eval v:_null_list->sign_placelist() + eval v:_null_list->sign_undefine() + eval v:_null_list->sign_unplacelist() +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/test_sort.vim b/test/old/testdir/test_sort.vim index 94a35e3cb5..2d03dd2f2a 100644 --- a/test/old/testdir/test_sort.vim +++ b/test/old/testdir/test_sort.vim @@ -1336,6 +1336,34 @@ func Test_sort_cmd() \ ] endif endif + if has('float') + let tests += [ + \ { + \ 'name' : 'float', + \ 'cmd' : 'sort f', + \ 'input' : [ + \ '1.234', + \ '0.88', + \ ' + 123.456', + \ '1.15e-6', + \ '-1.1e3', + \ '-1.01e3', + \ '', + \ '' + \ ], + \ 'expected' : [ + \ '', + \ '', + \ '-1.1e3', + \ '-1.01e3', + \ '1.15e-6', + \ '0.88', + \ '1.234', + \ ' + 123.456' + \ ] + \ }, + \ ] + endif for t in tests enew! @@ -1497,11 +1525,10 @@ func Test_sort_with_no_last_search_pat() call writefile(v:errors, 'Xresult') qall! [SCRIPT] - call writefile(lines, 'Xscript') + call writefile(lines, 'Xscript', 'D') if RunVim([], [], '--clean -S Xscript') call assert_equal([], readfile('Xresult')) endif - call delete('Xscript') call delete('Xresult') endfunc diff --git a/test/old/testdir/test_source.vim b/test/old/testdir/test_source.vim index d4d96e36bf..01c08b183c 100644 --- a/test/old/testdir/test_source.vim +++ b/test/old/testdir/test_source.vim @@ -53,12 +53,10 @@ endfunc " When deleting a file and immediately creating a new one the inode may be " recycled. Vim should not recognize it as the same script. func Test_different_script() - call writefile(['let s:var = "asdf"'], 'XoneScript') + call writefile(['let s:var = "asdf"'], 'XoneScript', 'D') source XoneScript - call delete('XoneScript') - call writefile(['let g:var = s:var'], 'XtwoScript') + call writefile(['let g:var = s:var'], 'XtwoScript', 'D') call assert_fails('source XtwoScript', 'E121:') - call delete('XtwoScript') endfunc " When sourcing a vim script, shebang should be ignored. diff --git a/test/old/testdir/test_source_utf8.vim b/test/old/testdir/test_source_utf8.vim index 66fabe0442..f3e11a4bbc 100644 --- a/test/old/testdir/test_source_utf8.vim +++ b/test/old/testdir/test_source_utf8.vim @@ -42,7 +42,7 @@ func Test_source_ctrl_v() \ "map __3 asd\<C-V>\<C-V>", \ "map __4 asd\<C-V>\<C-V>\<C-V>", \ "map __5 asd\<C-V>\<C-V>\<C-V>", - \ ], 'Xtestfile') + \ ], 'Xtestfile', 'D') source Xtestfile enew! exe "normal __1\<Esc>\<Esc>__2\<Esc>__3\<Esc>\<Esc>__4\<Esc>__5\<Esc>" @@ -52,7 +52,6 @@ func Test_source_ctrl_v() \ getline(1, 2)) enew! - call delete('Xtestfile') unmap __1 unmap __2 unmap __3 diff --git a/test/old/testdir/test_spell.vim b/test/old/testdir/test_spell.vim index a19b64a7de..bdd8a673fd 100644 --- a/test/old/testdir/test_spell.vim +++ b/test/old/testdir/test_spell.vim @@ -5,6 +5,7 @@ source check.vim CheckFeature spell source screendump.vim +source view_util.vim func TearDown() set nospell @@ -124,6 +125,7 @@ foobar/? set spelllang= call assert_fails("call spellbadword('maxch')", 'E756:') + call assert_fails("spelldump", 'E756:') call delete('Xwords.spl') call delete('Xwords') @@ -300,6 +302,20 @@ func Test_compl_with_CTRL_X_CTRL_K_using_spell() set spell& spelllang& dictionary& ignorecase& endfunc +func Test_compl_with_CTRL_X_s() + new + set spell spelllang=en_us showmode + inoremap <buffer><F2> <Cmd>let g:msg = Screenline(&lines)<CR> + + call feedkeys("STheatre\<C-X>s\<F2>\<C-Y>\<Esc>", 'tx') + call assert_equal(['Theater'], getline(1, '$')) + call assert_match('(^S^N^P)', g:msg) + + bwipe! + set spell& spelllang& showmode& + unlet g:msg +endfunc + func Test_spellrepall() new set spell @@ -796,8 +812,8 @@ func Test_zz_sal_and_addition() throw 'skipped: Nvim does not support enc=latin1' set enc=latin1 set spellfile= - call writefile(g:test_data_dic1, "Xtest.dic") - call writefile(g:test_data_aff_sal, "Xtest.aff") + call writefile(g:test_data_dic1, "Xtest.dic", 'D') + call writefile(g:test_data_aff_sal, "Xtest.aff", 'D') mkspell! Xtest Xtest set spl=Xtest.latin1.spl spell call assert_equal('kbltykk', soundfold('goobledygoook')) @@ -805,7 +821,7 @@ func Test_zz_sal_and_addition() call assert_equal('*fls kswts tl', soundfold('oeverloos gezwets edale')) "also use an addition file - call writefile(["/regions=usgbnz", "elequint/2", "elekwint/3"], "Xtest.latin1.add") + call writefile(["/regions=usgbnz", "elequint/2", "elekwint/3"], "Xtest.latin1.add", 'D') mkspell! Xtest.latin1.add.spl Xtest.latin1.add bwipe! @@ -842,10 +858,9 @@ endfunc func Test_region_error() messages clear - call writefile(["/regions=usgbnz", "elequint/0"], "Xtest.latin1.add") + call writefile(["/regions=usgbnz", "elequint/0"], "Xtest.latin1.add", 'D') mkspell! Xtest.latin1.add.spl Xtest.latin1.add call assert_match('Invalid region nr in Xtest.latin1.add line 2: 0', execute('messages')) - call delete('Xtest.latin1.add') call delete('Xtest.latin1.add.spl') endfunc diff --git a/test/old/testdir/test_spell_utf8.vim b/test/old/testdir/test_spell_utf8.vim index 91ada1ed38..bb2c354a3d 100644 --- a/test/old/testdir/test_spell_utf8.vim +++ b/test/old/testdir/test_spell_utf8.vim @@ -726,8 +726,8 @@ endfunc " Test with SAL instead of SOFO items; test automatic reloading func Test_spell_sal_and_addition() set spellfile= - call writefile(g:test_data_dic1, "Xtest.dic") - call writefile(g:test_data_aff_sal, "Xtest.aff") + call writefile(g:test_data_dic1, "Xtest.dic", 'D') + call writefile(g:test_data_aff_sal, "Xtest.aff", 'D') mkspell! Xtest Xtest set spl=Xtest.utf-8.spl spell call assert_equal('kbltykk', soundfold('goobledygoook')) @@ -735,7 +735,7 @@ func Test_spell_sal_and_addition() call assert_equal('*fls kswts tl', soundfold('oeverloos gezwets edale')) "also use an addition file - call writefile(["/regions=usgbnz", "elequint/2", "elekwint/3"], "Xtest.utf-8.add") + call writefile(["/regions=usgbnz", "elequint/2", "elekwint/3"], "Xtest.utf-8.add", 'D') mkspell! Xtest.utf-8.add.spl Xtest.utf-8.add bwipe! diff --git a/test/old/testdir/test_spellfile.vim b/test/old/testdir/test_spellfile.vim index 4d2a6cf35f..48e46641f4 100644 --- a/test/old/testdir/test_spellfile.vim +++ b/test/old/testdir/test_spellfile.vim @@ -191,6 +191,11 @@ func Spellfile_Test(content, emsg) " Add the spell file header and version (VIMspell2) let v = 0z56494D7370656C6C32 + a:content call writefile(v, splfile, 'b') + + " 'encoding' is set before each test to clear the previously loaded suggest + " file from memory. + set encoding=utf-8 + set runtimepath=./Xtest set spelllang=Xtest if a:emsg != '' @@ -207,7 +212,7 @@ endfunc " The spell file format is described in spellfile.c func Test_spellfile_format_error() let save_rtp = &rtp - call mkdir('Xtest/spell', 'p') + call mkdir('Xtest/spell', 'pR') let splfile = './Xtest/spell/Xtest.utf-8.spl' " empty spell file @@ -311,6 +316,12 @@ func Test_spellfile_format_error() " SN_SOFO: missing sofoto call Spellfile_Test(0z0600000000050001610000, 'E759:') + " SN_SOFO: empty sofofrom and sofoto + call Spellfile_Test(0z06000000000400000000FF000000000000000000000000, '') + + " SN_SOFO: multi-byte characters in sofofrom and sofoto + call Spellfile_Test(0z0600000000080002CF810002CF82FF000000000000000000000000, '') + " SN_COMPOUND: compmax is less than 2 call Spellfile_Test(0z08000000000101, 'E759:') @@ -320,6 +331,12 @@ func Test_spellfile_format_error() " SN_COMPOUND: missing compoptions call Spellfile_Test(0z080000000005040101, 'E758:') + " SN_COMPOUND: missing comppattern + call Spellfile_Test(0z08000000000704010100000001, 'E758:') + + " SN_COMPOUND: incorrect comppatlen + call Spellfile_Test(0z080000000007040101000000020165, 'E758:') + " SN_INFO: missing info call Spellfile_Test(0z0F0000000005040101, '') @@ -329,6 +346,12 @@ func Test_spellfile_format_error() " SN_MAP: missing midword call Spellfile_Test(0z0700000000040102, '') + " SN_MAP: empty map string + call Spellfile_Test(0z070000000000FF000000000000000000000000, '') + + " SN_MAP: duplicate multibyte character + call Spellfile_Test(0z070000000004DC81DC81, 'E783:') + " SN_SYLLABLE: missing SYLLABLE item call Spellfile_Test(0z0900000000040102, '') @@ -345,20 +368,28 @@ func Test_spellfile_format_error() " LWORDTREE: missing tree node value call Spellfile_Test(0zFF0000000402, 'E758:') + " LWORDTREE: incorrect sibling node count + call Spellfile_Test(0zFF00000001040000000000000000, 'E759:') + " KWORDTREE: missing tree node call Spellfile_Test(0zFF0000000000000004, 'E758:') " PREFIXTREE: missing tree node call Spellfile_Test(0zFF000000000000000000000004, 'E758:') + " PREFIXTREE: incorrect prefcondnr + call Spellfile_Test(0zFF000000000000000000000002010200000020, 'E759:') + + " PREFIXTREE: invalid nodeidx + call Spellfile_Test(0zFF00000000000000000000000201010000, 'E759:') + let &rtp = save_rtp - call delete('Xtest', 'rf') endfunc " Test for format errors in suggest file func Test_sugfile_format_error() let save_rtp = &rtp - call mkdir('Xtest/spell', 'p') + call mkdir('Xtest/spell', 'pR') let splfile = './Xtest/spell/Xtest.utf-8.spl' let sugfile = './Xtest/spell/Xtest.utf-8.sug' @@ -441,7 +472,6 @@ func Test_sugfile_format_error() set nospell spelllang& let &rtp = save_rtp - call delete('Xtest', 'rf') endfunc " Test for using :mkspell to create a spell file from a list of words @@ -454,7 +484,7 @@ func Test_wordlist_dic() /encoding=latin1 example [END] - call writefile(lines, 'Xwordlist.dic') + call writefile(lines, 'Xwordlist.dic', 'D') let output = execute('mkspell Xwordlist.spl Xwordlist.dic') call assert_match('Duplicate /encoding= line ignored in Xwordlist.dic line 4: /encoding=latin1', output) @@ -518,15 +548,28 @@ func Test_wordlist_dic() let output = execute('mkspell! -ascii Xwordlist.spl Xwordlist.dic') call assert_match('Ignored 1 words with non-ASCII characters', output) + " keep case of a word + let lines =<< trim [END] + example/= + [END] + call writefile(lines, 'Xwordlist.dic') + let output = execute('mkspell! Xwordlist.spl Xwordlist.dic') + call assert_match('Compressed keep-case:', output) + call delete('Xwordlist.spl') - call delete('Xwordlist.dic') endfunc " Test for the :mkspell command func Test_mkspell() call assert_fails('mkspell Xtest_us.spl', 'E751:') + call assert_fails('mkspell Xtest.spl abc', 'E484:') call assert_fails('mkspell a b c d e f g h i j k', 'E754:') + " create a .aff file but not the .dic file + call writefile([], 'Xtest.aff') + call assert_fails('mkspell Xtest.spl Xtest', 'E484:') + call delete('Xtest.aff') + call writefile([], 'Xtest.spl') call writefile([], 'Xtest.dic') call assert_fails('mkspell Xtest.spl Xtest.dic', 'E13:') @@ -554,8 +597,8 @@ func Test_aff_file_format_error() CheckNotMSWindows " No word count in .dic file - call writefile([], 'Xtest.dic') - call writefile([], 'Xtest.aff') + call writefile([], 'Xtest.dic', 'D') + call writefile([], 'Xtest.aff', 'D') call assert_fails('mkspell! Xtest.spl Xtest', 'E760:') " create a .dic file for the tests below @@ -656,7 +699,7 @@ func Test_aff_file_format_error() let output = execute('mkspell! Xtest.spl Xtest') call assert_match('Different combining flag in continued affix block in Xtest.aff line 3', output) - " Try to reuse a affix used for BAD flag + " Try to reuse an affix used for BAD flag call writefile(['BAD x', 'PFX x Y 1', 'PFX x 0 re x'], 'Xtest.aff') let output = execute('mkspell! Xtest.spl Xtest') call assert_match('Affix also used for BAD/RARE/KEEPCASE/NEEDAFFIX/NEEDCOMPOUND/NOSUGGEST in Xtest.aff line 2: x', output) @@ -730,6 +773,44 @@ func Test_aff_file_format_error() let output = execute('mkspell! Xtest.spl Xtest') call assert_match('Illegal flag in Xtest.aff line 2: L', output) + " Nvim: non-utf8 encoding not supported + " " missing character in UPP entry. The character table is used only in a + " " non-utf8 encoding + " call writefile(['FOL abc', 'LOW abc', 'UPP A'], 'Xtest.aff') + " let save_encoding = &encoding + " set encoding=cp949 + " call assert_fails('mkspell! Xtest.spl Xtest', 'E761:') + " let &encoding = save_encoding + " + " " character range doesn't match between FOL and LOW entries + " call writefile(["FOL \u0102bc", 'LOW abc', 'UPP ABC'], 'Xtest.aff') + " let save_encoding = &encoding + " set encoding=cp949 + " call assert_fails('mkspell! Xtest.spl Xtest', 'E762:') + " let &encoding = save_encoding + " + " " character range doesn't match between FOL and UPP entries + " call writefile(["FOL \u0102bc", "LOW \u0102bc", 'UPP ABC'], 'Xtest.aff') + " let save_encoding = &encoding + " set encoding=cp949 + " call assert_fails('mkspell! Xtest.spl Xtest', 'E762:') + " let &encoding = save_encoding + " + " " additional characters in LOW and UPP entries + " call writefile(["FOL ab", "LOW abc", 'UPP ABC'], 'Xtest.aff') + " let save_encoding = &encoding + " set encoding=cp949 + " call assert_fails('mkspell! Xtest.spl Xtest', 'E761:') + " let &encoding = save_encoding + " + " " missing UPP entry + " call writefile(["FOL abc", "LOW abc"], 'Xtest.aff') + " let save_encoding = &encoding + " set encoding=cp949 + " let output = execute('mkspell! Xtest.spl Xtest') + " call assert_match('Missing FOL/LOW/UPP line in Xtest.aff', output) + " let &encoding = save_encoding + " duplicate word in the .dic file call writefile(['2', 'good', 'good', 'good'], 'Xtest.dic') call writefile(['NAME vim'], 'Xtest.aff') @@ -737,8 +818,16 @@ func Test_aff_file_format_error() call assert_match('First duplicate word in Xtest.dic line 3: good', output) call assert_match('2 duplicate word(s) in Xtest.dic', output) - call delete('Xtest.dic') - call delete('Xtest.aff') + " use multiple .aff files with different values for COMPOUNDWORDMAX and + " MIDWORD (number and string) + call writefile(['1', 'world'], 'Xtest_US.dic', 'D') + call writefile(['1', 'world'], 'Xtest_CA.dic', 'D') + call writefile(["COMPOUNDWORDMAX 3", "MIDWORD '-"], 'Xtest_US.aff', 'D') + call writefile(["COMPOUNDWORDMAX 4", "MIDWORD '="], 'Xtest_CA.aff', 'D') + let output = execute('mkspell! Xtest.spl Xtest_US Xtest_CA') + call assert_match('COMPOUNDWORDMAX value differs from what is used in another .aff file', output) + call assert_match('MIDWORD value differs from what is used in another .aff file', output) + call delete('Xtest.spl') call delete('Xtest.sug') endfunc @@ -757,9 +846,25 @@ func Test_spell_add_word() %bw! endfunc +func Test_spell_add_long_word() + set spell spellfile=./Xspellfile.add spelllang=en + + let word = repeat('a', 9000) + let v:errmsg = '' + " Spell checking doesn't really work for such a long word, + " but this should not cause an E1510 error. + exe 'spellgood ' .. word + call assert_equal('', v:errmsg) + call assert_equal([word], readfile('./Xspellfile.add')) + + set spell& spellfile= spelllang& encoding=utf-8 + call delete('./Xspellfile.add') + call delete('./Xspellfile.add.spl') +endfunc + func Test_spellfile_verbose() - call writefile(['1', 'one'], 'XtestVerbose.dic') - call writefile([], 'XtestVerbose.aff') + call writefile(['1', 'one'], 'XtestVerbose.dic', 'D') + call writefile([], 'XtestVerbose.aff', 'D') mkspell! XtestVerbose-utf8.spl XtestVerbose set spell @@ -772,15 +877,13 @@ func Test_spellfile_verbose() call assert_notmatch('Reading spell file "XtestVerbose-utf8.spl"', a) set spell& spelllang& - call delete('XtestVerbose.dic') - call delete('XtestVerbose.aff') call delete('XtestVerbose-utf8.spl') endfunc " Test NOBREAK (see :help spell-NOBREAK) func Test_NOBREAK() - call writefile(['3', 'one', 'two', 'three' ], 'XtestNOBREAK.dic') - call writefile(['NOBREAK' ], 'XtestNOBREAK.aff') + call writefile(['3', 'one', 'two', 'three' ], 'XtestNOBREAK.dic', 'D') + call writefile(['NOBREAK' ], 'XtestNOBREAK.aff', 'D') mkspell! XtestNOBREAK-utf8.spl XtestNOBREAK set spell spelllang=XtestNOBREAK-utf8.spl @@ -802,8 +905,6 @@ func Test_NOBREAK() bw! set spell& spelllang& - call delete('XtestNOBREAK.dic') - call delete('XtestNOBREAK.aff') call delete('XtestNOBREAK-utf8.spl') endfunc @@ -813,11 +914,11 @@ func Test_spellfile_CHECKCOMPOUNDPATTERN() \ 'one/c', \ 'two/c', \ 'three/c', - \ 'four'], 'XtestCHECKCOMPOUNDPATTERN.dic') + \ 'four'], 'XtestCHECKCOMPOUNDPATTERN.dic', 'D') " Forbid compound words where first word ends with 'wo' and second starts with 'on'. call writefile(['CHECKCOMPOUNDPATTERN 1', \ 'CHECKCOMPOUNDPATTERN wo on', - \ 'COMPOUNDFLAG c'], 'XtestCHECKCOMPOUNDPATTERN.aff') + \ 'COMPOUNDFLAG c'], 'XtestCHECKCOMPOUNDPATTERN.aff', 'D') mkspell! XtestCHECKCOMPOUNDPATTERN-utf8.spl XtestCHECKCOMPOUNDPATTERN set spell spelllang=XtestCHECKCOMPOUNDPATTERN-utf8.spl @@ -841,8 +942,6 @@ func Test_spellfile_CHECKCOMPOUNDPATTERN() endfor set spell& spelllang& - call delete('XtestCHECKCOMPOUNDPATTERN.dic') - call delete('XtestCHECKCOMPOUNDPATTERN.aff') call delete('XtestCHECKCOMPOUNDPATTERN-utf8.spl') endfunc @@ -851,15 +950,15 @@ func Test_spellfile_NOCOMPOUNDSUGS() call writefile(['3', \ 'one/c', \ 'two/c', - \ 'three/c'], 'XtestNOCOMPOUNDSUGS.dic') + \ 'three/c'], 'XtestNOCOMPOUNDSUGS.dic', 'D') " pass 0 tests without NOCOMPOUNDSUGS, pass 1 tests with NOCOMPOUNDSUGS for pass in [0, 1] if pass == 0 - call writefile(['COMPOUNDFLAG c'], 'XtestNOCOMPOUNDSUGS.aff') + call writefile(['COMPOUNDFLAG c'], 'XtestNOCOMPOUNDSUGS.aff', 'D') else call writefile(['NOCOMPOUNDSUGS', - \ 'COMPOUNDFLAG c'], 'XtestNOCOMPOUNDSUGS.aff') + \ 'COMPOUNDFLAG c'], 'XtestNOCOMPOUNDSUGS.aff', 'D') endif mkspell! XtestNOCOMPOUNDSUGS-utf8.spl XtestNOCOMPOUNDSUGS @@ -887,8 +986,6 @@ func Test_spellfile_NOCOMPOUNDSUGS() endfor set spell& spelllang& - call delete('XtestNOCOMPOUNDSUGS.dic') - call delete('XtestNOCOMPOUNDSUGS.aff') call delete('XtestNOCOMPOUNDSUGS-utf8.spl') endfunc @@ -901,8 +998,8 @@ func Test_spellfile_COMMON() \ 'any', \ 'tee', \ 'the', - \ 'ted'], 'XtestCOMMON.dic') - call writefile(['COMMON the and'], 'XtestCOMMON.aff') + \ 'ted'], 'XtestCOMMON.dic', 'D') + call writefile(['COMMON the and'], 'XtestCOMMON.aff', 'D') mkspell! XtestCOMMON-utf8.spl XtestCOMMON set spell spelllang=XtestCOMMON-utf8.spl @@ -914,15 +1011,13 @@ func Test_spellfile_COMMON() call assert_equal(['the', 'tee'], spellsuggest('dhe', 2)) set spell& spelllang& - call delete('XtestCOMMON.dic') - call delete('XtestCOMMON.aff') call delete('XtestCOMMON-utf8.spl') endfunc " Test NOSUGGEST (see :help spell-COMMON) func Test_spellfile_NOSUGGEST() - call writefile(['2', 'foo/X', 'fog'], 'XtestNOSUGGEST.dic') - call writefile(['NOSUGGEST X'], 'XtestNOSUGGEST.aff') + call writefile(['2', 'foo/X', 'fog'], 'XtestNOSUGGEST.dic', 'D') + call writefile(['NOSUGGEST X'], 'XtestNOSUGGEST.aff', 'D') mkspell! XtestNOSUGGEST-utf8.spl XtestNOSUGGEST set spell spelllang=XtestNOSUGGEST-utf8.spl @@ -940,8 +1035,6 @@ func Test_spellfile_NOSUGGEST() call assert_equal(['fog'], spellsuggest('fogg', 1)) set spell& spelllang& - call delete('XtestNOSUGGEST.dic') - call delete('XtestNOSUGGEST.aff') call delete('XtestNOSUGGEST-utf8.spl') endfunc @@ -950,7 +1043,7 @@ endfunc func Test_spellfile_CIRCUMFIX() " Example taken verbatim from https://github.com/hunspell/hunspell/tree/master/tests call writefile(['1', - \ 'nagy/C po:adj'], 'XtestCIRCUMFIX.dic') + \ 'nagy/C po:adj'], 'XtestCIRCUMFIX.dic', 'D') call writefile(['# circumfixes: ~ obligate prefix/suffix combinations', \ '# superlative in Hungarian: leg- (prefix) AND -bb (suffix)', \ '', @@ -965,7 +1058,7 @@ func Test_spellfile_CIRCUMFIX() \ 'SFX C Y 3', \ 'SFX C 0 obb . is:COMPARATIVE', \ 'SFX C 0 obb/AX . is:SUPERLATIVE', - \ 'SFX C 0 obb/BX . is:SUPERSUPERLATIVE'], 'XtestCIRCUMFIX.aff') + \ 'SFX C 0 obb/BX . is:SUPERSUPERLATIVE'], 'XtestCIRCUMFIX.aff', 'D') mkspell! XtestCIRCUMFIX-utf8.spl XtestCIRCUMFIX set spell spelllang=XtestCIRCUMFIX-utf8.spl @@ -984,8 +1077,6 @@ func Test_spellfile_CIRCUMFIX() endfor set spell& spelllang& - call delete('XtestCIRCUMFIX.dic') - call delete('XtestCIRCUMFIX.aff') call delete('XtestCIRCUMFIX-utf8.spl') endfunc @@ -997,12 +1088,12 @@ func Test_spellfile_SFX_strip() \ 'SFX A are hiamo [cg]are', \ 'SFX A re mo iare', \ 'SFX A re vamo are'], - \ 'XtestSFX.aff') + \ 'XtestSFX.aff', 'D') " Examples of Italian verbs: " - cantare = to sing " - cercare = to search " - odiare = to hate - call writefile(['3', 'cantare/A', 'cercare/A', 'odiare/A'], 'XtestSFX.dic') + call writefile(['3', 'cantare/A', 'cercare/A', 'odiare/A'], 'XtestSFX.dic', 'D') mkspell! XtestSFX-utf8.spl XtestSFX set spell spelllang=XtestSFX-utf8.spl @@ -1026,8 +1117,6 @@ func Test_spellfile_SFX_strip() call assert_equal(['odiamo'], spellsuggest('odiiamo', 1)) set spell& spelllang& - call delete('XtestSFX.dic') - call delete('XtestSFX.aff') call delete('XtestSFX-utf8.spl') endfunc @@ -1036,7 +1125,7 @@ endfunc func Test_init_spellfile() let save_rtp = &rtp let save_encoding = &encoding - call mkdir('Xrtp/spell', 'p') + call mkdir('Xrtp/spell', 'pR') call writefile(['vim'], 'Xrtp/spell/Xtest.dic') silent mkspell Xrtp/spell/Xtest.utf-8.spl Xrtp/spell/Xtest.dic set runtimepath=./Xrtp @@ -1046,8 +1135,8 @@ func Test_init_spellfile() call assert_equal('./Xrtp/spell/Xtest.utf-8.add', &spellfile) call assert_equal(['abc'], readfile('Xrtp/spell/Xtest.utf-8.add')) call assert_true(filereadable('Xrtp/spell/Xtest.utf-8.spl')) + set spell& spelllang& spellfile& - call delete('Xrtp', 'rf') let &encoding = save_encoding let &rtp = save_rtp %bw! @@ -1073,12 +1162,10 @@ endfunc " this was using a NULL pointer func Test_mkspell_empty_dic() - call writefile(['1'], 'XtestEmpty.dic') - call writefile(['SOFOFROM abcd', 'SOFOTO ABCD', 'SAL CIA X'], 'XtestEmpty.aff') + call writefile(['1'], 'XtestEmpty.dic', 'D') + call writefile(['SOFOFROM abcd', 'SOFOTO ABCD', 'SAL CIA X'], 'XtestEmpty.aff', 'D') mkspell! XtestEmpty.spl XtestEmpty - call delete('XtestEmpty.dic') - call delete('XtestEmpty.aff') call delete('XtestEmpty.spl') endfunc diff --git a/test/old/testdir/test_spellrare.vim b/test/old/testdir/test_spellrare.vim index bbb13c27c2..ceb35cbd17 100644 --- a/test/old/testdir/test_spellrare.vim +++ b/test/old/testdir/test_spellrare.vim @@ -11,15 +11,15 @@ func Test_spellrareword() " Create a small word list to test that spellbadword('...') " can return ['...', 'rare']. let lines =<< trim END - foo - foobar/? - foobara/? -END - call writefile(lines, 'Xwords', 'D') - - mkspell! Xwords.spl Xwords - set spelllang=Xwords.spl - call assert_equal(['foobar', 'rare'], spellbadword('foo foobar')) + foo + foobar/? + foobara/? + END + call writefile(lines, 'Xwords', 'D') + + mkspell! Xwords.spl Xwords + set spelllang=Xwords.spl + call assert_equal(['foobar', 'rare'], spellbadword('foo foobar')) new call setline(1, ['foo', '', 'foo bar foo bar foobara foo foo foo foobar', '', 'End']) diff --git a/test/old/testdir/test_startup.vim b/test/old/testdir/test_startup.vim index db9f0c4c13..ed4c4526f7 100644 --- a/test/old/testdir/test_startup.vim +++ b/test/old/testdir/test_startup.vim @@ -163,6 +163,7 @@ endfunc " horizontally or vertically. func Test_o_arg() let after =<< trim [CODE] + set cpo&vim call writefile([winnr("$"), \ winheight(1), winheight(2), &lines, \ winwidth(1), winwidth(2), &columns, @@ -1275,4 +1276,36 @@ func Test_write_in_vimrc() call delete('Xvimrc') endfunc +func Test_echo_true_in_cmd() + CheckNotGui + + let lines =<< trim END + echo v:true + call writefile(['done'], 'Xresult') + quit + END + call writefile(lines, 'Xscript') + if RunVim([], [], '--cmd "source Xscript"') + call assert_equal(['done'], readfile('Xresult')) + endif + call delete('Xscript') + call delete('Xresult') +endfunc + +func Test_rename_buffer_on_startup() + CheckUnix + + let lines =<< trim END + call writefile(['done'], 'Xresult') + qa! + END + call writefile(lines, 'Xscript') + if RunVim([], [], "--clean -e -s --cmd 'file x|new|file x' --cmd 'so Xscript'") + call assert_equal(['done'], readfile('Xresult')) + endif + call delete('Xscript') + call delete('Xresult') +endfunc + + " vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/test_startup_utf8.vim b/test/old/testdir/test_startup_utf8.vim index 1684d80c80..e8b99e7937 100644 --- a/test/old/testdir/test_startup_utf8.vim +++ b/test/old/testdir/test_startup_utf8.vim @@ -6,7 +6,7 @@ source screendump.vim func Test_read_stdin_utf8() let linesin = ['テスト', '€ÀÈÌÒÙ'] - call writefile(linesin, 'Xtestin') + call writefile(linesin, 'Xtestin', 'D') let before = [ \ 'set enc=utf-8', \ 'set fencs=cp932,utf-8', @@ -26,24 +26,22 @@ func Test_read_stdin_utf8() else call assert_equal('', 'RunVimPiped failed.') endif + call delete('Xtestout') - call delete('Xtestin') endfunc func Test_read_fifo_utf8() - if !has('unix') - return - endif + CheckUnix " Using bash/zsh's process substitution. if executable('bash') set shell=bash elseif executable('zsh') set shell=zsh else - return + throw 'Skipped: bash or zsh is required' endif let linesin = ['テスト', '€ÀÈÌÒÙ'] - call writefile(linesin, 'Xtestin') + call writefile(linesin, 'Xtestin', 'D') let before = [ \ 'set enc=utf-8', \ 'set fencs=cp932,utf-8', @@ -58,8 +56,8 @@ func Test_read_fifo_utf8() else call assert_equal('', 'RunVim failed.') endif + call delete('Xtestout') - call delete('Xtestin') endfunc func Test_detect_ambiwidth() @@ -71,12 +69,13 @@ func Test_detect_ambiwidth() \ 'set ambiwidth=double', \ 'call test_option_not_set("ambiwidth")', \ 'redraw', - \ ], 'Xscript') + \ ], 'Xscript', 'D') let buf = RunVimInTerminal('-S Xscript', #{keep_t_u7: 1}) call TermWait(buf) call term_sendkeys(buf, "S\<C-R>=&ambiwidth\<CR>\<Esc>") call WaitForAssert({-> assert_match('single', term_getline(buf, 1))}) call StopVimInTerminal(buf) - call delete('Xscript') endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/test_stat.vim b/test/old/testdir/test_stat.vim index 13ade5bee1..b9fd7f5f28 100644 --- a/test/old/testdir/test_stat.vim +++ b/test/old/testdir/test_stat.vim @@ -63,7 +63,7 @@ func Test_checktime() let fname = 'Xtest.tmp' let fl = ['Hello World!'] - call writefile(fl, fname) + call writefile(fl, fname, 'D') set autoread exec 'e' fname call SleepForTimestamp() @@ -72,8 +72,6 @@ func Test_checktime() call writefile(fl, fname) checktime call assert_equal(fl[0], getline(1)) - - call delete(fname) endfunc func Test_checktime_fast() @@ -82,7 +80,7 @@ func Test_checktime_fast() let fname = 'Xtest.tmp' let fl = ['Hello World!'] - call writefile(fl, fname) + call writefile(fl, fname, 'D') set autoread exec 'e' fname let fl = readfile(fname) @@ -91,8 +89,6 @@ func Test_checktime_fast() call writefile(fl, fname) checktime call assert_equal(fl[0], getline(1)) - - call delete(fname) endfunc func Test_autoread_fast() @@ -106,12 +102,10 @@ func Test_autoread_fast() call setline(1, 'foo') w! sleep 10m - call writefile(['bar'], 'Xautoread') + call writefile(['bar'], 'Xautoread', 'D') sleep 10m checktime call assert_equal('bar', trim(getline(1))) - - call delete('Xautoread') endfunc func Test_autoread_file_deleted() diff --git a/test/old/testdir/test_statusline.vim b/test/old/testdir/test_statusline.vim index c48bac12b4..c8162ced07 100644 --- a/test/old/testdir/test_statusline.vim +++ b/test/old/testdir/test_statusline.vim @@ -417,7 +417,7 @@ func Test_statusline() " Test statusline works with 80+ items function! StatusLabel() redrawstatus - return '[label]' + return '[label]' endfunc let statusline = '%{StatusLabel()}' for i in range(150) @@ -483,14 +483,13 @@ func Test_statusline_removed_group() set laststatus=2 let &statusline = '%#StatColorHi2#%(✓%#StatColorHi2#%) Q≡' END - call writefile(lines, 'XTest_statusline') + call writefile(lines, 'XTest_statusline', 'D') let buf = RunVimInTerminal('-S XTest_statusline', {'rows': 10, 'cols': 50}) call VerifyScreenDump(buf, 'Test_statusline_1', {}) " clean up call StopVimInTerminal(buf) - call delete('XTest_statusline') endfunc func Test_statusline_using_mode() @@ -501,7 +500,7 @@ func Test_statusline_using_mode() split setlocal statusline=+%{mode()}+ END - call writefile(lines, 'XTest_statusline') + call writefile(lines, 'XTest_statusline', 'D') let buf = RunVimInTerminal('-S XTest_statusline', {'rows': 7, 'cols': 50}) call VerifyScreenDump(buf, 'Test_statusline_mode_1', {}) @@ -512,7 +511,6 @@ func Test_statusline_using_mode() " clean up call term_sendkeys(buf, "close\<CR>") call StopVimInTerminal(buf) - call delete('XTest_statusline') endfunc func Test_statusline_after_split_vsplit() @@ -567,13 +565,12 @@ func Test_statusline_highlight_truncate() hi! link User2 ErrorMsg set statusline=%.5(%1*ABC%2*DEF%1*GHI%) END - call writefile(lines, 'XTest_statusline') + call writefile(lines, 'XTest_statusline', 'D') let buf = RunVimInTerminal('-S XTest_statusline', {'rows': 6}) call VerifyScreenDump(buf, 'Test_statusline_hl', {}) call StopVimInTerminal(buf) - call delete('XTest_statusline') endfunc func Test_statusline_showcmd() diff --git a/test/old/testdir/test_substitute.vim b/test/old/testdir/test_substitute.vim index fdb0f6fc37..90c46abe8b 100644 --- a/test/old/testdir/test_substitute.vim +++ b/test/old/testdir/test_substitute.vim @@ -743,7 +743,7 @@ func Test_sub_highlight_zero_match() endfunc func Test_nocatch_sub_failure_handling() - " normal error results in all replacements + " normal error results in all replacements func Foo() foobar endfunc @@ -806,7 +806,7 @@ func Test_replace_keeppatterns() a foobar -substitute foo asdf +substitute foo asdf foo one two . @@ -815,21 +815,26 @@ one two /^substitute s/foo/bar/ call assert_equal('foo', @/) - call assert_equal('substitute bar asdf', getline('.')) + call assert_equal('substitute bar asdf foo', getline('.')) /^substitute keeppatterns s/asdf/xyz/ call assert_equal('^substitute', @/) - call assert_equal('substitute bar xyz', getline('.')) + call assert_equal('substitute bar xyz foo', getline('.')) + + /^substitute + & + call assert_equal('^substitute', @/) + call assert_equal('substitute bar xyz bar', getline('.')) exe "normal /bar /e\<CR>" call assert_equal(15, col('.')) normal - keeppatterns /xyz call assert_equal('bar ', @/) - call assert_equal('substitute bar xyz', getline('.')) + call assert_equal('substitute bar xyz bar', getline('.')) exe "normal 0dn" - call assert_equal('xyz', getline('.')) + call assert_equal('xyz bar', getline('.')) close! endfunc @@ -892,7 +897,7 @@ func Test_sub_with_no_last_pat() call writefile(v:errors, 'Xresult') qall! [SCRIPT] - call writefile(lines, 'Xscript') + call writefile(lines, 'Xscript', 'D') if RunVim([], [], '--clean -S Xscript') call assert_equal([], readfile('Xresult')) endif @@ -909,7 +914,6 @@ func Test_sub_with_no_last_pat() " call assert_equal([], readfile('Xresult')) " endif - call delete('Xscript') call delete('Xresult') endfunc @@ -1110,13 +1114,12 @@ func Test_sub_open_cmdline_win() redir END qall! [SCRIPT] - call writefile(lines, 'Xscript') + call writefile(lines, 'Xscript', 'D') if RunVim([], [], '-u NONE -S Xscript') call assert_match('E565: Not allowed to change text or change window', \ readfile('Xresult')->join('XX')) endif - call delete('Xscript') call delete('Xresult') endfunc @@ -1509,4 +1512,18 @@ func Test_substitute_expr_recursive() exe bufnr .. "bw!" endfunc +" Test for changing 'cpo' in a substitute expression +func Test_substitute_expr_cpo() + func XSubExpr() + set cpo= + return 'x' + endfunc + + let save_cpo = &cpo + call assert_equal('xxx', substitute('abc', '.', '\=XSubExpr()', 'g')) + call assert_equal(save_cpo, &cpo) + + delfunc XSubExpr +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/test_swap.vim b/test/old/testdir/test_swap.vim index 65b6c57850..8a1b3ce133 100644 --- a/test/old/testdir/test_swap.vim +++ b/test/old/testdir/test_swap.vim @@ -10,14 +10,13 @@ endfunc " Tests for 'directory' option. func Test_swap_directory() - if !has("unix") - return - endif + CheckUnix + let content = ['start of testfile', \ 'line 2 Abcdefghij', \ 'line 3 Abcdefghij', \ 'end of testfile'] - call writefile(content, 'Xtest1') + call writefile(content, 'Xtest1', 'D') " '.', swap file in the same directory as file set dir=.,~ @@ -31,7 +30,7 @@ func Test_swap_directory() " './dir', swap file in a directory relative to the file set dir=./Xtest2,.,~ - call mkdir("Xtest2") + call mkdir("Xtest2", 'R') edit Xtest1 call assert_equal([], glob(swfname, 1, 1, 1)) let swfname = "Xtest2/Xtest1.swp" @@ -41,7 +40,7 @@ func Test_swap_directory() " 'dir', swap file in directory relative to the current dir set dir=Xtest.je,~ - call mkdir("Xtest.je") + call mkdir("Xtest.je", 'R') call writefile(content, 'Xtest2/Xtest3') edit Xtest2/Xtest3 call assert_equal(["Xtest2/Xtest3"], glob("Xtest2/*", 1, 1, 1)) @@ -50,15 +49,11 @@ func Test_swap_directory() call assert_equal([swfname], glob("Xtest.je/*", 1, 1, 1)) set dir& - call delete("Xtest1") - call delete("Xtest2", "rf") - call delete("Xtest.je", "rf") endfunc func Test_swap_group() - if !has("unix") - return - endif + CheckUnix + let groups = split(system('groups')) if len(groups) <= 1 throw 'Skipped: need at least two groups, got ' . string(groups) @@ -148,7 +143,7 @@ func Test_swapinfo() let info = swapinfo('doesnotexist') call assert_equal('Cannot open file', info.error) - call writefile(['burp'], 'Xnotaswapfile') + call writefile(['burp'], 'Xnotaswapfile', 'D') let info = swapinfo('Xnotaswapfile') call assert_equal('Cannot read file', info.error) call delete('Xnotaswapfile') @@ -156,7 +151,6 @@ func Test_swapinfo() call writefile([repeat('x', 10000)], 'Xnotaswapfile') let info = swapinfo('Xnotaswapfile') call assert_equal('Not a swap file', info.error) - call delete('Xnotaswapfile') endfunc func Test_swapname() @@ -204,7 +198,7 @@ func Test_swapfile_delete() " Close the file and recreate the swap file. " Now editing the file will run into the process still existing quit - call writefile(swapfile_bytes, swapfile_name) + call writefile(swapfile_bytes, swapfile_name, 'D') let s:swap_choice = 'e' let s:swapname = '' split XswapfileText @@ -232,7 +226,6 @@ func Test_swapfile_delete() call assert_equal(fnamemodify(swapfile_name, ':t'), fnamemodify(s:swapname, ':t')) call delete('XswapfileText') - call delete(swapfile_name) augroup test_swapfile_delete autocmd! augroup END @@ -246,7 +239,7 @@ func Test_swap_recover() autocmd SwapExists * let v:swapchoice = 'r' augroup END - call mkdir('Xswap') + call mkdir('Xswap', 'R') let $Xswap = 'foo' " Check for issue #4369. set dir=Xswap// " Create a valid swapfile by editing a file. @@ -259,7 +252,7 @@ func Test_swap_recover() " Close the file and recreate the swap file. quit - call writefile(swapfile_bytes, swapfile_name) + call writefile(swapfile_bytes, swapfile_name, 'D') " Edit the file again. This triggers recovery. try split Xswap/text @@ -271,9 +264,6 @@ func Test_swap_recover() call assert_equal(['one', 'two', 'three'], getline(1, 3)) quit! - call delete('Xswap/text') - call delete(swapfile_name) - call delete('Xswap', 'd') unlet $Xswap set dir& augroup test_swap_recover @@ -301,7 +291,7 @@ func Test_swap_recover_ext() " Close and delete the file and recreate the swap file. quit call delete('Xtest.scr') - call writefile(swapfile_bytes, swapfile_name) + call writefile(swapfile_bytes, swapfile_name, 'D') " Edit the file again. This triggers recovery. try split Xtest.scr @@ -314,7 +304,6 @@ func Test_swap_recover_ext() quit! call delete('Xtest.scr') - call delete(swapfile_name) augroup test_swap_recover_ext autocmd! augroup END @@ -342,7 +331,7 @@ func Test_swap_split_win() " Close and delete the file and recreate the swap file. quit call delete('Xtest.scr') - call writefile(swapfile_bytes, swapfile_name) + call writefile(swapfile_bytes, swapfile_name, 'D') " Split edit the file again. This should fail to open the window try split Xtest.scr @@ -353,7 +342,6 @@ func Test_swap_split_win() call assert_equal(1, winnr('$')) call delete('Xtest.scr') - call delete(swapfile_name) augroup test_swap_splitwin autocmd! @@ -365,7 +353,7 @@ endfunc func Test_swap_prompt_splitwin() CheckRunVimInTerminal - call writefile(['foo bar'], 'Xfile1') + call writefile(['foo bar'], 'Xfile1', 'D') edit Xfile1 preserve " should help to make sure the swap file exists @@ -400,13 +388,12 @@ func Test_swap_prompt_splitwin() call StopVimInTerminal(buf) %bwipe! - call delete('Xfile1') endfunc func Test_swap_symlink() CheckUnix - call writefile(['text'], 'Xtestfile') + call writefile(['text'], 'Xtestfile', 'D') silent !ln -s -f Xtestfile Xtestlink set dir=. @@ -417,18 +404,16 @@ func Test_swap_symlink() call assert_match('Xtestfile\.swp$', s:swapname()) bwipe! - call mkdir('Xswapdir') + call mkdir('Xswapdir', 'R') exe 'set dir=' . getcwd() . '/Xswapdir//' " Check that this also works when 'directory' ends with '//' edit Xtestlink - call assert_match('Xtestfile\.swp$', s:swapname()) + call assert_match('Xswapdir[/\\]%.*testdir%Xtestfile\.swp$', s:swapname()) bwipe! set dir& - call delete('Xtestfile') call delete('Xtestlink') - call delete('Xswapdir', 'rf') endfunc func s:get_unused_pid(base) @@ -486,7 +471,7 @@ func Test_swap_auto_delete() " Change the process ID to avoid the "still running" warning. let swapfile_bytes[24:27] = s:pid_to_blob(s:get_unused_pid( \ s:blob_to_pid(swapfile_bytes[24:27]))) - call writefile(swapfile_bytes, swapfile_name) + call writefile(swapfile_bytes, swapfile_name, 'D') edit Xtest.scr " will end up using the same swap file after deleting the existing one call assert_equal(swapfile_name, swapname('%')) @@ -510,7 +495,6 @@ func Test_swap_auto_delete() bwipe! call delete('Xtest.scr') - call delete(swapfile_name) augroup test_swap_recover_ext autocmd! augroup END @@ -539,13 +523,13 @@ endfunc " Test for the v:swapchoice variable func Test_swapchoice() - call writefile(['aaa', 'bbb'], 'Xfile5') + call writefile(['aaa', 'bbb'], 'Xfile5', 'D') edit Xfile5 preserve let swapfname = swapname('') let b = readblob(swapfname) bw! - call writefile(b, swapfname) + call writefile(b, swapfname, 'D') autocmd! SwapExists @@ -584,7 +568,6 @@ func Test_swapchoice() %bw! call assert_false(filereadable(swapfname)) - call delete('Xfile5') call delete(swapfname) augroup test_swapchoice autocmd! diff --git a/test/old/testdir/test_syntax.vim b/test/old/testdir/test_syntax.vim index 35523df17d..207efb6223 100644 --- a/test/old/testdir/test_syntax.vim +++ b/test/old/testdir/test_syntax.vim @@ -549,8 +549,7 @@ endfunc func Test_bg_detection() CheckNotGui - " auto-detection of &bg, make sure sure it isn't set anywhere before - " this test + " auto-detection of &bg, make sure it isn't set anywhere before this test hi Normal ctermbg=0 call assert_equal('dark', &bg) hi Normal ctermbg=4 @@ -645,15 +644,16 @@ func Test_syntax_c() \ ' printf("Just an example piece of C code\n");', \ ' return 0x0ff;', \ '}', + \ "\t\t ", \ ' static void', \ 'myFunction(const double count, struct nothing, long there) {', - \ ' // 123: nothing to read here', - \ ' for (int i = 0; i < count; ++i) {', - \ ' break;', - \ ' }', - \ " Note: asdf", + \ "\t// 123: nothing to endif here", + \ "\tfor (int i = 0; i < count; ++i) {", + \ "\t break;", + \ "\t}", + \ "\tNote: asdf", \ '}', - \ ], 'Xtest.c') + \ ], 'Xtest.c', 'D') " This makes the default for 'background' use "dark", check that the " response to t_RB corrects it to "light". @@ -665,7 +665,6 @@ func Test_syntax_c() call StopVimInTerminal(buf) let $COLORFGBG = '' - call delete('Xtest.c') endfun " Test \z(...) along with \z1 @@ -699,10 +698,10 @@ func Test_syn_wrong_z_one() endfunc func Test_syntax_after_bufdo() - call writefile(['/* aaa comment */'], 'Xaaa.c') - call writefile(['/* bbb comment */'], 'Xbbb.c') - call writefile(['/* ccc comment */'], 'Xccc.c') - call writefile(['/* ddd comment */'], 'Xddd.c') + call writefile(['/* aaa comment */'], 'Xaaa.c', 'D') + call writefile(['/* bbb comment */'], 'Xbbb.c', 'D') + call writefile(['/* ccc comment */'], 'Xccc.c', 'D') + call writefile(['/* ddd comment */'], 'Xddd.c', 'D') let bnr = bufnr('%') new Xaaa.c @@ -730,10 +729,6 @@ func Test_syntax_after_bufdo() bwipe! Xccc.c bwipe! Xddd.c syntax off - call delete('Xaaa.c') - call delete('Xbbb.c') - call delete('Xccc.c') - call delete('Xddd.c') endfunc func Test_syntax_foldlevel() diff --git a/test/old/testdir/test_system.vim b/test/old/testdir/test_system.vim index 6c8373b335..30fab6d55f 100644 --- a/test/old/testdir/test_system.vim +++ b/test/old/testdir/test_system.vim @@ -53,7 +53,7 @@ func Test_system_exmode() let cmd = ' -es -c "source Xscript" +q; echo "result=$?"' " Need to put this in a script, "catch" isn't found after an unknown " function. - call writefile(['try', 'call doesnotexist()', 'catch', 'endtry'], 'Xscript') + call writefile(['try', 'call doesnotexist()', 'catch', 'endtry'], 'Xscript', 'D') let a = system(GetVimCommand() . cmd) call assert_match('result=0', a) call assert_equal(0, v:shell_error) @@ -69,7 +69,6 @@ func Test_system_exmode() let cmd = ' -es -c "source Xscript" +q' let a = system(GetVimCommand() . cmd) call assert_notequal(0, v:shell_error) - call delete('Xscript') if has('unix') " echo $? only works on Unix let cmd = ' -es -c "call doesnotexist()" +q; echo $?' diff --git a/test/old/testdir/test_tabpage.vim b/test/old/testdir/test_tabpage.vim index 2bd2907a55..482da2de7f 100644 --- a/test/old/testdir/test_tabpage.vim +++ b/test/old/testdir/test_tabpage.vim @@ -967,6 +967,64 @@ func Test_tabpage_alloc_failure() call assert_equal(1, tabpagenr('$')) endfunc +func Test_tabpage_tabclose() + " Default behaviour, move to the right. + call s:reconstruct_tabpage_for_test(6) + norm! 4gt + setl tcl= + tabclose + call assert_equal("n3", bufname()) + + " Move to the left. + call s:reconstruct_tabpage_for_test(6) + norm! 4gt + setl tcl=left + tabclose + call assert_equal("n1", bufname()) + + " Move to the last used tab page. + call s:reconstruct_tabpage_for_test(6) + norm! 5gt + norm! 2gt + setl tcl=uselast + tabclose + call assert_equal("n3", bufname()) + + " Same, but the last used tab page is invalid. Move to the right. + call s:reconstruct_tabpage_for_test(6) + norm! 5gt + norm! 3gt + setl tcl=uselast + tabclose 5 + tabclose! + call assert_equal("n2", bufname()) + + " Same, but the last used tab page is invalid. Move to the left. + call s:reconstruct_tabpage_for_test(6) + norm! 5gt + norm! 3gt + setl tcl=uselast,left + tabclose 5 + tabclose! + call assert_equal("n0", bufname()) + + " Move left when moving right is not possible. + call s:reconstruct_tabpage_for_test(6) + setl tcl= + norm! 6gt + tabclose + call assert_equal("n3", bufname()) + + " Move right when moving left is not possible. + call s:reconstruct_tabpage_for_test(6) + setl tcl=left + norm! 1gt + tabclose + call assert_equal("n0", bufname()) + + setl tcl& +endfunc + " this was giving ml_get errors func Test_tabpage_last_line() enew diff --git a/test/old/testdir/test_tagfunc.vim b/test/old/testdir/test_tagfunc.vim index 44916f2fc9..812603a430 100644 --- a/test/old/testdir/test_tagfunc.vim +++ b/test/old/testdir/test_tagfunc.vim @@ -89,11 +89,11 @@ func Test_tagfunc() return v:null endfunc set tags= tfu=NullTagFunc - call assert_fails('tag nothing', 'E433') + call assert_fails('tag nothing', 'E433:') delf NullTagFunc bwipe! - set tags& tfu& cpt& + set tags& tfu& cpt& call delete('Xfile1') endfunc diff --git a/test/old/testdir/test_tagjump.vim b/test/old/testdir/test_tagjump.vim index a614c19ce2..470c5c43b4 100644 --- a/test/old/testdir/test_tagjump.vim +++ b/test/old/testdir/test_tagjump.vim @@ -777,7 +777,7 @@ func Test_tag_guess() let code =<< trim [CODE] int FUNC1 (int x) { } - int + int func2 (int y) { } int * func3 () { } @@ -1001,8 +1001,63 @@ func Test_tag_stack() call settagstack(1, {'items' : []}) call assert_fails('pop', 'E73:') + " References to wiped buffer are deleted. + for i in range(10, 20) + edit Xtest + exe "tag var" .. i + endfor + edit Xtest + + let t = gettagstack() + call assert_equal(11, t.length) + call assert_equal(12, t.curidx) + + bwipe! + + let t = gettagstack() + call assert_equal(0, t.length) + call assert_equal(1, t.curidx) + + " References to wiped buffer are deleted with multiple tabpages. + let w1 = win_getid() + call settagstack(1, {'items' : []}) + for i in range(10, 20) | edit Xtest | exe "tag var" .. i | endfor + enew + + new + let w2 = win_getid() + call settagstack(1, {'items' : []}) + for i in range(10, 20) | edit Xtest | exe "tag var" .. i | endfor + enew + + tabnew + let w3 = win_getid() + call settagstack(1, {'items' : []}) + for i in range(10, 20) | edit Xtest | exe "tag var" .. i | endfor + enew + + new + let w4 = win_getid() + call settagstack(1, {'items' : []}) + for i in range(10, 20) | edit Xtest | exe "tag var" .. i | endfor + enew + + for w in [w1, w2, w3, w4] + let t = gettagstack(w) + call assert_equal(11, t.length) + call assert_equal(12, t.curidx) + endfor + + bwipe! Xtest + + for w in [w1, w2, w3, w4] + let t = gettagstack(w) + call assert_equal(0, t.length) + call assert_equal(1, t.curidx) + endfor + + %bwipe! set tags& - %bwipe endfunc " Test for browsing multiple matching tags diff --git a/test/old/testdir/test_taglist.vim b/test/old/testdir/test_taglist.vim index 75d28c3ec4..fbb682a9b2 100644 --- a/test/old/testdir/test_taglist.vim +++ b/test/old/testdir/test_taglist.vim @@ -135,6 +135,47 @@ func Test_tagsfile_without_trailing_newline() set tags& endfunc +" Check that specifying a stop directory in 'tags' works properly. +func Test_tagfiles_stopdir() + let save_cwd = getcwd() + + call mkdir('Xtagsdir1/Xtagsdir2/Xtagsdir3', 'pR') + call writefile([], 'Xtagsdir1/Xtags', 'D') + + cd Xtagsdir1/ + let &tags = './Xtags;' .. fnamemodify('..', ':p') + call assert_equal(1, len(tagfiles())) + + cd Xtagsdir2/ + let &tags = './Xtags;' .. fnamemodify('..', ':p') + call assert_equal(1, len(tagfiles())) + + cd Xtagsdir3/ + let &tags = './Xtags;' .. fnamemodify('..', ':p') + call assert_equal(0, len(tagfiles())) + + let &tags = './Xtags;../' + call assert_equal(0, len(tagfiles())) + + cd .. + call assert_equal(1, len(tagfiles())) + + cd .. + call assert_equal(1, len(tagfiles())) + + let &tags = './Xtags;..' + call assert_equal(1, len(tagfiles())) + + cd Xtagsdir2/ + call assert_equal(1, len(tagfiles())) + + cd Xtagsdir3/ + call assert_equal(0, len(tagfiles())) + + set tags& + call chdir(save_cwd) +endfunc + " Test for ignoring comments in a tags file func Test_tagfile_ignore_comments() call writefile([ diff --git a/test/old/testdir/test_termdebug.vim b/test/old/testdir/test_termdebug.vim index fd0c850577..eb88ea6f5f 100644 --- a/test/old/testdir/test_termdebug.vim +++ b/test/old/testdir/test_termdebug.vim @@ -124,13 +124,13 @@ func Test_termdebug_basic() " 60 is approx spaceBuffer * 3 if winwidth(0) <= 78 + 60 Var - call assert_equal(winnr(), winnr('$')) - call assert_equal(winlayout(), ['col', [['leaf', 1002], ['leaf', 1001], ['leaf', 1000], ['leaf', 1003 + cn]]]) + call assert_equal(winnr('$'), winnr()) + call assert_equal(['col', [['leaf', 1002], ['leaf', 1001], ['leaf', 1000], ['leaf', 1003 + cn]]], winlayout()) let cn += 1 bw! Asm - call assert_equal(winnr(), winnr('$')) - call assert_equal(winlayout(), ['col', [['leaf', 1002], ['leaf', 1001], ['leaf', 1000], ['leaf', 1003 + cn]]]) + call assert_equal(winnr('$'), winnr()) + call assert_equal(['col', [['leaf', 1002], ['leaf', 1001], ['leaf', 1000], ['leaf', 1003 + cn]]], winlayout()) let cn += 1 bw! endif @@ -139,16 +139,16 @@ func Test_termdebug_basic() let winw = winwidth(0) Var if winwidth(0) < winw - call assert_equal(winnr(), winnr('$') - 1) - call assert_equal(winlayout(), ['col', [['leaf', 1002], ['leaf', 1001], ['row', [['leaf', 1003 + cn], ['leaf', 1000]]]]]) + call assert_equal(winnr('$') - 1, winnr()) + call assert_equal(['col', [['leaf', 1002], ['leaf', 1001], ['row', [['leaf', 1003 + cn], ['leaf', 1000]]]]], winlayout()) let cn += 1 bw! endif let winw = winwidth(0) Asm if winwidth(0) < winw - call assert_equal(winnr(), winnr('$') - 1) - call assert_equal(winlayout(), ['col', [['leaf', 1002], ['leaf', 1001], ['row', [['leaf', 1003 + cn], ['leaf', 1000]]]]]) + call assert_equal(winnr('$') - 1, winnr()) + call assert_equal(['col', [['leaf', 1002], ['leaf', 1001], ['row', [['leaf', 1003 + cn], ['leaf', 1000]]]]], winlayout()) let cn += 1 bw! endif @@ -161,6 +161,18 @@ func Test_termdebug_basic() call WaitForAssert({-> assert_equal(1, winnr('$'))}) call assert_equal([], sign_getplaced('', #{group: 'TermDebug'})[0].signs) + for use_prompt in [0, 1] + let g:termdebug_config = {} + let g:termdebug_config['use_prompt'] = use_prompt + TermdebugCommand ./XTD_basic arg args + call WaitForAssert({-> assert_equal(3, winnr('$'))}) + wincmd t + quit! + redraw! + call WaitForAssert({-> assert_equal(1, winnr('$'))}) + unlet g:termdebug_config + endfor + call s:cleanup_files(bin_name) %bw! endfunc @@ -280,9 +292,20 @@ func Test_termdebug_mapping() call assert_equal(':echom "K"<cr>', maparg('K', 'n', 0, 1).rhs) %bw! + + " -- Test that local-buffer mappings are restored in the correct buffers -- + " local mappings for foo + file foo nnoremap <buffer> K :echom "bK"<cr> nnoremap <buffer> - :echom "b-"<cr> nnoremap <buffer> + :echom "b+"<cr> + + " no mappings for 'bar' + enew + file bar + + " Start termdebug from foo + buffer foo Termdebug call WaitForAssert({-> assert_equal(3, winnr('$'))}) wincmd b @@ -290,15 +313,41 @@ func Test_termdebug_mapping() call assert_true(maparg('-', 'n', 0, 1).buffer) call assert_true(maparg('+', 'n', 0, 1).buffer) call assert_equal(maparg('K', 'n', 0, 1).rhs, ':echom "bK"<cr>') + + Source + buffer bar + call assert_false(maparg('K', 'n', 0, 1)->empty()) + call assert_false(maparg('-', 'n', 0, 1)->empty()) + call assert_false(maparg('+', 'n', 0, 1)->empty()) + call assert_true(maparg('K', 'n', 0, 1).buffer->empty()) + call assert_true(maparg('-', 'n', 0, 1).buffer->empty()) + call assert_true(maparg('+', 'n', 0, 1).buffer->empty()) wincmd t quit! redraw! call WaitForAssert({-> assert_equal(1, winnr('$'))}) + + " Termdebug session ended. Buffer 'bar' shall have no mappings + call assert_true(bufname() ==# 'bar') + call assert_false(maparg('K', 'n', 0, 1)->empty()) + call assert_false(maparg('-', 'n', 0, 1)->empty()) + call assert_false(maparg('+', 'n', 0, 1)->empty()) + call assert_true(maparg('K', 'n', 0, 1).buffer->empty()) + call assert_true(maparg('-', 'n', 0, 1).buffer->empty()) + call assert_true(maparg('+', 'n', 0, 1).buffer->empty()) + + " Buffer 'foo' shall have the same mapping as before running the termdebug + " session + buffer foo + call assert_true(bufname() ==# 'foo') call assert_true(maparg('K', 'n', 0, 1).buffer) call assert_true(maparg('-', 'n', 0, 1).buffer) call assert_true(maparg('+', 'n', 0, 1).buffer) call assert_equal(':echom "bK"<cr>', maparg('K', 'n', 0, 1).rhs) + nunmap K + nunmap + + nunmap - %bw! endfunc @@ -340,5 +389,43 @@ func Test_termdebug_bufnames() unlet g:termdebug_config endfunc +function Test_termdebug_save_restore_variables() + " saved mousemodel + let &mousemodel='' + + " saved keys + nnoremap K :echo "hello world!"<cr> + let expected_map_K = maparg('K', 'n', 0 , 1) + nnoremap + :echo "hello plus!"<cr> + let expected_map_plus = maparg('+', 'n', 0 , 1) + let expected_map_minus = {} + + " saved &columns + let expected_columns = &columns + + " We want termdebug to overwrite 'K' map but not '+' map. + let g:termdebug_config = {} + let g:termdebug_config['map_K'] = 1 + + Termdebug + call WaitForAssert({-> assert_equal(3, winnr('$'))}) + call WaitForAssert({-> assert_match(&mousemodel, 'popup_setpos')}) + wincmd t + quit! + call WaitForAssert({-> assert_equal(1, winnr('$'))}) + + call assert_true(empty(&mousemodel)) + + call assert_true(empty(expected_map_minus)) + call assert_equal(expected_map_K.rhs, maparg('K', 'n', 0, 1).rhs) + call assert_equal(expected_map_plus.rhs, maparg('+', 'n', 0, 1).rhs) + + call assert_equal(expected_columns, &columns) + + nunmap K + nunmap + + unlet g:termdebug_config +endfunction + " vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/test_textobjects.vim b/test/old/testdir/test_textobjects.vim index 3b86ae97da..0f41f0a241 100644 --- a/test/old/testdir/test_textobjects.vim +++ b/test/old/testdir/test_textobjects.vim @@ -203,6 +203,18 @@ func Test_string_html_objects() normal! 2k0vaty call assert_equal("<div><div\nattr=\"attr\"\n></div></div>", @", e) + " tag, that includes a > in some attribute + let t = "<div attr=\"attr >> foo >> bar \">Hello</div>" + $put =t + normal! fHyit + call assert_equal("Hello", @", e) + + " tag, that includes a > in some attribute + let t = "<div attr='attr >> foo >> bar '>Hello 123</div>" + $put =t + normal! fHyit + call assert_equal("Hello 123", @", e) + set quoteescape& " this was going beyond the end of the line @@ -232,7 +244,7 @@ func Test_empty_html_tag() normal 0f<vitsaaa call assert_equal('aaa', getline(1)) - " selecting a tag block in an non-empty blank line should fail + " selecting a tag block in a non-empty blank line should fail call setline(1, ' ') call assert_beeps('normal $vaty') diff --git a/test/old/testdir/test_timers.vim b/test/old/testdir/test_timers.vim index 42114618fb..f412afc03d 100644 --- a/test/old/testdir/test_timers.vim +++ b/test/old/testdir/test_timers.vim @@ -348,8 +348,15 @@ endfunc " Test that the garbage collector isn't triggered if a timer callback invokes " vgetc(). func Test_nocatch_timer_garbage_collect() - " skipped: Nvim does not support test_garbagecollect_soon(), test_override() - return + " FIXME: why does this fail only on MacOS M1? + try + CheckNotMacM1 + throw 'Skipped: Nvim does not support test_garbagecollect_soon(), test_override()' + catch /Skipped/ + let g:skipped_reason = v:exception + return + endtry + " 'uptimetime. must be bigger than the timer timeout set ut=200 call test_garbagecollect_soon() diff --git a/test/old/testdir/test_true_false.vim b/test/old/testdir/test_true_false.vim index f3c7fff4a6..976a40e153 100644 --- a/test/old/testdir/test_true_false.vim +++ b/test/old/testdir/test_true_false.vim @@ -49,11 +49,11 @@ func Test_if() endfunc function Try_arg_true_false(expr, false_val, true_val) - for v in ['v:false', '0', '"0"', '"foo"', '" "'] + for v in ['v:false', '0', '"0"', '"foo"', '" "'] let r = eval(substitute(a:expr, '%v%', v, '')) call assert_equal(a:false_val, r, 'result for ' . v . ' is not ' . string(a:false_val) . ' but ' . string(r)) endfor - for v in ['v:true', '1', '"1"', '"1foo"'] + for v in ['v:true', '1', '"1"', '"1foo"'] let r = eval(substitute(a:expr, '%v%', v, '')) call assert_equal(a:true_val, r, 'result for ' . v . ' is not ' . string(a:true_val) . ' but ' . string(r)) endfor @@ -117,12 +117,11 @@ func Test_true_false_arg() endfunc function Try_arg_non_zero(expr, false_val, true_val) - CheckFeature float - for v in ['v:false', '0', '[1]', '{2:3}', '3.4'] + for v in ['v:false', '0', '[1]', '{2:3}', '3.4'] let r = eval(substitute(a:expr, '%v%', v, '')) call assert_equal(a:false_val, r, 'result for ' . v . ' is not ' . a:false_val . ' but ' . r) endfor - for v in ['v:true', '1', '" "', '"0"'] + for v in ['v:true', '1', '" "', '"0"'] let r = eval(substitute(a:expr, '%v%', v, '')) call assert_equal(a:true_val, r, 'result for ' . v . ' is not ' . a:true_val . ' but ' . r) endfor @@ -138,14 +137,14 @@ func Test_non_zero_arg() call Try_arg_non_zero("shellescape('foo%', %v%)", "'foo%'", "'foo\\%'") " visualmode() needs to be called twice to check - for v in [v:false, 0, [1], {2:3}, 3.4] + for v in [v:false, 0, [1], {2:3}, 3.4] normal vv let r = visualmode(v) call assert_equal('v', r, 'result for ' . string(v) . ' is not "v" but ' . r) let r = visualmode(v) call assert_equal('v', r, 'result for ' . string(v) . ' is not "v" but ' . r) endfor - for v in [v:true, 1, " ", "0"] + for v in [v:true, 1, " ", "0"] normal vv let r = visualmode(v) call assert_equal('v', r, 'result for ' . v . ' is not "v" but ' . r) diff --git a/test/old/testdir/test_trycatch.vim b/test/old/testdir/test_trycatch.vim index d60b793f1b..f2142f4210 100644 --- a/test/old/testdir/test_trycatch.vim +++ b/test/old/testdir/test_trycatch.vim @@ -50,7 +50,7 @@ func T25_F() Xpath 'i' endfunc -" Also try using "fina" and "final" and "finall" as abbraviations. +" Also try using "fina" and "final" and "finall" as abbreviations. func T25_G() if 1 try diff --git a/test/old/testdir/test_utf8.vim b/test/old/testdir/test_utf8.vim index 51ac47f082..5cac4066ea 100644 --- a/test/old/testdir/test_utf8.vim +++ b/test/old/testdir/test_utf8.vim @@ -1,5 +1,5 @@ " Tests for Unicode manipulations - + source check.vim source view_util.vim source screendump.vim @@ -112,7 +112,7 @@ func Test_list2str_str2list_latin1() let save_encoding = &encoding " set encoding=latin1 - + let lres = str2list(s, 1) let sres = list2str(l, 1) call assert_equal([65, 66, 67], str2list("ABC")) @@ -228,6 +228,9 @@ func Test_setcellwidths() call setcellwidths([[0x2103, 0x2103, 2]]) redraw call assert_equal(19, wincol()) + call setcellwidths([]) + redraw + call assert_equal((aw == 'single') ? 10 : 19, wincol()) endfor set ambiwidth& isprint& @@ -252,15 +255,21 @@ func Test_setcellwidths() call assert_fails('call setcellwidths([[0x33, 0x44, 2]])', 'E1114:') - set listchars=tab:--\\u2192 + set listchars=tab:--\\u2192 fillchars=stl:\\u2501 call assert_fails('call setcellwidths([[0x2192, 0x2192, 2]])', 'E834:') - - set fillchars=stl:\\u2501 call assert_fails('call setcellwidths([[0x2501, 0x2501, 2]])', 'E835:') + call setcellwidths([[0x201c, 0x201d, 1]]) + set listchars& fillchars& ambiwidth=double + + set listchars=nbsp:\\u201c fillchars=vert:\\u201d + call assert_fails('call setcellwidths([])', 'E834:') set listchars& + call assert_fails('call setcellwidths([])', 'E835:') set fillchars& + call setcellwidths([]) + set ambiwidth& bwipe! endfunc diff --git a/test/old/testdir/test_vartabs.vim b/test/old/testdir/test_vartabs.vim index e12c71d521..82c3a513f9 100644 --- a/test/old/testdir/test_vartabs.vim +++ b/test/old/testdir/test_vartabs.vim @@ -445,4 +445,77 @@ func Test_shiftwidth_vartabstop() setlocal shiftwidth& vartabstop& tabstop& endfunc +func Test_vartabstop_latin1() + throw "Skipped: Nvim does not support 'compatible'" + let save_encoding = &encoding + new + set encoding=iso8859-1 + set compatible linebreak list revins smarttab + set vartabstop=400 + exe "norm i00\t\<C-D>" + bwipe! + let &encoding = save_encoding + set nocompatible linebreak& list& revins& smarttab& vartabstop& +endfunc + +" Verify that right-shifting and left-shifting adjust lines to the proper +" tabstops. +func Test_vartabstop_shift_right_left() + new + set expandtab + set shiftwidth=0 + set vartabstop=17,11,7 + exe "norm! aword" + let expect = "word" + call assert_equal(expect, getline(1)) + + " Shift to first tabstop. + norm! >> + let expect = " word" + call assert_equal(expect, getline(1)) + + " Shift to second tabstop. + norm! >> + let expect = " word" + call assert_equal(expect, getline(1)) + + " Shift to third tabstop. + norm! >> + let expect = " word" + call assert_equal(expect, getline(1)) + + " Shift to fourth tabstop, repeating the third shift width. + norm! >> + let expect = " word" + call assert_equal(expect, getline(1)) + + " Shift back to the third tabstop. + norm! << + let expect = " word" + call assert_equal(expect, getline(1)) + + " Shift back to the second tabstop. + norm! << + let expect = " word" + call assert_equal(expect, getline(1)) + + " Shift back to the first tabstop. + norm! << + let expect = " word" + call assert_equal(expect, getline(1)) + + " Shift back to the left margin. + norm! << + let expect = "word" + call assert_equal(expect, getline(1)) + + " Shift again back to the left margin. + norm! << + let expect = "word" + call assert_equal(expect, getline(1)) + + bwipeout! +endfunc + + " vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/test_vimscript.vim b/test/old/testdir/test_vimscript.vim index 6ce59e1a2e..7f29c7f651 100644 --- a/test/old/testdir/test_vimscript.vim +++ b/test/old/testdir/test_vimscript.vim @@ -6502,16 +6502,22 @@ func Test_type() call assert_equal(2, type(function("tr", [8]))) call assert_equal(3, type([])) call assert_equal(4, type({})) - call assert_equal(5, type(0.0)) + if has('float') + call assert_equal(5, type(0.0)) + endif call assert_equal(6, type(v:false)) call assert_equal(6, type(v:true)) + " call assert_equal(7, type(v:none)) call assert_equal(7, type(v:null)) call assert_equal(v:t_number, type(0)) call assert_equal(v:t_string, type("")) call assert_equal(v:t_func, type(function("tr"))) + call assert_equal(v:t_func, type(function("tr", [8]))) call assert_equal(v:t_list, type([])) call assert_equal(v:t_dict, type({})) - call assert_equal(v:t_float, type(0.0)) + if has('float') + call assert_equal(v:t_float, type(0.0)) + endif call assert_equal(v:t_bool, type(v:false)) call assert_equal(v:t_bool, type(v:true)) " call assert_equal(v:t_none, type(v:none)) @@ -6829,10 +6835,12 @@ func Test_bitwise_functions() call assert_equal(16, and(127, 16)) eval 127->and(16)->assert_equal(16) call assert_equal(0, and(127, 128)) - call assert_fails("call and(1.0, 1)", 'E805:') call assert_fails("call and([], 1)", 'E745:') call assert_fails("call and({}, 1)", 'E728:') - call assert_fails("call and(1, 1.0)", 'E805:') + if has('float') + call assert_fails("call and(1.0, 1)", 'E805:') + call assert_fails("call and(1, 1.0)", 'E805:') + endif call assert_fails("call and(1, [])", 'E745:') call assert_fails("call and(1, {})", 'E728:') " or @@ -6840,10 +6848,12 @@ func Test_bitwise_functions() call assert_equal(15, or(8, 7)) eval 8->or(7)->assert_equal(15) call assert_equal(123, or(0, 123)) - call assert_fails("call or(1.0, 1)", 'E805:') call assert_fails("call or([], 1)", 'E745:') call assert_fails("call or({}, 1)", 'E728:') - call assert_fails("call or(1, 1.0)", 'E805:') + if has('float') + call assert_fails("call or(1.0, 1)", 'E805:') + call assert_fails("call or(1, 1.0)", 'E805:') + endif call assert_fails("call or(1, [])", 'E745:') call assert_fails("call or(1, {})", 'E728:') " xor @@ -6851,10 +6861,12 @@ func Test_bitwise_functions() call assert_equal(111, xor(127, 16)) eval 127->xor(16)->assert_equal(111) call assert_equal(255, xor(127, 128)) - call assert_fails("call xor(1.0, 1)", 'E805:') + if has('float') + call assert_fails("call xor(1.0, 1)", 'E805:') + call assert_fails("call xor(1, 1.0)", 'E805:') + endif call assert_fails("call xor([], 1)", 'E745:') call assert_fails("call xor({}, 1)", 'E728:') - call assert_fails("call xor(1, 1.0)", 'E805:') call assert_fails("call xor(1, [])", 'E745:') call assert_fails("call xor(1, {})", 'E728:') " invert @@ -6862,7 +6874,9 @@ func Test_bitwise_functions() eval 127->invert()->and(65535)->assert_equal(65408) call assert_equal(65519, and(invert(16), 65535)) call assert_equal(65407, and(invert(128), 65535)) - call assert_fails("call invert(1.0)", 'E805:') + if has('float') + call assert_fails("call invert(1.0)", 'E805:') + endif call assert_fails("call invert([])", 'E745:') call assert_fails("call invert({})", 'E728:') endfunc @@ -7435,6 +7449,57 @@ func Test_for_over_string() let res ..= c .. '-' endfor call assert_equal('', res) + + " Test for using "_" as the loop variable + let i = 0 + let s = 'abc' + for _ in s + call assert_equal(s[i], _) + let i += 1 + endfor +endfunc + +" Test for deeply nested :source command {{{1 +func Test_deeply_nested_source() + throw 'Skipped: Vim9 script is N/A' + let lines =<< trim END + + so + sil 0scr + delete + so + 0 + END + call writefile(["vim9 silent! @0 \n/"] + lines, 'Xnested.vim', 'D') + + " this must not crash + let cmd = GetVimCommand() .. " -e -s -S Xnested.vim -c qa!" + call system(cmd) +endfunc + +func Test_exception_silent() + XpathINIT + let lines =<< trim END + func Throw() + Xpath 'a' + throw "Uncaught" + " This line is not executed. + Xpath 'b' + endfunc + " The exception is suppressed due to the presence of silent!. + silent! call Throw() + try + call DoesNotExist() + catch /E117:/ + Xpath 'c' + endtry + Xpath 'd' + END + let verify =<< trim END + call assert_equal('acd', g:Xpath) + END + + call RunInNewVim(lines, verify) endfunc "------------------------------------------------------------------------------- diff --git a/test/old/testdir/test_virtualedit.vim b/test/old/testdir/test_virtualedit.vim index 8d9656e058..900dcd41cc 100644 --- a/test/old/testdir/test_virtualedit.vim +++ b/test/old/testdir/test_virtualedit.vim @@ -696,14 +696,14 @@ func Test_virtualedit_mouse() set virtualedit& endfunc -" this was replacing the NUL at the end of the line +" this was replacing the NUL at the end of the line func Test_virtualedit_replace_after_tab() new s/\v/ 0 set ve=all let @" = '' sil! norm vPvr0 - + call assert_equal("\t0", getline(1)) set ve& bwipe! diff --git a/test/old/testdir/test_visual.vim b/test/old/testdir/test_visual.vim index b7b5f611c4..e25327ddd4 100644 --- a/test/old/testdir/test_visual.vim +++ b/test/old/testdir/test_visual.vim @@ -1170,8 +1170,8 @@ endfunc func Test_visual_put_in_block_using_zp() new " paste using zP - call setline(1, ['/path;text', '/path;text', '/path;text', '', - \ '/subdir', + call setline(1, ['/path;text', '/path;text', '/path;text', '', + \ '/subdir', \ '/longsubdir', \ '/longlongsubdir']) exe "normal! 5G\<c-v>2j$y" @@ -1179,8 +1179,8 @@ func Test_visual_put_in_block_using_zp() call assert_equal(['/path/subdir;text', '/path/longsubdir;text', '/path/longlongsubdir;text'], getline(1, 3)) %d " paste using zP - call setline(1, ['/path;text', '/path;text', '/path;text', '', - \ '/subdir', + call setline(1, ['/path;text', '/path;text', '/path;text', '', + \ '/subdir', \ '/longsubdir', \ '/longlongsubdir']) exe "normal! 5G\<c-v>2j$y" @@ -1193,7 +1193,7 @@ func Test_visual_put_in_block_using_zy_and_zp() new " Test 1) Paste using zp - after the cursor without trailing spaces - call setline(1, ['/path;text', '/path;text', '/path;text', '', + call setline(1, ['/path;text', '/path;text', '/path;text', '', \ 'texttext /subdir columntext', \ 'texttext /longsubdir columntext', \ 'texttext /longlongsubdir columntext']) @@ -1203,7 +1203,7 @@ func Test_visual_put_in_block_using_zy_and_zp() " Test 2) Paste using zP - in front of the cursor without trailing spaces %d - call setline(1, ['/path;text', '/path;text', '/path;text', '', + call setline(1, ['/path;text', '/path;text', '/path;text', '', \ 'texttext /subdir columntext', \ 'texttext /longsubdir columntext', \ 'texttext /longlongsubdir columntext']) @@ -1213,7 +1213,7 @@ func Test_visual_put_in_block_using_zy_and_zp() " Test 3) Paste using p - with trailing spaces %d - call setline(1, ['/path;text', '/path;text', '/path;text', '', + call setline(1, ['/path;text', '/path;text', '/path;text', '', \ 'texttext /subdir columntext', \ 'texttext /longsubdir columntext', \ 'texttext /longlongsubdir columntext']) @@ -1223,7 +1223,7 @@ func Test_visual_put_in_block_using_zy_and_zp() " Test 4) Paste using P - with trailing spaces %d - call setline(1, ['/path;text', '/path;text', '/path;text', '', + call setline(1, ['/path;text', '/path;text', '/path;text', '', \ 'texttext /subdir columntext', \ 'texttext /longsubdir columntext', \ 'texttext /longlongsubdir columntext']) @@ -1233,7 +1233,7 @@ func Test_visual_put_in_block_using_zy_and_zp() " Test 5) Yank with spaces inside the block %d - call setline(1, ['/path;text', '/path;text', '/path;text', '', + call setline(1, ['/path;text', '/path;text', '/path;text', '', \ 'texttext /sub dir/ columntext', \ 'texttext /lon gsubdir/ columntext', \ 'texttext /lon glongsubdir/ columntext']) @@ -1968,6 +1968,14 @@ func Test_visual_getregion() #" using invalid value for "type" call assert_fails("call getregion(getpos('.'), getpos('.'), {'type': '' })", 'E475:') call assert_fails("call getregionpos(getpos('.'), getpos('.'), {'type': '' })", 'E475:') + call assert_fails("call getregion(getpos('.'), getpos('.'), {'type': 'v0' })", 'E475:') + call assert_fails("call getregionpos(getpos('.'), getpos('.'), {'type': 'v0' })", 'E475:') + call assert_fails("call getregion(getpos('.'), getpos('.'), {'type': 'V0' })", 'E475:') + call assert_fails("call getregionpos(getpos('.'), getpos('.'), {'type': 'V0' })", 'E475:') + call assert_fails("call getregion(getpos('.'), getpos('.'), {'type': '\<C-v>0' })", 'E475:') + call assert_fails("call getregionpos(getpos('.'), getpos('.'), {'type': '\<C-v>0' })", 'E475:') + call assert_fails("call getregion(getpos('.'), getpos('.'), {'type': '\<C-v>1:' })", 'E475:') + call assert_fails("call getregionpos(getpos('.'), getpos('.'), {'type': '\<C-v>1:' })", 'E475:') #" using a mark from another buffer to current buffer new @@ -2069,10 +2077,12 @@ func Test_visual_getregion() \ getregion(getpos('v'), getpos('.'), {'type': "\<C-v>" })) call assert_equal([ \ [[bufnr('%'), 1, 5, 0], [bufnr('%'), 1, 5, 0]], - \ [[bufnr('%'), 2, 10, 1], [bufnr('%'), 2, 10, 2]], + \ [[bufnr('%'), 2, 7, 1], [bufnr('%'), 2, 7, 2]], \ [[bufnr('%'), 3, 5, 0], [bufnr('%'), 3, 5, 0]], \ ], \ getregionpos(getpos('v'), getpos('.'), {'type': "\<C-v>" })) + call assert_equal(['efghijk«', '🇦«🇧«🇨«🇩', '12345'], + \ getregion(getpos('v'), getpos('.'), {'type': 'v' })) call assert_equal([ \ [[bufnr('%'), 1, 5, 0], [bufnr('%'), 1, 13, 0]], \ [[bufnr('%'), 2, 1, 0], [bufnr('%'), 2, 22, 0]], @@ -2080,6 +2090,28 @@ func Test_visual_getregion() \ ], \ getregionpos(getpos('v'), getpos('.'), {'type': 'v' })) + call cursor(1, 5) + call feedkeys("\<Esc>\<C-v>5l2j", 'xt') + call assert_equal(['efghij', ' «🇨« ', '567890'], + \ getregion(getpos('v'), getpos('.'), {'type': "\<C-v>" })) + call assert_equal([ + \ [[bufnr('%'), 1, 5, 0], [bufnr('%'), 1, 10, 0]], + \ [[bufnr('%'), 2, 7, 1], [bufnr('%'), 2, 19, 1]], + \ [[bufnr('%'), 3, 5, 0], [bufnr('%'), 3, 10, 0]], + \ ], + \ getregionpos(getpos('v'), getpos('.'), {'type': "\<C-v>" })) + + call cursor(1, 4) + call feedkeys("\<Esc>\<C-v>02j", 'xt') + call assert_equal(['abcd', '🇦« ', '1234'], + \ getregion(getpos('v'), getpos('.'), {'type': "\<C-v>" })) + call assert_equal([ + \ [[bufnr('%'), 1, 1, 0], [bufnr('%'), 1, 4, 0]], + \ [[bufnr('%'), 2, 1, 0], [bufnr('%'), 2, 7, 1]], + \ [[bufnr('%'), 3, 1, 0], [bufnr('%'), 3, 4, 0]], + \ ], + \ getregionpos(getpos('v'), getpos('.'), {'type': "\<C-v>" })) + #" characterwise selection with multibyte chars call cursor(1, 1) call feedkeys("\<Esc>vj", 'xt') @@ -2546,30 +2578,127 @@ func Test_getregion_invalid_buf() bwipe! endfunc -func Test_getregion_maxcol() - new +func Test_getregion_after_yank() + func! Check_Results(type) + call assert_equal(g:expected_region, + \ getregion(getpos("'["), getpos("']"), #{ type: a:type })) + call assert_equal(g:expected_regionpos, + \ getregionpos(getpos("'["), getpos("']"), #{ type: a:type })) + call assert_equal(g:expected_region, + \ getregion(getpos("']"), getpos("'["), #{ type: a:type })) + call assert_equal(g:expected_regionpos, + \ getregionpos(getpos("']"), getpos("'["), #{ type: a:type })) + let g:checked = 1 + endfunc + autocmd TextYankPost * \ : if v:event.operator ==? 'y' - \ | call assert_equal([ - \ [[bufnr('%'), 1, 1, 0], [bufnr('%'), 1, 4, 0]], - \ ], - \ getregionpos(getpos("'["), getpos("']"), - \ #{ mode: visualmode() })) - \ | call assert_equal(['abcd'], - \ getregion(getpos("'["), getpos("']"), - \ #{ mode: visualmode() })) - \ | call assert_equal([ - \ [[bufnr('%'), 1, 1, 0], [bufnr('%'), 1, 4, 0]], - \ ], - \ getregionpos(getpos("']"), getpos("'["), - \ #{ mode: visualmode() })) - \ | call assert_equal(['abcd'], - \ getregion(getpos("']"), getpos("'["), - \ #{ mode: visualmode() })) + \ | call Check_Results(v:event.regtype) \ | endif - call setline(1, ['abcd', 'efghij']) + + new + call setline(1, ['abcd', 'efghijk', 'lmn']) + + let g:expected_region = ['abcd'] + let g:expected_regionpos = [ + \ [[bufnr('%'), 1, 1, 0], [bufnr('%'), 1, 4, 0]], + \ ] + let g:checked = 0 normal yy + call assert_equal(1, g:checked) + call Check_Results(getregtype('"')) + + let g:expected_region = ['cd', 'ghijk', 'n'] + let g:expected_regionpos = [ + \ [[bufnr('%'), 1, 3, 0], [bufnr('%'), 1, 4, 0]], + \ [[bufnr('%'), 2, 3, 0], [bufnr('%'), 2, 7, 0]], + \ [[bufnr('%'), 3, 3, 0], [bufnr('%'), 3, 3, 0]], + \ ] + let g:checked = 0 + call feedkeys("gg0ll\<C-V>jj$y", 'tx') + call assert_equal(1, g:checked) + call Check_Results(getregtype('"')) + call assert_equal(g:expected_region, getreg('"', v:true, v:true)) + + let g:expected_region = ['bc', 'fg', 'mn'] + let g:expected_regionpos = [ + \ [[bufnr('%'), 1, 2, 0], [bufnr('%'), 1, 3, 0]], + \ [[bufnr('%'), 2, 2, 0], [bufnr('%'), 2, 3, 0]], + \ [[bufnr('%'), 3, 2, 0], [bufnr('%'), 3, 3, 0]], + \ ] + let g:checked = 0 + call feedkeys("gg0l\<C-V>jjly", 'tx') + call assert_equal(1, g:checked) + call Check_Results(getregtype('"')) + call assert_equal(g:expected_region, getreg('"', v:true, v:true)) + + bwipe! + + new + let lines = ['asdfghjkl', '«口=口»', 'qwertyuiop', '口口=口口', 'zxcvbnm'] + call setline(1, lines) + + let g:expected_region = lines + let g:expected_regionpos = [ + \ [[bufnr('%'), 1, 1, 0], [bufnr('%'), 1, 9, 0]], + \ [[bufnr('%'), 2, 1, 0], [bufnr('%'), 2, 11, 0]], + \ [[bufnr('%'), 3, 1, 0], [bufnr('%'), 3, 10, 0]], + \ [[bufnr('%'), 4, 1, 0], [bufnr('%'), 4, 13, 0]], + \ [[bufnr('%'), 5, 1, 0], [bufnr('%'), 5, 7, 0]], + \ ] + let g:checked = 0 + call feedkeys('ggyG', 'tx') + call assert_equal(1, g:checked) + call Check_Results(getregtype('"')) + call assert_equal(g:expected_region, getreg('"', v:true, v:true)) + + let g:expected_region = ['=口»', 'qwertyuiop', '口口=口'] + let g:expected_regionpos = [ + \ [[bufnr('%'), 2, 6, 0], [bufnr('%'), 2, 11, 0]], + \ [[bufnr('%'), 3, 1, 0], [bufnr('%'), 3, 10, 0]], + \ [[bufnr('%'), 4, 1, 0], [bufnr('%'), 4, 10, 0]], + \ ] + let g:checked = 0 + call feedkeys('2gg02lv2j2ly', 'tx') + call assert_equal(1, g:checked) + call Check_Results(getregtype('"')) + call assert_equal(g:expected_region, getreg('"', v:true, v:true)) + + let g:expected_region = ['asdf', '«口=', 'qwer', '口口', 'zxcv'] + let g:expected_regionpos = [ + \ [[bufnr('%'), 1, 1, 0], [bufnr('%'), 1, 4, 0]], + \ [[bufnr('%'), 2, 1, 0], [bufnr('%'), 2, 6, 0]], + \ [[bufnr('%'), 3, 1, 0], [bufnr('%'), 3, 4, 0]], + \ [[bufnr('%'), 4, 1, 0], [bufnr('%'), 4, 6, 0]], + \ [[bufnr('%'), 5, 1, 0], [bufnr('%'), 5, 4, 0]], + \ ] + let g:checked = 0 + call feedkeys("G0\<C-V>3l4ky", 'tx') + call assert_equal(1, g:checked) + call Check_Results(getregtype('"')) + call assert_equal(g:expected_region, getreg('"', v:true, v:true)) + + let g:expected_region = ['ghjkl', '口»', 'tyuiop', '=口口', 'bnm'] + let g:expected_regionpos = [ + \ [[bufnr('%'), 1, 5, 0], [bufnr('%'), 1, 9, 0]], + \ [[bufnr('%'), 2, 7, 0], [bufnr('%'), 2, 11, 0]], + \ [[bufnr('%'), 3, 5, 0], [bufnr('%'), 3, 10, 0]], + \ [[bufnr('%'), 4, 7, 0], [bufnr('%'), 4, 13, 0]], + \ [[bufnr('%'), 5, 5, 0], [bufnr('%'), 5, 7, 0]], + \ ] + let g:checked = 0 + call feedkeys("G04l\<C-V>$4ky", 'tx') + call assert_equal(1, g:checked) + call Check_Results(getregtype('"')) + call assert_equal(g:expected_region, getreg('"', v:true, v:true)) + bwipe! + + unlet g:expected_region + unlet g:expected_regionpos + unlet g:checked + autocmd! TextYankPost + delfunc Check_Results endfunc func Test_visual_block_cursor_delete() @@ -2580,4 +2709,13 @@ func Test_visual_block_cursor_delete() bwipe! endfunc +func Test_visual_block_cursor_insert_enter() + new + call setline(1, ['asdf asdf', 'asdf asdf', 'asdf asdf', 'asdf asdf']) + call cursor(1, 5) + exe ":norm! \<c-v>3jcw\<cr>" + call assert_equal(['asdfw', 'asdf', 'asdfasdf', 'asdfasdf', 'asdfasdf'], getline(1, '$')) + bwipe! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/test_winbuf_close.vim b/test/old/testdir/test_winbuf_close.vim index 26b4ba8778..d2fa6f66bb 100644 --- a/test/old/testdir/test_winbuf_close.vim +++ b/test/old/testdir/test_winbuf_close.vim @@ -223,7 +223,7 @@ func Test_window_close_splitright_noequalalways() execute "normal \<c-w>b" let h = winheight(0) let w = win_getid() - new + new q call assert_equal(h, winheight(0), "Window height does not match eight before opening and closing another window") call assert_equal(w, win_getid(), "Did not return to original window after opening and closing a window") diff --git a/test/old/testdir/test_winfixbuf.vim b/test/old/testdir/test_winfixbuf.vim index b41eaf3c9b..1777bec184 100644 --- a/test/old/testdir/test_winfixbuf.vim +++ b/test/old/testdir/test_winfixbuf.vim @@ -2934,6 +2934,7 @@ func Test_tfirst() \ "Xtags", 'D') call writefile(["one", "two", "three"], "Xfile", 'D') call writefile(["one"], "Xother", 'D') + tag one edit Xother set winfixbuf diff --git a/test/old/testdir/test_zip_plugin.vim b/test/old/testdir/test_zip_plugin.vim new file mode 100644 index 0000000000..a817d8371e --- /dev/null +++ b/test/old/testdir/test_zip_plugin.vim @@ -0,0 +1,237 @@ +so check.vim + +CheckExecutable unzip + +if 0 " Find uncovered line + profile start zip_profile + profile! file */zip*.vim +endif + +runtime plugin/zipPlugin.vim + +func Test_zip_basic() + + "## get our zip file + if !filecopy("samples/test.zip", "X.zip") + call assert_report("Can't copy samples/test.zip") + return + endif + defer delete("X.zip") + + e X.zip + + "## Check header + call assert_match('^" zip\.vim version v\d\+', getline(1)) + call assert_match('^" Browsing zipfile .*/X.zip', getline(2)) + call assert_match('^" Select a file with cursor and press ENTER', getline(3)) + call assert_match('^$', getline(4)) + + "## Check files listing + call assert_equal(["Xzip/", "Xzip/dir/", "Xzip/file.txt"], getline(5, 7)) + + "## Check ENTER on header + :1 + exe ":normal \<cr>" + call assert_equal("X.zip", @%) + + "## Check ENTER on directory + :1|:/^$//dir/ + call assert_match('Please specify a file, not a directory', + \ execute("normal \<CR>")) + + "## Check ENTER on file + :1 + call search('file.txt') + exe ":normal \<cr>" + call assert_match('zipfile://.*/X.zip::Xzip/file.txt', @%) + call assert_equal('one', getline(1)) + + "## Check editing file + if executable("zip") + s/one/two/ + call assert_equal("two", getline(1)) + w + bw|bw + e X.zip + + :1|:/^$//file/ + exe "normal \<cr>" + call assert_equal("two", getline(1)) + endif + + only + e X.zip + + "## Check extracting file + :1|:/^$//file/ + normal x + call assert_true(filereadable("Xzip/file.txt")) + + "## Check not overwriting existing file + call assert_match('<Xzip/file.txt> .* not overwriting!', execute("normal x")) + + call delete("Xzip", "rf") + + "## Check extracting directory + :1|:/^$//dir/ + call assert_match('Please specify a file, not a directory', execute("normal x")) + call assert_equal("X.zip", @%) + + "## Check "x" on header + :1 + normal x + call assert_equal("X.zip", @%) + bw + + "## Check opening zip when "unzip" program is missing + let save_zip_unzipcmd = g:zip_unzipcmd + let g:zip_unzipcmd = "/" + call assert_match('unzip not available on your system', execute("e X.zip")) + + "## Check when "unzip" don't work + if executable("false") + let g:zip_unzipcmd = "false" + call assert_match('X\.zip is not a zip file', execute("e X.zip")) + endif + bw + + let g:zip_unzipcmd = save_zip_unzipcmd + e X.zip + + "## Check opening file when "unzip" is missing + let g:zip_unzipcmd = "/" + call assert_match('sorry, your system doesn''t appear to have the / program', + \ execute("normal \<CR>")) + + bw|bw + let g:zip_unzipcmd = save_zip_unzipcmd + e X.zip + + "## Check :write when "zip" program is missing + :1|:/^$//file/ + exe "normal \<cr>Goanother\<esc>" + let save_zip_zipcmd = g:zip_zipcmd + let g:zip_zipcmd = "/" + call assert_match('sorry, your system doesn''t appear to have the / program', + \ execute("write")) + + "## Check when "zip" report failure + if executable("false") + let g:zip_zipcmd = "false" + call assert_match('sorry, unable to update .*/X.zip with Xzip/file.txt', + \ execute("write")) + endif + bw!|bw + + let g:zip_zipcmd = save_zip_zipcmd + + "## Check opening an no zipfile + call writefile(["qsdf"], "Xcorupt.zip", "D") + e! Xcorupt.zip + call assert_equal("qsdf", getline(1)) + + bw + + "## Check no existing zipfile + call assert_match('File not readable', execute("e Xnot_exists.zip")) + + bw +endfunc + +func Test_zip_glob_fname() + CheckNotMSWindows + " does not work on Windows, why? + + "## copy sample zip file + if !filecopy("samples/testa.zip", "X.zip") + call assert_report("Can't copy samples/testa.zip") + return + endif + defer delete("X.zip") + defer delete('zipglob', 'rf') + + e X.zip + + "## 1) Check extracting strange files + :1 + let fname = 'a[a].txt' + call search('\V' .. fname) + normal x + call assert_true(filereadable('zipglob/' .. fname)) + call delete('zipglob', 'rf') + + :1 + let fname = 'a*.txt' + call search('\V' .. fname) + normal x + call assert_true(filereadable('zipglob/' .. fname)) + call delete('zipglob', 'rf') + + :1 + let fname = 'a?.txt' + call search('\V' .. fname) + normal x + call assert_true(filereadable('zipglob/' .. fname)) + call delete('zipglob', 'rf') + + :1 + let fname = 'a\.txt' + call search('\V' .. escape(fname, '\\')) + normal x + call assert_true(filereadable('zipglob/' .. fname)) + call delete('zipglob', 'rf') + + :1 + let fname = 'a\\.txt' + call search('\V' .. escape(fname, '\\')) + normal x + call assert_true(filereadable('zipglob/' .. fname)) + call delete('zipglob', 'rf') + + "## 2) Check entering strange file names + :1 + let fname = 'a[a].txt' + call search('\V' .. fname) + exe ":normal \<cr>" + call assert_match('zipfile://.*/X.zip::zipglob/a\[a\].txt', @%) + call assert_equal('a test file with []', getline(1)) + bw + + e X.zip + :1 + let fname = 'a*.txt' + call search('\V' .. fname) + exe ":normal \<cr>" + call assert_match('zipfile://.*/X.zip::zipglob/a\*.txt', @%) + call assert_equal('a test file with a*', getline(1)) + bw + + e X.zip + :1 + let fname = 'a?.txt' + call search('\V' .. fname) + exe ":normal \<cr>" + call assert_match('zipfile://.*/X.zip::zipglob/a?.txt', @%) + call assert_equal('a test file with a?', getline(1)) + bw + + e X.zip + :1 + let fname = 'a\.txt' + call search('\V' .. escape(fname, '\\')) + exe ":normal \<cr>" + call assert_match('zipfile://.*/X.zip::zipglob/a\\.txt', @%) + call assert_equal('a test file with a\', getline(1)) + bw + + e X.zip + :1 + let fname = 'a\\.txt' + call search('\V' .. escape(fname, '\\')) + exe ":normal \<cr>" + call assert_match('zipfile://.*/X.zip::zipglob/a\\\\.txt', @%) + call assert_equal('a test file with a double \', getline(1)) + bw + + bw +endfunc diff --git a/test/testutil.lua b/test/testutil.lua index 439f13cf49..a920f658a1 100644 --- a/test/testutil.lua +++ b/test/testutil.lua @@ -16,7 +16,7 @@ local function shell_quote(str) return str end ---- This module uses functions from the context of the test runner. +--- Functions executing in the context of the test runner (not the current nvim test session). --- @class test.testutil local M = { paths = Paths, @@ -42,6 +42,29 @@ function M.isdir(path) return stat.type == 'directory' end +--- (Only on Windows) Replaces yucky "\\" slashes with delicious "/" slashes in a string, or all +--- string values in a table (recursively). +--- +--- @param obj string|table +--- @return any +function M.fix_slashes(obj) + if not M.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.fix_slashes(v) + end + return ret + end + assert(false, 'expected string or table of strings, got ' .. type(obj)) +end + --- @param ... string|string[] --- @return string function M.argss_to_cmd(...) @@ -143,7 +166,7 @@ end --- ---@param pat (string) Lua pattern to match lines in the log file ---@param logfile? (string) Full path to log file (default=$NVIM_LOG_FILE) ----@param nrlines? (number) Search up to this many log lines +---@param nrlines? (number) Search up to this many log lines (default 10) ---@param inverse? (boolean) Assert that the pattern does NOT match. function M.assert_log(pat, logfile, nrlines, inverse) logfile = logfile or os.getenv('NVIM_LOG_FILE') or '.nvimlog' @@ -369,7 +392,7 @@ function M.check_logs() ) end -function M.sysname() +local function sysname() return uv.os_uname().sysname:lower() end @@ -380,11 +403,11 @@ function M.is_os(s) error('unknown platform: ' .. tostring(s)) end return not not ( - (s == 'win' and (M.sysname():find('windows') or M.sysname():find('mingw'))) - or (s == 'mac' and M.sysname() == 'darwin') - or (s == 'freebsd' and M.sysname() == 'freebsd') - or (s == 'openbsd' and M.sysname() == 'openbsd') - or (s == 'bsd' and M.sysname():find('bsd')) + (s == 'win' and (sysname():find('windows') or sysname():find('mingw'))) + or (s == 'mac' and sysname() == 'darwin') + or (s == 'freebsd' and sysname() == 'freebsd') + or (s == 'openbsd' and sysname() == 'openbsd') + or (s == 'bsd' and sysname():find('bsd')) ) end @@ -402,18 +425,27 @@ end local tmpname_id = 0 local tmpdir = tmpdir_get() ---- Creates a new temporary file for use by tests. -function M.tmpname() +--- Generates a unique filepath for use by tests, in a test-specific "…/Xtest_tmpdir/T42.7" +--- directory (which is cleaned up by the test runner), and writes the file unless `create=false`. +--- +---@param create? boolean (default true) Write the file. +function M.tmpname(create) if tmpdir_is_local(tmpdir) then -- Cannot control os.tmpname() dir, so hack our own tmpname() impl. tmpname_id = tmpname_id + 1 -- "…/Xtest_tmpdir/T42.7" local fname = ('%s/%s.%d'):format(tmpdir, (_G._nvim_test_id or 'nvim-test'), tmpname_id) - io.open(fname, 'w'):close() + if create ~= false then + io.open(fname, 'w'):close() + end return fname end local fname = os.tmpname() + if create == false then + os.remove(fname) + end + if M.is_os('win') and fname:sub(1, 2) == '\\s' then -- In Windows tmpname() returns a filename starting with -- special sequence \s, prepend $TEMP path diff --git a/test/unit/api/private_helpers_spec.lua b/test/unit/api/private_helpers_spec.lua index a31374bd70..bdfc83a031 100644 --- a/test/unit/api/private_helpers_spec.lua +++ b/test/unit/api/private_helpers_spec.lua @@ -43,9 +43,9 @@ describe('vim_to_object', function() simple_test('converts empty string', '') simple_test('converts non-empty string', 'foobar') simple_test('converts integer 10', { [type_key] = int_type, value = 10 }) - simple_test('converts empty dictionary', {}) - simple_test('converts dictionary with scalar values', { test = 10, test2 = true, test3 = 'test' }) - simple_test('converts dictionary with containers inside', { test = {}, test2 = { 1, 2 } }) + simple_test('converts empty dict', {}) + simple_test('converts dict with scalar values', { test = 10, test2 = true, test3 = 'test' }) + simple_test('converts dict with containers inside', { test = {}, test2 = { 1, 2 } }) simple_test('converts empty list', { [type_key] = list_type }) simple_test('converts list with scalar values', { 1, 2, 'test', 'foo' }) simple_test( diff --git a/test/unit/api/testutil.lua b/test/unit/api/testutil.lua index 0946ef194c..bb387ae0e1 100644 --- a/test/unit/api/testutil.lua +++ b/test/unit/api/testutil.lua @@ -35,10 +35,10 @@ local function init_obj2lua_tab() end return ret end, - [tonumber(api.kObjectTypeDictionary)] = function(obj) + [tonumber(api.kObjectTypeDict)] = function(obj) local ret = {} - for i = 1, tonumber(obj.data.dictionary.size) do - local kv_pair = obj.data.dictionary.items[i - 1] + for i = 1, tonumber(obj.data.dict.size) do + local kv_pair = obj.data.dict.items[i - 1] ret[ffi.string(kv_pair.key.data, kv_pair.key.size)] = obj2lua(kv_pair.value) end return ret @@ -112,8 +112,8 @@ local lua2obj_type_tab = { end end local len = #kvs - local dct = obj(api.kObjectTypeDictionary, { - dictionary = { + local dct = obj(api.kObjectTypeDict, { + dict = { size = len, capacity = len, items = ffi.cast('KeyValuePair *', api.xmalloc(len * ffi.sizeof('KeyValuePair'))), @@ -121,7 +121,7 @@ local lua2obj_type_tab = { }) for i = 1, len do local key, val = unpack(kvs[i]) - dct.data.dictionary.items[i - 1] = ffi.new( + dct.data.dict.items[i - 1] = ffi.new( 'KeyValuePair', { key = ffi.gc(lua2obj(key), nil).data.string, value = ffi.gc(lua2obj(val), nil) } ) diff --git a/test/unit/eval/encode_spec.lua b/test/unit/eval/encode_spec.lua index 5b9188163e..9f193bc2f9 100644 --- a/test/unit/eval/encode_spec.lua +++ b/test/unit/eval/encode_spec.lua @@ -21,81 +21,81 @@ describe('encode_list_write()', function() itp('writes empty string', function() local l = list() - eq(0, encode_list_write(l, '')) + encode_list_write(l, '') eq({ [type_key] = list_type }, lst2tbl(l)) end) itp('writes ASCII string literal with printable characters', function() local l = list() - eq(0, encode_list_write(l, 'abc')) + encode_list_write(l, 'abc') eq({ 'abc' }, lst2tbl(l)) end) itp('writes string starting with NL', function() local l = list() - eq(0, encode_list_write(l, '\nabc')) + encode_list_write(l, '\nabc') eq({ null_string, 'abc' }, lst2tbl(l)) end) itp('writes string starting with NL twice', function() local l = list() - eq(0, encode_list_write(l, '\nabc')) + encode_list_write(l, '\nabc') eq({ null_string, 'abc' }, lst2tbl(l)) - eq(0, encode_list_write(l, '\nabc')) + encode_list_write(l, '\nabc') eq({ null_string, 'abc', 'abc' }, lst2tbl(l)) end) itp('writes string ending with NL', function() local l = list() - eq(0, encode_list_write(l, 'abc\n')) + encode_list_write(l, 'abc\n') eq({ 'abc', null_string }, lst2tbl(l)) end) itp('writes string ending with NL twice', function() local l = list() - eq(0, encode_list_write(l, 'abc\n')) + encode_list_write(l, 'abc\n') eq({ 'abc', null_string }, lst2tbl(l)) - eq(0, encode_list_write(l, 'abc\n')) + encode_list_write(l, 'abc\n') eq({ 'abc', 'abc', null_string }, lst2tbl(l)) end) itp('writes string starting, ending and containing NL twice', function() local l = list() - eq(0, encode_list_write(l, '\na\nb\n')) + encode_list_write(l, '\na\nb\n') eq({ null_string, 'a', 'b', null_string }, lst2tbl(l)) - eq(0, encode_list_write(l, '\na\nb\n')) + encode_list_write(l, '\na\nb\n') eq({ null_string, 'a', 'b', null_string, 'a', 'b', null_string }, lst2tbl(l)) end) itp('writes string starting, ending and containing NUL with NL between twice', function() local l = list() - eq(0, encode_list_write(l, '\0\n\0\n\0')) + encode_list_write(l, '\0\n\0\n\0') eq({ '\n', '\n', '\n' }, lst2tbl(l)) - eq(0, encode_list_write(l, '\0\n\0\n\0')) + encode_list_write(l, '\0\n\0\n\0') eq({ '\n', '\n', '\n\n', '\n', '\n' }, lst2tbl(l)) end) itp('writes string starting, ending and containing NL with NUL between twice', function() local l = list() - eq(0, encode_list_write(l, '\n\0\n\0\n')) + encode_list_write(l, '\n\0\n\0\n') eq({ null_string, '\n', '\n', null_string }, lst2tbl(l)) - eq(0, encode_list_write(l, '\n\0\n\0\n')) + encode_list_write(l, '\n\0\n\0\n') eq({ null_string, '\n', '\n', null_string, '\n', '\n', null_string }, lst2tbl(l)) end) itp('writes string containing a single NL twice', function() local l = list() - eq(0, encode_list_write(l, '\n')) + encode_list_write(l, '\n') eq({ null_string, null_string }, lst2tbl(l)) - eq(0, encode_list_write(l, '\n')) + encode_list_write(l, '\n') eq({ null_string, null_string, null_string }, lst2tbl(l)) end) itp('writes string containing a few NLs twice', function() local l = list() - eq(0, encode_list_write(l, '\n\n\n')) + encode_list_write(l, '\n\n\n') eq({ null_string, null_string, null_string, null_string }, lst2tbl(l)) - eq(0, encode_list_write(l, '\n\n\n')) + encode_list_write(l, '\n\n\n') eq( { null_string, null_string, null_string, null_string, null_string, null_string, null_string }, lst2tbl(l) diff --git a/test/unit/eval/typval_spec.lua b/test/unit/eval/typval_spec.lua index c69c9b0fae..14fd7986e7 100644 --- a/test/unit/eval/typval_spec.lua +++ b/test/unit/eval/typval_spec.lua @@ -1267,26 +1267,19 @@ describe('typval.c', function() local l2 = list() -- NULL lists are equal to empty lists - eq(true, lib.tv_list_equal(l, nil, true, false)) - eq(true, lib.tv_list_equal(nil, l, false, false)) - eq(true, lib.tv_list_equal(nil, l, false, true)) - eq(true, lib.tv_list_equal(l, nil, true, true)) + eq(true, lib.tv_list_equal(l, nil, true)) + eq(true, lib.tv_list_equal(nil, l, false)) -- NULL lists are equal themselves - eq(true, lib.tv_list_equal(nil, nil, true, false)) - eq(true, lib.tv_list_equal(nil, nil, false, false)) - eq(true, lib.tv_list_equal(nil, nil, false, true)) - eq(true, lib.tv_list_equal(nil, nil, true, true)) + eq(true, lib.tv_list_equal(nil, nil, true)) + eq(true, lib.tv_list_equal(nil, nil, false)) -- As well as empty lists - eq(true, lib.tv_list_equal(l, l, true, false)) - eq(true, lib.tv_list_equal(l, l2, false, false)) - eq(true, lib.tv_list_equal(l2, l, false, true)) - eq(true, lib.tv_list_equal(l2, l2, true, true)) - end) - -- Must not use recursive=true argument in the following tests because it - -- indicates that tv_equal_recurse_limit and recursive_cnt were set which - -- is essential. This argument will be set when comparing inner lists. + eq(true, lib.tv_list_equal(l, l, true)) + eq(true, lib.tv_list_equal(l, l2, false)) + eq(true, lib.tv_list_equal(l2, l, false)) + eq(true, lib.tv_list_equal(l2, l2, true)) + end) itp('compares lists correctly when case is not ignored', function() local l1 = list('abc', { 1, 2, 'Abc' }, 'def') local l2 = list('abc', { 1, 2, 'Abc' }) @@ -1298,15 +1291,15 @@ describe('typval.c', function() local l8 = list('abc', nil, 'def') local l9 = list('abc', { 1, 2, nil }, 'def') - eq(true, lib.tv_list_equal(l1, l1, false, false)) - eq(false, lib.tv_list_equal(l1, l2, false, false)) - eq(false, lib.tv_list_equal(l1, l3, false, false)) - eq(false, lib.tv_list_equal(l1, l4, false, false)) - eq(false, lib.tv_list_equal(l1, l5, false, false)) - eq(true, lib.tv_list_equal(l1, l6, false, false)) - eq(false, lib.tv_list_equal(l1, l7, false, false)) - eq(false, lib.tv_list_equal(l1, l8, false, false)) - eq(false, lib.tv_list_equal(l1, l9, false, false)) + eq(true, lib.tv_list_equal(l1, l1, false)) + eq(false, lib.tv_list_equal(l1, l2, false)) + eq(false, lib.tv_list_equal(l1, l3, false)) + eq(false, lib.tv_list_equal(l1, l4, false)) + eq(false, lib.tv_list_equal(l1, l5, false)) + eq(true, lib.tv_list_equal(l1, l6, false)) + eq(false, lib.tv_list_equal(l1, l7, false)) + eq(false, lib.tv_list_equal(l1, l8, false)) + eq(false, lib.tv_list_equal(l1, l9, false)) end) itp('compares lists correctly when case is ignored', function() local l1 = list('abc', { 1, 2, 'Abc' }, 'def') @@ -1319,15 +1312,15 @@ describe('typval.c', function() local l8 = list('abc', nil, 'def') local l9 = list('abc', { 1, 2, nil }, 'def') - eq(true, lib.tv_list_equal(l1, l1, true, false)) - eq(false, lib.tv_list_equal(l1, l2, true, false)) - eq(true, lib.tv_list_equal(l1, l3, true, false)) - eq(false, lib.tv_list_equal(l1, l4, true, false)) - eq(true, lib.tv_list_equal(l1, l5, true, false)) - eq(true, lib.tv_list_equal(l1, l6, true, false)) - eq(true, lib.tv_list_equal(l1, l7, true, false)) - eq(false, lib.tv_list_equal(l1, l8, true, false)) - eq(false, lib.tv_list_equal(l1, l9, true, false)) + eq(true, lib.tv_list_equal(l1, l1, true)) + eq(false, lib.tv_list_equal(l1, l2, true)) + eq(true, lib.tv_list_equal(l1, l3, true)) + eq(false, lib.tv_list_equal(l1, l4, true)) + eq(true, lib.tv_list_equal(l1, l5, true)) + eq(true, lib.tv_list_equal(l1, l6, true)) + eq(true, lib.tv_list_equal(l1, l7, true)) + eq(false, lib.tv_list_equal(l1, l8, true)) + eq(false, lib.tv_list_equal(l1, l9, true)) end) end) describe('find', function() @@ -2326,7 +2319,7 @@ describe('typval.c', function() return lib.tv_dict_extend(d1, d2, action) end, emsg) end - itp('works', function() + pending('works (skip due to flakiness)', function() local d1 = dict() alloc_log:check({ a.dict(d1) }) eq({}, dct2tbl(d1)) @@ -2448,8 +2441,8 @@ describe('typval.c', function() end) end) describe('equal()', function() - local function tv_dict_equal(d1, d2, ic, recursive) - return lib.tv_dict_equal(d1, d2, ic or false, recursive or false) + local function tv_dict_equal(d1, d2, ic) + return lib.tv_dict_equal(d1, d2, ic or false) end itp('works', function() eq(true, tv_dict_equal(nil, nil)) @@ -2494,7 +2487,6 @@ describe('typval.c', function() eq(true, tv_dict_equal(d_kupper_upper, d_kupper_lower, true)) eq(false, tv_dict_equal(d_kupper_upper, d_lower, true)) eq(false, tv_dict_equal(d_kupper_upper, d_upper, true)) - eq(true, tv_dict_equal(d_upper, d_upper, true, true)) alloc_log:check({}) end) end) @@ -2923,26 +2915,19 @@ describe('typval.c', function() local nl = lua2typvalt(null_list) -- NULL lists are equal to empty lists - eq(true, lib.tv_equal(l, nl, true, false)) - eq(true, lib.tv_equal(nl, l, false, false)) - eq(true, lib.tv_equal(nl, l, false, true)) - eq(true, lib.tv_equal(l, nl, true, true)) + eq(true, lib.tv_equal(l, nl, true)) + eq(true, lib.tv_equal(nl, l, false)) -- NULL lists are equal themselves - eq(true, lib.tv_equal(nl, nl, true, false)) - eq(true, lib.tv_equal(nl, nl, false, false)) - eq(true, lib.tv_equal(nl, nl, false, true)) - eq(true, lib.tv_equal(nl, nl, true, true)) + eq(true, lib.tv_equal(nl, nl, true)) + eq(true, lib.tv_equal(nl, nl, false)) -- As well as empty lists - eq(true, lib.tv_equal(l, l, true, false)) - eq(true, lib.tv_equal(l, l2, false, false)) - eq(true, lib.tv_equal(l2, l, false, true)) - eq(true, lib.tv_equal(l2, l2, true, true)) - end) - -- Must not use recursive=true argument in the following tests because it - -- indicates that tv_equal_recurse_limit and recursive_cnt were set which - -- is essential. This argument will be set when comparing inner lists. + eq(true, lib.tv_equal(l, l, true)) + eq(true, lib.tv_equal(l, l2, false)) + eq(true, lib.tv_equal(l2, l, false)) + eq(true, lib.tv_equal(l2, l2, true)) + end) itp('compares lists correctly when case is not ignored', function() local l1 = lua2typvalt({ 'abc', { 1, 2, 'Abc' }, 'def' }) local l2 = lua2typvalt({ 'abc', { 1, 2, 'Abc' } }) @@ -2954,15 +2939,15 @@ describe('typval.c', function() local l8 = lua2typvalt({ 'abc', nil, 'def' }) local l9 = lua2typvalt({ 'abc', { 1, 2, nil }, 'def' }) - eq(true, lib.tv_equal(l1, l1, false, false)) - eq(false, lib.tv_equal(l1, l2, false, false)) - eq(false, lib.tv_equal(l1, l3, false, false)) - eq(false, lib.tv_equal(l1, l4, false, false)) - eq(false, lib.tv_equal(l1, l5, false, false)) - eq(true, lib.tv_equal(l1, l6, false, false)) - eq(false, lib.tv_equal(l1, l7, false, false)) - eq(false, lib.tv_equal(l1, l8, false, false)) - eq(false, lib.tv_equal(l1, l9, false, false)) + eq(true, lib.tv_equal(l1, l1, false)) + eq(false, lib.tv_equal(l1, l2, false)) + eq(false, lib.tv_equal(l1, l3, false)) + eq(false, lib.tv_equal(l1, l4, false)) + eq(false, lib.tv_equal(l1, l5, false)) + eq(true, lib.tv_equal(l1, l6, false)) + eq(false, lib.tv_equal(l1, l7, false)) + eq(false, lib.tv_equal(l1, l8, false)) + eq(false, lib.tv_equal(l1, l9, false)) end) itp('compares lists correctly when case is ignored', function() local l1 = lua2typvalt({ 'abc', { 1, 2, 'Abc' }, 'def' }) @@ -2975,18 +2960,18 @@ describe('typval.c', function() local l8 = lua2typvalt({ 'abc', nil, 'def' }) local l9 = lua2typvalt({ 'abc', { 1, 2, nil }, 'def' }) - eq(true, lib.tv_equal(l1, l1, true, false)) - eq(false, lib.tv_equal(l1, l2, true, false)) - eq(true, lib.tv_equal(l1, l3, true, false)) - eq(false, lib.tv_equal(l1, l4, true, false)) - eq(true, lib.tv_equal(l1, l5, true, false)) - eq(true, lib.tv_equal(l1, l6, true, false)) - eq(true, lib.tv_equal(l1, l7, true, false)) - eq(false, lib.tv_equal(l1, l8, true, false)) - eq(false, lib.tv_equal(l1, l9, true, false)) - end) - local function tv_equal(d1, d2, ic, recursive) - return lib.tv_equal(d1, d2, ic or false, recursive or false) + eq(true, lib.tv_equal(l1, l1, true)) + eq(false, lib.tv_equal(l1, l2, true)) + eq(true, lib.tv_equal(l1, l3, true)) + eq(false, lib.tv_equal(l1, l4, true)) + eq(true, lib.tv_equal(l1, l5, true)) + eq(true, lib.tv_equal(l1, l6, true)) + eq(true, lib.tv_equal(l1, l7, true)) + eq(false, lib.tv_equal(l1, l8, true)) + eq(false, lib.tv_equal(l1, l9, true)) + end) + local function tv_equal(d1, d2, ic) + return lib.tv_equal(d1, d2, ic or false) end itp('works with dictionaries', function() local nd = lua2typvalt(null_dict) @@ -3033,7 +3018,6 @@ describe('typval.c', function() eq(true, tv_equal(d_kupper_upper, d_kupper_lower, true)) eq(false, tv_equal(d_kupper_upper, d_lower, true)) eq(false, tv_equal(d_kupper_upper, d_upper, true)) - eq(true, tv_equal(d_upper, d_upper, true, true)) alloc_log:check({}) end) end) diff --git a/test/unit/fixtures/rbuffer.c b/test/unit/fixtures/rbuffer.c deleted file mode 100644 index d587d6b054..0000000000 --- a/test/unit/fixtures/rbuffer.c +++ /dev/null @@ -1,28 +0,0 @@ -#include "nvim/rbuffer.h" -#include "rbuffer.h" - - -void ut_rbuffer_each_read_chunk(RBuffer *buf, each_ptr_cb cb) -{ - RBUFFER_UNTIL_EMPTY(buf, rptr, rcnt) { - cb(rptr, rcnt); - rbuffer_consumed(buf, rcnt); - } -} - -void ut_rbuffer_each_write_chunk(RBuffer *buf, each_ptr_cb cb) -{ - RBUFFER_UNTIL_FULL(buf, wptr, wcnt) { - cb(wptr, wcnt); - rbuffer_produced(buf, wcnt); - } -} -void ut_rbuffer_each(RBuffer *buf, each_cb cb) -{ - RBUFFER_EACH(buf, c, i) cb(c, i); -} - -void ut_rbuffer_each_reverse(RBuffer *buf, each_cb cb) -{ - RBUFFER_EACH_REVERSE(buf, c, i) cb(c, i); -} diff --git a/test/unit/fixtures/rbuffer.h b/test/unit/fixtures/rbuffer.h deleted file mode 100644 index 640092c627..0000000000 --- a/test/unit/fixtures/rbuffer.h +++ /dev/null @@ -1,9 +0,0 @@ -#include "nvim/rbuffer.h" - -typedef void(*each_ptr_cb)(char *ptr, size_t cnt); -typedef void(*each_cb)(char c, size_t i); - -void ut_rbuffer_each_read_chunk(RBuffer *buf, each_ptr_cb cb); -void ut_rbuffer_each_write_chunk(RBuffer *buf, each_ptr_cb cb); -void ut_rbuffer_each(RBuffer *buf, each_cb cb); -void ut_rbuffer_each_reverse(RBuffer *buf, each_cb cb); diff --git a/test/unit/formatc.lua b/test/unit/formatc.lua index ce9cb81f4a..04a8b4009f 100644 --- a/test/unit/formatc.lua +++ b/test/unit/formatc.lua @@ -264,6 +264,7 @@ local function formatc(str) -- and ';' indicates we're at the end of a statement, so we put end -- it with a newline. token[1] = ';\n' + end_at_brace = false end elseif typ == 'whitespace' then -- replace all whitespace by one space diff --git a/test/unit/mbyte_spec.lua b/test/unit/mbyte_spec.lua index 8fcc67d20b..bdc111de2c 100644 --- a/test/unit/mbyte_spec.lua +++ b/test/unit/mbyte_spec.lua @@ -3,8 +3,14 @@ local itp = t.gen_itp(it) local ffi = t.ffi local eq = t.eq +local to_cstr = t.to_cstr -local lib = t.cimport('./src/nvim/mbyte.h', './src/nvim/charset.h', './src/nvim/grid.h') +local lib = t.cimport( + './src/nvim/mbyte.h', + './src/nvim/charset.h', + './src/nvim/grid.h', + './src/nvim/option_vars.h' +) describe('mbyte', function() -- Convert from bytes to string @@ -45,12 +51,21 @@ describe('mbyte', function() end) end - describe('utfc_ptr2schar_len', function() + describe('utfc_ptr2schar', function() local function test_seq(seq) local firstc = ffi.new('int[1]') local buf = ffi.new('char[32]') - lib.schar_get(buf, lib.utfc_ptr2schar_len(to_string(seq), #seq, firstc)) - return { ffi.string(buf), firstc[0] } + lib.schar_get(buf, lib.utfc_ptr2schar(to_string(seq), firstc)) + local str = ffi.string(buf) + if 1 > 2 then -- for debugging + local tabel = {} + for i = 1, #str do + table.insert(tabel, string.format('0x%02x', string.byte(str, i))) + end + print('{ ' .. table.concat(tabel, ', ') .. ' }') + io.stdout:flush() + end + return { str, firstc[0] } end local function byte(val) @@ -88,7 +103,9 @@ describe('mbyte', function() eq(byte(0x7f), test_seq { 0x7f, 0xc2, 0x80 }) -- Combining character is U+0300 - eq({ '\x7f\xcc\x80', 0x7f }, test_seq { 0x7f, 0xcc, 0x80 }) + eq({ '\x29\xcc\x80', 0x29 }, test_seq { 0x29, 0xcc, 0x80 }) + -- invalid start byte for combining + eq({ '\x7f', 0x7f }, test_seq { 0x7f, 0xcc, 0x80 }) -- No UTF-8 sequence eq({ '', 0xc2 }, test_seq { 0xc2, 0x7f, 0xcc }) @@ -102,18 +119,21 @@ describe('mbyte', function() itp('4-byte sequences', function() -- No following combining character eq(byte(0x7f), test_seq { 0x7f, 0x7f, 0xcc, 0x80 }) + eq(byte(0x29), test_seq { 0x29, 0x29, 0xcc, 0x80 }) -- No second UTF-8 character eq(byte(0x7f), test_seq { 0x7f, 0xc2, 0xcc, 0x80 }) -- Combining character U+0300 - eq({ '\x7f\xcc\x80', 0x7f }, test_seq { 0x7f, 0xcc, 0x80, 0xcc }) + eq({ '\x29\xcc\x80', 0x29 }, test_seq { 0x29, 0xcc, 0x80, 0xcc }) -- No UTF-8 sequence eq({ '', 0xc2 }, test_seq { 0xc2, 0x7f, 0xcc, 0x80 }) -- No following UTF-8 character eq({ '\xc2\x80', 0x80 }, test_seq { 0xc2, 0x80, 0xcc, 0xcc }) -- Combining character U+0301 - eq({ '\xc2\x80\xcc\x81', 0x80 }, test_seq { 0xc2, 0x80, 0xcc, 0x81 }) + eq({ '\xc2\xbc\xcc\x81', 0xbc }, test_seq { 0xc2, 0xbc, 0xcc, 0x81 }) + -- U+0080 : not a valid start char + eq({ '\xc2\x80', 0x80 }, test_seq { 0xc2, 0x80, 0xcc, 0x81 }) -- One UTF-8 character eq({ '\xf4\x80\x80\x80', 0x100000 }, test_seq { 0xf4, 0x80, 0x80, 0x80 }) @@ -126,36 +146,36 @@ describe('mbyte', function() eq(byte(0x7f), test_seq { 0x7f, 0xc2, 0xcc, 0x80, 0x80 }) -- Combining character U+0300 - eq({ '\x7f\xcc\x80', 0x7f }, test_seq { 0x7f, 0xcc, 0x80, 0xcc, 0x00 }) + eq({ '\x29\xcc\x80', 0x29 }, test_seq { 0x29, 0xcc, 0x80, 0xcc, 0x00 }) -- Combining characters U+0300 and U+0301 - eq({ '\x7f\xcc\x80\xcc\x81', 0x7f }, test_seq { 0x7f, 0xcc, 0x80, 0xcc, 0x81 }) + eq({ '\x29\xcc\x80\xcc\x81', 0x29 }, test_seq { 0x29, 0xcc, 0x80, 0xcc, 0x81 }) -- Combining characters U+0300, U+0301, U+0302 eq( - { '\x7f\xcc\x80\xcc\x81\xcc\x82', 0x7f }, - test_seq { 0x7f, 0xcc, 0x80, 0xcc, 0x81, 0xcc, 0x82 } + { '\x29\xcc\x80\xcc\x81\xcc\x82', 0x29 }, + test_seq { 0x29, 0xcc, 0x80, 0xcc, 0x81, 0xcc, 0x82 } ) -- Combining characters U+0300, U+0301, U+0302, U+0303 eq( - { '\x7f\xcc\x80\xcc\x81\xcc\x82\xcc\x83', 0x7f }, - test_seq { 0x7f, 0xcc, 0x80, 0xcc, 0x81, 0xcc, 0x82, 0xcc, 0x83 } + { '\x29\xcc\x80\xcc\x81\xcc\x82\xcc\x83', 0x29 }, + test_seq { 0x29, 0xcc, 0x80, 0xcc, 0x81, 0xcc, 0x82, 0xcc, 0x83 } ) -- Combining characters U+0300, U+0301, U+0302, U+0303, U+0304 eq( - { '\x7f\xcc\x80\xcc\x81\xcc\x82\xcc\x83\xcc\x84', 0x7f }, - test_seq { 0x7f, 0xcc, 0x80, 0xcc, 0x81, 0xcc, 0x82, 0xcc, 0x83, 0xcc, 0x84 } + { '\x29\xcc\x80\xcc\x81\xcc\x82\xcc\x83\xcc\x84', 0x29 }, + test_seq { 0x29, 0xcc, 0x80, 0xcc, 0x81, 0xcc, 0x82, 0xcc, 0x83, 0xcc, 0x84 } ) -- Combining characters U+0300, U+0301, U+0302, U+0303, U+0304, U+0305 eq( - { '\x7f\xcc\x80\xcc\x81\xcc\x82\xcc\x83\xcc\x84\xcc\x85', 0x7f }, - test_seq { 0x7f, 0xcc, 0x80, 0xcc, 0x81, 0xcc, 0x82, 0xcc, 0x83, 0xcc, 0x84, 0xcc, 0x85 } + { '\x29\xcc\x80\xcc\x81\xcc\x82\xcc\x83\xcc\x84\xcc\x85', 0x29 }, + test_seq { 0x29, 0xcc, 0x80, 0xcc, 0x81, 0xcc, 0x82, 0xcc, 0x83, 0xcc, 0x84, 0xcc, 0x85 } ) -- Combining characters U+0300, U+0301, U+0302, U+0303, U+0304, U+0305, U+0306 eq( - { '\x7f\xcc\x80\xcc\x81\xcc\x82\xcc\x83\xcc\x84\xcc\x85\xcc\x86', 0x7f }, + { '\x29\xcc\x80\xcc\x81\xcc\x82\xcc\x83\xcc\x84\xcc\x85\xcc\x86', 0x29 }, test_seq { - 0x7f, + 0x29, 0xcc, 0x80, 0xcc, @@ -175,18 +195,18 @@ describe('mbyte', function() -- Only three following combining characters U+0300, U+0301, U+0302 eq( - { '\x7f\xcc\x80\xcc\x81\xcc\x82', 0x7f }, - test_seq { 0x7f, 0xcc, 0x80, 0xcc, 0x81, 0xcc, 0x82, 0xc2, 0x80, 0xcc, 0x84, 0xcc, 0x85 } + { '\x29\xcc\x80\xcc\x81\xcc\x82', 0x29 }, + test_seq { 0x29, 0xcc, 0x80, 0xcc, 0x81, 0xcc, 0x82, 0xc2, 0x80, 0xcc, 0x84, 0xcc, 0x85 } ) -- No UTF-8 sequence eq({ '', 0xc2 }, test_seq { 0xc2, 0x7f, 0xcc, 0x80, 0x80 }) -- No following UTF-8 character - eq({ '\xc2\x80', 0x80 }, test_seq { 0xc2, 0x80, 0xcc, 0xcc, 0x80 }) + eq({ '\xc2\xbc', 0xbc }, test_seq { 0xc2, 0xbc, 0xcc, 0xcc, 0x80 }) -- Combining character U+0301 - eq({ '\xc2\x80\xcc\x81', 0x80 }, test_seq { 0xc2, 0x80, 0xcc, 0x81, 0x7f }) + eq({ '\xc2\xbc\xcc\x81', 0xbc }, test_seq { 0xc2, 0xbc, 0xcc, 0x81, 0x7f }) -- Combining character U+0301 - eq({ '\xc2\x80\xcc\x81', 0x80 }, test_seq { 0xc2, 0x80, 0xcc, 0x81, 0xcc }) + eq({ '\xc2\xbc\xcc\x81', 0xbc }, test_seq { 0xc2, 0xbc, 0xcc, 0x81, 0xcc }) -- One UTF-8 character eq({ '\xf4\x80\x80\x80', 0x100000 }, test_seq { 0xf4, 0x80, 0x80, 0x80, 0x7f }) @@ -205,8 +225,6 @@ describe('mbyte', function() end) describe('utf_cp_bounds_len', function() - local to_cstr = t.to_cstr - local tests = { { name = 'for valid string', @@ -273,4 +291,72 @@ describe('mbyte', function() eq(expected_offsets, { b = b_offsets, e = e_offsets }) end) end) + + itp('utf_head_off', function() + local function check(str, expected_glyphs) + local len = #str + local cstr = to_cstr(str) + local breaks = { 0 } -- SOT + local pos = 0 + local mb_glyphs = {} + while pos < len do + local clen = lib.utfc_ptr2len(cstr + pos) + if clen == 0 then + eq(0, string.byte(str, pos + 1)) -- only NUL bytes can has length zery + clen = 1 -- but skip it, otherwise we get stuck + end + if clen > 1 then + table.insert(mb_glyphs, string.sub(str, pos + 1, pos + clen)) + end + pos = pos + clen + table.insert(breaks, pos) + end + eq(breaks[#breaks], len) -- include EOT as break + -- we could also send in breaks, but this is more human readable + eq(mb_glyphs, expected_glyphs) + + for i = 1, #breaks - 1 do + local start, next = breaks[i], breaks[i + 1] + + for p = start, next - 1 do + eq(p - start, lib.utf_head_off(cstr, cstr + p)) + end + end + eq(0, lib.utf_head_off(cstr, cstr + len)) -- NUL byte is safe + end + -- stylua doesn't like ZWJ chars.. + -- stylua: ignore start + check('hej och hå 🧑🌾!', { 'å', '🧑🌾' }) + + -- emoji (various kinds of combinations, use g8 to see them) + check("🏳️⚧️🧑🌾❤️😂🏴☠️", {"🏳️⚧️", "🧑🌾", "❤️", "😂", "🏴☠️"}) + check('🏳️⚧️xy🧑🌾\r❤️😂å🏴☠️', { '🏳️⚧️', '🧑🌾', '❤️', '😂', 'å', '🏴☠️', '' }) + check('🏳️⚧️\000🧑🌾\000❤️\000😂\000å\000🏴☠️\000', { '🏳️⚧️', '🧑🌾', '❤️', '😂', 'å', '🏴☠️', '' }) + check('\195🏳️⚧️\198🧑🌾\165❤️\168\195😂\255🏴☠️\129\165', { '🏳️⚧️', '🧑🌾', '❤️', '😂', '🏴☠️', '' }) + + check('🇦🅱️ 🇦🇽 🇦🇨🇦 🇲🇽🇹🇱',{'🇦', '🅱️', '🇦🇽', '🇦🇨', '🇦', '🇲🇽', '🇹🇱'}) + check('🏴🏴', {'🏴', '🏴'}) + + check('å\165ü\195aëq\168β\000\169本\255', {'å', 'ü', 'ë', 'β', '本'}) + + lib.p_arshape = true -- default + check('سلام', { 'س', 'لا', 'م' }) + lib.p_arshape = false + check('سلام', { 'س', 'ل', 'ا', 'م' }) + + check('L̓̉̑̒̌̚ơ̗̌̒̄̀ŕ̈̈̎̐̕è̇̅̄̄̐m̖̟̟̅̄̚', {'L̓̉̑̒̌̚', 'ơ̗̌̒̄̀', 'ŕ̈̈̎̐̕', 'è̇̅̄̄̐', 'm̖̟̟̅̄̚'}) + -- stylua: ignore end + end) + + describe('utf_fold', function() + itp('does not crash with surrogates #30527', function() + eq(0xddfb, lib.utf_fold(0xddfb)) -- low surrogate, invalid as a character + eq(0xd800, lib.utf_fold(0xd800)) -- high surrogate, invalid as a character + end) + + itp("doesn't crash on invalid codepoints", function() + eq(9000000, lib.utf_fold(9000000)) + eq(0, lib.utf_fold(0)) + end) + end) end) diff --git a/test/unit/path_spec.lua b/test/unit/path_spec.lua index 6f6a80f44e..ffad552a8a 100644 --- a/test/unit/path_spec.lua +++ b/test/unit/path_spec.lua @@ -468,8 +468,11 @@ describe('path.c', function() eq(OK, result) end) - itp('concatenates directory name if it does not contain a slash', function() - local expected = uv.cwd() .. '/..' + itp('produces absolute path for .. without a slash', function() + local old_dir = uv.cwd() + uv.chdir('..') + local expected = uv.cwd() + uv.chdir(old_dir) local filename = '..' local buflen = get_buf_len(expected, filename) local do_expand = 1 @@ -478,21 +481,18 @@ describe('path.c', function() eq(OK, result) end) - itp( - 'enters given directory (instead of just concatenating the strings) if possible and if path contains a slash', - function() - local old_dir = uv.cwd() - uv.chdir('..') - local expected = uv.cwd() .. '/test.file' - uv.chdir(old_dir) - local filename = '../test.file' - local buflen = get_buf_len(expected, filename) - local do_expand = 1 - local buf, result = vim_FullName(filename, buflen, do_expand) - eq(expected, ffi.string(buf)) - eq(OK, result) - end - ) + itp('produces absolute path if possible and if path contains a slash', function() + local old_dir = uv.cwd() + uv.chdir('..') + local expected = uv.cwd() .. '/test.file' + uv.chdir(old_dir) + local filename = '../test.file' + local buflen = get_buf_len(expected, filename) + local do_expand = 1 + local buf, result = vim_FullName(filename, buflen, do_expand) + eq(expected, ffi.string(buf)) + eq(OK, result) + end) itp('just copies the path if it is already absolute and force=0', function() local absolute_path = '/absolute/path' diff --git a/test/unit/rbuffer_spec.lua b/test/unit/rbuffer_spec.lua deleted file mode 100644 index ad18ea2ddc..0000000000 --- a/test/unit/rbuffer_spec.lua +++ /dev/null @@ -1,340 +0,0 @@ -local t = require('test.unit.testutil') -local itp = t.gen_itp(it) - -local eq = t.eq -local ffi = t.ffi -local cstr = t.cstr -local to_cstr = t.to_cstr -local child_call_once = t.child_call_once - -local rbuffer = t.cimport('./test/unit/fixtures/rbuffer.h') - -describe('rbuffer functions', function() - local capacity = 16 - local rbuf - - local function inspect() - return ffi.string(rbuf.start_ptr, capacity) - end - - local function write(str) - local buf = to_cstr(str) - return rbuffer.rbuffer_write(rbuf, buf, #str) - end - - local function read(len) - local buf = cstr(len) - len = rbuffer.rbuffer_read(rbuf, buf, len) - return ffi.string(buf, len) - end - - local function get(idx) - return ffi.string(rbuffer.rbuffer_get(rbuf, idx), 1) - end - - before_each(function() - child_call_once(function() - rbuf = ffi.gc(rbuffer.rbuffer_new(capacity), rbuffer.rbuffer_free) - -- fill the internal buffer with the character '0' to simplify inspecting - ffi.C.memset(rbuf.start_ptr, string.byte('0'), capacity) - end) - end) - - describe('RBUFFER_UNTIL_FULL', function() - local chunks - - local function collect_write_chunks() - rbuffer.ut_rbuffer_each_write_chunk(rbuf, function(wptr, wcnt) - table.insert(chunks, ffi.string(wptr, wcnt)) - end) - end - - before_each(function() - chunks = {} - end) - - describe('with empty buffer in one contiguous chunk', function() - itp('is called once with the empty chunk', function() - collect_write_chunks() - eq({ '0000000000000000' }, chunks) - end) - end) - - describe('with partially empty buffer in one contiguous chunk', function() - itp('is called once with the empty chunk', function() - write('string') - collect_write_chunks() - eq({ '0000000000' }, chunks) - end) - end) - - describe('with filled buffer in one contiguous chunk', function() - itp('is not called', function() - write('abcdefghijklmnopq') - collect_write_chunks() - eq({}, chunks) - end) - end) - - describe('with buffer partially empty in two contiguous chunks', function() - itp('is called twice with each filled chunk', function() - write('1234567890') - read(8) - collect_write_chunks() - eq({ '000000', '12345678' }, chunks) - end) - end) - - describe('with buffer empty in two contiguous chunks', function() - itp('is called twice with each filled chunk', function() - write('12345678') - read(8) - collect_write_chunks() - eq({ '00000000', '12345678' }, chunks) - end) - end) - - describe('with buffer filled in two contiguous chunks', function() - itp('is not called', function() - write('12345678') - read(8) - write('abcdefghijklmnopq') - collect_write_chunks() - eq({}, chunks) - end) - end) - end) - - describe('RBUFFER_UNTIL_EMPTY', function() - local chunks - - local function collect_read_chunks() - rbuffer.ut_rbuffer_each_read_chunk(rbuf, function(rptr, rcnt) - table.insert(chunks, ffi.string(rptr, rcnt)) - end) - end - - before_each(function() - chunks = {} - end) - - describe('with empty buffer', function() - itp('is not called', function() - collect_read_chunks() - eq({}, chunks) - end) - end) - - describe('with partially filled buffer in one contiguous chunk', function() - itp('is called once with the filled chunk', function() - write('string') - collect_read_chunks() - eq({ 'string' }, chunks) - end) - end) - - describe('with filled buffer in one contiguous chunk', function() - itp('is called once with the filled chunk', function() - write('abcdefghijklmnopq') - collect_read_chunks() - eq({ 'abcdefghijklmnop' }, chunks) - end) - end) - - describe('with buffer partially filled in two contiguous chunks', function() - itp('is called twice with each filled chunk', function() - write('1234567890') - read(10) - write('long string') - collect_read_chunks() - eq({ 'long s', 'tring' }, chunks) - end) - end) - - describe('with buffer filled in two contiguous chunks', function() - itp('is called twice with each filled chunk', function() - write('12345678') - read(8) - write('abcdefghijklmnopq') - collect_read_chunks() - eq({ 'abcdefgh', 'ijklmnop' }, chunks) - end) - end) - end) - - describe('RBUFFER_EACH', function() - local chars - - local function collect_chars() - rbuffer.ut_rbuffer_each(rbuf, function(c, i) - table.insert(chars, { string.char(c), tonumber(i) }) - end) - end - before_each(function() - chars = {} - end) - - describe('with empty buffer', function() - itp('is not called', function() - collect_chars() - eq({}, chars) - end) - end) - - describe('with buffer filled in two contiguous chunks', function() - itp('collects each character and index', function() - write('1234567890') - read(10) - write('long string') - collect_chars() - eq({ - { 'l', 0 }, - { 'o', 1 }, - { 'n', 2 }, - { 'g', 3 }, - { ' ', 4 }, - { 's', 5 }, - { 't', 6 }, - { 'r', 7 }, - { 'i', 8 }, - { 'n', 9 }, - { 'g', 10 }, - }, chars) - end) - end) - end) - - describe('RBUFFER_EACH_REVERSE', function() - local chars - - local function collect_chars() - rbuffer.ut_rbuffer_each_reverse(rbuf, function(c, i) - table.insert(chars, { string.char(c), tonumber(i) }) - end) - end - before_each(function() - chars = {} - end) - - describe('with empty buffer', function() - itp('is not called', function() - collect_chars() - eq({}, chars) - end) - end) - - describe('with buffer filled in two contiguous chunks', function() - itp('collects each character and index', function() - write('1234567890') - read(10) - write('long string') - collect_chars() - eq({ - { 'g', 10 }, - { 'n', 9 }, - { 'i', 8 }, - { 'r', 7 }, - { 't', 6 }, - { 's', 5 }, - { ' ', 4 }, - { 'g', 3 }, - { 'n', 2 }, - { 'o', 1 }, - { 'l', 0 }, - }, chars) - end) - end) - end) - - describe('rbuffer_cmp', function() - local function cmp(str) - local rv = rbuffer.rbuffer_cmp(rbuf, to_cstr(str), #str) - if rv == 0 then - return 0 - else - return rv / math.abs(rv) - end - end - - describe('with buffer filled in two contiguous chunks', function() - itp('compares the common longest sequence', function() - write('1234567890') - read(10) - write('long string') - eq(0, cmp('long string')) - eq(0, cmp('long strin')) - eq(-1, cmp('long striM')) - eq(1, cmp('long strio')) - eq(0, cmp('long')) - eq(-1, cmp('lonG')) - eq(1, cmp('lonh')) - end) - end) - - describe('with empty buffer', function() - itp('returns 0 since no characters are compared', function() - eq(0, cmp('')) - end) - end) - end) - - describe('rbuffer_write', function() - itp('fills the internal buffer and returns the write count', function() - eq(12, write('short string')) - eq('short string0000', inspect()) - end) - - itp('wont write beyond capacity', function() - eq(16, write('very very long string')) - eq('very very long s', inspect()) - end) - end) - - describe('rbuffer_read', function() - itp('reads what was previously written', function() - write('to read') - eq('to read', read(20)) - end) - - itp('reads nothing if the buffer is empty', function() - eq('', read(20)) - write('empty') - eq('empty', read(20)) - eq('', read(20)) - end) - end) - - describe('rbuffer_get', function() - itp('fetch the pointer at offset, wrapping if required', function() - write('1234567890') - read(10) - write('long string') - eq('l', get(0)) - eq('o', get(1)) - eq('n', get(2)) - eq('g', get(3)) - eq(' ', get(4)) - eq('s', get(5)) - eq('t', get(6)) - eq('r', get(7)) - eq('i', get(8)) - eq('n', get(9)) - eq('g', get(10)) - end) - end) - - describe('wrapping behavior', function() - itp('writing/reading wraps across the end of the internal buffer', function() - write('1234567890') - eq('1234', read(4)) - eq('5678', read(4)) - write('987654321') - eq('3214567890987654', inspect()) - eq('90987654321', read(20)) - eq('', read(4)) - write('abcdefghijklmnopqrs') - eq('nopabcdefghijklm', inspect()) - eq('abcdefghijklmnop', read(20)) - end) - end) -end) diff --git a/test/unit/statusline_spec.lua b/test/unit/statusline_spec.lua index 973d9ec992..a97a4f41d7 100644 --- a/test/unit/statusline_spec.lua +++ b/test/unit/statusline_spec.lua @@ -56,14 +56,14 @@ describe('build_stl_str_hl', function() -- @param input_stl The format string for the statusline -- @param expected_stl The expected result string for the statusline -- - -- @param arg Options can be placed in an optional dictionary as the last parameter + -- @param arg Options can be placed in an optional dict as the last parameter -- .expected_cell_count The expected number of cells build_stl_str_hl will return -- .expected_byte_length The expected byte length of the string (defaults to byte length of expected_stl) -- .file_name The name of the file to be tested (useful in %f type tests) -- .fillchar The character that will be used to fill any 'extra' space in the stl local function statusline_test(description, statusline_cell_count, input_stl, expected_stl, arg) -- arg is the optional parameter - -- so we either fill in option with arg or an empty dictionary + -- so we either fill in option with arg or an empty dict local option = arg or {} local fillchar = option.fillchar or ' ' diff --git a/test/unit/termkey_spec.lua b/test/unit/termkey_spec.lua new file mode 100644 index 0000000000..0381cfd15a --- /dev/null +++ b/test/unit/termkey_spec.lua @@ -0,0 +1,975 @@ +local t = require('test.unit.testutil') +local itp = t.gen_itp(it) +local bit = require('bit') + +--- @alias TermKeyKey {utf8: string, type: integer, modifiers: integer, code: {codepoint: integer, sym: any, number: integer}} + +--- @class termkey +--- @field TERMKEY_CANON_SPACESYMBOL integer +--- @field TERMKEY_FLAG_SPACESYMBOL integer +--- @field TERMKEY_FLAG_UTF8 integer +--- @field TERMKEY_FORMAT_ALTISMETA integer +--- @field TERMKEY_FORMAT_CARETCTRL integer +--- @field TERMKEY_FORMAT_LONGMOD integer +--- @field TERMKEY_FORMAT_LOWERMOD integer +--- @field TERMKEY_FORMAT_LOWERSPACE integer +--- @field TERMKEY_FORMAT_MOUSE_POS integer +--- @field TERMKEY_FORMAT_SPACEMOD integer +--- @field TERMKEY_FORMAT_WRAPBRACKET integer +--- @field TERMKEY_KEYMOD_ALT integer +--- @field TERMKEY_KEYMOD_CTRL integer +--- @field TERMKEY_MOUSE_DRAG integer +--- @field TERMKEY_MOUSE_PRESS integer +--- @field TERMKEY_MOUSE_RELEASE integer +--- @field TERMKEY_RES_AGAIN integer +--- @field TERMKEY_RES_KEY integer +--- @field TERMKEY_RES_NONE integer +--- @field TERMKEY_SYM_DOWN integer +--- @field TERMKEY_SYM_PAGEUP integer +--- @field TERMKEY_SYM_SPACE integer +--- @field TERMKEY_SYM_UNKNOWN integer +--- @field TERMKEY_SYM_UP integer +--- @field TERMKEY_TYPE_DCS integer +--- @field TERMKEY_TYPE_FUNCTION integer +--- @field TERMKEY_TYPE_KEYSYM integer +--- @field TERMKEY_TYPE_MODEREPORT integer +--- @field TERMKEY_TYPE_MOUSE integer +--- @field TERMKEY_TYPE_OSC integer +--- @field TERMKEY_TYPE_POSITION integer +--- @field TERMKEY_TYPE_UNICODE integer +--- @field TERMKEY_TYPE_UNKNOWN_CSI integer +--- @field termkey_canonicalise fun(any, any):any +--- @field termkey_destroy fun(any) +--- @field termkey_get_buffer_remaining fun(any):integer +--- @field termkey_get_buffer_size fun(any):integer +--- @field termkey_get_canonflags fun(any):any +--- @field termkey_get_keyname fun(any, any):any +--- @field termkey_getkey fun(any, any):any +--- @field termkey_getkey_force fun(any, any):any +--- @field termkey_interpret_csi fun(any, any, any, any, any):any +--- @field termkey_interpret_modereport fun(any, any, any, any, any):any +--- @field termkey_interpret_mouse fun(any, any, TermKeyKey, integer, integer, integer):any +--- @field termkey_interpret_position fun(any, any, any, any):any +--- @field termkey_interpret_string fun(any, TermKeyKey, any):any +--- @field termkey_lookup_keyname fun(any, any, any):any +--- @field termkey_new_abstract fun(string, integer):any +--- @field termkey_push_bytes fun(any, string, integer):integer +--- @field termkey_set_buffer_size fun(any, integer):integer +--- @field termkey_set_canonflags fun(any, any):any +--- @field termkey_set_flags fun(any, integer) +--- @field termkey_start fun(any):integer +--- @field termkey_stop fun(any):integer +--- @field termkey_strfkey fun(any, string, integer, any, any):integer +local termkey = t.cimport( + './src/nvim/tui/termkey/termkey.h', + './src/nvim/tui/termkey/termkey-internal.h', + './src/nvim/tui/termkey/termkey_defs.h', + './src/nvim/tui/termkey/driver-csi.h' +) + +describe('termkey', function() + itp('01base', function() + local tk = termkey.termkey_new_abstract('vt100', 0) + t.neq(tk, nil) + + t.eq(termkey.termkey_get_buffer_size(tk), 256) + t.eq(tk.is_started, 1) -- tk->is_started true after construction + + termkey.termkey_stop(tk) + t.neq(tk.is_started, 1) -- tk->is_started false after termkey_stop() + + termkey.termkey_start(tk) + t.eq(tk.is_started, 1) -- tk->is_started true after termkey_start() + + termkey.termkey_destroy(tk) + end) + + itp('02getkey', function() + local tk = termkey.termkey_new_abstract('vt100', 0) + local key = t.ffi.new('TermKeyKey') ---@type TermKeyKey + + t.eq(termkey.termkey_get_buffer_remaining(tk), 256) -- buffer free initially 256 + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_NONE) -- getkey yields RES_NONE when empty + + t.eq(termkey.termkey_push_bytes(tk, 'h', 1), 1) -- push_bytes returns 1 + + t.eq(termkey.termkey_get_buffer_remaining(tk), 255) -- buffer free 255 after push_bytes + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY after h + + t.eq(key.type, termkey.TERMKEY_TYPE_UNICODE) -- key.type after h + t.eq(key.code.codepoint, string.byte('h')) -- key.code.codepoint after h + t.eq(key.modifiers, 0) -- key.modifiers after h + t.eq(t.ffi.string(key.utf8), 'h') -- key.utf8 after h + + t.eq(termkey.termkey_get_buffer_remaining(tk), 256) -- buffer free 256 after getkey + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_NONE) -- getkey yields RES_NONE a second time + + termkey.termkey_push_bytes(tk, '\x01', 1) + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY after C-a + + t.eq(key.type, termkey.TERMKEY_TYPE_UNICODE) -- key.type after C-a + t.eq(key.code.codepoint, string.byte('a')) -- key.code.codepoint after C-a + t.eq(key.modifiers, termkey.TERMKEY_KEYMOD_CTRL) -- key.modifiers after C-a + + termkey.termkey_push_bytes(tk, '\033OA', 3) + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY after Up + + -- is_int(key.type, TERMKEY_TYPE_KEYSYM, "key.type after Up"); + -- is_int(key.code.sym, TERMKEY_SYM_UP, "key.code.sym after Up"); + t.eq(key.modifiers, 0) -- key.modifiers after Up + + t.eq(termkey.termkey_push_bytes(tk, '\033O', 2), 2) -- push_bytes returns 2 + + -- is_int(termkey_get_buffer_remaining(tk), 254, "buffer free 254 after partial write"); + + -- is_int(termkey_getkey(tk, &key), TERMKEY_RES_AGAIN, "getkey yields RES_AGAIN after partial write"); + + termkey.termkey_push_bytes(tk, 'C', 1) + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY after Right completion + + -- is_int(key.type, TERMKEY_TYPE_KEYSYM, "key.type after Right"); + -- is_int(key.code.sym, TERMKEY_SYM_RIGHT, "key.code.sym after Right"); + -- is_int(key.modifiers, 0, "key.modifiers after Right"); + + -- is_int(termkey_get_buffer_remaining(tk), 256, "buffer free 256 after completion"); + + termkey.termkey_push_bytes(tk, '\033[27;5u', 7) + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY after Ctrl-Escape + + -- is_int(key.type, TERMKEY_TYPE_KEYSYM, "key.type after Ctrl-Escape"); + -- is_int(key.code.sym, TERMKEY_SYM_ESCAPE, "key.code.sym after Ctrl-Escape"); + -- is_int(key.modifiers, TERMKEY_KEYMOD_CTRL, "key.modifiers after Ctrl-Escape"); + + termkey.termkey_push_bytes(tk, '\0', 1) + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY after Ctrl-Space + + t.eq(key.type, termkey.TERMKEY_TYPE_UNICODE) -- key.type after Ctrl-Space + -- t.eq(key.code.codepoint, string.byte(' ')) -- key.code.codepoint after Ctrl-Space + -- is_int(key.modifiers, TERMKEY_KEYMOD_CTRL, "key.modifiers after Ctrl-Space"); + + termkey.termkey_destroy(tk) + end) + + itp('03utf8', function() + local tk = termkey.termkey_new_abstract('vt100', termkey.TERMKEY_FLAG_UTF8) + local key = t.ffi.new('TermKeyKey') ---@type TermKeyKey + + termkey.termkey_push_bytes(tk, 'a', 1) + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY low ASCII + t.eq(key.type, termkey.TERMKEY_TYPE_UNICODE) -- key.type low ASCII + t.eq(key.code.codepoint, string.byte('a')) -- key.code.codepoint low ASCII + + -- 2-byte UTF-8 range is U+0080 to U+07FF (0xDF 0xBF) + -- However, we'd best avoid the C1 range, so we'll start at U+00A0 (0xC2 0xA0) + + termkey.termkey_push_bytes(tk, '\xC2\xA0', 2) + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 2 low + t.eq(key.type, termkey.TERMKEY_TYPE_UNICODE) -- key.type UTF-8 2 low + t.eq(key.code.codepoint, 0x00A0) -- key.code.codepoint UTF-8 2 low + + termkey.termkey_push_bytes(tk, '\xDF\xBF', 2) + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 2 high + t.eq(key.type, termkey.TERMKEY_TYPE_UNICODE) -- key.type UTF-8 2 high + t.eq(key.code.codepoint, 0x07FF) -- key.code.codepoint UTF-8 2 high + + -- 3-byte UTF-8 range is U+0800 (0xE0 0xA0 0x80) to U+FFFD (0xEF 0xBF 0xBD) + + termkey.termkey_push_bytes(tk, '\xE0\xA0\x80', 3) + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 3 low + t.eq(key.type, termkey.TERMKEY_TYPE_UNICODE) -- key.type UTF-8 3 low + t.eq(key.code.codepoint, 0x0800) -- key.code.codepoint UTF-8 3 low + + termkey.termkey_push_bytes(tk, '\xEF\xBF\xBD', 3) + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 3 high + t.eq(key.type, termkey.TERMKEY_TYPE_UNICODE) -- key.type UTF-8 3 high + t.eq(key.code.codepoint, 0xFFFD) -- key.code.codepoint UTF-8 3 high + + -- 4-byte UTF-8 range is U+10000 (0xF0 0x90 0x80 0x80) to U+10FFFF (0xF4 0x8F 0xBF 0xBF) + + termkey.termkey_push_bytes(tk, '\xF0\x90\x80\x80', 4) + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 4 low + t.eq(key.type, termkey.TERMKEY_TYPE_UNICODE) -- key.type UTF-8 4 low + t.eq(key.code.codepoint, 0x10000) -- key.code.codepoint UTF-8 4 low + + termkey.termkey_push_bytes(tk, '\xF4\x8F\xBF\xBF', 4) + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 4 high + t.eq(key.type, termkey.TERMKEY_TYPE_UNICODE) -- key.type UTF-8 4 high + t.eq(key.code.codepoint, 0x10FFFF) -- key.code.codepoint UTF-8 4 high + + -- Invalid continuations + + termkey.termkey_push_bytes(tk, '\xC2!', 2) + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 2 invalid cont + t.eq(key.code.codepoint, 0xFFFD) -- key.code.codepoint UTF-8 2 invalid cont + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 2 invalid after + t.eq(key.code.codepoint, string.byte('!')) -- key.code.codepoint UTF-8 2 invalid after + + termkey.termkey_push_bytes(tk, '\xE0!', 2) + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 3 invalid cont + t.eq(key.code.codepoint, 0xFFFD) -- key.code.codepoint UTF-8 3 invalid cont + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 3 invalid after + t.eq(key.code.codepoint, string.byte('!')) -- key.code.codepoint UTF-8 3 invalid after + + termkey.termkey_push_bytes(tk, '\xE0\xA0!', 3) + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 3 invalid cont 2 + t.eq(key.code.codepoint, 0xFFFD) -- key.code.codepoint UTF-8 3 invalid cont 2 + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 3 invalid after + t.eq(key.code.codepoint, string.byte('!')) -- key.code.codepoint UTF-8 3 invalid after + + termkey.termkey_push_bytes(tk, '\xF0!', 2) + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 4 invalid cont + t.eq(key.code.codepoint, 0xFFFD) -- key.code.codepoint UTF-8 4 invalid cont + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 4 invalid after + t.eq(key.code.codepoint, string.byte('!')) -- key.code.codepoint UTF-8 4 invalid after + + termkey.termkey_push_bytes(tk, '\xF0\x90!', 3) + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 4 invalid cont 2 + t.eq(key.code.codepoint, 0xFFFD) -- key.code.codepoint UTF-8 4 invalid cont 2 + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 4 invalid after + t.eq(key.code.codepoint, string.byte('!')) -- key.code.codepoint UTF-8 4 invalid after + + termkey.termkey_push_bytes(tk, '\xF0\x90\x80!', 4) + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 4 invalid cont 3 + t.eq(key.code.codepoint, 0xFFFD) -- key.code.codepoint UTF-8 4 invalid cont 3 + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 4 invalid after + t.eq(key.code.codepoint, string.byte('!')) -- key.code.codepoint UTF-8 4 invalid after + + -- Partials + + termkey.termkey_push_bytes(tk, '\xC2', 1) + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_AGAIN) -- getkey yields RES_AGAIN UTF-8 2 partial + + termkey.termkey_push_bytes(tk, '\xA0', 1) + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 2 partial + t.eq(key.code.codepoint, 0x00A0) -- key.code.codepoint UTF-8 2 partial + + termkey.termkey_push_bytes(tk, '\xE0', 1) + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_AGAIN) -- getkey yields RES_AGAIN UTF-8 3 partial + + termkey.termkey_push_bytes(tk, '\xA0', 1) + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_AGAIN) -- getkey yields RES_AGAIN UTF-8 3 partial + + termkey.termkey_push_bytes(tk, '\x80', 1) + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 3 partial + t.eq(key.code.codepoint, 0x0800) -- key.code.codepoint UTF-8 3 partial + + termkey.termkey_push_bytes(tk, '\xF0', 1) + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_AGAIN) -- getkey yields RES_AGAIN UTF-8 4 partial + + termkey.termkey_push_bytes(tk, '\x90', 1) + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_AGAIN) -- getkey yields RES_AGAIN UTF-8 4 partial + + termkey.termkey_push_bytes(tk, '\x80', 1) + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_AGAIN) -- getkey yields RES_AGAIN UTF-8 4 partial + + termkey.termkey_push_bytes(tk, '\x80', 1) + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY UTF-8 4 partial + t.eq(key.code.codepoint, 0x10000) -- key.code.codepoint UTF-8 4 partial + + termkey.termkey_destroy(tk) + end) + + itp('04flags', function() + local tk = termkey.termkey_new_abstract('vt100', 0) + local key = t.ffi.new('TermKeyKey') ---@type TermKeyKey + + termkey.termkey_push_bytes(tk, ' ', 1) + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY after space + + t.eq(key.type, termkey.TERMKEY_TYPE_UNICODE) -- key.type after space + t.eq(key.code.codepoint, string.byte(' ')) -- key.code.codepoint after space + t.eq(key.modifiers, 0) -- key.modifiers after space + + termkey.termkey_set_flags(tk, termkey.TERMKEY_FLAG_SPACESYMBOL) + + termkey.termkey_push_bytes(tk, ' ', 1) + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY after space + + t.eq(key.type, termkey.TERMKEY_TYPE_KEYSYM) -- key.type after space with FLAG_SPACESYMBOL + t.eq(key.code.sym, termkey.TERMKEY_SYM_SPACE) -- key.code.sym after space with FLAG_SPACESYMBOL + t.eq(key.modifiers, 0) -- key.modifiers after space with FLAG_SPACESYMBOL + + termkey.termkey_destroy(tk) + end) + + itp('06buffer', function() + local tk = termkey.termkey_new_abstract('vt100', 0) + local key = t.ffi.new('TermKeyKey') ---@type TermKeyKey + + t.eq(termkey.termkey_get_buffer_remaining(tk), 256) -- buffer free initially 256 + t.eq(termkey.termkey_get_buffer_size(tk), 256) -- buffer size initially 256 + + t.eq(termkey.termkey_push_bytes(tk, 'h', 1), 1) -- push_bytes returns 1 + + t.eq(termkey.termkey_get_buffer_remaining(tk), 255) -- buffer free 255 after push_bytes + t.eq(termkey.termkey_get_buffer_size(tk), 256) -- buffer size 256 after push_bytes + + t.eq(not not termkey.termkey_set_buffer_size(tk, 512), true) -- buffer set size OK + + t.eq(termkey.termkey_get_buffer_remaining(tk), 511) -- buffer free 511 after push_bytes + t.eq(termkey.termkey_get_buffer_size(tk), 512) -- buffer size 512 after push_bytes + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- buffered key still usable after resize + + termkey.termkey_destroy(tk) + end) + + local function termkey_keyname2sym(tk, keyname) + local sym = t.ffi.new('TermKeySym[1]') + local endp = termkey.termkey_lookup_keyname(tk, keyname, sym) + if endp == nil then + return termkey.TERMKEY_SYM_UNKNOWN + end + return sym + end + + itp('10keyname', function() + local tk = termkey.termkey_new_abstract('vt100', 0) + + local sym = termkey_keyname2sym(tk, 'SomeUnknownKey') + t.eq(sym, termkey.TERMKEY_SYM_UNKNOWN) -- keyname2sym SomeUnknownKey + + sym = termkey_keyname2sym(tk, 'Space') + t.eq(sym[0], termkey.TERMKEY_SYM_SPACE) -- keyname2sym Space + + local _end = termkey.termkey_lookup_keyname(tk, 'Up', sym) + t.neq(_end, nil) -- termkey_get_keyname Up returns non-NULL + t.eq(t.ffi.string(_end), '') -- termkey_get_keyname Up return points at endofstring + t.eq(sym[0], termkey.TERMKEY_SYM_UP) -- termkey_get_keyname Up yields Up symbol + + _end = termkey.termkey_lookup_keyname(tk, 'DownMore', sym) + t.neq(_end, nil) -- termkey_get_keyname DownMore returns non-NULL + t.eq(t.ffi.string(_end), 'More') -- termkey_get_keyname DownMore return points at More + t.eq(sym[0], termkey.TERMKEY_SYM_DOWN) -- termkey_get_keyname DownMore yields Down symbol + + _end = termkey.termkey_lookup_keyname(tk, 'SomeUnknownKey', sym) + t.eq(_end, nil) -- termkey_get_keyname SomeUnknownKey returns NULL + + t.eq(t.ffi.string(termkey.termkey_get_keyname(tk, termkey.TERMKEY_SYM_SPACE)), 'Space') -- "get_keyname SPACE"); + + termkey.termkey_destroy(tk) + end) + + itp('11strfkey', function() + local tk = termkey.termkey_new_abstract('vt100', 0) + ---@type TermKeyKey + local key = t.ffi.new( + 'TermKeyKey', + { type = termkey.TERMKEY_TYPE_UNICODE, code = { codepoint = string.byte('A') } } + ) + local buffer = t.ffi.new('char[16]') + + local len = termkey.termkey_strfkey(tk, buffer, t.ffi.sizeof(buffer), key, 0) + t.eq(len, 1) -- length for unicode/A/0 + t.eq(t.ffi.string(buffer), 'A') -- buffer for unicode/A/0 + + len = termkey.termkey_strfkey( + tk, + buffer, + t.ffi.sizeof(buffer), + key, + termkey.TERMKEY_FORMAT_WRAPBRACKET + ) + t.eq(len, 1) -- length for unicode/A/0 wrapbracket + t.eq(t.ffi.string(buffer), 'A') -- buffer for unicode/A/0 wrapbracket + + ---@type TermKeyKey + key = t.ffi.new('TermKeyKey', { + type = termkey.TERMKEY_TYPE_UNICODE, + code = { codepoint = string.byte('b') }, + modifiers = termkey.TERMKEY_KEYMOD_CTRL, + }) + + len = termkey.termkey_strfkey(tk, buffer, t.ffi.sizeof(buffer), key, 0) + t.eq(len, 3) -- length for unicode/b/CTRL + t.eq(t.ffi.string(buffer), 'C-b') -- buffer for unicode/b/CTRL + + len = + termkey.termkey_strfkey(tk, buffer, t.ffi.sizeof(buffer), key, termkey.TERMKEY_FORMAT_LONGMOD) + t.eq(len, 6) -- length for unicode/b/CTRL longmod + t.eq(t.ffi.string(buffer), 'Ctrl-b') -- buffer for unicode/b/CTRL longmod + + len = termkey.termkey_strfkey( + tk, + buffer, + t.ffi.sizeof(buffer), + key, + bit.bor(termkey.TERMKEY_FORMAT_LONGMOD, termkey.TERMKEY_FORMAT_SPACEMOD) + ) + t.eq(len, 6) -- length for unicode/b/CTRL longmod|spacemod + t.eq(t.ffi.string(buffer), 'Ctrl b') -- buffer for unicode/b/CTRL longmod|spacemod + + len = termkey.termkey_strfkey( + tk, + buffer, + t.ffi.sizeof(buffer), + key, + bit.bor(termkey.TERMKEY_FORMAT_LONGMOD, termkey.TERMKEY_FORMAT_LOWERMOD) + ) + t.eq(len, 6) -- length for unicode/b/CTRL longmod|lowermod + t.eq(t.ffi.string(buffer), 'ctrl-b') -- buffer for unicode/b/CTRL longmod|lowermod + + len = termkey.termkey_strfkey( + tk, + buffer, + t.ffi.sizeof(buffer), + key, + bit.bor( + termkey.TERMKEY_FORMAT_LONGMOD, + termkey.TERMKEY_FORMAT_SPACEMOD, + termkey.TERMKEY_FORMAT_LOWERMOD + ) + ) + t.eq(len, 6) -- length for unicode/b/CTRL longmod|spacemod|lowermode + t.eq(t.ffi.string(buffer), 'ctrl b') -- buffer for unicode/b/CTRL longmod|spacemod|lowermode + + len = termkey.termkey_strfkey( + tk, + buffer, + t.ffi.sizeof(buffer), + key, + termkey.TERMKEY_FORMAT_CARETCTRL + ) + t.eq(len, 2) -- length for unicode/b/CTRL caretctrl + t.eq(t.ffi.string(buffer), '^B') -- buffer for unicode/b/CTRL caretctrl + + len = termkey.termkey_strfkey( + tk, + buffer, + t.ffi.sizeof(buffer), + key, + termkey.TERMKEY_FORMAT_WRAPBRACKET + ) + t.eq(len, 5) -- length for unicode/b/CTRL wrapbracket + t.eq(t.ffi.string(buffer), '<C-b>') -- buffer for unicode/b/CTRL wrapbracket + + ---@type TermKeyKey + key = t.ffi.new('TermKeyKey', { + type = termkey.TERMKEY_TYPE_UNICODE, + code = { codepoint = string.byte('c') }, + modifiers = termkey.TERMKEY_KEYMOD_ALT, + }) + + len = termkey.termkey_strfkey(tk, buffer, t.ffi.sizeof(buffer), key, 0) + t.eq(len, 3) -- length for unicode/c/ALT + t.eq(t.ffi.string(buffer), 'A-c') -- buffer for unicode/c/ALT + + len = + termkey.termkey_strfkey(tk, buffer, t.ffi.sizeof(buffer), key, termkey.TERMKEY_FORMAT_LONGMOD) + t.eq(len, 5) -- length for unicode/c/ALT longmod + t.eq(t.ffi.string(buffer), 'Alt-c') -- buffer for unicode/c/ALT longmod + + len = termkey.termkey_strfkey( + tk, + buffer, + t.ffi.sizeof(buffer), + key, + termkey.TERMKEY_FORMAT_ALTISMETA + ) + t.eq(len, 3) -- length for unicode/c/ALT altismeta + t.eq(t.ffi.string(buffer), 'M-c') -- buffer for unicode/c/ALT altismeta + + len = termkey.termkey_strfkey( + tk, + buffer, + t.ffi.sizeof(buffer), + key, + bit.bor(termkey.TERMKEY_FORMAT_LONGMOD, termkey.TERMKEY_FORMAT_ALTISMETA) + ) + t.eq(len, 6) -- length for unicode/c/ALT longmod|altismeta + t.eq(t.ffi.string(buffer), 'Meta-c') -- buffer for unicode/c/ALT longmod|altismeta + + ---@type TermKeyKey + key = t.ffi.new( + 'TermKeyKey', + { type = termkey.TERMKEY_TYPE_KEYSYM, code = { sym = termkey.TERMKEY_SYM_UP } } + ) + + len = termkey.termkey_strfkey(tk, buffer, t.ffi.sizeof(buffer), key, 0) + t.eq(len, 2) -- length for sym/Up/0 + t.eq(t.ffi.string(buffer), 'Up') -- buffer for sym/Up/0 + + len = termkey.termkey_strfkey( + tk, + buffer, + t.ffi.sizeof(buffer), + key, + termkey.TERMKEY_FORMAT_WRAPBRACKET + ) + t.eq(len, 4) -- length for sym/Up/0 wrapbracket + t.eq(t.ffi.string(buffer), '<Up>') -- buffer for sym/Up/0 wrapbracket + + ---@type TermKeyKey + key = t.ffi.new( + 'TermKeyKey', + { type = termkey.TERMKEY_TYPE_KEYSYM, code = { sym = termkey.TERMKEY_SYM_PAGEUP } } + ) + + len = termkey.termkey_strfkey(tk, buffer, t.ffi.sizeof(buffer), key, 0) + t.eq(len, 6) -- length for sym/PageUp/0 + t.eq(t.ffi.string(buffer), 'PageUp') -- buffer for sym/PageUp/0 + + len = termkey.termkey_strfkey( + tk, + buffer, + t.ffi.sizeof(buffer), + key, + termkey.TERMKEY_FORMAT_LOWERSPACE + ) + t.eq(len, 7) -- length for sym/PageUp/0 lowerspace + t.eq(t.ffi.string(buffer), 'page up') -- buffer for sym/PageUp/0 lowerspace + + -- If size of buffer is too small, strfkey should return something consistent + len = termkey.termkey_strfkey(tk, buffer, 4, key, 0) + t.eq(len, 6) -- length for sym/PageUp/0 + t.eq(t.ffi.string(buffer), 'Pag') -- buffer of len 4 for sym/PageUp/0 + + len = termkey.termkey_strfkey(tk, buffer, 4, key, termkey.TERMKEY_FORMAT_LOWERSPACE) + t.eq(len, 7) -- length for sym/PageUp/0 lowerspace + t.eq(t.ffi.string(buffer), 'pag') -- buffer of len 4 for sym/PageUp/0 lowerspace + + key = t.ffi.new('TermKeyKey', { type = termkey.TERMKEY_TYPE_FUNCTION, code = { number = 5 } }) ---@type TermKeyKey + + len = termkey.termkey_strfkey(tk, buffer, t.ffi.sizeof(buffer), key, 0) + t.eq(len, 2) -- length for func/5/0 + t.eq(t.ffi.string(buffer), 'F5') -- buffer for func/5/0 + + len = termkey.termkey_strfkey( + tk, + buffer, + t.ffi.sizeof(buffer), + key, + termkey.TERMKEY_FORMAT_WRAPBRACKET + ) + t.eq(len, 4) -- length for func/5/0 wrapbracket + t.eq(t.ffi.string(buffer), '<F5>') -- buffer for func/5/0 wrapbracket + + len = termkey.termkey_strfkey( + tk, + buffer, + t.ffi.sizeof(buffer), + key, + termkey.TERMKEY_FORMAT_LOWERSPACE + ) + t.eq(len, 2) -- length for func/5/0 lowerspace + t.eq(t.ffi.string(buffer), 'f5') -- buffer for func/5/0 lowerspace + + termkey.termkey_destroy(tk) + end) + + itp('13cmpkey', function() + local function termkey_keycmp(tk, key1, key2) + termkey.termkey_canonicalise(tk, key1) + termkey.termkey_canonicalise(tk, key2) + + if key1.type ~= key2.type then + return key1.type - key2.type + end + + if key1.type == termkey.TERMKEY_TYPE_UNICODE then + if key1.code.codepoint ~= key2.code.codepoint then + return key1.code.codepoint - key2.code.codepoint + end + end + + return key1.modifiers - key2.modifiers + end + + local tk = termkey.termkey_new_abstract('vt100', 0) + ---@type TermKeyKey + local key1 = t.ffi.new('TermKeyKey', { + type = termkey.TERMKEY_TYPE_UNICODE, + code = { codepoint = string.byte('A') }, + modifiers = 0, + }) + ---@type TermKeyKey + local key2 = t.ffi.new('TermKeyKey', { + type = termkey.TERMKEY_TYPE_UNICODE, + code = { codepoint = string.byte('A') }, + modifiers = 0, + }) + + t.eq(termkey_keycmp(tk, key1, key1), 0) -- cmpkey same structure + t.eq(termkey_keycmp(tk, key1, key2), 0) -- cmpkey identical structure + + key2.modifiers = termkey.TERMKEY_KEYMOD_CTRL + + t.eq(termkey_keycmp(tk, key1, key2) < 0, true) -- cmpkey orders CTRL after nomod + t.eq(termkey_keycmp(tk, key2, key1) > 0, true) -- cmpkey orders nomod before CTRL + + key2.code.codepoint = string.byte('B') + key2.modifiers = 0 + + t.eq(termkey_keycmp(tk, key1, key2) < 0, true) -- cmpkey orders 'B' after 'A' + t.eq(termkey_keycmp(tk, key2, key1) > 0, true) -- cmpkey orders 'A' before 'B' + + key1.modifiers = termkey.TERMKEY_KEYMOD_CTRL + + t.eq(termkey_keycmp(tk, key1, key2) < 0, true) -- cmpkey orders nomod 'B' after CTRL 'A' + t.eq(termkey_keycmp(tk, key2, key1) > 0, true) -- cmpkey orders CTRL 'A' before nomod 'B' + + key2.type = termkey.TERMKEY_TYPE_KEYSYM + key2.code.sym = termkey.TERMKEY_SYM_UP + + t.eq(termkey_keycmp(tk, key1, key2) < 0, true) -- cmpkey orders KEYSYM after UNICODE + t.eq(termkey_keycmp(tk, key2, key1) > 0, true) -- cmpkey orders UNICODE before KEYSYM + + key1.type = termkey.TERMKEY_TYPE_KEYSYM + key1.code.sym = termkey.TERMKEY_SYM_SPACE + key1.modifiers = 0 + key2.type = termkey.TERMKEY_TYPE_UNICODE + key2.code.codepoint = string.byte(' ') + key2.modifiers = 0 + + t.eq(termkey_keycmp(tk, key1, key2), 0) -- cmpkey considers KEYSYM/SPACE and UNICODE/SP identical + + termkey.termkey_set_canonflags( + tk, + bit.bor(termkey.termkey_get_canonflags(tk), termkey.TERMKEY_CANON_SPACESYMBOL) + ) + t.eq(termkey_keycmp(tk, key1, key2), 0) -- "cmpkey considers KEYSYM/SPACE and UNICODE/SP identical under SPACESYMBOL"); + + termkey.termkey_destroy(tk) + end) + + itp('30mouse', function() + local tk = termkey.termkey_new_abstract('vt100', 0) + local key = t.ffi.new('TermKeyKey', { type = -1 }) ---@type TermKeyKey + local ev = t.ffi.new('TermKeyMouseEvent[1]') + local button = t.ffi.new('int[1]') + local line = t.ffi.new('int[1]') + local col = t.ffi.new('int[1]') + local buffer = t.ffi.new('char[32]') + + termkey.termkey_push_bytes(tk, '\x1b[M !!', 6) + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY for mouse press + + t.eq(key.type, termkey.TERMKEY_TYPE_MOUSE) -- key.type for mouse press + + t.eq(termkey.termkey_interpret_mouse(tk, key, ev, button, line, col), termkey.TERMKEY_RES_KEY) -- interpret_mouse yields RES_KEY + + t.eq(ev[0], termkey.TERMKEY_MOUSE_PRESS) -- mouse event for press + t.eq(button[0], 1) -- mouse button for press + t.eq(line[0], 1) -- mouse line for press + t.eq(col[0], 1) -- mouse column for press + t.eq(key.modifiers, 0) -- modifiers for press + + local len = termkey.termkey_strfkey(tk, buffer, t.ffi.sizeof(buffer), key, 0) + t.eq(len, 13) -- string length for press + t.eq(t.ffi.string(buffer), 'MousePress(1)') -- string buffer for press + + len = termkey.termkey_strfkey( + tk, + buffer, + t.ffi.sizeof(buffer), + key, + termkey.TERMKEY_FORMAT_MOUSE_POS + ) + t.eq(len, 21) -- string length for press + t.eq(t.ffi.string(buffer), 'MousePress(1) @ (1,1)') -- string buffer for press + + termkey.termkey_push_bytes(tk, '\x1b[M@"!', 6) + + termkey.termkey_getkey(tk, key) + t.eq(termkey.termkey_interpret_mouse(tk, key, ev, button, line, col), termkey.TERMKEY_RES_KEY) -- interpret_mouse yields RES_KEY + + t.eq(ev[0], termkey.TERMKEY_MOUSE_DRAG) -- mouse event for drag + t.eq(button[0], 1) -- mouse button for drag + t.eq(line[0], 1) -- mouse line for drag + t.eq(col[0], 2) -- mouse column for drag + t.eq(key.modifiers, 0) -- modifiers for press + + termkey.termkey_push_bytes(tk, '\x1b[M##!', 6) + + termkey.termkey_getkey(tk, key) + t.eq(termkey.termkey_interpret_mouse(tk, key, ev, button, line, col), termkey.TERMKEY_RES_KEY) -- interpret_mouse yields RES_KEY + + t.eq(ev[0], termkey.TERMKEY_MOUSE_RELEASE) -- mouse event for release + t.eq(line[0], 1) -- mouse line for release + t.eq(col[0], 3) -- mouse column for release + t.eq(key.modifiers, 0) -- modifiers for press + + termkey.termkey_push_bytes(tk, '\x1b[M0++', 6) + + termkey.termkey_getkey(tk, key) + t.eq(termkey.termkey_interpret_mouse(tk, key, ev, button, line, col), termkey.TERMKEY_RES_KEY) -- interpret_mouse yields RES_KEY + + t.eq(ev[0], termkey.TERMKEY_MOUSE_PRESS) -- mouse event for Ctrl-press + t.eq(button[0], 1) -- mouse button for Ctrl-press + t.eq(line[0], 11) -- mouse line for Ctrl-press + t.eq(col[0], 11) -- mouse column for Ctrl-press + t.eq(key.modifiers, termkey.TERMKEY_KEYMOD_CTRL) -- modifiers for Ctrl-press + + len = termkey.termkey_strfkey(tk, buffer, t.ffi.sizeof(buffer), key, 0) + t.eq(len, 15) -- string length for Ctrl-press + t.eq(t.ffi.string(buffer), 'C-MousePress(1)') -- string buffer for Ctrl-press + + termkey.termkey_push_bytes(tk, '\x1b[M`!!', 6) + + termkey.termkey_getkey(tk, key) + t.eq(termkey.termkey_interpret_mouse(tk, key, ev, button, line, col), termkey.TERMKEY_RES_KEY) -- interpret_mouse yields RES_KEY + + t.eq(ev[0], termkey.TERMKEY_MOUSE_PRESS) -- mouse event for wheel down + t.eq(button[0], 4) -- mouse button for wheel down + + termkey.termkey_push_bytes(tk, '\x1b[Mb!!', 6) + + termkey.termkey_getkey(tk, key) + t.eq(termkey.termkey_interpret_mouse(tk, key, ev, button, line, col), termkey.TERMKEY_RES_KEY) -- interpret_mouse yields RES_KEY + + t.eq(ev[0], termkey.TERMKEY_MOUSE_PRESS) -- mouse event for wheel left + t.eq(button[0], 6) -- mouse button for wheel left + + -- rxvt protocol + termkey.termkey_push_bytes(tk, '\x1b[0;20;20M', 10) + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY for mouse press rxvt protocol + + t.eq(key.type, termkey.TERMKEY_TYPE_MOUSE) -- key.type for mouse press rxvt protocol + + t.eq(termkey.termkey_interpret_mouse(tk, key, ev, button, line, col), termkey.TERMKEY_RES_KEY) -- interpret_mouse yields RES_KEY + + t.eq(ev[0], termkey.TERMKEY_MOUSE_PRESS) -- mouse event for press rxvt protocol + t.eq(button[0], 1) -- mouse button for press rxvt protocol + t.eq(line[0], 20) -- mouse line for press rxvt protocol + t.eq(col[0], 20) -- mouse column for press rxvt protocol + t.eq(key.modifiers, 0) -- modifiers for press rxvt protocol + + termkey.termkey_push_bytes(tk, '\x1b[3;20;20M', 10) + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY for mouse release rxvt protocol + + t.eq(key.type, termkey.TERMKEY_TYPE_MOUSE) -- key.type for mouse release rxvt protocol + + t.eq(termkey.termkey_interpret_mouse(tk, key, ev, button, line, col), termkey.TERMKEY_RES_KEY) -- interpret_mouse yields RES_KEY + + t.eq(ev[0], termkey.TERMKEY_MOUSE_RELEASE) -- mouse event for release rxvt protocol + t.eq(line[0], 20) -- mouse line for release rxvt protocol + t.eq(col[0], 20) -- mouse column for release rxvt protocol + t.eq(key.modifiers, 0) -- modifiers for release rxvt protocol + + -- SGR protocol + termkey.termkey_push_bytes(tk, '\x1b[<0;30;30M', 11) + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY for mouse press SGR encoding + + t.eq(key.type, termkey.TERMKEY_TYPE_MOUSE) -- key.type for mouse press SGR encoding + + t.eq(termkey.termkey_interpret_mouse(tk, key, ev, button, line, col), termkey.TERMKEY_RES_KEY) -- interpret_mouse yields RES_KEY + + t.eq(ev[0], termkey.TERMKEY_MOUSE_PRESS) -- mouse event for press SGR + t.eq(button[0], 1) -- mouse button for press SGR + t.eq(line[0], 30) -- mouse line for press SGR + t.eq(col[0], 30) -- mouse column for press SGR + t.eq(key.modifiers, 0) -- modifiers for press SGR + + termkey.termkey_push_bytes(tk, '\x1b[<0;30;30m', 11) + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY for mouse release SGR encoding + + t.eq(key.type, termkey.TERMKEY_TYPE_MOUSE) -- key.type for mouse release SGR encoding + + t.eq(termkey.termkey_interpret_mouse(tk, key, ev, button, line, col), termkey.TERMKEY_RES_KEY) -- interpret_mouse yields RES_KEY + + t.eq(ev[0], termkey.TERMKEY_MOUSE_RELEASE) -- mouse event for release SGR + + termkey.termkey_push_bytes(tk, '\x1b[<0;500;300M', 13) + + termkey.termkey_getkey(tk, key) + termkey.termkey_interpret_mouse(tk, key, ev, button, line, col) + + t.eq(line[0], 300) -- mouse line for press SGR wide + t.eq(col[0], 500) -- mouse column for press SGR wide + + termkey.termkey_destroy(tk) + end) + + itp('31position', function() + local tk = termkey.termkey_new_abstract('vt100', 0) + local key = t.ffi.new('TermKeyKey') ---@type TermKeyKey + local line = t.ffi.new('int[1]') + local col = t.ffi.new('int[1]') + + termkey.termkey_push_bytes(tk, '\x1b[?15;7R', 8) + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY for position report + + t.eq(key.type, termkey.TERMKEY_TYPE_POSITION) -- key.type for position report + + t.eq(termkey.termkey_interpret_position(tk, key, line, col), termkey.TERMKEY_RES_KEY) -- interpret_position yields RES_KEY + + t.eq(line[0], 15) -- line for position report + t.eq(col[0], 7) -- column for position report + + -- A plain CSI R is likely to be <F3> though. + -- This is tricky :/ + + termkey.termkey_push_bytes(tk, '\x1b[R', 3) + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY for <F3> + + t.eq(key.type, termkey.TERMKEY_TYPE_FUNCTION) -- key.type for <F3> + t.eq(key.code.number, 3) -- key.code.number for <F3> + + termkey.termkey_destroy(tk) + end) + + itp('32modereport', function() + local tk = termkey.termkey_new_abstract('vt100', 0) + local key = t.ffi.new('TermKeyKey') ---@type TermKeyKey + local initial = t.ffi.new('int[1]') + local mode = t.ffi.new('int[1]') + local value = t.ffi.new('int[1]') + + termkey.termkey_push_bytes(tk, '\x1b[?1;2$y', 8) + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY for mode report + + t.eq(key.type, termkey.TERMKEY_TYPE_MODEREPORT) -- key.type for mode report + + t.eq( + termkey.termkey_interpret_modereport(tk, key, initial, mode, value), + termkey.TERMKEY_RES_KEY + ) -- interpret_modereoprt yields RES_KEY + + t.eq(initial[0], 63) -- initial indicator from mode report + t.eq(mode[0], 1) -- mode number from mode report + t.eq(value[0], 2) -- mode value from mode report + + termkey.termkey_push_bytes(tk, '\x1b[4;1$y', 7) + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY for mode report + + t.eq(key.type, termkey.TERMKEY_TYPE_MODEREPORT) -- key.type for mode report + + t.eq( + termkey.termkey_interpret_modereport(tk, key, initial, mode, value), + termkey.TERMKEY_RES_KEY + ) -- interpret_modereoprt yields RES_KEY + + t.eq(initial[0], 0) -- initial indicator from mode report + t.eq(mode[0], 4) -- mode number from mode report + t.eq(value[0], 1) -- mode value from mode report + + termkey.termkey_destroy(tk) + end) + + itp('38csi', function() + local tk = termkey.termkey_new_abstract('vt100', 0) + local key = t.ffi.new('TermKeyKey') ---@type TermKeyKey + local args = t.ffi.new('TermKeyCsiParam[16]') + local nargs = t.ffi.new('size_t[1]') + local command = t.ffi.new('unsigned[1]') + + termkey.termkey_push_bytes(tk, '\x1b[5;25v', 7) + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY for CSI v + + t.eq(key.type, termkey.TERMKEY_TYPE_UNKNOWN_CSI) -- key.type for unknown CSI + + t.eq(termkey.termkey_interpret_csi(tk, key, args, nargs, command), termkey.TERMKEY_RES_KEY) -- interpret_csi yields RES_KEY + + t.eq(nargs[0], 2) -- nargs for unknown CSI + -- t.eq(args[0], 5) -- args[0] for unknown CSI + -- t.eq(args[1], 25) -- args[1] for unknown CSI + t.eq(command[0], 118) -- command for unknown CSI + + termkey.termkey_push_bytes(tk, '\x1b[?w', 4) + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY for CSI ? w + t.eq(key.type, termkey.TERMKEY_TYPE_UNKNOWN_CSI) -- key.type for unknown CSI + t.eq(termkey.termkey_interpret_csi(tk, key, args, nargs, command), termkey.TERMKEY_RES_KEY) -- interpret_csi yields RES_KEY + t.eq(command[0], bit.bor(bit.lshift(63, 8), 119)) -- command for unknown CSI + + termkey.termkey_push_bytes(tk, '\x1b[?$x', 5) + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY for CSI ? $x + t.eq(key.type, termkey.TERMKEY_TYPE_UNKNOWN_CSI) -- key.type for unknown CSI + t.eq(termkey.termkey_interpret_csi(tk, key, args, nargs, command), termkey.TERMKEY_RES_KEY) -- interpret_csi yields RES_KEY + t.eq(command[0], bit.bor(bit.lshift(36, 16), bit.lshift(63, 8), 120)) -- command for unknown CSI + + termkey.termkey_destroy(tk) + end) + + itp('39dcs', function() + local tk = termkey.termkey_new_abstract('xterm', 0) + local key = t.ffi.new('TermKeyKey') ---@type TermKeyKey + + -- 7bit DCS + termkey.termkey_push_bytes(tk, '\x1bP1$r1 q\x1b\\', 10) + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY for DCS + + t.eq(key.type, termkey.TERMKEY_TYPE_DCS) -- key.type for DCS + t.eq(key.modifiers, 0) -- key.modifiers for DCS + + local str = t.ffi.new('const char*[1]') + t.eq(termkey.termkey_interpret_string(tk, key, str), termkey.TERMKEY_RES_KEY) -- termkey_interpret_string() gives string + t.eq(t.ffi.string(str[0]), '1$r1 q') -- termkey_interpret_string() yields correct string + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_NONE) -- getkey again yields RES_NONE + + -- 8bit DCS + termkey.termkey_push_bytes(tk, '\x901$r2 q\x9c', 8) + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY for DCS + + t.eq(key.type, termkey.TERMKEY_TYPE_DCS) -- key.type for DCS + t.eq(key.modifiers, 0) -- key.modifiers for DCS + + t.eq(termkey.termkey_interpret_string(tk, key, str), termkey.TERMKEY_RES_KEY) -- "termkey_interpret_string() gives string"); + t.eq(t.ffi.string(str[0]), '1$r2 q') -- "termkey_interpret_string() yields correct string"); + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_NONE) -- "getkey again yields RES_NONE"); + + -- 7bit OSC + termkey.termkey_push_bytes(tk, '\x1b]15;abc\x1b\\', 10) + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_KEY) -- getkey yields RES_KEY for OSC + + t.eq(key.type, termkey.TERMKEY_TYPE_OSC) -- key.type for OSC + t.eq(key.modifiers, 0) -- key.modifiers for OSC + + t.eq(termkey.termkey_interpret_string(tk, key, str), termkey.TERMKEY_RES_KEY) -- "termkey_interpret_string() gives string"); + t.eq(t.ffi.string(str[0]), '15;abc') -- "termkey_interpret_string() yields correct string"); + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_NONE) -- getkey again yields RES_NONE + + -- False alarm + termkey.termkey_push_bytes(tk, '\x1bP', 2) + + t.eq(termkey.termkey_getkey(tk, key), termkey.TERMKEY_RES_AGAIN) -- getkey yields RES_AGAIN for false alarm + + t.eq(termkey.termkey_getkey_force(tk, key), termkey.TERMKEY_RES_KEY) -- getkey_force yields RES_KEY for false alarm + + t.eq(key.type, termkey.TERMKEY_TYPE_UNICODE) -- key.type for false alarm + t.eq(key.code.codepoint, string.byte('P')) -- key.code.codepoint for false alarm + t.eq(key.modifiers, termkey.TERMKEY_KEYMOD_ALT) -- key.modifiers for false alarm + + termkey.termkey_destroy(tk) + end) +end) |