diff options
Diffstat (limited to 'test')
226 files changed, 19018 insertions, 9029 deletions
diff --git a/test/README.md b/test/README.md index cc630cb8bf..a67040e68c 100644 --- a/test/README.md +++ b/test/README.md @@ -91,25 +91,33 @@ or: Debugging tests --------------- +- Each test gets a test id which looks like "T123". This also appears in the + log file. Child processes spawned from a test appear in the logs with the + *parent* name followed by "/c". Example: + ``` + DBG 2022-06-15T18:37:45.226 T57.58016.0 UI: flush + DBG 2022-06-15T18:37:45.226 T57.58016.0 inbuf_poll:442: blocking... events_enabled=0 events_pending=0 + 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 + ``` - You can set `$GDB` to [run tests under gdbserver](https://github.com/neovim/neovim/pull/1527). And if `$VALGRIND` is set it will pass `--vgdb=yes` to valgrind instead of starting gdbserver directly. -- Hanging tests often happen due to unexpected `:h press-enter` prompts. The +- Hanging tests can happen due to unexpected "press-enter" prompts. The default screen width is 50 columns. Commands that try to print lines longer than 50 columns in the command-line, e.g. `:edit very...long...path`, will - trigger the prompt. In this case, a shorter path or `:silent edit` should be - used. + trigger the prompt. Try using a shorter path, or `:silent edit`. - If you can't figure out what is going on, try to visualize the screen. Put this at the beginning of your test: - - ```lua - local Screen = require('test.functional.ui.screen') - local screen = Screen.new() - screen:attach() - ``` - - Afterwards, put `screen:snapshot_util()` at any position in your test. See the - comment at the top of `test/functional/ui/screen.lua` for more. + ```lua + local Screen = require('test.functional.ui.screen') + local screen = Screen.new() + screen:attach() + ``` + Then put `screen:snapshot_util()` anywhere in your test. See the comments in + `test/functional/ui/screen.lua` for more info. Filtering Tests --------------- @@ -247,12 +255,17 @@ Number; !must be defined to function properly): - `BUSTED_ARGS` (F) (U): arguments forwarded to `busted`. +- `CC` (U) (S): specifies which C compiler to use to preprocess files. + Currently only compilers with gcc-compatible arguments are supported. + - `GDB` (F) (D): makes nvim instances to be run under `gdbserver`. It will be accessible on `localhost:7777`: use `gdb build/bin/nvim`, type `target remote :7777` inside. - `GDBSERVER_PORT` (F) (I): overrides port used for `GDB`. +- `LOG_DIR` (FU) (S!): specifies where to seek for valgrind and ASAN log files. + - `VALGRIND` (F) (D): makes nvim instances to be run under `valgrind`. Log files are named `valgrind-%p.log` in this case. Note that non-empty valgrind log may fail tests. Valgrind arguments may be seen in @@ -269,11 +282,7 @@ Number; !must be defined to function properly): - `NVIM_LUA_NOTRACK` (F) (D): disable reference counting of Lua objects -- `NVIM_PROG`, `NVIM_PRG` (F) (S): override path to Neovim executable (default - to `build/bin/nvim`). - -- `CC` (U) (S): specifies which C compiler to use to preprocess files. - Currently only compilers with gcc-compatible arguments are supported. +- `NVIM_PRG` (F) (S): path to Nvim executable (default: `build/bin/nvim`). - `NVIM_TEST_MAIN_CDEFS` (U) (1): makes `ffi.cdef` run in main process. This raises a possibility of bugs due to conflicts in header definitions, despite @@ -295,8 +304,6 @@ Number; !must be defined to function properly): - `NVIM_TEST_RUN_FAILING_TESTS` (U) (1): makes `itp` run tests which are known to fail (marked by setting third argument to `true`). -- `LOG_DIR` (FU) (S!): specifies where to seek for valgrind and ASAN log files. - - `NVIM_TEST_CORE_*` (FU) (S): a set of environment variables which specify where to search for core files. Are supposed to be defined all at once. diff --git a/test/benchmark/treesitter_spec.lua b/test/benchmark/treesitter_spec.lua new file mode 100644 index 0000000000..5ce128c54a --- /dev/null +++ b/test/benchmark/treesitter_spec.lua @@ -0,0 +1,53 @@ +local helpers = require('test.functional.helpers')(after_each) + +local clear = helpers.clear +local exec_lua = helpers.exec_lua + +describe('treesitter perf', function() + + setup(function() + clear() + end) + + it('can handle large folds', function() + helpers.command'edit ./src/nvim/eval.c' + exec_lua[[ + local parser = vim.treesitter.get_parser(0, "c", {}) + vim.treesitter.highlighter.new(parser) + + local function keys(k) + vim.api.nvim_feedkeys(k, 't', true) + end + + vim.opt.foldmethod = "manual" + vim.opt.lazyredraw = false + + vim.cmd '1000,7000fold' + vim.cmd '999' + + local function mk_keys(n) + local acc = "" + for _ = 1, n do + acc = acc .. "j" + end + for _ = 1, n do + acc = acc .. "k" + end + + return "qq" .. acc .. "q" + end + + local start = vim.loop.hrtime() + keys(mk_keys(10)) + + for _ = 1, 100 do + keys "@q" + vim.cmd'redraw!' + end + + return vim.loop.hrtime() - start + ]] + + end) + +end) diff --git a/test/busted/outputHandlers/nvim.lua b/test/busted/outputHandlers/nvim.lua index 0e9801b94b..2ce32c3b7a 100644 --- a/test/busted/outputHandlers/nvim.lua +++ b/test/busted/outputHandlers/nvim.lua @@ -26,35 +26,35 @@ return function(options) local repeatSuiteString = '\nRepeating all tests (run %d of %d) . . .\n\n' local randomizeString = c.note('Note: Randomizing test order with a seed of %d.\n') - local globalSetup = c.sect('[----------]') .. ' Global test environment setup.\n' - local fileStartString = c.sect('[----------]') .. ' Running tests from ' .. c.file('%s') .. '\n' - local runString = c.sect('[ RUN ]') .. ' ' .. c.test('%s') .. ': ' + local globalSetup = c.sect('--------') .. ' Global test environment setup.\n' + local fileStartString = c.sect('--------') .. ' Running tests from ' .. c.file('%s') .. '\n' + local runString = c.sect('RUN ') .. ' ' .. c.test('%s') .. ': ' local successString = c.succ('OK') .. '\n' local skippedString = c.skip('SKIP') .. '\n' local failureString = c.fail('FAIL') .. '\n' local errorString = c.errr('ERR') .. '\n' - local fileEndString = c.sect('[----------]') .. ' '.. c.nmbr('%d') .. ' %s from ' .. c.file('%s') .. ' ' .. c.time('(%.2f ms total)') .. '\n\n' - local globalTeardown = c.sect('[----------]') .. ' Global test environment teardown.\n' - local suiteEndString = c.sect('[==========]') .. ' ' .. c.nmbr('%d') .. ' %s from ' .. c.nmbr('%d') .. ' test %s ran. ' .. c.time('(%.2f ms total)') .. '\n' - local successStatus = c.succ('[ PASSED ]') .. ' ' .. c.nmbr('%d') .. ' %s.\n' + local fileEndString = c.sect('--------') .. ' '.. c.nmbr('%d') .. ' %s from ' .. c.file('%s') .. ' ' .. c.time('(%.2f ms total)') .. '\n\n' + local globalTeardown = c.sect('--------') .. ' Global test environment teardown.\n' + local suiteEndString = c.sect('========') .. ' ' .. c.nmbr('%d') .. ' %s from ' .. c.nmbr('%d') .. ' test %s ran. ' .. c.time('(%.2f ms total)') .. '\n' + local successStatus = c.succ('PASSED ') .. ' ' .. c.nmbr('%d') .. ' %s.\n' local timeString = c.time('%.2f ms') local summaryStrings = { skipped = { - header = c.skip('[ SKIPPED ]') .. ' ' .. c.nmbr('%d') .. ' %s, listed below:\n', - test = c.skip('[ SKIPPED ]') .. ' %s\n', + header = c.skip('SKIPPED ') .. ' ' .. c.nmbr('%d') .. ' %s, listed below:\n', + test = c.skip('SKIPPED ') .. ' %s\n', footer = ' ' .. c.nmbr('%d') .. ' SKIPPED %s\n', }, failure = { - header = c.fail('[ FAILED ]') .. ' ' .. c.nmbr('%d') .. ' %s, listed below:\n', - test = c.fail('[ FAILED ]') .. ' %s\n', + header = c.fail('FAILED ') .. ' ' .. c.nmbr('%d') .. ' %s, listed below:\n', + test = c.fail('FAILED ') .. ' %s\n', footer = ' ' .. c.nmbr('%d') .. ' FAILED %s\n', }, error = { - header = c.errr('[ ERROR ]') .. ' ' .. c.nmbr('%d') .. ' %s, listed below:\n', - test = c.errr('[ ERROR ]') .. ' %s\n', + header = c.errr('ERROR ') .. ' ' .. c.nmbr('%d') .. ' %s, listed below:\n', + test = c.errr('ERROR ') .. ' %s\n', footer = ' ' .. c.nmbr('%d') .. ' %s\n', }, } @@ -192,9 +192,11 @@ return function(options) local tests = (testCount == 1 and 'test' or 'tests') local files = (fileCount == 1 and 'file' or 'files') io.write(globalTeardown) - io.write(global_helpers.read_nvim_log(nil, true)) io.write(suiteEndString:format(testCount, tests, fileCount, files, elapsedTime_ms)) io.write(getSummaryString()) + if failureCount > 0 or errorCount > 0 then + io.write(global_helpers.read_nvim_log(nil, true)) + end io.flush() return nil, true @@ -217,7 +219,9 @@ return function(options) end handler.testStart = function(element, _parent) - io.write(runString:format(handler.getFullName(element))) + local testid = _G._nvim_test_id or '' + local desc = ('%s %s'):format(testid, handler.getFullName(element)) + io.write(runString:format(desc)) io.flush() return nil, true diff --git a/test/config/paths.lua.in b/test/cmakeconfig/paths.lua.in index e3979981ba..e3979981ba 100644 --- a/test/config/paths.lua.in +++ b/test/cmakeconfig/paths.lua.in diff --git a/test/functional/api/autocmd_spec.lua b/test/functional/api/autocmd_spec.lua new file mode 100644 index 0000000000..a923f5df0e --- /dev/null +++ b/test/functional/api/autocmd_spec.lua @@ -0,0 +1,1249 @@ +local helpers = require('test.functional.helpers')(after_each) + +local clear = helpers.clear +local command = helpers.command +local eq = helpers.eq +local neq = helpers.neq +local exec_lua = helpers.exec_lua +local matches = helpers.matches +local meths = helpers.meths +local source = helpers.source +local pcall_err = helpers.pcall_err + +before_each(clear) + +describe('autocmd api', function() + describe('nvim_create_autocmd', function() + it('does not allow "command" and "callback" in the same autocmd', function() + local ok, _ = pcall(meths.create_autocmd, "BufReadPost", { + pattern = "*.py,*.pyi", + command = "echo 'Should Have Errored", + callback = "not allowed", + }) + + eq(false, ok) + end) + + it('doesnt leak when you use ++once', function() + eq(1, exec_lua([[ + local count = 0 + + vim.api.nvim_create_autocmd("FileType", { + pattern = "*", + callback = function() count = count + 1 end, + once = true + }) + + vim.cmd "set filetype=txt" + vim.cmd "set filetype=python" + + return count + ]], {})) + end) + + it('allows passing buffer by key', function() + meths.set_var('called', 0) + + meths.create_autocmd("FileType", { + command = "let g:called = g:called + 1", + buffer = 0, + }) + + meths.command "set filetype=txt" + eq(1, meths.get_var('called')) + + -- switch to a new buffer + meths.command "new" + meths.command "set filetype=python" + + eq(1, meths.get_var('called')) + end) + + it('does not allow passing buffer and patterns', function() + local ok = pcall(meths.create_autocmd, "Filetype", { + command = "let g:called = g:called + 1", + buffer = 0, + pattern = "*.py", + }) + + eq(false, ok) + end) + + it('does not allow passing invalid buffers', function() + local ok, msg = pcall(meths.create_autocmd, "Filetype", { + command = "let g:called = g:called + 1", + buffer = -1, + }) + + eq(false, ok) + matches('Invalid buffer id', msg) + end) + + it('errors on non-functions for cb', function() + eq(false, pcall(exec_lua, [[ + vim.api.nvim_create_autocmd("BufReadPost", { + pattern = "*.py,*.pyi", + callback = 5, + }) + ]])) + end) + + it('allow passing pattern and <buffer> in same pattern', function() + local ok = pcall(meths.create_autocmd, "BufReadPost", { + pattern = "*.py,<buffer>", + command = "echo 'Should Not Error'" + }) + + eq(true, ok) + end) + + it('should handle multiple values as comma separated list', function() + meths.create_autocmd("BufReadPost", { + pattern = "*.py,*.pyi", + command = "echo 'Should Not Have Errored'" + }) + + -- We should have one autocmd for *.py and one for *.pyi + eq(2, #meths.get_autocmds { event = "BufReadPost" }) + end) + + it('should handle multiple values as array', function() + meths.create_autocmd("BufReadPost", { + pattern = { "*.py", "*.pyi", }, + command = "echo 'Should Not Have Errored'" + }) + + -- We should have one autocmd for *.py and one for *.pyi + eq(2, #meths.get_autocmds { event = "BufReadPost" }) + end) + + describe('desc', function() + it('can add description to one autocmd', function() + local cmd = "echo 'Should Not Have Errored'" + local desc = "Can show description" + meths.create_autocmd("BufReadPost", { + pattern = "*.py", + command = cmd, + desc = desc, + }) + + eq(desc, meths.get_autocmds { event = "BufReadPost" }[1].desc) + eq(cmd, meths.get_autocmds { event = "BufReadPost" }[1].command) + end) + + it('can add description to one autocmd that uses a callback', function() + local desc = 'Can show description' + meths.set_var('desc', desc) + + local result = exec_lua([[ + local callback = function() print 'Should Not Have Errored' end + vim.api.nvim_create_autocmd("BufReadPost", { + pattern = "*.py", + callback = callback, + desc = vim.g.desc, + }) + local aus = vim.api.nvim_get_autocmds({ event = 'BufReadPost' }) + local first = aus[1] + return { + desc = first.desc, + cbtype = type(first.callback) + } + ]]) + + eq({ desc = desc, cbtype = 'function' }, result) + end) + + it('will not add a description unless it was provided', function() + exec_lua([[ + local callback = function() print 'Should Not Have Errored' end + vim.api.nvim_create_autocmd("BufReadPost", { + pattern = "*.py", + callback = callback, + }) + ]]) + + eq(nil, meths.get_autocmds({ event = 'BufReadPost' })[1].desc) + end) + + it('can add description to multiple autocmd', function() + meths.create_autocmd("BufReadPost", { + pattern = {"*.py", "*.pyi"}, + command = "echo 'Should Not Have Errored'", + desc = "Can show description", + }) + + local aus = meths.get_autocmds { event = "BufReadPost" } + eq(2, #aus) + eq("Can show description", aus[1].desc) + eq("Can show description", aus[2].desc) + end) + end) + + pending('script and verbose settings', function() + it('marks API client', function() + meths.create_autocmd("BufReadPost", { + pattern = "*.py", + command = "echo 'Should Not Have Errored'", + desc = "Can show description", + }) + + local aus = meths.get_autocmds { event = "BufReadPost" } + eq(1, #aus, aus) + end) + end) + + it('removes an autocommand if the callback returns true', function() + meths.set_var("some_condition", false) + + exec_lua [[ + vim.api.nvim_create_autocmd("User", { + pattern = "Test", + desc = "A test autocommand", + callback = function() + return vim.g.some_condition + end, + }) + ]] + + meths.exec_autocmds("User", {pattern = "Test"}) + + local aus = meths.get_autocmds({ event = 'User', pattern = 'Test' }) + local first = aus[1] + eq(first.id, 1) + + meths.set_var("some_condition", true) + meths.exec_autocmds("User", {pattern = "Test"}) + eq({}, meths.get_autocmds({event = "User", pattern = "Test"})) + end) + + it('receives an args table', function() + local res = exec_lua [[ + local group_id = vim.api.nvim_create_augroup("TestGroup", {}) + local autocmd_id = vim.api.nvim_create_autocmd("User", { + group = "TestGroup", + pattern = "Te*", + callback = function(args) + vim.g.autocmd_args = args + end, + }) + + return {group_id, autocmd_id} + ]] + + meths.exec_autocmds("User", {pattern = "Test pattern"}) + eq({ + id = res[2], + group = res[1], + event = "User", + match = "Test pattern", + file = "Test pattern", + buf = 1, + }, meths.get_var("autocmd_args")) + + -- Test without a group + res = exec_lua [[ + local autocmd_id = vim.api.nvim_create_autocmd("User", { + pattern = "*", + callback = function(args) + vim.g.autocmd_args = args + end, + }) + + return {autocmd_id} + ]] + + meths.exec_autocmds("User", {pattern = "some_pat"}) + eq({ + id = res[1], + group = nil, + event = "User", + match = "some_pat", + file = "some_pat", + buf = 1, + }, meths.get_var("autocmd_args")) + + end) + + it('can receive arbitrary data', function() + local function test(data) + eq(data, exec_lua([[ + local input = ... + local output + vim.api.nvim_create_autocmd("User", { + pattern = "Test", + callback = function(args) + output = args.data + end, + }) + + vim.api.nvim_exec_autocmds("User", { + pattern = "Test", + data = input, + }) + + return output + ]], data)) + end + + test("Hello") + test(42) + test(true) + test({ "list" }) + test({ foo = "bar" }) + end) + end) + + describe('nvim_get_autocmds', function() + describe('events', function() + it('should return one autocmd when there is only one for an event', function() + command [[au! InsertEnter]] + command [[au InsertEnter * :echo "1"]] + + local aus = meths.get_autocmds { event = "InsertEnter" } + eq(1, #aus) + end) + + it('should return two autocmds when there are two for an event', function() + command [[au! InsertEnter]] + command [[au InsertEnter * :echo "1"]] + command [[au InsertEnter * :echo "2"]] + + local aus = meths.get_autocmds { event = "InsertEnter" } + eq(2, #aus) + end) + + it('should return the same thing if you use string or list', function() + command [[au! InsertEnter]] + command [[au InsertEnter * :echo "1"]] + command [[au InsertEnter * :echo "2"]] + + local string_aus = meths.get_autocmds { event = "InsertEnter" } + local array_aus = meths.get_autocmds { event = { "InsertEnter" } } + eq(string_aus, array_aus) + end) + + it('should return two autocmds when there are two for an event', function() + command [[au! InsertEnter]] + command [[au! InsertLeave]] + command [[au InsertEnter * :echo "1"]] + command [[au InsertEnter * :echo "2"]] + + local aus = meths.get_autocmds { event = { "InsertEnter", "InsertLeave" } } + eq(2, #aus) + end) + + it('should return different IDs for different autocmds', function() + command [[au! InsertEnter]] + command [[au! InsertLeave]] + command [[au InsertEnter * :echo "1"]] + source [[ + call nvim_create_autocmd("InsertLeave", #{ + \ command: ":echo 2", + \ }) + ]] + + local aus = meths.get_autocmds { event = { "InsertEnter", "InsertLeave" } } + local first = aus[1] + eq(first.id, nil) + + -- TODO: Maybe don't have this number, just assert it's not nil + local second = aus[2] + neq(second.id, nil) + + meths.del_autocmd(second.id) + local new_aus = meths.get_autocmds { event = { "InsertEnter", "InsertLeave" } } + eq(1, #new_aus) + eq(first, new_aus[1]) + end) + + it('should return event name', function() + command [[au! InsertEnter]] + command [[au InsertEnter * :echo "1"]] + + local aus = meths.get_autocmds { event = "InsertEnter" } + eq({ { buflocal = false, command = ':echo "1"', event = "InsertEnter", once = false, pattern = "*" } }, aus) + end) + + it('should work with buffer numbers', function() + command [[new]] + command [[au! InsertEnter]] + command [[au InsertEnter <buffer=1> :echo "1"]] + command [[au InsertEnter <buffer=2> :echo "2"]] + + local aus = meths.get_autocmds { event = "InsertEnter", buffer = 0 } + eq({{ + buffer = 2, + buflocal = true, + command = ':echo "2"', + event = 'InsertEnter', + once = false, + pattern = '<buffer=2>', + }}, aus) + + aus = meths.get_autocmds { event = "InsertEnter", buffer = 1 } + eq({{ + buffer = 1, + buflocal = true, + command = ':echo "1"', + event = "InsertEnter", + once = false, + pattern = "<buffer=1>", + }}, aus) + + aus = meths.get_autocmds { event = "InsertEnter", buffer = { 1, 2 } } + eq({{ + buffer = 1, + buflocal = true, + command = ':echo "1"', + event = "InsertEnter", + once = false, + pattern = "<buffer=1>", + }, { + buffer = 2, + buflocal = true, + command = ':echo "2"', + event = "InsertEnter", + once = false, + pattern = "<buffer=2>", + }}, aus) + + eq("Invalid value for 'buffer': must be an integer or array of integers", pcall_err(meths.get_autocmds, { event = "InsertEnter", buffer = "foo" })) + eq("Invalid value for 'buffer': must be an integer", pcall_err(meths.get_autocmds, { event = "InsertEnter", buffer = { "foo", 42 } })) + eq("Invalid buffer id: 42", pcall_err(meths.get_autocmds, { event = "InsertEnter", buffer = { 42 } })) + + local bufs = {} + for _ = 1, 257 do + table.insert(bufs, meths.create_buf(true, false)) + end + + eq("Too many buffers. Please limit yourself to 256 or fewer", pcall_err(meths.get_autocmds, { event = "InsertEnter", buffer = bufs })) + end) + + it('should return autocmds when group is specified by id', function() + local auid = meths.create_augroup("nvim_test_augroup", { clear = true }) + meths.create_autocmd("FileType", { group = auid, command = 'echo "1"' }) + meths.create_autocmd("FileType", { group = auid, command = 'echo "2"' }) + + local aus = meths.get_autocmds { group = auid } + eq(2, #aus) + + local aus2 = meths.get_autocmds { group = auid, event = "InsertEnter" } + eq(0, #aus2) + end) + + it('should return autocmds when group is specified by name', function() + local auname = "nvim_test_augroup" + meths.create_augroup(auname, { clear = true }) + meths.create_autocmd("FileType", { group = auname, command = 'echo "1"' }) + meths.create_autocmd("FileType", { group = auname, command = 'echo "2"' }) + + local aus = meths.get_autocmds { group = auname } + eq(2, #aus) + + local aus2 = meths.get_autocmds { group = auname, event = "InsertEnter" } + eq(0, #aus2) + end) + + it('should respect nested', function() + local bufs = exec_lua [[ + local count = 0 + vim.api.nvim_create_autocmd("BufNew", { + once = false, + nested = true, + callback = function() + count = count + 1 + if count > 5 then + return true + end + + vim.cmd(string.format("new README_%s.md", count)) + end + }) + + vim.cmd "new First.md" + + return vim.api.nvim_list_bufs() + ]] + + -- 1 for the first buffer + -- 2 for First.md + -- 3-7 for the 5 we make in the autocmd + eq({1, 2, 3, 4, 5, 6, 7}, bufs) + end) + + it('can retrieve a callback from an autocmd', function() + local content = 'I Am A Callback' + meths.set_var('content', content) + + local result = exec_lua([[ + local cb = function() return vim.g.content end + vim.api.nvim_create_autocmd("User", { + pattern = "TestTrigger", + desc = "A test autocommand with a callback", + callback = cb, + }) + local aus = vim.api.nvim_get_autocmds({ event = 'User', pattern = 'TestTrigger'}) + local first = aus[1] + return { + cb = { + type = type(first.callback), + can_retrieve = first.callback() == vim.g.content + } + } + ]]) + + eq("function", result.cb.type) + eq(true, result.cb.can_retrieve) + end) + + it('will return an empty string as the command for an autocmd that uses a callback', function() + local result = exec_lua([[ + local callback = function() print 'I Am A Callback' end + vim.api.nvim_create_autocmd("BufWritePost", { + pattern = "*.py", + callback = callback, + }) + local aus = vim.api.nvim_get_autocmds({ event = 'BufWritePost' }) + local first = aus[1] + return { + command = first.command, + cbtype = type(first.callback) + } + ]]) + + eq({ command = "", cbtype = 'function' }, result) + end) + end) + + describe('groups', function() + before_each(function() + command [[au! InsertEnter]] + + command [[au InsertEnter * :echo "No Group"]] + + command [[augroup GroupOne]] + command [[ au InsertEnter * :echo "GroupOne:1"]] + command [[augroup END]] + + command [[augroup GroupTwo]] + command [[ au InsertEnter * :echo "GroupTwo:2"]] + command [[ au InsertEnter * :echo "GroupTwo:3"]] + command [[augroup END]] + end) + + it('should return all groups if no group is specified', function() + local aus = meths.get_autocmds { event = "InsertEnter" } + if #aus ~= 4 then + eq({}, aus) + end + + eq(4, #aus) + end) + + it('should return only the group specified', function() + local aus = meths.get_autocmds { + event = "InsertEnter", + group = "GroupOne", + } + + eq(1, #aus) + eq([[:echo "GroupOne:1"]], aus[1].command) + eq("GroupOne", aus[1].group_name) + end) + + it('should return only the group specified, multiple values', function() + local aus = meths.get_autocmds { + event = "InsertEnter", + group = "GroupTwo", + } + + eq(2, #aus) + eq([[:echo "GroupTwo:2"]], aus[1].command) + eq("GroupTwo", aus[1].group_name) + eq([[:echo "GroupTwo:3"]], aus[2].command) + eq("GroupTwo", aus[2].group_name) + end) + end) + + describe('groups: 2', function() + it('raises error for undefined augroup name', function() + local success, code = unpack(meths.exec_lua([[ + return {pcall(function() + vim.api.nvim_create_autocmd("FileType", { + pattern = "*", + group = "NotDefined", + command = "echo 'hello'", + }) + end)} + ]], {})) + + eq(false, success) + matches('invalid augroup: NotDefined', code) + end) + + it('raises error for undefined augroup id', function() + local success, code = unpack(meths.exec_lua([[ + return {pcall(function() + -- Make sure the augroup is deleted + vim.api.nvim_del_augroup_by_id(1) + + vim.api.nvim_create_autocmd("FileType", { + pattern = "*", + group = 1, + command = "echo 'hello'", + }) + end)} + ]], {})) + + eq(false, success) + matches('invalid augroup: 1', code) + end) + + it('raises error for invalid group type', function() + local success, code = unpack(meths.exec_lua([[ + return {pcall(function() + vim.api.nvim_create_autocmd("FileType", { + pattern = "*", + group = true, + command = "echo 'hello'", + }) + end)} + ]], {})) + + eq(false, success) + matches("'group' must be a string or an integer", code) + end) + end) + + describe('patterns', function() + before_each(function() + command [[au! InsertEnter]] + + command [[au InsertEnter * :echo "No Group"]] + command [[au InsertEnter *.one :echo "GroupOne:1"]] + command [[au InsertEnter *.two :echo "GroupTwo:2"]] + command [[au InsertEnter *.two :echo "GroupTwo:3"]] + command [[au InsertEnter <buffer> :echo "Buffer"]] + end) + + it('should should return for literal match', function() + local aus = meths.get_autocmds { + event = "InsertEnter", + pattern = "*" + } + + eq(1, #aus) + eq([[:echo "No Group"]], aus[1].command) + end) + + it('should return for multiple matches', function() + -- vim.api.nvim_get_autocmds + local aus = meths.get_autocmds { + event = "InsertEnter", + pattern = { "*.one", "*.two" }, + } + + eq(3, #aus) + eq([[:echo "GroupOne:1"]], aus[1].command) + eq([[:echo "GroupTwo:2"]], aus[2].command) + eq([[:echo "GroupTwo:3"]], aus[3].command) + end) + + it('should work for buffer autocmds', function() + local normalized_aus = meths.get_autocmds { + event = "InsertEnter", + pattern = "<buffer=1>", + } + + local raw_aus = meths.get_autocmds { + event = "InsertEnter", + pattern = "<buffer>", + } + + local zero_aus = meths.get_autocmds { + event = "InsertEnter", + pattern = "<buffer=0>", + } + + eq(normalized_aus, raw_aus) + eq(normalized_aus, zero_aus) + eq([[:echo "Buffer"]], normalized_aus[1].command) + end) + end) + end) + + describe('nvim_exec_autocmds', function() + it("can trigger builtin autocmds", function() + meths.set_var("autocmd_executed", false) + + meths.create_autocmd("BufReadPost", { + pattern = "*", + command = "let g:autocmd_executed = v:true", + }) + + eq(false, meths.get_var("autocmd_executed")) + meths.exec_autocmds("BufReadPost", {}) + eq(true, meths.get_var("autocmd_executed")) + end) + + it("can trigger multiple patterns", function() + meths.set_var("autocmd_executed", 0) + + meths.create_autocmd("BufReadPost", { + pattern = "*", + command = "let g:autocmd_executed += 1", + }) + + meths.exec_autocmds("BufReadPost", { pattern = { "*.lua", "*.vim" } }) + eq(2, meths.get_var("autocmd_executed")) + + meths.create_autocmd("BufReadPre", { + pattern = { "bar", "foo" }, + command = "let g:autocmd_executed += 10", + }) + + meths.exec_autocmds("BufReadPre", { pattern = { "foo", "bar", "baz", "frederick" }}) + eq(22, meths.get_var("autocmd_executed")) + end) + + it("can pass the buffer", function() + meths.set_var("buffer_executed", -1) + eq(-1, meths.get_var("buffer_executed")) + + meths.create_autocmd("BufLeave", { + pattern = "*", + command = 'let g:buffer_executed = +expand("<abuf>")', + }) + + -- Doesn't execute for other non-matching events + meths.exec_autocmds("CursorHold", { buffer = 1 }) + eq(-1, meths.get_var("buffer_executed")) + + meths.exec_autocmds("BufLeave", { buffer = 1 }) + eq(1, meths.get_var("buffer_executed")) + end) + + it("can pass the filename, pattern match", function() + meths.set_var("filename_executed", 'none') + eq('none', meths.get_var("filename_executed")) + + meths.create_autocmd("BufEnter", { + pattern = "*.py", + command = 'let g:filename_executed = expand("<afile>")', + }) + + -- Doesn't execute for other non-matching events + meths.exec_autocmds("CursorHold", { buffer = 1 }) + eq('none', meths.get_var("filename_executed")) + + meths.command('edit __init__.py') + eq('__init__.py', meths.get_var("filename_executed")) + end) + + it('cannot pass buf and fname', function() + local ok = pcall(meths.exec_autocmds, "BufReadPre", { pattern = "literally_cannot_error.rs", buffer = 1 }) + eq(false, ok) + end) + + it("can pass the filename, exact match", function() + meths.set_var("filename_executed", 'none') + eq('none', meths.get_var("filename_executed")) + + meths.command('edit other_file.txt') + meths.command('edit __init__.py') + eq('none', meths.get_var("filename_executed")) + + meths.create_autocmd("CursorHoldI", { + pattern = "__init__.py", + command = 'let g:filename_executed = expand("<afile>")', + }) + + -- Doesn't execute for other non-matching events + meths.exec_autocmds("CursorHoldI", { buffer = 1 }) + eq('none', meths.get_var("filename_executed")) + + meths.exec_autocmds("CursorHoldI", { buffer = meths.get_current_buf() }) + eq('__init__.py', meths.get_var("filename_executed")) + + -- Reset filename + meths.set_var("filename_executed", 'none') + + meths.exec_autocmds("CursorHoldI", { pattern = '__init__.py' }) + eq('__init__.py', meths.get_var("filename_executed")) + end) + + it("works with user autocmds", function() + meths.set_var("matched", 'none') + + meths.create_autocmd("User", { + pattern = "TestCommand", + command = 'let g:matched = "matched"' + }) + + meths.exec_autocmds("User", { pattern = "OtherCommand" }) + eq('none', meths.get_var('matched')) + meths.exec_autocmds("User", { pattern = "TestCommand" }) + eq('matched', meths.get_var('matched')) + end) + + it('can pass group by id', function() + meths.set_var("group_executed", false) + + local auid = meths.create_augroup("nvim_test_augroup", { clear = true }) + meths.create_autocmd("FileType", { + group = auid, + command = 'let g:group_executed = v:true', + }) + + eq(false, meths.get_var("group_executed")) + meths.exec_autocmds("FileType", { group = auid }) + eq(true, meths.get_var("group_executed")) + end) + + it('can pass group by name', function() + meths.set_var("group_executed", false) + + local auname = "nvim_test_augroup" + meths.create_augroup(auname, { clear = true }) + meths.create_autocmd("FileType", { + group = auname, + command = 'let g:group_executed = v:true', + }) + + eq(false, meths.get_var("group_executed")) + meths.exec_autocmds("FileType", { group = auname }) + eq(true, meths.get_var("group_executed")) + end) + end) + + describe('nvim_create_augroup', function() + before_each(function() + clear() + + meths.set_var('executed', 0) + end) + + local make_counting_autocmd = function(opts) + opts = opts or {} + + local resulting = { + pattern = "*", + command = "let g:executed = g:executed + 1", + } + + resulting.group = opts.group + resulting.once = opts.once + + meths.create_autocmd("FileType", resulting) + end + + local set_ft = function(ft) + ft = ft or "txt" + source(string.format("set filetype=%s", ft)) + end + + local get_executed_count = function() + return meths.get_var('executed') + end + + it('can be added in a group', function() + local augroup = "TestGroup" + meths.create_augroup(augroup, { clear = true }) + make_counting_autocmd { group = augroup } + + set_ft("txt") + set_ft("python") + + eq(2, get_executed_count()) + end) + + it('works getting called multiple times', function() + make_counting_autocmd() + set_ft() + set_ft() + set_ft() + + eq(3, get_executed_count()) + end) + + it('handles ++once', function() + make_counting_autocmd {once = true} + set_ft('txt') + set_ft('help') + set_ft('txt') + set_ft('help') + + eq(1, get_executed_count()) + end) + + it('errors on unexpected keys', function() + local success, code = pcall(meths.create_autocmd, "FileType", { + pattern = "*", + not_a_valid_key = "NotDefined", + }) + + eq(false, success) + matches('not_a_valid_key', code) + end) + + it('can execute simple callback', function() + exec_lua([[ + vim.g.executed = false + + vim.api.nvim_create_autocmd("FileType", { + pattern = "*", + callback = function() vim.g.executed = true end, + }) + ]], {}) + + + eq(true, exec_lua([[ + vim.cmd "set filetype=txt" + return vim.g.executed + ]], {})) + end) + + it('calls multiple lua callbacks for the same autocmd execution', function() + eq(4, exec_lua([[ + local count = 0 + local counter = function() + count = count + 1 + end + + vim.api.nvim_create_autocmd("FileType", { + pattern = "*", + callback = counter, + }) + + vim.api.nvim_create_autocmd("FileType", { + pattern = "*", + callback = counter, + }) + + vim.cmd "set filetype=txt" + vim.cmd "set filetype=txt" + + return count + ]], {})) + end) + + it('properly releases functions with ++once', function() + exec_lua([[ + WeakTable = setmetatable({}, { __mode = "k" }) + + OnceCount = 0 + + MyVal = {} + WeakTable[MyVal] = true + + vim.api.nvim_create_autocmd("FileType", { + pattern = "*", + callback = function() + OnceCount = OnceCount + 1 + MyVal = {} + end, + once = true + }) + ]]) + + command [[set filetype=txt]] + eq(1, exec_lua([[return OnceCount]], {})) + + exec_lua([[collectgarbage()]], {}) + + command [[set filetype=txt]] + eq(1, exec_lua([[return OnceCount]], {})) + + eq(0, exec_lua([[ + local count = 0 + for _ in pairs(WeakTable) do + count = count + 1 + end + + return count + ]]), "Should have no keys remaining") + end) + + it('groups can be cleared', function() + local augroup = "TestGroup" + meths.create_augroup(augroup, { clear = true }) + meths.create_autocmd("FileType", { + group = augroup, + command = "let g:executed = g:executed + 1" + }) + + set_ft("txt") + set_ft("txt") + eq(2, get_executed_count(), "should only count twice") + + meths.create_augroup(augroup, { clear = true }) + eq({}, meths.get_autocmds { group = augroup }) + + set_ft("txt") + set_ft("txt") + eq(2, get_executed_count(), "No additional counts") + end) + + it('can delete non-existent groups with pcall', function() + eq(false, exec_lua[[return pcall(vim.api.nvim_del_augroup_by_name, 'noexist')]]) + eq('Vim:E367: No such group: "noexist"', pcall_err(meths.del_augroup_by_name, 'noexist')) + + eq(false, exec_lua[[return pcall(vim.api.nvim_del_augroup_by_id, -12342)]]) + eq('Vim:E367: No such group: "--Deleted--"', pcall_err(meths.del_augroup_by_id, -12312)) + end) + + it('groups work with once', function() + local augroup = "TestGroup" + + meths.create_augroup(augroup, { clear = true }) + make_counting_autocmd { group = augroup, once = true } + + set_ft("txt") + set_ft("python") + + eq(1, get_executed_count()) + end) + + it('autocmds can be registered multiple times.', function() + local augroup = "TestGroup" + + meths.create_augroup(augroup, { clear = true }) + make_counting_autocmd { group = augroup, once = false } + make_counting_autocmd { group = augroup, once = false } + make_counting_autocmd { group = augroup, once = false } + + set_ft("txt") + set_ft("python") + + eq(3 * 2, get_executed_count()) + end) + + it('can be deleted', function() + local augroup = "WillBeDeleted" + + meths.create_augroup(augroup, { clear = true }) + meths.create_autocmd({"Filetype"}, { + pattern = "*", + command = "echo 'does not matter'", + }) + + -- Clears the augroup from before, which erases the autocmd + meths.create_augroup(augroup, { clear = true }) + + local result = #meths.get_autocmds { group = augroup } + + eq(0, result) + end) + + it('can be used for buffer local autocmds', function() + local augroup = "WillBeDeleted" + + meths.set_var("value_set", false) + + meths.create_augroup(augroup, { clear = true }) + meths.create_autocmd("Filetype", { + pattern = "<buffer>", + command = "let g:value_set = v:true", + }) + + command "new" + command "set filetype=python" + + eq(false, meths.get_var("value_set")) + end) + + it('can accept vimscript functions', function() + source [[ + let g:vimscript_executed = 0 + + function! MyVimscriptFunction() abort + let g:vimscript_executed = g:vimscript_executed + 1 + endfunction + + call nvim_create_autocmd("FileType", #{ + \ pattern: ["python", "javascript"], + \ callback: "MyVimscriptFunction", + \ }) + + set filetype=txt + set filetype=python + set filetype=txt + set filetype=javascript + set filetype=txt + ]] + + eq(2, meths.get_var("vimscript_executed")) + end) + end) + + describe('augroup!', function() + it('legacy: should clear and not return any autocmds for delete groups', function() + command('augroup TEMP_A') + command(' autocmd! BufReadPost *.py :echo "Hello"') + command('augroup END') + + command('augroup! TEMP_A') + + eq(false, pcall(meths.get_autocmds, { group = 'TEMP_A' })) + + -- For some reason, augroup! doesn't clear the autocmds themselves, which is just wild + -- but we managed to keep this behavior. + eq(1, #meths.get_autocmds { event = 'BufReadPost' }) + end) + + it('legacy: remove augroups that have no autocmds', function() + command('augroup TEMP_AB') + command('augroup END') + + command('augroup! TEMP_AB') + + eq(false, pcall(meths.get_autocmds, { group = 'TEMP_AB' })) + eq(0, #meths.get_autocmds { event = 'BufReadPost' }) + end) + + it('legacy: multiple remove and add augroup', function() + command('augroup TEMP_ABC') + command(' au!') + command(' autocmd BufReadPost *.py echo "Hello"') + command('augroup END') + + command('augroup! TEMP_ABC') + + -- Should still have one autocmd :'( + local aus = meths.get_autocmds { event = 'BufReadPost' } + eq(1, #aus, aus) + + command('augroup TEMP_ABC') + command(' au!') + command(' autocmd BufReadPost *.py echo "Hello"') + command('augroup END') + + -- Should now have two autocmds :'( + aus = meths.get_autocmds { event = 'BufReadPost' } + eq(2, #aus, aus) + + command('augroup! TEMP_ABC') + + eq(false, pcall(meths.get_autocmds, { group = 'TEMP_ABC' })) + eq(2, #meths.get_autocmds { event = 'BufReadPost' }) + end) + + it('api: should clear and not return any autocmds for delete groups by id', function() + command('augroup TEMP_ABCD') + command('autocmd! BufReadPost *.py :echo "Hello"') + command('augroup END') + + local augroup_id = meths.create_augroup("TEMP_ABCD", { clear = false }) + meths.del_augroup_by_id(augroup_id) + + -- For good reason, we kill all the autocmds from del_augroup, + -- so now this works as expected + eq(false, pcall(meths.get_autocmds, { group = 'TEMP_ABCD' })) + eq(0, #meths.get_autocmds { event = 'BufReadPost' }) + end) + + it('api: should clear and not return any autocmds for delete groups by name', function() + command('augroup TEMP_ABCDE') + command('autocmd! BufReadPost *.py :echo "Hello"') + command('augroup END') + + meths.del_augroup_by_name("TEMP_ABCDE") + + -- For good reason, we kill all the autocmds from del_augroup, + -- so now this works as expected + eq(false, pcall(meths.get_autocmds, { group = 'TEMP_ABCDE' })) + eq(0, #meths.get_autocmds { event = 'BufReadPost' }) + end) + end) + + describe('nvim_clear_autocmds', function() + it('should clear based on event + pattern', function() + command('autocmd InsertEnter *.py :echo "Python can be cool sometimes"') + command('autocmd InsertEnter *.txt :echo "Text Files Are Cool"') + + local search = { event = "InsertEnter", pattern = "*.txt" } + local before_delete = meths.get_autocmds(search) + eq(1, #before_delete) + + local before_delete_all = meths.get_autocmds { event = search.event } + eq(2, #before_delete_all) + + meths.clear_autocmds(search) + local after_delete = meths.get_autocmds(search) + eq(0, #after_delete) + + local after_delete_all = meths.get_autocmds { event = search.event } + eq(1, #after_delete_all) + end) + + it('should clear based on event', function() + command('autocmd InsertEnter *.py :echo "Python can be cool sometimes"') + command('autocmd InsertEnter *.txt :echo "Text Files Are Cool"') + + local search = { event = "InsertEnter"} + local before_delete = meths.get_autocmds(search) + eq(2, #before_delete) + + meths.clear_autocmds(search) + local after_delete = meths.get_autocmds(search) + eq(0, #after_delete) + end) + + it('should clear based on pattern', function() + command('autocmd InsertEnter *.TestPat1 :echo "Enter 1"') + command('autocmd InsertLeave *.TestPat1 :echo "Leave 1"') + command('autocmd InsertEnter *.TestPat2 :echo "Enter 2"') + command('autocmd InsertLeave *.TestPat2 :echo "Leave 2"') + + local search = { pattern = "*.TestPat1"} + local before_delete = meths.get_autocmds(search) + eq(2, #before_delete) + local before_delete_events = meths.get_autocmds { event = { "InsertEnter", "InsertLeave" } } + eq(4, #before_delete_events) + + meths.clear_autocmds(search) + local after_delete = meths.get_autocmds(search) + eq(0, #after_delete) + + local after_delete_events = meths.get_autocmds { event = { "InsertEnter", "InsertLeave" } } + eq(2, #after_delete_events) + end) + + it('should allow clearing by buffer', function() + command('autocmd! InsertEnter') + command('autocmd InsertEnter <buffer> :echo "Enter Buffer"') + command('autocmd InsertEnter *.TestPat1 :echo "Enter Pattern"') + + local search = { event = "InsertEnter" } + local before_delete = meths.get_autocmds(search) + eq(2, #before_delete) + + meths.clear_autocmds { buffer = 0 } + local after_delete = meths.get_autocmds(search) + eq(1, #after_delete) + eq("*.TestPat1", after_delete[1].pattern) + end) + + it('should allow clearing by buffer and group', function() + command('augroup TestNvimClearAutocmds') + command(' au!') + command(' autocmd InsertEnter <buffer> :echo "Enter Buffer"') + command(' autocmd InsertEnter *.TestPat1 :echo "Enter Pattern"') + command('augroup END') + + local search = { event = "InsertEnter", group = "TestNvimClearAutocmds" } + local before_delete = meths.get_autocmds(search) + eq(2, #before_delete) + + -- Doesn't clear without passing group. + meths.clear_autocmds { buffer = 0 } + local without_group = meths.get_autocmds(search) + eq(2, #without_group) + + -- Doest clear with passing group. + meths.clear_autocmds { buffer = 0, group = search.group } + local with_group = meths.get_autocmds(search) + eq(1, #with_group) + end) + end) +end) diff --git a/test/functional/api/buffer_spec.lua b/test/functional/api/buffer_spec.lua index 688f3abba5..dc668e7201 100644 --- a/test/functional/api/buffer_spec.lua +++ b/test/functional/api/buffer_spec.lua @@ -227,8 +227,8 @@ describe('api/buf', function() it('can get a single line with strict indexing', function() set_lines(0, 1, true, {'line1.a'}) eq(1, line_count()) -- sanity - eq(false, pcall(get_lines, 1, 2, true)) - eq(false, pcall(get_lines, -3, -2, true)) + eq('Index out of bounds', pcall_err(get_lines, 1, 2, true)) + eq('Index out of bounds', pcall_err(get_lines, -3, -2, true)) end) it('can get a single line with non-strict indexing', function() @@ -240,11 +240,11 @@ describe('api/buf', function() it('can set and delete a single line with strict indexing', function() set_lines(0, 1, true, {'line1.a'}) - eq(false, pcall(set_lines, 1, 2, true, {'line1.b'})) - eq(false, pcall(set_lines, -3, -2, true, {'line1.c'})) + eq('Index out of bounds', pcall_err(set_lines, 1, 2, true, {'line1.b'})) + eq('Index out of bounds', pcall_err(set_lines, -3, -2, true, {'line1.c'})) eq({'line1.a'}, get_lines(0, -1, true)) - eq(false, pcall(set_lines, 1, 2, true, {})) - eq(false, pcall(set_lines, -3, -2, true, {})) + eq('Index out of bounds', pcall_err(set_lines, 1, 2, true, {})) + eq('Index out of bounds', pcall_err(set_lines, -3, -2, true, {})) eq({'line1.a'}, get_lines(0, -1, true)) end) @@ -302,9 +302,9 @@ describe('api/buf', function() set_lines(0, -1, true, {'a', 'b', 'c'}) eq({'a', 'b', 'c'}, get_lines(0, -1, true)) --sanity - eq(false, pcall(get_lines, 3, 4, true)) - eq(false, pcall(get_lines, 3, 10, true)) - eq(false, pcall(get_lines, -5, -5, true)) + eq('Index out of bounds', pcall_err(get_lines, 3, 4, true)) + eq('Index out of bounds', pcall_err(get_lines, 3, 10, true)) + eq('Index out of bounds', pcall_err(get_lines, -5, -5, true)) -- empty or inverted ranges are not errors eq({}, get_lines(3, -1, true)) eq({}, get_lines(-3, -4, true)) @@ -316,10 +316,10 @@ describe('api/buf', function() eq({'c'}, get_lines(-2, 5, false)) eq({'a', 'b', 'c'}, get_lines(0, 6, false)) - eq(false, pcall(set_lines, 4, 6, true, {'d'})) + eq('Index out of bounds', pcall_err(set_lines, 4, 6, true, {'d'})) set_lines(4, 6, false, {'d'}) eq({'a', 'b', 'c', 'd'}, get_lines(0, -1, true)) - eq(false, pcall(set_lines, -6, -6, true, {'e'})) + eq('Index out of bounds', pcall_err(set_lines, -6, -6, true, {'e'})) set_lines(-6, -6, false, {'e'}) eq({'e', 'a', 'b', 'c', 'd'}, get_lines(0, -1, true)) end) @@ -392,7 +392,7 @@ describe('api/buf', function() end) end) - describe('nvim_buf_get_lines, nvim_buf_set_text', function() + describe('nvim_buf_set_text', function() local get_lines, set_text = curbufmeths.get_lines, curbufmeths.set_text it('works', function() @@ -423,6 +423,17 @@ describe('api/buf', function() -- will join multiple lines if needed set_text(0, 6, 3, 4, {'bar'}) eq({'hello bar'}, get_lines(0, 1, true)) + + -- can use negative line numbers + set_text(-2, 0, -2, 5, {'goodbye'}) + eq({'goodbye bar', ''}, get_lines(0, -1, true)) + + set_text(-1, 0, -1, 0, {'text'}) + eq({'goodbye bar', 'text'}, get_lines(0, 2, true)) + + -- can append to a line + set_text(1, 4, -1, 4, {' and', 'more'}) + eq({'goodbye bar', 'text and', 'more'}, get_lines(0, 3, true)) end) it('works with undo', function() @@ -506,12 +517,12 @@ describe('api/buf', function() eq({0, 6}, curbufmeths.get_extmark_by_id(ns, id2, {})) eq({0, 6}, curbufmeths.get_extmark_by_id(ns, id3, {})) - -- marks should be shifted over by the correct number of bytes for multibyte - -- chars - set_text(0, 0, 0, 0, {'Ø'}) - eq({0, 3}, curbufmeths.get_extmark_by_id(ns, id1, {})) - eq({0, 8}, curbufmeths.get_extmark_by_id(ns, id2, {})) - eq({0, 8}, curbufmeths.get_extmark_by_id(ns, id3, {})) + -- marks should be shifted over by the correct number of bytes for multibyte + -- chars + set_text(0, 0, 0, 0, {'Ø'}) + eq({0, 3}, curbufmeths.get_extmark_by_id(ns, id1, {})) + eq({0, 8}, curbufmeths.get_extmark_by_id(ns, id2, {})) + eq({0, 8}, curbufmeths.get_extmark_by_id(ns, id3, {})) end) it("correctly marks changed region for redraw #13890", function() @@ -533,7 +544,60 @@ describe('api/buf', function() | ]]) + end) + + it('errors on out-of-range', function() + insert([[ + hello foo! + text]]) + eq('start_row out of bounds', pcall_err(set_text, 2, 0, 3, 0, {})) + eq('start_row out of bounds', pcall_err(set_text, -3, 0, 0, 0, {})) + eq('end_row out of bounds', pcall_err(set_text, 0, 0, 2, 0, {})) + eq('end_row out of bounds', pcall_err(set_text, 0, 0, -3, 0, {})) + eq('start_col out of bounds', pcall_err(set_text, 1, 5, 1, 5, {})) + eq('end_col out of bounds', pcall_err(set_text, 1, 0, 1, 5, {})) + end) + it('errors when start is greater than end', function() + insert([[ + hello foo! + text]]) + eq('start is higher than end', pcall_err(set_text, 1, 0, 0, 0, {})) + eq('start is higher than end', pcall_err(set_text, 0, 1, 0, 0, {})) + end) + end) + + describe('nvim_buf_get_text', function() + local get_text = curbufmeths.get_text + + before_each(function() + insert([[ + hello foo! + text]]) + end) + + it('works', function() + eq({'hello'}, get_text(0, 0, 0, 5, {})) + eq({'hello foo!'}, get_text(0, 0, 0, 42, {})) + eq({'foo!'}, get_text(0, 6, 0, 10, {})) + eq({'foo!', 'tex'}, get_text(0, 6, 1, 3, {})) + eq({'foo!', 'tex'}, get_text(-2, 6, -1, 3, {})) + eq({''}, get_text(0, 18, 0, 20, {})) + eq({'ext'}, get_text(-1, 1, -1, 4, {})) + end) + + it('errors on out-of-range', function() + eq('Index out of bounds', pcall_err(get_text, 2, 0, 3, 0, {})) + eq('Index out of bounds', pcall_err(get_text, -3, 0, 0, 0, {})) + eq('Index out of bounds', pcall_err(get_text, 0, 0, 2, 0, {})) + eq('Index out of bounds', pcall_err(get_text, 0, 0, -3, 0, {})) + -- no ml_get errors should happen #19017 + eq('', meths.get_vvar('errmsg')) + end) + + it('errors when start is greater than end', function() + eq('start is higher than end', pcall_err(get_text, 1, 0, 0, 0, {})) + eq('start_col must be less than end_col', pcall_err(get_text, 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 c9c9be5406..3d257e9477 100644 --- a/test/functional/api/buffer_updates_spec.lua +++ b/test/functional/api/buffer_updates_spec.lua @@ -96,6 +96,8 @@ local function reopenwithfolds(b) end describe('API: buffer events:', function() + before_each(clear) + it('when lines are added', function() local b, tick = editoriginal(true) @@ -785,7 +787,8 @@ describe('API: buffer events:', function() local function lines_subset(first, second) for i = 1,#first do - if first[i] ~= second[i] then + -- need to ignore trailing spaces + if first[i]:gsub(' +$', '') ~= second[i]:gsub(' +$', '') then return false end end diff --git a/test/functional/api/command_spec.lua b/test/functional/api/command_spec.lua index 6c2c136edc..7eb7ee73f9 100644 --- a/test/functional/api/command_spec.lua +++ b/test/functional/api/command_spec.lua @@ -16,8 +16,8 @@ local feed = helpers.feed local funcs = helpers.funcs describe('nvim_get_commands', function() - local cmd_dict = { addr=NIL, bang=false, bar=false, complete=NIL, complete_arg=NIL, count=NIL, definition='echo "Hello World"', name='Hello', nargs='1', range=NIL, register=false, script_id=0, } - local cmd_dict2 = { addr=NIL, bang=false, bar=false, complete=NIL, complete_arg=NIL, count=NIL, definition='pwd', name='Pwd', nargs='?', range=NIL, register=false, script_id=0, } + local cmd_dict = { addr=NIL, bang=false, bar=false, complete=NIL, complete_arg=NIL, count=NIL, definition='echo "Hello World"', name='Hello', nargs='1', preview=false, range=NIL, register=false, keepscript=false, script_id=0, } + local cmd_dict2 = { addr=NIL, bang=false, bar=false, complete=NIL, complete_arg=NIL, count=NIL, definition='pwd', name='Pwd', nargs='?', preview=false, range=NIL, register=false, keepscript=false, script_id=0, } before_each(clear) it('gets empty list if no commands were defined', function() @@ -59,12 +59,13 @@ describe('nvim_get_commands', function() end) it('gets various command attributes', function() - local cmd0 = { addr='arguments', bang=false, bar=false, complete='dir', complete_arg=NIL, count='10', definition='pwd <args>', name='TestCmd', nargs='1', range='10', register=false, script_id=0, } - local cmd1 = { addr=NIL, bang=false, bar=false, complete='custom', complete_arg='ListUsers', count=NIL, definition='!finger <args>', name='Finger', nargs='+', range=NIL, register=false, script_id=1, } - local cmd2 = { addr=NIL, bang=true, bar=false, complete=NIL, complete_arg=NIL, count=NIL, definition='call \128\253R2_foo(<q-args>)', name='Cmd2', nargs='*', range=NIL, register=false, script_id=2, } - local cmd3 = { addr=NIL, bang=false, bar=true, complete=NIL, complete_arg=NIL, count=NIL, definition='call \128\253R3_ohyeah()', name='Cmd3', nargs='0', range=NIL, register=false, script_id=3, } - local cmd4 = { addr=NIL, bang=false, bar=false, complete=NIL, complete_arg=NIL, count=NIL, definition='call \128\253R4_just_great()', name='Cmd4', nargs='0', range=NIL, register=true, script_id=4, } + local cmd0 = { addr='arguments', bang=false, bar=false, complete='dir', complete_arg=NIL, count='10', definition='pwd <args>', name='TestCmd', nargs='1', preview=false, range='10', register=false, keepscript=false, script_id=0, } + local cmd1 = { addr=NIL, bang=false, bar=false, complete='custom', complete_arg='ListUsers', count=NIL, definition='!finger <args>', name='Finger', nargs='+', preview=false, range=NIL, register=false, keepscript=false, script_id=1, } + local cmd2 = { addr=NIL, bang=true, bar=false, complete=NIL, complete_arg=NIL, count=NIL, definition='call \128\253R2_foo(<q-args>)', name='Cmd2', nargs='*', preview=false, range=NIL, register=false, keepscript=false, script_id=2, } + local cmd3 = { addr=NIL, bang=false, bar=true, complete=NIL, complete_arg=NIL, count=NIL, definition='call \128\253R3_ohyeah()', name='Cmd3', nargs='0', preview=false, range=NIL, register=false, keepscript=false, script_id=3, } + local cmd4 = { addr=NIL, bang=false, bar=false, complete=NIL, complete_arg=NIL, count=NIL, definition='call \128\253R4_just_great()', name='Cmd4', nargs='0', preview=false, range=NIL, register=true, keepscript=false, script_id=4, } source([[ + let s:foo = 1 command -complete=custom,ListUsers -nargs=+ Finger !finger <args> ]]) eq({Finger=cmd1}, meths.get_commands({builtin=false})) @@ -72,12 +73,18 @@ describe('nvim_get_commands', function() eq({Finger=cmd1, TestCmd=cmd0}, meths.get_commands({builtin=false})) source([[ + function! s:foo() abort + endfunction command -bang -nargs=* Cmd2 call <SID>foo(<q-args>) ]]) source([[ + function! s:ohyeah() abort + endfunction command -bar -nargs=0 Cmd3 call <SID>ohyeah() ]]) source([[ + function! s:just_great() abort + endfunction command -register Cmd4 call <SID>just_great() ]]) -- TODO(justinmk): Order is stable but undefined. Sort before return? @@ -85,11 +92,11 @@ describe('nvim_get_commands', function() end) end) -describe('nvim_add_user_command', function() +describe('nvim_create_user_command', function() before_each(clear) it('works with strings', function() - meths.add_user_command('SomeCommand', 'let g:command_fired = <args>', {nargs = 1}) + meths.create_user_command('SomeCommand', 'let g:command_fired = <args>', {nargs = 1}) meths.command('SomeCommand 42') eq(42, meths.eval('g:command_fired')) end) @@ -97,7 +104,7 @@ describe('nvim_add_user_command', function() it('works with Lua functions', function() exec_lua [[ result = {} - vim.api.nvim_add_user_command('CommandWithLuaCallback', function(opts) + vim.api.nvim_create_user_command('CommandWithLuaCallback', function(opts) result = opts end, { nargs = "*", @@ -107,51 +114,267 @@ describe('nvim_add_user_command', function() ]] eq({ - args = "hello", + args = [[this is a\ test]], + fargs = {"this", "is", "a test"}, bang = false, line1 = 1, line2 = 1, mods = "", + smods = { + browse = false, + confirm = false, + emsg_silent = false, + hide = false, + keepalt = false, + keepjumps = false, + keepmarks = false, + keeppatterns = false, + lockmarks = false, + noautocmd = false, + noswapfile = false, + sandbox = false, + silent = false, + split = "", + tab = 0, + unsilent = false, + verbose = -1, + vertical = false, + }, range = 0, count = 2, reg = "", - }, exec_lua [[ - vim.api.nvim_command('CommandWithLuaCallback hello') + }, exec_lua [=[ + vim.api.nvim_command([[CommandWithLuaCallback this is a\ test]]) return result - ]]) + ]=]) eq({ - args = "", + args = [[this includes\ a backslash: \\]], + fargs = {"this", "includes a", "backslash:", "\\"}, + bang = false, + line1 = 1, + line2 = 1, + mods = "", + smods = { + browse = false, + confirm = false, + emsg_silent = false, + hide = false, + keepalt = false, + keepjumps = false, + keepmarks = false, + keeppatterns = false, + lockmarks = false, + noautocmd = false, + noswapfile = false, + sandbox = false, + silent = false, + split = "", + tab = 0, + unsilent = false, + verbose = -1, + vertical = false, + }, + range = 0, + count = 2, + reg = "", + }, exec_lua [=[ + vim.api.nvim_command([[CommandWithLuaCallback this includes\ a backslash: \\]]) + return result + ]=]) + + eq({ + args = "a\\b", + fargs = {"a\\b"}, + bang = false, + line1 = 1, + line2 = 1, + mods = "", + smods = { + browse = false, + confirm = false, + emsg_silent = false, + hide = false, + keepalt = false, + keepjumps = false, + keepmarks = false, + keeppatterns = false, + lockmarks = false, + noautocmd = false, + noswapfile = false, + sandbox = false, + silent = false, + split = "", + tab = 0, + unsilent = false, + verbose = -1, + vertical = false, + }, + range = 0, + count = 2, + reg = "", + }, exec_lua [=[ + vim.api.nvim_command('CommandWithLuaCallback a\\b') + return result + ]=]) + + eq({ + args = 'h\tey ', + fargs = {[[h]], [[ey]]}, bang = true, line1 = 10, line2 = 10, - mods = "botright", + mods = "confirm unsilent botright", + smods = { + browse = false, + confirm = true, + emsg_silent = false, + hide = false, + keepalt = false, + keepjumps = false, + keepmarks = false, + keeppatterns = false, + lockmarks = false, + noautocmd = false, + noswapfile = false, + sandbox = false, + silent = false, + split = "botright", + tab = 0, + unsilent = true, + verbose = -1, + vertical = false, + }, range = 1, count = 10, reg = "", - }, exec_lua [[ - vim.api.nvim_command('botright 10CommandWithLuaCallback!') + }, exec_lua [=[ + vim.api.nvim_command('unsilent botright confirm 10CommandWithLuaCallback! h\tey ') return result - ]]) + ]=]) eq({ - args = "", + args = "h", + fargs = {"h"}, bang = false, line1 = 1, line2 = 42, mods = "", + smods = { + browse = false, + confirm = false, + emsg_silent = false, + hide = false, + keepalt = false, + keepjumps = false, + keepmarks = false, + keeppatterns = false, + lockmarks = false, + noautocmd = false, + noswapfile = false, + sandbox = false, + silent = false, + split = "", + tab = 0, + unsilent = false, + verbose = -1, + vertical = false, + }, range = 1, count = 42, reg = "", }, exec_lua [[ - vim.api.nvim_command('CommandWithLuaCallback 42') + vim.api.nvim_command('CommandWithLuaCallback 42 h') return result ]]) + + eq({ + args = "", + fargs = {}, -- fargs works without args + bang = false, + line1 = 1, + line2 = 1, + mods = "", + smods = { + browse = false, + confirm = false, + emsg_silent = false, + hide = false, + keepalt = false, + keepjumps = false, + keepmarks = false, + keeppatterns = false, + lockmarks = false, + noautocmd = false, + noswapfile = false, + sandbox = false, + silent = false, + split = "", + tab = 0, + unsilent = false, + verbose = -1, + vertical = false, + }, + range = 0, + count = 2, + reg = "", + }, exec_lua [[ + vim.api.nvim_command('CommandWithLuaCallback') + return result + ]]) + + -- f-args doesn't split when command nargs is 1 or "?" + exec_lua [[ + result = {} + vim.api.nvim_create_user_command('CommandWithOneArg', function(opts) + result = opts + end, { + nargs = "?", + bang = true, + count = 2, + }) + ]] + + eq({ + args = "hello I'm one argument", + fargs = {"hello I'm one argument"}, -- Doesn't split args + bang = false, + line1 = 1, + line2 = 1, + mods = "", + smods = { + browse = false, + confirm = false, + emsg_silent = false, + hide = false, + keepalt = false, + keepjumps = false, + keepmarks = false, + keeppatterns = false, + lockmarks = false, + noautocmd = false, + noswapfile = false, + sandbox = false, + silent = false, + split = "", + tab = 0, + unsilent = false, + verbose = -1, + vertical = false, + }, + range = 0, + count = 2, + reg = "", + }, exec_lua [[ + vim.api.nvim_command('CommandWithOneArg hello I\'m one argument') + return result + ]]) + end) it('can define buffer-local commands', function() local bufnr = meths.create_buf(false, false) - bufmeths.add_user_command(bufnr, "Hello", "", {}) + bufmeths.create_user_command(bufnr, "Hello", "", {}) matches("Not an editor command: Hello", pcall_err(meths.command, "Hello")) meths.set_current_buf(bufnr) meths.command("Hello") @@ -160,7 +383,7 @@ describe('nvim_add_user_command', function() it('can use a Lua complete function', function() exec_lua [[ - vim.api.nvim_add_user_command('Test', '', { + vim.api.nvim_create_user_command('Test', '', { nargs = "*", complete = function(arg, cmdline, pos) local options = {"aaa", "bbb", "ccc"} @@ -180,20 +403,52 @@ describe('nvim_add_user_command', function() feed('<C-U>Test b<Tab>') eq('Test bbb', funcs.getcmdline()) end) + + it('does not allow invalid command names', function() + matches("'name' must begin with an uppercase letter", pcall_err(exec_lua, [[ + vim.api.nvim_create_user_command('test', 'echo "hi"', {}) + ]])) + + matches('Invalid command name', pcall_err(exec_lua, [[ + vim.api.nvim_create_user_command('t@', 'echo "hi"', {}) + ]])) + + matches('Invalid command name', pcall_err(exec_lua, [[ + vim.api.nvim_create_user_command('T@st', 'echo "hi"', {}) + ]])) + + matches('Invalid command name', pcall_err(exec_lua, [[ + vim.api.nvim_create_user_command('Test!', 'echo "hi"', {}) + ]])) + + matches('Invalid command name', pcall_err(exec_lua, [[ + vim.api.nvim_create_user_command('💩', 'echo "hi"', {}) + ]])) + end) + + it('smods can be used with nvim_cmd', function() + exec_lua[[ + vim.api.nvim_create_user_command('MyEcho', function(opts) + vim.api.nvim_cmd({ cmd = 'echo', args = { '&verbose' }, mods = opts.smods }, {}) + end, {}) + ]] + + eq("3", meths.cmd({ cmd = 'MyEcho', mods = { verbose = 3 } }, { output = true })) + end) end) describe('nvim_del_user_command', function() before_each(clear) it('can delete global commands', function() - meths.add_user_command('Hello', 'echo "Hi"', {}) + meths.create_user_command('Hello', 'echo "Hi"', {}) meths.command('Hello') meths.del_user_command('Hello') matches("Not an editor command: Hello", pcall_err(meths.command, "Hello")) end) it('can delete buffer-local commands', function() - bufmeths.add_user_command(0, 'Hello', 'echo "Hi"', {}) + bufmeths.create_user_command(0, 'Hello', 'echo "Hi"', {}) meths.command('Hello') bufmeths.del_user_command(0, 'Hello') matches("Not an editor command: Hello", pcall_err(meths.command, "Hello")) diff --git a/test/functional/api/extmark_spec.lua b/test/functional/api/extmark_spec.lua index a8f538b951..bc8d811c6d 100644 --- a/test/functional/api/extmark_spec.lua +++ b/test/functional/api/extmark_spec.lua @@ -110,6 +110,22 @@ describe('API/extmarks', function() pcall_err(set_extmark, ns, marks[2], 0, 0, { end_col = 1, end_row = 1 })) end) + it("can end extranges past final newline when strict mode is false", function() + set_extmark(ns, marks[1], 0, 0, { + end_col = 1, + end_row = 1, + strict = false, + }) + end) + + it("can end extranges past final column when strict mode is false", function() + set_extmark(ns, marks[1], 0, 0, { + end_col = 6, + end_row = 0, + strict = false, + }) + end) + it('adds, updates and deletes marks', function() local rv = set_extmark(ns, marks[1], positions[1][1], positions[1][2]) eq(marks[1], rv) @@ -908,7 +924,7 @@ describe('API/extmarks', function() eq(3, set_extmark(ns, 3, positions[2][1], positions[2][2])) eq(4, set_extmark(ns, 0, positions[1][1], positions[1][2])) - -- mixing manual and allocated id:s are not recommened, but it should + -- mixing manual and allocated id:s are not recommended, but it should -- do something reasonable eq(6, set_extmark(ns, 6, positions[2][1], positions[2][2])) eq(7, set_extmark(ns, 0, positions[1][1], positions[1][2])) @@ -1430,7 +1446,60 @@ describe('API/extmarks', function() end_col = 0, end_line = 1 }) - eq({ {1, 0, 0, { end_col = 0, end_row = 1 }} }, get_extmarks(ns, 0, -1, {details=true})) + eq({ {1, 0, 0, { + end_col = 0, + end_row = 1, + right_gravity = true, + end_right_gravity = false, + }} }, get_extmarks(ns, 0, -1, {details=true})) + end) + + it('can get details', function() + set_extmark(ns, marks[1], 0, 0, { + end_col = 0, + end_row = 1, + right_gravity = false, + end_right_gravity = true, + priority = 0, + hl_eol = true, + hl_mode = "blend", + hl_group = "String", + virt_text = { { "text", "Statement" } }, + virt_text_pos = "right_align", + virt_text_hide = true, + virt_lines = { { { "lines", "Statement" } }}, + virt_lines_above = true, + virt_lines_leftcol = true, + }) + set_extmark(ns, marks[2], 0, 0, { + priority = 0, + virt_text = { { "text", "Statement" } }, + virt_text_win_col = 1, + }) + eq({0, 0, { + end_col = 0, + end_row = 1, + right_gravity = false, + end_right_gravity = true, + priority = 0, + hl_eol = true, + hl_mode = "blend", + hl_group = "String", + virt_text = { { "text", "Statement" } }, + virt_text_pos = "right_align", + virt_text_hide = true, + virt_lines = { { { "lines", "Statement" } }}, + virt_lines_above = true, + virt_lines_leftcol = true, + } }, get_extmark_by_id(ns, marks[1], { details = true })) + eq({0, 0, { + right_gravity = true, + priority = 0, + virt_text = { { "text", "Statement" } }, + virt_text_hide = false, + virt_text_pos = "win_col", + virt_text_win_col = 1, + } }, get_extmark_by_id(ns, marks[2], { details = true })) end) end) @@ -1537,3 +1606,209 @@ describe('Extmarks buffer api with many marks', function() eq({}, get_marks(ns2)) end) end) + +describe('API/win_extmark', function() + local screen + local marks, line1, line2 + local ns + + before_each(function() + -- Initialize some namespaces and insert text into a buffer + marks = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12} + + line1 = "non ui-watched line" + line2 = "ui-watched line" + + clear() + + insert(line1) + feed("o<esc>") + insert(line2) + ns = request('nvim_create_namespace', "extmark-ui") + end) + + it('sends and only sends ui-watched marks to ui', function() + screen = Screen.new(20, 4) + screen:attach() + -- should send this + set_extmark(ns, marks[1], 1, 0, { ui_watched = true }) + -- should not send this + set_extmark(ns, marks[2], 0, 0, { ui_watched = false }) + screen:expect({ + grid = [[ + non ui-watched line | + ui-watched lin^e | + ~ | + | + ]], + extmarks = { + [2] = { + -- positioned at the end of the 2nd line + { {id = 1000}, 1, 1, 1, 16 }, + } + }, + }) + end) + + it('sends multiple ui-watched marks to ui', function() + screen = Screen.new(20, 4) + screen:attach() + -- should send all of these + set_extmark(ns, marks[1], 1, 0, { ui_watched = true, virt_text_pos = "overlay" }) + set_extmark(ns, marks[2], 1, 2, { ui_watched = true, virt_text_pos = "overlay" }) + set_extmark(ns, marks[3], 1, 4, { ui_watched = true, virt_text_pos = "overlay" }) + set_extmark(ns, marks[4], 1, 6, { ui_watched = true, virt_text_pos = "overlay" }) + set_extmark(ns, marks[5], 1, 8, { ui_watched = true }) + screen:expect({ + grid = [[ + non ui-watched line | + ui-watched lin^e | + ~ | + | + ]], + extmarks = { + [2] = { + -- earlier notifications + { {id = 1000}, 1, 1, 1, 0 }, + { {id = 1000}, 1, 1, 1, 0 }, { {id = 1000}, 1, 2, 1, 2 }, + { {id = 1000}, 1, 1, 1, 0 }, { {id = 1000}, 1, 2, 1, 2 }, { {id = 1000}, 1, 3, 1, 4 }, + { {id = 1000}, 1, 1, 1, 0 }, { {id = 1000}, 1, 2, 1, 2 }, { {id = 1000}, 1, 3, 1, 4 }, { {id = 1000}, 1, 4, 1, 6 }, + -- final + -- overlay + { {id = 1000}, 1, 1, 1, 0 }, + { {id = 1000}, 1, 2, 1, 2 }, + { {id = 1000}, 1, 3, 1, 4 }, + { {id = 1000}, 1, 4, 1, 6 }, + -- eol + { {id = 1000}, 1, 5, 1, 16 }, + } + }, + }) + end) + + it('updates ui-watched marks', function() + screen = Screen.new(20, 4) + screen:attach() + -- should send this + set_extmark(ns, marks[1], 1, 0, { ui_watched = true }) + -- should not send this + set_extmark(ns, marks[2], 0, 0, { ui_watched = false }) + -- make some changes + insert(" update") + screen:expect({ + grid = [[ + non ui-watched line | + ui-watched linupdat^e| + e | + | + ]], + extmarks = { + [2] = { + -- positioned at the end of the 2nd line + { {id = 1000}, 1, 1, 1, 16 }, + -- updated and wrapped to 3rd line + { {id = 1000}, 1, 1, 2, 2 }, + } + } + }) + feed("<c-e>") + screen:expect({ + grid = [[ + ui-watched linupdat^e| + e | + ~ | + | + ]], + extmarks = { + [2] = { + -- positioned at the end of the 2nd line + { {id = 1000}, 1, 1, 1, 16 }, + -- updated and wrapped to 3rd line + { {id = 1000}, 1, 1, 2, 2 }, + -- scrolled up one line, should be handled by grid scroll + } + } + }) + end) + + it('sends ui-watched to splits', function() + screen = Screen.new(20, 8) + screen:attach({ext_multigrid=true}) + -- should send this + set_extmark(ns, marks[1], 1, 0, { ui_watched = true }) + -- should not send this + set_extmark(ns, marks[2], 0, 0, { ui_watched = false }) + command('split') + screen:expect({ + grid = [[ + ## grid 1 + [4:--------------------]| + [4:--------------------]| + [4:--------------------]| + [No Name] [+] | + [2:--------------------]| + [2:--------------------]| + [No Name] [+] | + [3:--------------------]| + ## grid 2 + non ui-watched line | + ui-watched line | + ## grid 3 + | + ## grid 4 + non ui-watched line | + ui-watched lin^e | + ~ | + ]], + extmarks = { + [2] = { + -- positioned at the end of the 2nd line + { {id = 1000}, 1, 1, 1, 16 }, + -- updated after split + { {id = 1000}, 1, 1, 1, 16 }, + }, + [4] = { + -- only after split + { {id = 1001}, 1, 1, 1, 16 }, + } + } + }) + -- make some changes + insert(" update") + screen:expect({ + grid = [[ + ## grid 1 + [4:--------------------]| + [4:--------------------]| + [4:--------------------]| + [No Name] [+] | + [2:--------------------]| + [2:--------------------]| + [No Name] [+] | + [3:--------------------]| + ## grid 2 + non ui-watched line | + ui-watched linupd@@@| + ## grid 3 + | + ## grid 4 + non ui-watched line | + ui-watched linupdat^e| + e | + ]], + extmarks = { + [2] = { + -- positioned at the end of the 2nd line + { {id = 1000}, 1, 1, 1, 16 }, + -- updated after split + { {id = 1000}, 1, 1, 1, 16 }, + }, + [4] = { + { {id = 1001}, 1, 1, 1, 16 }, + -- updated + { {id = 1001}, 1, 1, 2, 2 }, + } + } + }) + end) +end) diff --git a/test/functional/api/highlight_spec.lua b/test/functional/api/highlight_spec.lua index 21e3094f8e..933103046c 100644 --- a/test/functional/api/highlight_spec.lua +++ b/test/functional/api/highlight_spec.lua @@ -3,6 +3,7 @@ local clear, nvim = helpers.clear, helpers.nvim local Screen = require('test.functional.ui.screen') local eq, eval = helpers.eq, helpers.eval local command = helpers.command +local exec_capture = helpers.exec_capture local meths = helpers.meths local funcs = helpers.funcs local pcall_err = helpers.pcall_err @@ -27,8 +28,11 @@ describe('API: highlight',function() bold = true, italic = true, reverse = true, - undercurl = true, underline = true, + undercurl = true, + underdouble = true, + underdotted = true, + underdashed = true, strikethrough = true, } @@ -51,7 +55,7 @@ describe('API: highlight',function() eq('Invalid highlight id: 30000', string.match(emsg, 'Invalid.*')) -- Test all highlight properties. - command('hi NewHighlight gui=underline,bold,undercurl,italic,reverse,strikethrough') + command('hi NewHighlight gui=underline,bold,undercurl,underdouble,underdotted,underdashed,italic,reverse,strikethrough') eq(expected_rgb2, nvim("get_hl_by_id", hl_id, true)) -- Test nil argument. @@ -129,6 +133,13 @@ describe('API: highlight',function() eq({ underline = true, standout = true, }, meths.get_hl_by_name('cursorline', 0)); + -- Test cterm & Normal values. #18024 (tail) & #18980 + -- Ensure Normal, and groups that match Normal return their fg & bg cterm values + meths.set_hl(0, 'Normal', {ctermfg = 17, ctermbg = 213}) + meths.set_hl(0, 'NotNormal', {ctermfg = 17, ctermbg = 213}) + -- Note colors are "cterm" values, not rgb-as-ints + eq({foreground = 17, background = 213}, nvim("get_hl_by_name", 'Normal', false)) + eq({foreground = 17, background = 213}, nvim("get_hl_by_name", 'NotNormal', false)) end) it('nvim_get_hl_id_by_name', function() @@ -194,10 +205,15 @@ describe("API: set highlight", function() reverse = true, undercurl = true, underline = true, + underdashed = true, + underdotted = true, + underdouble = true, + strikethrough = true, cterm = { italic = true, reverse = true, undercurl = true, + strikethrough = true, } } local highlight3_result_gui = { @@ -208,6 +224,10 @@ describe("API: set highlight", function() reverse = true, undercurl = true, underline = true, + underdashed = true, + underdotted = true, + underdouble = true, + strikethrough = true, } local highlight3_result_cterm = { background = highlight_color.ctermbg, @@ -215,6 +235,7 @@ describe("API: set highlight", function() italic = true, reverse = true, undercurl = true, + strikethrough = true, } local function get_ns() @@ -237,6 +258,12 @@ describe("API: set highlight", function() eq(highlight2_result, meths.get_hl_by_name('Test_hl', false)) end) + it ("can set empty cterm attr", function() + local ns = get_ns() + meths.set_hl(ns, 'Test_hl', { cterm = {} }) + eq({}, meths.get_hl_by_name('Test_hl', false)) + end) + it ("cterm attr defaults to gui attr", function() local ns = get_ns() meths.set_hl(ns, 'Test_hl', highlight1) @@ -252,4 +279,76 @@ describe("API: set highlight", function() eq(highlight3_result_gui, meths.get_hl_by_name('Test_hl', true)) eq(highlight3_result_cterm, meths.get_hl_by_name('Test_hl', false)) end) + + it ("can set a highlight in the global namespace", function() + meths.set_hl(0, 'Test_hl', highlight2_config) + eq('Test_hl xxx cterm=underline,reverse ctermfg=8 ctermbg=15 gui=underline,reverse', + exec_capture('highlight Test_hl')) + + meths.set_hl(0, 'Test_hl', { background = highlight_color.bg }) + eq('Test_hl xxx guibg=#0032aa', + exec_capture('highlight Test_hl')) + + meths.set_hl(0, 'Test_hl2', highlight3_config) + eq('Test_hl2 xxx cterm=undercurl,italic,reverse,strikethrough ctermfg=8 ctermbg=15 gui=bold,underline,undercurl,underdouble,underdotted,underdashed,italic,reverse,strikethrough guifg=#ff0000 guibg=#0032aa', + exec_capture('highlight Test_hl2')) + + -- Colors are stored with the name they are defined, but + -- with canonical casing + meths.set_hl(0, 'Test_hl3', { bg = 'reD', fg = 'bLue'}) + eq('Test_hl3 xxx guifg=Blue guibg=Red', + exec_capture('highlight Test_hl3')) + end) + + it ("can modify a highlight in the global namespace", function() + meths.set_hl(0, 'Test_hl3', { bg = 'red', fg = 'blue'}) + eq('Test_hl3 xxx guifg=Blue guibg=Red', + exec_capture('highlight Test_hl3')) + + meths.set_hl(0, 'Test_hl3', { bg = 'red' }) + eq('Test_hl3 xxx guibg=Red', + exec_capture('highlight Test_hl3')) + + meths.set_hl(0, 'Test_hl3', { ctermbg = 9, ctermfg = 12}) + eq('Test_hl3 xxx ctermfg=12 ctermbg=9', + exec_capture('highlight Test_hl3')) + + meths.set_hl(0, 'Test_hl3', { ctermbg = 'red' , ctermfg = 'blue'}) + eq('Test_hl3 xxx ctermfg=12 ctermbg=9', + exec_capture('highlight Test_hl3')) + + meths.set_hl(0, 'Test_hl3', { ctermbg = 9 }) + eq('Test_hl3 xxx ctermbg=9', + exec_capture('highlight Test_hl3')) + + eq("'redd' is not a valid color", + pcall_err(meths.set_hl, 0, 'Test_hl3', {fg='redd'})) + + eq("'bleu' is not a valid color", + pcall_err(meths.set_hl, 0, 'Test_hl3', {ctermfg='bleu'})) + + meths.set_hl(0, 'Test_hl3', {fg='#FF00FF'}) + eq('Test_hl3 xxx guifg=#ff00ff', + exec_capture('highlight Test_hl3')) + + eq("'#FF00FF' is not a valid color", + pcall_err(meths.set_hl, 0, 'Test_hl3', {ctermfg='#FF00FF'})) + + for _, fg_val in ipairs{ nil, 'NONE', 'nOnE', '', -1 } do + meths.set_hl(0, 'Test_hl3', {fg = fg_val}) + eq('Test_hl3 xxx cleared', + exec_capture('highlight Test_hl3')) + end + + meths.set_hl(0, 'Test_hl3', {fg='#FF00FF', blend=50}) + eq('Test_hl3 xxx guifg=#ff00ff blend=50', + exec_capture('highlight Test_hl3')) + + end) + + it ("correctly sets 'Normal' internal properties", function() + -- Normal has some special handling internally. #18024 + meths.set_hl(0, 'Normal', {fg='#000083', bg='#0000F3'}) + eq({foreground = 131, background = 243}, nvim("get_hl_by_name", 'Normal', true)) + end) end) diff --git a/test/functional/api/keymap_spec.lua b/test/functional/api/keymap_spec.lua index 450a76ddac..6bc6651e04 100644 --- a/test/functional/api/keymap_spec.lua +++ b/test/functional/api/keymap_spec.lua @@ -15,6 +15,9 @@ local pcall_err = helpers.pcall_err local shallowcopy = helpers.shallowcopy local sleep = helpers.sleep +local sid_api_client = -9 +local sid_lua = -8 + describe('nvim_get_keymap', function() before_each(clear) @@ -318,7 +321,7 @@ describe('nvim_get_keymap', function() eq({space_table}, meths.get_keymap('n')) end) - it('can handle lua keymaps', function() + it('can handle lua mappings', function() eq(0, exec_lua [[ GlobalCount = 0 vim.api.nvim_set_keymap ('n', 'asdf', '', {callback = function() GlobalCount = GlobalCount + 1 end }) @@ -333,14 +336,14 @@ describe('nvim_get_keymap', function() return GlobalCount ]]) local mapargs = meths.get_keymap('n') - assert.Truthy(type(mapargs[1].callback) == 'number', 'callback is not luaref number') + assert(type(mapargs[1].callback) == 'number', 'callback is not luaref number') mapargs[1].callback = nil eq({ lhs='asdf', script=0, silent=0, expr=0, - sid=0, + sid=sid_lua, buffer=0, nowait=0, mode='n', @@ -357,7 +360,7 @@ describe('nvim_get_keymap', function() script=0, silent=0, expr=0, - sid=0, + sid=sid_api_client, buffer=0, nowait=0, mode='n', @@ -400,7 +403,7 @@ describe('nvim_set_keymap, nvim_del_keymap', function() to_return.silent = not opts.silent and 0 or 1 to_return.nowait = not opts.nowait and 0 or 1 to_return.expr = not opts.expr and 0 or 1 - to_return.sid = not opts.sid and 0 or opts.sid + to_return.sid = not opts.sid and sid_api_client or opts.sid to_return.buffer = not opts.buffer and 0 or opts.buffer to_return.lnum = not opts.lnum and 0 or opts.lnum to_return.desc = opts.desc @@ -579,7 +582,7 @@ describe('nvim_set_keymap, nvim_del_keymap', function() it('can set mappings containing literal keycodes', function() meths.set_keymap('n', '\n\r\n', 'rhs', {}) local expected = generate_mapargs('n', '<NL><CR><NL>', 'rhs') - eq(expected, get_mapargs('n', '<C-j><CR><C-j>')) + eq(expected, get_mapargs('n', '<NL><CR><NL>')) end) it('can set mappings whose RHS is a <Nop>', function() @@ -603,6 +606,13 @@ describe('nvim_set_keymap, nvim_del_keymap', function() eq({''}, curbufmeths.get_lines(0, -1, 0)) eq(generate_mapargs('i', 'lhs', '<NOP>', {}), get_mapargs('i', 'lhs')) + + -- a single ^V in RHS is also <Nop> (see :h map-empty-rhs) + meths.set_keymap('i', 'lhs', '\022', {}) + command('normal ilhs') + eq({''}, curbufmeths.get_lines(0, -1, 0)) + eq(generate_mapargs('i', 'lhs', '\022', {}), + get_mapargs('i', 'lhs')) end) it('treats an empty RHS in a mapping like a <Nop>', function() @@ -625,7 +635,7 @@ describe('nvim_set_keymap, nvim_del_keymap', function() it('interprets control sequences in expr-quotes correctly when called ' ..'inside vim', function() command([[call nvim_set_keymap('i', "\<space>", "\<tab>", {})]]) - eq(generate_mapargs('i', '<Space>', '\t', {}), + eq(generate_mapargs('i', '<Space>', '\t', {sid=0}), get_mapargs('i', '<Space>')) feed('i ') eq({'\t'}, curbufmeths.get_lines(0, -1, 0)) @@ -782,7 +792,7 @@ describe('nvim_set_keymap, nvim_del_keymap', function() end) - it (':map command shows lua keymap correctly', function() + it (':map command shows lua mapping correctly', function() exec_lua [[ vim.api.nvim_set_keymap ('n', 'asdf', '', {callback = function() print('jkl;') end }) ]] @@ -790,7 +800,7 @@ describe('nvim_set_keymap, nvim_del_keymap', function() "^\nn asdf <Lua function %d+>")) end) - it ('mapcheck() returns lua keymap correctly', function() + it ('mapcheck() returns lua mapping correctly', function() exec_lua [[ vim.api.nvim_set_keymap ('n', 'asdf', '', {callback = function() print('jkl;') end }) ]] @@ -798,16 +808,16 @@ describe('nvim_set_keymap, nvim_del_keymap', function() "^<Lua function %d+>")) end) - it ('maparg() returns lua keymap correctly', function() + it ('maparg() returns lua mapping correctly', function() exec_lua [[ vim.api.nvim_set_keymap ('n', 'asdf', '', {callback = function() print('jkl;') end }) ]] assert.truthy(string.match(funcs.maparg('asdf', 'n'), "^<Lua function %d+>")) local mapargs = funcs.maparg('asdf', 'n', false, true) - assert.Truthy(type(mapargs.callback) == 'number', 'callback is not luaref number') + assert(type(mapargs.callback) == 'number', 'callback is not luaref number') mapargs.callback = nil - eq(generate_mapargs('n', 'asdf', nil, {}), mapargs) + eq(generate_mapargs('n', 'asdf', nil, {sid=sid_lua}), mapargs) end) it('can make lua expr mappings', function() @@ -871,7 +881,28 @@ describe('nvim_set_keymap, nvim_del_keymap', function() eq('\nNo mapping found', helpers.exec_capture('nmap asdf')) end) - it('can set descriptions on keymaps', function() + it('no double-free when unmapping simplifiable lua mappings', function() + eq(0, exec_lua [[ + GlobalCount = 0 + vim.api.nvim_set_keymap('n', '<C-I>', '', {callback = function() GlobalCount = GlobalCount + 1 end }) + return GlobalCount + ]]) + + feed('<C-I>\n') + + eq(1, exec_lua[[return GlobalCount]]) + + exec_lua [[ + vim.api.nvim_del_keymap('n', '<C-I>') + ]] + + feed('<C-I>\n') + + eq(1, exec_lua[[return GlobalCount]]) + eq('\nNo mapping found', helpers.exec_capture('nmap <C-I>')) + end) + + it('can set descriptions on mappings', function() meths.set_keymap('n', 'lhs', 'rhs', {desc="map description"}) eq(generate_mapargs('n', 'lhs', 'rhs', {desc="map description"}), get_mapargs('n', 'lhs')) eq("\nn lhs rhs\n map description", @@ -1037,4 +1068,25 @@ describe('nvim_buf_set_keymap, nvim_buf_del_keymap', function() eq(1, exec_lua[[return GlobalCount]]) eq('\nNo mapping found', helpers.exec_capture('nmap asdf')) end) + + it('no double-free when unmapping simplifiable lua mappings', function() + eq(0, exec_lua [[ + GlobalCount = 0 + vim.api.nvim_buf_set_keymap(0, 'n', '<C-I>', '', {callback = function() GlobalCount = GlobalCount + 1 end }) + return GlobalCount + ]]) + + feed('<C-I>\n') + + eq(1, exec_lua[[return GlobalCount]]) + + exec_lua [[ + vim.api.nvim_buf_del_keymap(0, 'n', '<C-I>') + ]] + + feed('<C-I>\n') + + eq(1, exec_lua[[return GlobalCount]]) + eq('\nNo mapping found', helpers.exec_capture('nmap <C-I>')) + end) end) diff --git a/test/functional/api/proc_spec.lua b/test/functional/api/proc_spec.lua index d828bdf948..0fbf58a8e7 100644 --- a/test/functional/api/proc_spec.lua +++ b/test/functional/api/proc_spec.lua @@ -63,9 +63,9 @@ describe('API', function() local pid = funcs.getpid() local pinfo = request('nvim_get_proc', pid) eq((iswin() and 'nvim.exe' or 'nvim'), pinfo.name) - eq(pinfo.pid, pid) - eq(type(pinfo.ppid), 'number') - neq(pinfo.ppid, pid) + eq(pid, pinfo.pid) + eq('number', type(pinfo.ppid)) + neq(pid, pinfo.ppid) end) it('validates input', function() diff --git a/test/functional/api/server_requests_spec.lua b/test/functional/api/server_requests_spec.lua index 309d9084c8..00a4dd041d 100644 --- a/test/functional/api/server_requests_spec.lua +++ b/test/functional/api/server_requests_spec.lua @@ -170,7 +170,7 @@ describe('server -> client', function() if method == "notification" then eq('done!', eval('rpcrequest('..cid..', "nested")')) elseif method == "nested_done" then - ok(false, 'this should never have been sent') + ok(false, 'never sent', 'sent') end end @@ -181,12 +181,6 @@ describe('server -> client', function() end) describe('recursive (child) nvim client', function() - if helpers.isCI('travis') and helpers.is_os('mac') then - -- XXX: Hangs Travis macOS since e9061117a5b8f195c3f26a5cb94e18ddd7752d86. - pending("[Hangs on Travis macOS. #5002]", function() end) - return - end - before_each(function() command("let vim = rpcstart('"..nvim_prog.."', ['-u', 'NONE', '-i', 'NONE', '--cmd', 'set noswapfile', '--embed', '--headless'])") neq(0, eval('vim')) diff --git a/test/functional/api/version_spec.lua b/test/functional/api/version_spec.lua index bf67d4788b..771192e9ab 100644 --- a/test/functional/api/version_spec.lua +++ b/test/functional/api/version_spec.lua @@ -33,9 +33,11 @@ describe("api_info()['version']", function() local major = version['major'] local minor = version['minor'] local patch = version['patch'] + local prerelease = version['prerelease'] eq("number", type(major)) eq("number", type(minor)) eq("number", type(patch)) + eq("boolean", type(prerelease)) eq(1, funcs.has("nvim-"..major.."."..minor.."."..patch)) eq(0, funcs.has("nvim-"..major.."."..minor.."."..(patch + 1))) eq(0, funcs.has("nvim-"..major.."."..(minor + 1).."."..patch)) @@ -91,6 +93,7 @@ describe("api metadata", function() local api, compat, stable, api_level local old_api = {} setup(function() + clear() -- Ensure a session before requesting api_info. api = meths.get_api_info()[2] compat = api.version.api_compatible api_level = api.version.api_level diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index 22201e21a2..3724dbf820 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -1,11 +1,13 @@ local helpers = require('test.functional.helpers')(after_each) local Screen = require('test.functional.ui.screen') +local lfs = require('lfs') local fmt = string.format local assert_alive = helpers.assert_alive local NIL = helpers.NIL local clear, nvim, eq, neq = helpers.clear, helpers.nvim, helpers.eq, helpers.neq local command = helpers.command +local exec = helpers.exec local eval = helpers.eval local expect = helpers.expect local funcs = helpers.funcs @@ -23,6 +25,9 @@ local next_msg = helpers.next_msg local tmpname = helpers.tmpname local write_file = helpers.write_file local exec_lua = helpers.exec_lua +local exc_exec = helpers.exc_exec +local insert = helpers.insert +local expect_exit = helpers.expect_exit local pcall_err = helpers.pcall_err local format_string = helpers.format_string @@ -120,9 +125,9 @@ describe('API', function() -- Functions nvim('exec', 'function Foo()\ncall setline(1,["xxx"])\nendfunction', false) - eq(nvim('get_current_line'), '') + eq('', nvim('get_current_line')) nvim('exec', 'call Foo()', false) - eq(nvim('get_current_line'), 'xxx') + eq('xxx', nvim('get_current_line')) -- Autocmds nvim('exec','autocmd BufAdd * :let x1 = "Hello"', false) @@ -333,6 +338,7 @@ describe('API', function() describe('nvim_command_output', function() it('does not induce hit-enter prompt', function() + nvim("ui_attach", 80, 20, {}) -- Induce a hit-enter prompt use nvim_input (non-blocking). nvim('command', 'set cmdheight=1') nvim('input', [[:echo "hi\nhi2"<CR>]]) @@ -535,6 +541,31 @@ describe('API', function() end) end) + describe('nvim_set_current_dir', function() + local start_dir + + before_each(function() + clear() + funcs.mkdir("Xtestdir") + start_dir = funcs.getcwd() + end) + + after_each(function() + helpers.rmdir("Xtestdir") + end) + + it('works', function() + meths.set_current_dir("Xtestdir") + eq(funcs.getcwd(), start_dir .. helpers.get_pathsep() .. "Xtestdir") + end) + + it('sets previous directory', function() + meths.set_current_dir("Xtestdir") + meths.exec('cd -', false) + eq(funcs.getcwd(), start_dir) + end) + end) + describe('nvim_exec_lua', function() it('works', function() meths.exec_lua('vim.api.nvim_set_var("test", 3)', {}) @@ -609,34 +640,374 @@ describe('API', function() eq('Invalid phase: 4', pcall_err(request, 'nvim_paste', 'foo', true, 4)) end) - it('stream: multiple chunks form one undo-block', function() - nvim('paste', '1/chunk 1 (start)\n', true, 1) - nvim('paste', '1/chunk 2 (end)\n', true, 3) - local expected1 = [[ - 1/chunk 1 (start) - 1/chunk 2 (end) - ]] - expect(expected1) - nvim('paste', '2/chunk 1 (start)\n', true, 1) - nvim('paste', '2/chunk 2\n', true, 2) - expect([[ - 1/chunk 1 (start) - 1/chunk 2 (end) - 2/chunk 1 (start) - 2/chunk 2 - ]]) - nvim('paste', '2/chunk 3\n', true, 2) - nvim('paste', '2/chunk 4 (end)\n', true, 3) - expect([[ - 1/chunk 1 (start) - 1/chunk 2 (end) - 2/chunk 1 (start) - 2/chunk 2 - 2/chunk 3 - 2/chunk 4 (end) - ]]) - feed('u') -- Undo. - expect(expected1) + local function run_streamed_paste_tests() + it('stream: multiple chunks form one undo-block', function() + nvim('paste', '1/chunk 1 (start)\n', true, 1) + nvim('paste', '1/chunk 2 (end)\n', true, 3) + local expected1 = [[ + 1/chunk 1 (start) + 1/chunk 2 (end) + ]] + expect(expected1) + nvim('paste', '2/chunk 1 (start)\n', true, 1) + nvim('paste', '2/chunk 2\n', true, 2) + expect([[ + 1/chunk 1 (start) + 1/chunk 2 (end) + 2/chunk 1 (start) + 2/chunk 2 + ]]) + nvim('paste', '2/chunk 3\n', true, 2) + nvim('paste', '2/chunk 4 (end)\n', true, 3) + expect([[ + 1/chunk 1 (start) + 1/chunk 2 (end) + 2/chunk 1 (start) + 2/chunk 2 + 2/chunk 3 + 2/chunk 4 (end) + ]]) + feed('u') -- Undo. + expect(expected1) + end) + it('stream: Insert mode', function() + -- If nvim_paste() calls :undojoin without making any changes, this makes it an error. + feed('afoo<Esc>u') + feed('i') + nvim('paste', 'aaaaaa', false, 1) + nvim('paste', 'bbbbbb', false, 2) + nvim('paste', 'cccccc', false, 2) + nvim('paste', 'dddddd', false, 3) + expect('aaaaaabbbbbbccccccdddddd') + feed('<Esc>u') + expect('') + end) + describe('stream: Normal mode', function() + describe('on empty line', function() + before_each(function() + -- If nvim_paste() calls :undojoin without making any changes, this makes it an error. + feed('afoo<Esc>u') + end) + after_each(function() + feed('u') + expect('') + end) + it('pasting one line', function() + nvim('paste', 'aaaaaa', false, 1) + nvim('paste', 'bbbbbb', false, 2) + nvim('paste', 'cccccc', false, 2) + nvim('paste', 'dddddd', false, 3) + expect('aaaaaabbbbbbccccccdddddd') + end) + it('pasting multiple lines', function() + nvim('paste', 'aaaaaa\n', false, 1) + nvim('paste', 'bbbbbb\n', false, 2) + nvim('paste', 'cccccc\n', false, 2) + nvim('paste', 'dddddd', false, 3) + expect([[ + aaaaaa + bbbbbb + cccccc + dddddd]]) + end) + end) + describe('not at the end of a line', function() + before_each(function() + feed('i||<Esc>') + -- If nvim_paste() calls :undojoin without making any changes, this makes it an error. + feed('afoo<Esc>u') + feed('0') + end) + after_each(function() + feed('u') + expect('||') + end) + it('pasting one line', function() + nvim('paste', 'aaaaaa', false, 1) + nvim('paste', 'bbbbbb', false, 2) + nvim('paste', 'cccccc', false, 2) + nvim('paste', 'dddddd', false, 3) + expect('|aaaaaabbbbbbccccccdddddd|') + end) + it('pasting multiple lines', function() + nvim('paste', 'aaaaaa\n', false, 1) + nvim('paste', 'bbbbbb\n', false, 2) + nvim('paste', 'cccccc\n', false, 2) + nvim('paste', 'dddddd', false, 3) + expect([[ + |aaaaaa + bbbbbb + cccccc + dddddd|]]) + end) + end) + describe('at the end of a line', function() + before_each(function() + feed('i||<Esc>') + -- If nvim_paste() calls :undojoin without making any changes, this makes it an error. + feed('afoo<Esc>u') + feed('2|') + end) + after_each(function() + feed('u') + expect('||') + end) + it('pasting one line', function() + nvim('paste', 'aaaaaa', false, 1) + nvim('paste', 'bbbbbb', false, 2) + nvim('paste', 'cccccc', false, 2) + nvim('paste', 'dddddd', false, 3) + expect('||aaaaaabbbbbbccccccdddddd') + end) + it('pasting multiple lines', function() + nvim('paste', 'aaaaaa\n', false, 1) + nvim('paste', 'bbbbbb\n', false, 2) + nvim('paste', 'cccccc\n', false, 2) + nvim('paste', 'dddddd', false, 3) + expect([[ + ||aaaaaa + bbbbbb + cccccc + dddddd]]) + end) + end) + end) + describe('stream: Visual mode', function() + describe('neither end at the end of a line', function() + before_each(function() + feed('i|xxx<CR>xxx|<Esc>') + -- If nvim_paste() calls :undojoin without making any changes, this makes it an error. + feed('afoo<Esc>u') + feed('3|vhk') + end) + after_each(function() + feed('u') + expect([[ + |xxx + xxx|]]) + end) + it('with non-empty chunks', function() + nvim('paste', 'aaaaaa', false, 1) + nvim('paste', 'bbbbbb', false, 2) + nvim('paste', 'cccccc', false, 2) + nvim('paste', 'dddddd', false, 3) + expect('|aaaaaabbbbbbccccccdddddd|') + end) + it('with empty first chunk', function() + nvim('paste', '', false, 1) + nvim('paste', 'bbbbbb', false, 2) + nvim('paste', 'cccccc', false, 2) + nvim('paste', 'dddddd', false, 3) + expect('|bbbbbbccccccdddddd|') + end) + it('with all chunks empty', function() + nvim('paste', '', false, 1) + nvim('paste', '', false, 2) + nvim('paste', '', false, 2) + nvim('paste', '', false, 3) + expect('||') + end) + end) + describe('cursor at the end of a line', function() + before_each(function() + feed('i||xxx<CR>xxx<Esc>') + -- If nvim_paste() calls :undojoin without making any changes, this makes it an error. + feed('afoo<Esc>u') + feed('3|vko') + end) + after_each(function() + feed('u') + expect([[ + ||xxx + xxx]]) + end) + it('with non-empty chunks', function() + nvim('paste', 'aaaaaa', false, 1) + nvim('paste', 'bbbbbb', false, 2) + nvim('paste', 'cccccc', false, 2) + nvim('paste', 'dddddd', false, 3) + expect('||aaaaaabbbbbbccccccdddddd') + end) + it('with empty first chunk', function() + nvim('paste', '', false, 1) + nvim('paste', 'bbbbbb', false, 2) + nvim('paste', 'cccccc', false, 2) + nvim('paste', 'dddddd', false, 3) + expect('||bbbbbbccccccdddddd') + end) + end) + describe('other end at the end of a line', function() + before_each(function() + feed('i||xxx<CR>xxx<Esc>') + -- If nvim_paste() calls :undojoin without making any changes, this makes it an error. + feed('afoo<Esc>u') + feed('3|vk') + end) + after_each(function() + feed('u') + expect([[ + ||xxx + xxx]]) + end) + it('with non-empty chunks', function() + nvim('paste', 'aaaaaa', false, 1) + nvim('paste', 'bbbbbb', false, 2) + nvim('paste', 'cccccc', false, 2) + nvim('paste', 'dddddd', false, 3) + expect('||aaaaaabbbbbbccccccdddddd') + end) + it('with empty first chunk', function() + nvim('paste', '', false, 1) + nvim('paste', 'bbbbbb', false, 2) + nvim('paste', 'cccccc', false, 2) + nvim('paste', 'dddddd', false, 3) + expect('||bbbbbbccccccdddddd') + end) + end) + end) + describe('stream: linewise Visual mode', function() + before_each(function() + feed('i123456789<CR>987654321<CR>123456789<Esc>') + -- If nvim_paste() calls :undojoin without making any changes, this makes it an error. + feed('afoo<Esc>u') + end) + after_each(function() + feed('u') + expect([[ + 123456789 + 987654321 + 123456789]]) + end) + describe('selecting the start of a file', function() + before_each(function() + feed('ggV') + end) + it('pasting text without final new line', function() + nvim('paste', 'aaaaaa\n', false, 1) + nvim('paste', 'bbbbbb\n', false, 2) + nvim('paste', 'cccccc\n', false, 2) + nvim('paste', 'dddddd', false, 3) + expect([[ + aaaaaa + bbbbbb + cccccc + dddddd987654321 + 123456789]]) + end) + it('pasting text with final new line', function() + nvim('paste', 'aaaaaa\n', false, 1) + nvim('paste', 'bbbbbb\n', false, 2) + nvim('paste', 'cccccc\n', false, 2) + nvim('paste', 'dddddd\n', false, 3) + expect([[ + aaaaaa + bbbbbb + cccccc + dddddd + 987654321 + 123456789]]) + end) + end) + describe('selecting the middle of a file', function() + before_each(function() + feed('2ggV') + end) + it('pasting text without final new line', function() + nvim('paste', 'aaaaaa\n', false, 1) + nvim('paste', 'bbbbbb\n', false, 2) + nvim('paste', 'cccccc\n', false, 2) + nvim('paste', 'dddddd', false, 3) + expect([[ + 123456789 + aaaaaa + bbbbbb + cccccc + dddddd123456789]]) + end) + it('pasting text with final new line', function() + nvim('paste', 'aaaaaa\n', false, 1) + nvim('paste', 'bbbbbb\n', false, 2) + nvim('paste', 'cccccc\n', false, 2) + nvim('paste', 'dddddd\n', false, 3) + expect([[ + 123456789 + aaaaaa + bbbbbb + cccccc + dddddd + 123456789]]) + end) + end) + describe('selecting the end of a file', function() + before_each(function() + feed('3ggV') + end) + it('pasting text without final new line', function() + nvim('paste', 'aaaaaa\n', false, 1) + nvim('paste', 'bbbbbb\n', false, 2) + nvim('paste', 'cccccc\n', false, 2) + nvim('paste', 'dddddd', false, 3) + expect([[ + 123456789 + 987654321 + aaaaaa + bbbbbb + cccccc + dddddd]]) + end) + it('pasting text with final new line', function() + nvim('paste', 'aaaaaa\n', false, 1) + nvim('paste', 'bbbbbb\n', false, 2) + nvim('paste', 'cccccc\n', false, 2) + nvim('paste', 'dddddd\n', false, 3) + expect([[ + 123456789 + 987654321 + aaaaaa + bbbbbb + cccccc + dddddd + ]]) + end) + end) + describe('selecting the whole file', function() + before_each(function() + feed('ggVG') + end) + it('pasting text without final new line', function() + nvim('paste', 'aaaaaa\n', false, 1) + nvim('paste', 'bbbbbb\n', false, 2) + nvim('paste', 'cccccc\n', false, 2) + nvim('paste', 'dddddd', false, 3) + expect([[ + aaaaaa + bbbbbb + cccccc + dddddd]]) + end) + it('pasting text with final new line', function() + nvim('paste', 'aaaaaa\n', false, 1) + nvim('paste', 'bbbbbb\n', false, 2) + nvim('paste', 'cccccc\n', false, 2) + nvim('paste', 'dddddd\n', false, 3) + expect([[ + aaaaaa + bbbbbb + cccccc + dddddd + ]]) + end) + end) + end) + end + describe('without virtualedit,', function() + run_streamed_paste_tests() + end) + describe('with virtualedit=onemore,', function() + before_each(function() + command('set virtualedit=onemore') + end) + run_streamed_paste_tests() end) it('non-streaming', function() -- With final "\n". @@ -711,6 +1082,43 @@ describe('API', function() eeffgghh iijjkkll]]) end) + it('when searching in Visual mode', function() + feed('v/') + nvim('paste', 'aabbccdd', true, -1) + eq('aabbccdd', funcs.getcmdline()) + expect('') + end) + it('mappings are disabled in Cmdline mode', function() + command('cnoremap a b') + feed(':') + nvim('paste', 'a', true, -1) + eq('a', funcs.getcmdline()) + end) + it('pasting with empty last chunk in Cmdline mode', function() + local screen = Screen.new(20, 4) + screen:attach() + feed(':') + nvim('paste', 'Foo', true, 1) + nvim('paste', '', true, 3) + screen:expect([[ + | + ~ | + ~ | + :Foo^ | + ]]) + end) + it('pasting text with control characters in Cmdline mode', function() + local screen = Screen.new(20, 4) + screen:attach() + feed(':') + nvim('paste', 'normal! \023\022\006\027', true, -1) + screen:expect([[ + | + ~ | + ~ | + :normal! ^W^V^F^[^ | + ]]) + end) it('crlf=false does not break lines at CR, CRLF', function() 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') @@ -872,6 +1280,30 @@ describe('API', function() command('lockvar lua') eq('Key is locked: lua', pcall_err(meths.del_var, 'lua')) eq('Key is locked: lua', pcall_err(meths.set_var, 'lua', 1)) + + exec([[ + function Test() + endfunction + function s:Test() + endfunction + let g:Unknown_func = function('Test') + let g:Unknown_script_func = function('s:Test') + ]]) + eq(NIL, meths.get_var('Unknown_func')) + eq(NIL, meths.get_var('Unknown_script_func')) + + -- Check if autoload works properly + local pathsep = helpers.get_pathsep() + local xconfig = 'Xhome' .. pathsep .. 'Xconfig' + local xdata = 'Xhome' .. pathsep .. 'Xdata' + local autoload_folder = table.concat({xconfig, 'nvim', 'autoload'}, pathsep) + local autoload_file = table.concat({autoload_folder , 'testload.vim'}, pathsep) + mkdir_p(autoload_folder) + write_file(autoload_file , [[let testload#value = 2]]) + + clear{ args_rm={'-u'}, env={ XDG_CONFIG_HOME=xconfig, XDG_DATA_HOME=xdata } } + eq(2, meths.get_var('testload#value')) + rmdir('Xhome') end) it('nvim_get_vvar, nvim_set_vvar', function() @@ -1008,6 +1440,46 @@ describe('API', function() nvim('set_option_value', 'autoread', NIL, {scope = 'local'}) eq(NIL, nvim('get_option_value', 'autoread', {scope = 'local'})) end) + + it('set window options', function() + -- Same as to nvim_win_set_option + nvim('set_option_value', 'colorcolumn', '4,3', {win=0}) + eq('4,3', nvim('get_option_value', 'colorcolumn', {scope = 'local'})) + command("set modified hidden") + command("enew") -- edit new buffer, window option is preserved + eq('4,3', nvim('get_option_value', 'colorcolumn', {scope = 'local'})) + end) + + it('set local window options', function() + -- Different to nvim_win_set_option + nvim('set_option_value', 'colorcolumn', '4,3', {win=0, scope='local'}) + eq('4,3', nvim('get_option_value', 'colorcolumn', {win = 0, scope = 'local'})) + command("set modified hidden") + command("enew") -- edit new buffer, window option is reset + eq('', nvim('get_option_value', 'colorcolumn', {win = 0, scope = 'local'})) + end) + + it('get buffer or window-local options', function() + nvim('command', 'new') + local buf = nvim('get_current_buf').id + nvim('buf_set_option', buf, 'tagfunc', 'foobar') + eq('foobar', nvim('get_option_value', 'tagfunc', {buf = buf})) + + local win = nvim('get_current_win').id + nvim('win_set_option', win, 'number', true) + eq(true, nvim('get_option_value', 'number', {win = win})) + end) + + it('getting current buffer option does not adjust cursor #19381', function() + nvim('command', 'new') + local buf = nvim('get_current_buf').id + local win = nvim('get_current_win').id + insert('some text') + feed('0v$') + eq({1, 9}, nvim('win_get_cursor', win)) + nvim('get_option_value', 'filetype', {buf = buf}) + eq({1, 9}, nvim('win_get_cursor', win)) + end) end) describe('nvim_{get,set}_current_buf, nvim_list_bufs', function() @@ -1093,7 +1565,20 @@ describe('API', function() eq({mode='n', blocking=false}, nvim("get_mode")) end) + it("during press-enter prompt without UI returns blocking=false", function() + eq({mode='n', blocking=false}, nvim("get_mode")) + command("echom 'msg1'") + command("echom 'msg2'") + command("echom 'msg3'") + command("echom 'msg4'") + command("echom 'msg5'") + eq({mode='n', blocking=false}, nvim("get_mode")) + nvim("input", ":messages<CR>") + eq({mode='n', blocking=false}, nvim("get_mode")) + end) + it("during press-enter prompt returns blocking=true", function() + nvim("ui_attach", 80, 20, {}) eq({mode='n', blocking=false}, nvim("get_mode")) command("echom 'msg1'") command("echom 'msg2'") @@ -1117,6 +1602,7 @@ describe('API', function() -- TODO: bug #6247#issuecomment-286403810 it("batched with input", function() + nvim("ui_attach", 80, 20, {}) eq({mode='n', blocking=false}, nvim("get_mode")) command("echom 'msg1'") command("echom 'msg2'") @@ -1152,6 +1638,18 @@ describe('API', function() feed(':digraphs<cr>') eq({mode='rm', blocking=true}, nvim("get_mode")) end) + + it('after <Nop> mapping returns blocking=false #17257', function() + command('nnoremap <F2> <Nop>') + feed('<F2>') + eq({mode='n', blocking=false}, nvim("get_mode")) + end) + + it('after empty string <expr> mapping returns blocking=false #17257', function() + command('nnoremap <expr> <F2> ""') + feed('<F2>') + eq({mode='n', blocking=false}, nvim("get_mode")) + end) end) describe('RPC (K_EVENT)', function() @@ -1367,18 +1865,18 @@ describe('API', function() end) describe('nvim_feedkeys', function() - it('CSI escaping', function() + it('K_SPECIAL escaping', function() local function on_setup() -- notice the special char(…) \xe2\80\xa6 nvim('feedkeys', ':let x1="…"\n', '', true) -- Both nvim_replace_termcodes and nvim_feedkeys escape \x80 local inp = helpers.nvim('replace_termcodes', ':let x2="…"<CR>', true, true, true) - nvim('feedkeys', inp, '', true) -- escape_csi=true + nvim('feedkeys', inp, '', true) -- escape_ks=true - -- nvim_feedkeys with CSI escaping disabled + -- nvim_feedkeys with K_SPECIAL escaping disabled inp = helpers.nvim('replace_termcodes', ':let x3="…"<CR>', true, true, true) - nvim('feedkeys', inp, '', false) -- escape_csi=false + nvim('feedkeys', inp, '', false) -- escape_ks=false helpers.stop() end @@ -1386,10 +1884,10 @@ describe('API', function() -- spin the loop a bit helpers.run(nil, nil, on_setup) - eq(nvim('get_var', 'x1'), '…') + eq('…', nvim('get_var', 'x1')) -- Because of the double escaping this is neq - neq(nvim('get_var', 'x2'), '…') - eq(nvim('get_var', 'x3'), '…') + neq('…', nvim('get_var', 'x2')) + eq('…', nvim('get_var', 'x3')) end) end) @@ -2142,7 +2640,7 @@ describe('API', function() it('does not cause heap-use-after-free on exit while setting options', function() command('au OptionSet * q') - command('silent! call nvim_create_buf(0, 1)') + expect_exit(command, 'silent! call nvim_create_buf(0, 1)') end) end) @@ -2187,6 +2685,14 @@ describe('API', function() eq({}, meths.get_runtime_file("foobarlang/", true)) end) + it('can handle bad patterns', function() + if helpers.pending_win32(pending) then return end + + eq("Vim:E220: Missing }.", pcall_err(meths.get_runtime_file, "{", false)) + + eq('Vim(echo):E5555: API call: Vim:E220: Missing }.', + exc_exec("echo nvim_get_runtime_file('{', v:false)")) + end) end) describe('nvim_get_all_options_info', function() @@ -2551,6 +3057,38 @@ describe('API', function() 'Should be truncated%<', { maxwidth = 15 })) end) + it('supports ASCII fillchar', function() + eq({ str = 'a~~~b', width = 5 }, + meths.eval_statusline('a%=b', { fillchar = '~', maxwidth = 5 })) + end) + it('supports single-width multibyte fillchar', function() + eq({ str = 'a━━━b', width = 5 }, + meths.eval_statusline('a%=b', { fillchar = '━', maxwidth = 5 })) + end) + it('treats double-width fillchar as single-width', function() + eq({ str = 'a哦哦哦b', width = 5 }, + meths.eval_statusline('a%=b', { fillchar = '哦', maxwidth = 5 })) + end) + it('treats control character fillchar as single-width', function() + eq({ str = 'a\031\031\031b', width = 5 }, + meths.eval_statusline('a%=b', { fillchar = '\031', maxwidth = 5 })) + end) + it('rejects multiple-character fillchar', function() + eq('fillchar must be a single character', + pcall_err(meths.eval_statusline, '', { fillchar = 'aa' })) + end) + it('rejects empty string fillchar', function() + eq('fillchar must be a single character', + pcall_err(meths.eval_statusline, '', { fillchar = '' })) + end) + it('rejects non-string fillchar', function() + eq('fillchar must be a single character', + pcall_err(meths.eval_statusline, '', { fillchar = 1 })) + end) + it('rejects invalid string', function() + eq('E539: Illegal character <}>', + pcall_err(meths.eval_statusline, '%{%}', {})) + end) describe('highlight parsing', function() it('works', function() eq({ @@ -2605,6 +3143,678 @@ describe('API', function() 'TextWithNoHighlight%#WarningMsg#TextWithWarningHighlight', { use_tabline = true, highlights = true })) end) + it('works with winbar', function() + eq({ + str = 'TextWithNoHighlightTextWithWarningHighlight', + width = 43, + highlights = { + { start = 0, group = 'WinBar' }, + { start = 19, group = 'WarningMsg' } + } + }, + meths.eval_statusline( + 'TextWithNoHighlight%#WarningMsg#TextWithWarningHighlight', + { use_winbar = true, highlights = true })) + end) + end) + end) + describe('nvim_parse_cmd', function() + it('works', function() + eq({ + cmd = 'echo', + args = { 'foo' }, + bang = false, + range = {}, + count = -1, + reg = '', + addr = 'none', + magic = { + file = false, + bar = false + }, + nargs = '*', + nextcmd = '', + mods = { + browse = false, + confirm = false, + emsg_silent = false, + filter = { + pattern = "", + force = false + }, + hide = false, + keepalt = false, + keepjumps = false, + keepmarks = false, + keeppatterns = false, + lockmarks = false, + noautocmd = false, + noswapfile = false, + sandbox = false, + silent = false, + split = "", + tab = 0, + unsilent = false, + verbose = -1, + vertical = false, + } + }, meths.parse_cmd('echo foo', {})) + end) + it('works with ranges', function() + eq({ + cmd = 'substitute', + args = { '/math.random/math.max/' }, + bang = false, + range = { 4, 6 }, + count = -1, + reg = '', + addr = 'line', + magic = { + file = false, + bar = false + }, + nargs = '*', + nextcmd = '', + mods = { + browse = false, + confirm = false, + emsg_silent = false, + filter = { + pattern = "", + force = false + }, + hide = false, + keepalt = false, + keepjumps = false, + keepmarks = false, + keeppatterns = false, + lockmarks = false, + noautocmd = false, + noswapfile = false, + sandbox = false, + silent = false, + split = "", + tab = 0, + unsilent = false, + verbose = -1, + vertical = false, + } + }, meths.parse_cmd('4,6s/math.random/math.max/', {})) + end) + it('works with count', function() + eq({ + cmd = 'buffer', + args = {}, + bang = false, + range = { 1 }, + count = 1, + reg = '', + addr = 'buf', + magic = { + file = false, + bar = true + }, + nargs = '*', + nextcmd = '', + mods = { + browse = false, + confirm = false, + emsg_silent = false, + filter = { + pattern = "", + force = false + }, + hide = false, + keepalt = false, + keepjumps = false, + keepmarks = false, + keeppatterns = false, + lockmarks = false, + noautocmd = false, + noswapfile = false, + sandbox = false, + silent = false, + split = "", + tab = 0, + unsilent = false, + verbose = -1, + vertical = false, + } + }, meths.parse_cmd('buffer 1', {})) + end) + it('works with register', function() + eq({ + cmd = 'put', + args = {}, + bang = false, + range = {}, + count = -1, + reg = '+', + addr = 'line', + magic = { + file = false, + bar = true + }, + nargs = '0', + nextcmd = '', + mods = { + browse = false, + confirm = false, + emsg_silent = false, + filter = { + pattern = "", + force = false + }, + hide = false, + keepalt = false, + keepjumps = false, + keepmarks = false, + keeppatterns = false, + lockmarks = false, + noautocmd = false, + noswapfile = false, + sandbox = false, + silent = false, + split = "", + tab = 0, + unsilent = false, + verbose = -1, + vertical = false, + } + }, meths.parse_cmd('put +', {})) + end) + it('works with range, count and register', function() + eq({ + cmd = 'delete', + args = {}, + bang = false, + range = { 3, 7 }, + count = 7, + reg = '*', + addr = 'line', + magic = { + file = false, + bar = true + }, + nargs = '0', + nextcmd = '', + mods = { + browse = false, + confirm = false, + emsg_silent = false, + filter = { + pattern = "", + force = false + }, + hide = false, + keepalt = false, + keepjumps = false, + keepmarks = false, + keeppatterns = false, + lockmarks = false, + noautocmd = false, + noswapfile = false, + sandbox = false, + silent = false, + split = "", + tab = 0, + unsilent = false, + verbose = -1, + vertical = false, + } + }, meths.parse_cmd('1,3delete * 5', {})) + end) + it('works with bang', function() + eq({ + cmd = 'write', + args = {}, + bang = true, + range = {}, + count = -1, + reg = '', + addr = 'line', + magic = { + file = true, + bar = true + }, + nargs = '?', + nextcmd = '', + mods = { + browse = false, + confirm = false, + emsg_silent = false, + filter = { + pattern = "", + force = false + }, + hide = false, + keepalt = false, + keepjumps = false, + keepmarks = false, + keeppatterns = false, + lockmarks = false, + noautocmd = false, + noswapfile = false, + sandbox = false, + silent = false, + split = "", + tab = 0, + unsilent = false, + verbose = -1, + vertical = false, + }, + }, meths.parse_cmd('w!', {})) + end) + it('works with modifiers', function() + eq({ + cmd = 'split', + args = { 'foo.txt' }, + bang = false, + range = {}, + count = -1, + reg = '', + addr = '?', + magic = { + file = true, + bar = true + }, + nargs = '?', + nextcmd = '', + mods = { + browse = false, + confirm = false, + emsg_silent = true, + filter = { + pattern = "foo", + force = false + }, + hide = false, + keepalt = false, + keepjumps = false, + keepmarks = false, + keeppatterns = false, + lockmarks = false, + noautocmd = false, + noswapfile = false, + sandbox = false, + silent = true, + split = "topleft", + tab = 2, + unsilent = false, + verbose = 15, + vertical = false, + }, + }, meths.parse_cmd('15verbose silent! aboveleft topleft tab filter /foo/ split foo.txt', {})) + eq({ + cmd = 'split', + args = { 'foo.txt' }, + bang = false, + range = {}, + count = -1, + reg = '', + addr = '?', + magic = { + file = true, + bar = true + }, + nargs = '?', + nextcmd = '', + mods = { + browse = false, + confirm = true, + emsg_silent = false, + filter = { + pattern = "foo", + force = true + }, + hide = false, + keepalt = false, + keepjumps = false, + keepmarks = false, + keeppatterns = false, + lockmarks = false, + noautocmd = false, + noswapfile = false, + sandbox = false, + silent = false, + split = "botright", + tab = 0, + unsilent = true, + verbose = 0, + vertical = false, + }, + }, meths.parse_cmd('0verbose unsilent botright confirm filter! /foo/ split foo.txt', {})) + end) + it('works with user commands', function() + command('command -bang -nargs=+ -range -addr=lines MyCommand echo foo') + eq({ + cmd = 'MyCommand', + args = { 'test', 'it' }, + bang = true, + range = { 4, 6 }, + count = -1, + reg = '', + addr = 'line', + magic = { + file = false, + bar = false + }, + nargs = '+', + nextcmd = '', + mods = { + browse = false, + confirm = false, + emsg_silent = false, + filter = { + pattern = "", + force = false + }, + hide = false, + keepalt = false, + keepjumps = false, + keepmarks = false, + keeppatterns = false, + lockmarks = false, + noautocmd = false, + noswapfile = false, + sandbox = false, + silent = false, + split = "", + tab = 0, + unsilent = false, + verbose = -1, + vertical = false, + } + }, meths.parse_cmd('4,6MyCommand! test it', {})) + end) + it('works for commands separated by bar', function() + eq({ + cmd = 'argadd', + args = { 'a.txt' }, + bang = false, + range = {}, + count = -1, + reg = '', + addr = 'arg', + magic = { + file = true, + bar = true + }, + nargs = '*', + nextcmd = 'argadd b.txt', + mods = { + browse = false, + confirm = false, + emsg_silent = false, + filter = { + pattern = "", + force = false + }, + hide = false, + keepalt = false, + keepjumps = false, + keepmarks = false, + keeppatterns = false, + lockmarks = false, + noautocmd = false, + noswapfile = false, + sandbox = false, + silent = false, + split = "", + tab = 0, + unsilent = false, + verbose = -1, + vertical = false, + } + }, meths.parse_cmd('argadd a.txt | argadd b.txt', {})) + end) + it('works for nargs=1', function() + command('command -nargs=1 MyCommand echo <q-args>') + eq({ + cmd = 'MyCommand', + args = { 'test it' }, + bang = false, + range = {}, + count = -1, + reg = '', + addr = 'none', + magic = { + file = false, + bar = false + }, + nargs = '1', + nextcmd = '', + mods = { + browse = false, + confirm = false, + emsg_silent = false, + filter = { + pattern = "", + force = false + }, + hide = false, + keepalt = false, + keepjumps = false, + keepmarks = false, + keeppatterns = false, + lockmarks = false, + noautocmd = false, + noswapfile = false, + sandbox = false, + silent = false, + split = "", + tab = 0, + unsilent = false, + verbose = -1, + vertical = false, + } + }, meths.parse_cmd('MyCommand test it', {})) + end) + it('errors for invalid command', function() + eq('Error while parsing command line', pcall_err(meths.parse_cmd, '', {})) + eq('Error while parsing command line', pcall_err(meths.parse_cmd, '" foo', {})) + eq('Error while parsing command line: E492: Not an editor command: Fubar', + pcall_err(meths.parse_cmd, 'Fubar', {})) + command('command! Fubar echo foo') + eq('Error while parsing command line: E477: No ! allowed', + pcall_err(meths.parse_cmd, 'Fubar!', {})) + eq('Error while parsing command line: E481: No range allowed', + pcall_err(meths.parse_cmd, '4,6Fubar', {})) + command('command! Foobar echo foo') + eq('Error while parsing command line: E464: Ambiguous use of user-defined command', + pcall_err(meths.parse_cmd, 'F', {})) + 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 + bar]]) + feed('gQ1') + screen:expect([[ + foo | + bar | + {0:~ }| + {0:~ }| + {1: }| + Entering Ex mode. Type "visual" to go to Normal mode. | + :1^ | + ]]) + eq('Error while parsing command line', pcall_err(meths.parse_cmd, '', {})) + feed('<CR>') + screen:expect([[ + foo | + bar | + {1: }| + Entering Ex mode. Type "visual" to go to Normal mode. | + :1 | + foo | + :^ | + ]]) + end) + end) + describe('nvim_cmd', function() + it('works', function () + meths.cmd({ cmd = "set", args = { "cursorline" } }, {}) + eq(true, meths.get_option_value("cursorline", {})) + end) + it('captures output', function() + eq("foo", meths.cmd({ cmd = "echo", args = { '"foo"' } }, { output = true })) + end) + it('sets correct script context', function() + meths.cmd({ cmd = "set", args = { "cursorline" } }, {}) + local str = meths.exec([[verbose set cursorline?]], true) + neq(nil, str:find("cursorline\n\tLast set from API client %(channel id %d+%)")) + end) + it('works with range', function() + insert [[ + line1 + line2 + line3 + line4 + you didn't expect this + line5 + line6 + ]] + meths.cmd({ cmd = "del", range = {2, 4} }, {}) + expect [[ + line1 + you didn't expect this + line5 + line6 + ]] + end) + it('works with count', function() + insert [[ + line1 + line2 + line3 + line4 + you didn't expect this + line5 + line6 + ]] + meths.cmd({ cmd = "del", range = { 2 }, count = 4 }, {}) + expect [[ + line1 + line5 + line6 + ]] + end) + it('works with register', function() + insert [[ + line1 + line2 + line3 + line4 + you didn't expect this + line5 + line6 + ]] + meths.cmd({ cmd = "del", range = { 2, 4 }, reg = 'a' }, {}) + meths.exec("1put a", false) + expect [[ + line1 + line2 + line3 + line4 + you didn't expect this + line5 + line6 + ]] + end) + it('works with bang', function () + meths.create_user_command("Foo", 'echo "<bang>"', { bang = true }) + eq("!", meths.cmd({ cmd = "Foo", bang = true }, { output = true })) + eq("", meths.cmd({ cmd = "Foo", bang = false }, { output = true })) + end) + it('works with modifiers', function() + meths.create_user_command("Foo", 'set verbose', {}) + eq(" verbose=1", meths.cmd({ cmd = "Foo", mods = { verbose = 1 } }, { output = true })) + eq(0, meths.get_option_value("verbose", {})) + command('edit foo.txt | edit bar.txt') + eq(' 1 #h "foo.txt" line 1', + meths.cmd({ cmd = "buffers", mods = { filter = { pattern = "foo", force = false } } }, + { output = true })) + eq(' 2 %a "bar.txt" line 1', + meths.cmd({ cmd = "buffers", mods = { filter = { pattern = "foo", force = true } } }, + { output = true })) + end) + it('works with magic.file', function() + exec_lua([[ + vim.api.nvim_create_user_command("Foo", function(opts) + vim.api.nvim_echo({{ opts.fargs[1] }}, false, {}) + end, { nargs = 1 }) + ]]) + eq(lfs.currentdir(), + meths.cmd({ cmd = "Foo", args = { '%:p:h' }, magic = { file = true } }, + { output = true })) + end) + it('splits arguments correctly', function() + meths.exec([[ + function! FooFunc(...) + echo a:000 + endfunction + ]], false) + meths.create_user_command("Foo", "call FooFunc(<f-args>)", { nargs = '+' }) + eq([=[['a quick', 'brown fox', 'jumps over the', 'lazy dog']]=], + meths.cmd({ cmd = "Foo", args = { "a quick", "brown fox", "jumps over the", "lazy dog"}}, + { output = true })) + eq([=[['test \ \\ \"""\', 'more\ tests\" ']]=], + meths.cmd({ cmd = "Foo", args = { [[test \ \\ \"""\]], [[more\ tests\" ]] } }, + { output = true })) + end) + it('splits arguments correctly for Lua callback', function() + meths.exec_lua([[ + local function FooFunc(opts) + vim.pretty_print(opts.fargs) + end + + vim.api.nvim_create_user_command("Foo", FooFunc, { nargs = '+' }) + ]], {}) + eq([[{ "a quick", "brown fox", "jumps over the", "lazy dog" }]], + meths.cmd({ cmd = "Foo", args = { "a quick", "brown fox", "jumps over the", "lazy dog"}}, + { output = true })) + eq([[{ 'test \\ \\\\ \\"""\\', 'more\\ tests\\" ' }]], + meths.cmd({ cmd = "Foo", args = { [[test \ \\ \"""\]], [[more\ tests\" ]] } }, + { output = true })) + end) + it('works with buffer names', function() + command("edit foo.txt | edit bar.txt") + meths.cmd({ cmd = "buffer", args = { "foo.txt" } }, {}) + eq("foo.txt", funcs.fnamemodify(meths.buf_get_name(0), ":t")) + meths.cmd({ cmd = "buffer", args = { "bar.txt" } }, {}) + eq("bar.txt", funcs.fnamemodify(meths.buf_get_name(0), ":t")) + end) + it('triggers CmdUndefined event if command is not found', function() + meths.exec_lua([[ + vim.api.nvim_create_autocmd("CmdUndefined", + { pattern = "Foo", + callback = function() + vim.api.nvim_create_user_command("Foo", "echo 'foo'", {}) + end + }) + ]], {}) + eq("foo", meths.cmd({ cmd = "Foo" }, { output = true })) + end) + it('errors if command is not implemented', function() + eq("Command not implemented: winpos", pcall_err(meths.cmd, { cmd = "winpos" }, {})) + end) + it('works with empty arguments list', function() + meths.cmd({ cmd = "update" }, {}) + meths.cmd({ cmd = "buffer", count = 0 }, {}) + end) + it('doesn\'t suppress errors when used in keymapping', function() + meths.exec_lua([[ + vim.keymap.set("n", "[l", + function() vim.api.nvim_cmd({ cmd = "echo", args = {"foo"} }, {}) end) + ]], {}) + feed("[l") + neq(nil, string.find(eval("v:errmsg"), "E5108:")) end) end) end) diff --git a/test/functional/api/window_spec.lua b/test/functional/api/window_spec.lua index 4d2ffa316a..901d24327c 100644 --- a/test/functional/api/window_spec.lua +++ b/test/functional/api/window_spec.lua @@ -1,4 +1,5 @@ local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') local clear, nvim, curbuf, curbuf_contents, window, curwin, eq, neq, ok, feed, insert, eval, tabpage = helpers.clear, helpers.nvim, helpers.curbuf, helpers.curbuf_contents, helpers.window, helpers.curwin, helpers.eq, @@ -73,8 +74,7 @@ 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('does not leak memory when using invalid window ID with invalid pos', function() eq('Invalid window id: 1', pcall_err(meths.win_set_cursor, 1, {"b\na"})) end) @@ -147,6 +147,46 @@ describe('API/win', function() eq({2, 5}, window('get_cursor', win)) end) + 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') + insert([[ + aaa + bbb + ccc + ddd]]) + local oldwin = curwin() + command('vsplit') + screen:expect([[ + aaa │aaa | + bbb │bbb | + ccc │ccc | + {2:dd^d }│{2:ddd }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {3:[No Name] [+] 4,3 All }{4:[No Name] [+] 4,3 All}| + | + ]]) + window('set_cursor', oldwin, {1, 0}) + screen:expect([[ + aaa │{2:aaa }| + bbb │bbb | + ccc │ccc | + {2:dd^d }│ddd | + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {3:[No Name] [+] 4,3 All }{4:[No Name] [+] 1,1 All}| + | + ]]) + end) end) describe('{get,set}_height', function() diff --git a/test/functional/autocmd/autocmd_oldtest_spec.lua b/test/functional/autocmd/autocmd_oldtest_spec.lua new file mode 100644 index 0000000000..ad3687d7b0 --- /dev/null +++ b/test/functional/autocmd/autocmd_oldtest_spec.lua @@ -0,0 +1,86 @@ +local helpers = require('test.functional.helpers')(after_each) + +local clear = helpers.clear +local eq = helpers.eq +local meths = helpers.meths +local funcs = helpers.funcs + +local exec = function(str) + meths.exec(str, false) +end + +describe('oldtests', function() + before_each(clear) + + local exec_lines = function(str) + return funcs.split(funcs.execute(str), "\n") + end + + local add_an_autocmd = function() + exec [[ + augroup vimBarTest + au BufReadCmd * echo 'hello' + augroup END + ]] + + eq(3, #exec_lines('au vimBarTest')) + eq(1, #meths.get_autocmds({ group = 'vimBarTest' })) + end + + it('should recognize a bar before the {event}', function() + -- Good spacing + add_an_autocmd() + exec [[ augroup vimBarTest | au! | augroup END ]] + eq(1, #exec_lines('au vimBarTest')) + eq({}, meths.get_autocmds({ group = 'vimBarTest' })) + + -- Sad spacing + add_an_autocmd() + exec [[ augroup vimBarTest| au!| augroup END ]] + eq(1, #exec_lines('au vimBarTest')) + + + -- test that a bar is recognized after the {event} + add_an_autocmd() + exec [[ augroup vimBarTest| au!BufReadCmd| augroup END ]] + eq(1, #exec_lines('au vimBarTest')) + + add_an_autocmd() + exec [[ au! vimBarTest|echo 'hello' ]] + eq(1, #exec_lines('au vimBarTest')) + end) + + it('should fire on unload buf', function() + funcs.writefile({'Test file Xxx1'}, 'Xxx1') + funcs.writefile({'Test file Xxx2'}, 'Xxx2') + + local content = [[ + func UnloadAllBufs() + let i = 1 + while i <= bufnr('$') + if i != bufnr('%') && bufloaded(i) + exe i . 'bunload' + endif + let i += 1 + endwhile + endfunc + au BufUnload * call UnloadAllBufs() + au VimLeave * call writefile(['Test Finished'], 'Xout') + set nohidden + edit Xxx1 + split Xxx2 + q + ]] + + funcs.writefile(funcs.split(content, "\n"), 'Xtest') + + funcs.delete('Xout') + funcs.system(meths.get_vvar('progpath') .. ' -u NORC -i NONE -N -S Xtest') + eq(1, funcs.filereadable('Xout')) + + funcs.delete('Xxx1') + funcs.delete('Xxx2') + funcs.delete('Xtest') + funcs.delete('Xout') + end) +end) diff --git a/test/functional/autocmd/autocmd_spec.lua b/test/functional/autocmd/autocmd_spec.lua index 93d71a9e45..90254b7415 100644 --- a/test/functional/autocmd/autocmd_spec.lua +++ b/test/functional/autocmd/autocmd_spec.lua @@ -2,8 +2,10 @@ local helpers = require('test.functional.helpers')(after_each) local Screen = require('test.functional.ui.screen') local assert_visible = helpers.assert_visible +local assert_alive = helpers.assert_alive local dedent = helpers.dedent local eq = helpers.eq +local neq = helpers.neq local eval = helpers.eval local feed = helpers.feed local clear = helpers.clear @@ -13,7 +15,9 @@ local funcs = helpers.funcs local expect = helpers.expect local command = helpers.command local exc_exec = helpers.exc_exec +local exec_lua = helpers.exec_lua local curbufmeths = helpers.curbufmeths +local retry = helpers.retry local source = helpers.source describe('autocmd', function() @@ -56,6 +60,23 @@ describe('autocmd', function() eq(expected, eval('g:evs')) end) + it('first edit causes BufUnload on NoName', function() + local expected = { + {'BufUnload', ''}, + {'BufDelete', ''}, + {'BufWipeout', ''}, + {'BufEnter', 'testfile1'}, + } + command('let g:evs = []') + command('autocmd BufEnter * :call add(g:evs, ["BufEnter", expand("<afile>")])') + command('autocmd BufDelete * :call add(g:evs, ["BufDelete", expand("<afile>")])') + command('autocmd BufLeave * :call add(g:evs, ["BufLeave", expand("<afile>")])') + command('autocmd BufUnload * :call add(g:evs, ["BufUnload", expand("<afile>")])') + command('autocmd BufWipeout * :call add(g:evs, ["BufWipeout", expand("<afile>")])') + command('edit testfile1') + eq(expected, eval('g:evs')) + end) + it('WinClosed is non-recursive', function() command('let g:triggered = 0') command('autocmd WinClosed * :let g:triggered+=1 | :bdelete 2') @@ -333,6 +354,87 @@ describe('autocmd', function() pcall_err(command, "call nvim_set_current_win(g:winid)")) end) + 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. + 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) + ]] + screen:expect [[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {3:[No Name] }| + | + {2:[No Name] }| + | + ]] + -- 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) + ]] + assert_alive() + 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)]] + screen:expect [[ + ^ | + {1:~ }| + {3:[No Name] }| + | + {1:~ }| + {2:[Scratch] }| + | + {1:~ }| + {2:[No Name] }| + | + ]] + + -- After all of our messing around, aucmd_win should still be floating. + -- Use :only to ensure _G.buf is hidden again (so the aucmd_win is used). + eq("editor", exec_lua [[ + vim.cmd "only" + vim.api.nvim_buf_call(_G.buf, function() + _G.config = vim.api.nvim_win_get_config(0) + end) + return _G.config.relative + ]]) + end) + + describe('closing last non-floating window in tab from `aucmd_win`', function() + before_each(function() + command('edit Xa.txt') + command('tabnew Xb.txt') + command('autocmd BufAdd Xa.txt 1close') + end) + + it('gives E814 when there are no other floating windows', function() + eq('Vim(close):E814: Cannot close window, only autocmd window would remain', + pcall_err(command, 'doautoall BufAdd')) + end) + + it('gives E814 when there are other floating windows', function() + meths.open_win(0, true, {width = 10, height = 10, relative = 'editor', row = 10, col = 10}) + eq('Vim(close):E814: Cannot close window, only autocmd window would remain', + pcall_err(command, 'doautoall BufAdd')) + end) + end) + it(':doautocmd does not warn "No matching autocommands" #10689', function() local screen = Screen.new(32, 3) screen:attach() @@ -354,4 +456,125 @@ describe('autocmd', function() :doautocmd SessionLoadPost | ]]} end) + + describe('v:event is readonly #18063', function() + it('during ChanOpen event', function() + command('autocmd ChanOpen * let v:event.info.id = 0') + funcs.jobstart({'cat'}) + retry(nil, nil, function() + eq('E46: Cannot change read-only variable "v:event.info"', meths.get_vvar('errmsg')) + end) + end) + + it('during ChanOpen event', function() + command('autocmd ChanInfo * let v:event.info.id = 0') + meths.set_client_info('foo', {}, 'remote', {}, {}) + retry(nil, nil, function() + eq('E46: Cannot change read-only variable "v:event.info"', meths.get_vvar('errmsg')) + end) + end) + + it('during RecordingLeave event', function() + command([[autocmd RecordingLeave * let v:event.regname = '']]) + eq('Vim(let):E46: Cannot change read-only variable "v:event.regname"', + pcall_err(command, 'normal! qqq')) + end) + + it('during TermClose event', function() + command('autocmd TermClose * let v:event.status = 0') + command('terminal') + eq('Vim(let):E46: Cannot change read-only variable "v:event.status"', + pcall_err(command, 'bdelete!')) + end) + end) + + describe('old_tests', function() + it('vimscript: WinNew ++once', function() + source [[ + " Without ++once WinNew triggers twice + let g:did_split = 0 + augroup Testing + au! + au WinNew * let g:did_split += 1 + augroup END + split + split + call assert_equal(2, g:did_split) + call assert_true(exists('#WinNew')) + close + close + + " With ++once WinNew triggers once + let g:did_split = 0 + augroup Testing + au! + au WinNew * ++once let g:did_split += 1 + augroup END + split + split + call assert_equal(1, g:did_split) + call assert_false(exists('#WinNew')) + close + close + + call assert_fails('au WinNew * ++once ++once echo bad', 'E983:') + ]] + + meths.set_var('did_split', 0) + + source [[ + augroup Testing + au! + au WinNew * let g:did_split += 1 + augroup END + + split + split + ]] + + eq(2, meths.get_var('did_split')) + eq(1, funcs.exists('#WinNew')) + + -- Now with once + meths.set_var('did_split', 0) + + source [[ + augroup Testing + au! + au WinNew * ++once let g:did_split += 1 + augroup END + + split + split + ]] + + eq(1, meths.get_var('did_split')) + eq(0, funcs.exists('#WinNew')) + + -- call assert_fails('au WinNew * ++once ++once echo bad', 'E983:') + local ok, msg = pcall(source, [[ + au WinNew * ++once ++once echo bad + ]]) + + eq(false, ok) + eq(true, not not string.find(msg, 'E983:')) + end) + + it('should have autocmds in filetypedetect group', function() + source [[filetype on]] + neq({}, meths.get_autocmds { group = "filetypedetect" }) + end) + + it('should allow comma-separated patterns', function() + source [[ + augroup TestingPatterns + au! + autocmd BufReadCmd *.shada,*.shada.tmp.[a-z] echo 'hello' + autocmd BufReadCmd *.shada,*.shada.tmp.[a-z] echo 'hello' + augroup END + ]] + + eq(4, #meths.get_autocmds { event = "BufReadCmd", group = "TestingPatterns" }) + end) + end) end) diff --git a/test/functional/autocmd/cursormoved_spec.lua b/test/functional/autocmd/cursormoved_spec.lua index 9641d4b096..85d8628d7e 100644 --- a/test/functional/autocmd/cursormoved_spec.lua +++ b/test/functional/autocmd/cursormoved_spec.lua @@ -35,6 +35,7 @@ describe('CursorMoved', function() it("is not triggered by cursor movement prior to first CursorMoved instantiation", function() source([[ let g:cursormoved = 0 + autocmd! CursorMoved autocmd CursorMoved * let g:cursormoved += 1 ]]) eq(0, eval('g:cursormoved')) diff --git a/test/functional/autocmd/dirchanged_spec.lua b/test/functional/autocmd/dirchanged_spec.lua index f4a1642ebf..45dc06b39b 100644 --- a/test/functional/autocmd/dirchanged_spec.lua +++ b/test/functional/autocmd/dirchanged_spec.lua @@ -8,7 +8,7 @@ local eval = h.eval local request = h.request local iswin = h.iswin -describe('autocmd DirChanged', function() +describe('autocmd DirChanged and DirChangedPre', function() local curdir = string.gsub(lfs.currentdir(), '\\', '/') local dirs = { curdir .. '/Xtest-functional-autocmd-dirchanged.dir1', @@ -26,31 +26,43 @@ describe('autocmd DirChanged', function() before_each(function() clear() + command('autocmd DirChangedPre * let [g:evpre, g:amatchpre, g:cdprecount] ' + ..'= [copy(v:event), expand("<amatch>"), 1 + get(g:, "cdprecount", 0)]') command('autocmd DirChanged * let [g:getcwd, g:ev, g:amatch, g:cdcount] ' - ..' = [getcwd(), copy(v:event), expand("<amatch>"), 1 + get(g:, "cdcount", 0)]') + ..'= [getcwd(), copy(v:event), expand("<amatch>"), 1 + get(g:, "cdcount", 0)]') -- Normalize path separators. + command([[autocmd DirChangedPre * let g:evpre['directory'] = substitute(g:evpre['directory'], '\\', '/', 'g')]]) command([[autocmd DirChanged * let g:ev['cwd'] = substitute(g:ev['cwd'], '\\', '/', 'g')]]) - command([[autocmd DirChanged * let g:getcwd = substitute(g:getcwd, '\\', '/', 'g')]]) + command([[autocmd DirChanged * let g:getcwd = substitute(g:getcwd, '\\', '/', 'g')]]) end) - it('sets v:event and <amatch>', function() + it('set v:event and <amatch>', function() command('lcd '..dirs[1]) + eq({directory=dirs[1], scope='window', changed_window=false}, eval('g:evpre')) eq({cwd=dirs[1], scope='window', changed_window=false}, eval('g:ev')) + eq('window', eval('g:amatchpre')) eq('window', eval('g:amatch')) + eq(1, eval('g:cdprecount')) eq(1, eval('g:cdcount')) command('tcd '..dirs[2]) + eq({directory=dirs[2], scope='tabpage', changed_window=false}, eval('g:evpre')) eq({cwd=dirs[2], scope='tabpage', changed_window=false}, eval('g:ev')) + eq('tabpage', eval('g:amatchpre')) eq('tabpage', eval('g:amatch')) + eq(2, eval('g:cdprecount')) eq(2, eval('g:cdcount')) command('cd '..dirs[3]) + eq({directory=dirs[3], scope='global', changed_window=false}, eval('g:evpre')) eq({cwd=dirs[3], scope='global', changed_window=false}, eval('g:ev')) + eq('global', eval('g:amatchpre')) eq('global', eval('g:amatch')) + eq(3, eval('g:cdprecount')) eq(3, eval('g:cdcount')) end) - it('sets getcwd() during event #6260', function() + it('DirChanged set getcwd() during event #6260', function() command('lcd '..dirs[1]) eq(dirs[1], eval('g:getcwd')) @@ -61,7 +73,7 @@ describe('autocmd DirChanged', function() eq(dirs[3], eval('g:getcwd')) end) - it('disallows recursion', function() + it('disallow recursion', function() command('set shellslash') -- Set up a _nested_ handler. command('autocmd DirChanged * nested lcd '..dirs[3]) @@ -72,23 +84,36 @@ describe('autocmd DirChanged', function() eq(dirs[3], eval('getcwd()')) end) - it('does not trigger if :cd fails', function() + it('only DirChangedPre is triggered if :cd fails', function() command('let g:ev = {}') + command('let g:cdcount = 0') local status1, err1 = pcall(function() - command('lcd '..dirs[1] .. '/doesnotexist') + command('lcd '..dirs[1]..'/doesnotexist') end) + eq({directory=dirs[1]..'/doesnotexist', scope='window', changed_window=false}, eval('g:evpre')) eq({}, eval('g:ev')) + eq('window', eval('g:amatchpre')) + eq(1, eval('g:cdprecount')) + eq(0, eval('g:cdcount')) local status2, err2 = pcall(function() - command('lcd '..dirs[2] .. '/doesnotexist') + command('lcd '..dirs[2]..'/doesnotexist') end) + eq({directory=dirs[2]..'/doesnotexist', scope='window', changed_window=false}, eval('g:evpre')) eq({}, eval('g:ev')) + eq('window', eval('g:amatchpre')) + eq(2, eval('g:cdprecount')) + eq(0, eval('g:cdcount')) local status3, err3 = pcall(function() - command('lcd '..dirs[3] .. '/doesnotexist') + command('lcd '..dirs[3]..'/doesnotexist') end) + eq({directory=dirs[3]..'/doesnotexist', scope='window', changed_window=false}, eval('g:evpre')) eq({}, eval('g:ev')) + eq('window', eval('g:amatchpre')) + eq(3, eval('g:cdprecount')) + eq(0, eval('g:cdcount')) eq(false, status1) eq(false, status2) @@ -99,85 +124,121 @@ describe('autocmd DirChanged', function() eq('E344:', string.match(err3, "E%d*:")) end) - it("is triggered by 'autochdir'", function() + it("are triggered by 'autochdir'", function() command('set autochdir') command('split '..dirs[1]..'/foo') + eq({directory=dirs[1], scope='window', changed_window=false}, eval('g:evpre')) eq({cwd=dirs[1], scope='window', changed_window=false}, eval('g:ev')) + eq('auto', eval('g:amatchpre')) eq('auto', eval('g:amatch')) + eq(1, eval('g:cdprecount')) + eq(1, eval('g:cdcount')) command('split '..dirs[2]..'/bar') + eq({directory=dirs[2], scope='window', changed_window=false}, eval('g:evpre')) eq({cwd=dirs[2], scope='window', changed_window=false}, eval('g:ev')) eq('auto', eval('g:amatch')) - + eq(2, eval('g:cdprecount')) eq(2, eval('g:cdcount')) end) - it('does not trigger if directory has not changed', function() + it('do not trigger if directory has not changed', function() command('lcd '..dirs[1]) + eq({directory=dirs[1], scope='window', changed_window=false}, eval('g:evpre')) eq({cwd=dirs[1], scope='window', changed_window=false}, eval('g:ev')) + eq('window', eval('g:amatchpre')) eq('window', eval('g:amatch')) + eq(1, eval('g:cdprecount')) eq(1, eval('g:cdcount')) + command('let g:evpre = {}') command('let g:ev = {}') command('lcd '..dirs[1]) + eq({}, eval('g:evpre')) eq({}, eval('g:ev')) + eq(1, eval('g:cdprecount')) eq(1, eval('g:cdcount')) if iswin() then command('lcd '..win_dirs[1]) + eq({}, eval('g:evpre')) eq({}, eval('g:ev')) + eq(1, eval('g:cdprecount')) eq(1, eval('g:cdcount')) end command('tcd '..dirs[2]) + eq({directory=dirs[2], scope='tabpage', changed_window=false}, eval('g:evpre')) eq({cwd=dirs[2], scope='tabpage', changed_window=false}, eval('g:ev')) + eq('tabpage', eval('g:amatchpre')) eq('tabpage', eval('g:amatch')) + eq(2, eval('g:cdprecount')) eq(2, eval('g:cdcount')) + command('let g:evpre = {}') command('let g:ev = {}') command('tcd '..dirs[2]) + eq({}, eval('g:evpre')) eq({}, eval('g:ev')) + eq(2, eval('g:cdprecount')) eq(2, eval('g:cdcount')) if iswin() then command('tcd '..win_dirs[2]) + eq({}, eval('g:evpre')) eq({}, eval('g:ev')) + eq(2, eval('g:cdprecount')) eq(2, eval('g:cdcount')) end command('cd '..dirs[3]) + eq({directory=dirs[3], scope='global', changed_window=false}, eval('g:evpre')) eq({cwd=dirs[3], scope='global', changed_window=false}, eval('g:ev')) eq('global', eval('g:amatch')) + eq(3, eval('g:cdprecount')) eq(3, eval('g:cdcount')) + command('let g:evpre = {}') command('let g:ev = {}') command('cd '..dirs[3]) + eq({}, eval('g:evpre')) eq({}, eval('g:ev')) + eq(3, eval('g:cdprecount')) eq(3, eval('g:cdcount')) if iswin() then command('cd '..win_dirs[3]) + eq({}, eval('g:evpre')) eq({}, eval('g:ev')) + eq(3, eval('g:cdprecount')) eq(3, eval('g:cdcount')) end command('set autochdir') command('split '..dirs[1]..'/foo') + eq({directory=dirs[1], scope='window', changed_window=false}, eval('g:evpre')) eq({cwd=dirs[1], scope='window', changed_window=false}, eval('g:ev')) + eq('auto', eval('g:amatchpre')) eq('auto', eval('g:amatch')) + eq(4, eval('g:cdprecount')) eq(4, eval('g:cdcount')) + command('let g:evpre = {}') command('let g:ev = {}') command('split '..dirs[1]..'/bar') + eq({}, eval('g:evpre')) eq({}, eval('g:ev')) + eq(4, eval('g:cdprecount')) eq(4, eval('g:cdcount')) if iswin() then command('split '..win_dirs[1]..'/baz') + eq({}, eval('g:evpre')) eq({}, eval('g:ev')) + eq(4, eval('g:cdprecount')) eq(4, eval('g:cdcount')) end end) - it("is triggered by switching to win/tab with different CWD #6054", function() + it("are triggered by switching to win/tab with different CWD #6054", function() command('lcd '..dirs[3]) -- window 3 command('split '..dirs[2]..'/foo') -- window 2 command('lcd '..dirs[2]) @@ -185,72 +246,105 @@ describe('autocmd DirChanged', function() command('lcd '..dirs[1]) command('2wincmd w') -- window 2 + eq({directory=dirs[2], scope='window', changed_window=true}, eval('g:evpre')) eq({cwd=dirs[2], scope='window', changed_window=true}, eval('g:ev')) + eq('window', eval('g:amatchpre')) eq('window', eval('g:amatch')) + eq(4, eval('g:cdprecount')) eq(4, eval('g:cdcount')) command('tabnew') -- tab 2 (tab-local CWD) + eq(4, eval('g:cdprecount')) -- same CWD, no DirChangedPre event eq(4, eval('g:cdcount')) -- same CWD, no DirChanged event command('tcd '..dirs[3]) command('tabnext') -- tab 1 (no tab-local CWD) + eq({directory=dirs[2], scope='window', changed_window=true}, eval('g:evpre')) eq({cwd=dirs[2], scope='window', changed_window=true}, eval('g:ev')) + eq('window', eval('g:amatchpre')) eq('window', eval('g:amatch')) command('tabnext') -- tab 2 + eq({directory=dirs[3], scope='tabpage', changed_window=true}, eval('g:evpre')) eq({cwd=dirs[3], scope='tabpage', changed_window=true}, eval('g:ev')) + eq('tabpage', eval('g:amatchpre')) eq('tabpage', eval('g:amatch')) + eq(7, eval('g:cdprecount')) eq(7, eval('g:cdcount')) command('tabnext') -- tab 1 command('3wincmd w') -- window 3 + eq(9, eval('g:cdprecount')) eq(9, eval('g:cdcount')) command('tabnext') -- tab 2 (has the *same* CWD) + eq(9, eval('g:cdprecount')) -- same CWD, no DirChangedPre event eq(9, eval('g:cdcount')) -- same CWD, no DirChanged event if iswin() then command('tabnew') -- tab 3 + eq(9, eval('g:cdprecount')) -- same CWD, no DirChangedPre event eq(9, eval('g:cdcount')) -- same CWD, no DirChanged event command('tcd '..win_dirs[3]) + eq(9, eval('g:cdprecount')) -- same CWD, no DirChangedPre event eq(9, eval('g:cdcount')) -- same CWD, no DirChanged event command('tabnext') -- tab 1 + eq(9, eval('g:cdprecount')) -- same CWD, no DirChangedPre event eq(9, eval('g:cdcount')) -- same CWD, no DirChanged event command('tabprevious') -- tab 3 + eq(9, eval('g:cdprecount')) -- same CWD, no DirChangedPre event eq(9, eval('g:cdcount')) -- same CWD, no DirChanged event command('tabprevious') -- tab 2 + eq(9, eval('g:cdprecount')) -- same CWD, no DirChangedPre event eq(9, eval('g:cdcount')) -- same CWD, no DirChanged event command('tabprevious') -- tab 1 + eq(9, eval('g:cdprecount')) -- same CWD, no DirChangedPre event eq(9, eval('g:cdcount')) -- same CWD, no DirChanged event command('lcd '..win_dirs[3]) -- window 3 + eq(9, eval('g:cdprecount')) -- same CWD, no DirChangedPre event eq(9, eval('g:cdcount')) -- same CWD, no DirChanged event command('tabnext') -- tab 2 + eq(9, eval('g:cdprecount')) -- same CWD, no DirChangedPre event eq(9, eval('g:cdcount')) -- same CWD, no DirChanged event command('tabnext') -- tab 3 + eq(9, eval('g:cdprecount')) -- same CWD, no DirChangedPre event eq(9, eval('g:cdcount')) -- same CWD, no DirChanged event command('tabnext') -- tab 1 + eq(9, eval('g:cdprecount')) -- same CWD, no DirChangedPre event eq(9, eval('g:cdcount')) -- same CWD, no DirChanged event command('tabprevious') -- tab 3 + eq(9, eval('g:cdprecount')) -- same CWD, no DirChangedPre event eq(9, eval('g:cdcount')) -- same CWD, no DirChanged event end end) - it('is triggered by nvim_set_current_dir()', function() + it('are triggered by nvim_set_current_dir()', function() request('nvim_set_current_dir', dirs[1]) + eq({directory=dirs[1], scope='global', changed_window=false}, eval('g:evpre')) eq({cwd=dirs[1], scope='global', changed_window=false}, eval('g:ev')) + eq(1, eval('g:cdprecount')) + eq(1, eval('g:cdcount')) request('nvim_set_current_dir', dirs[2]) + eq({directory=dirs[2], scope='global', changed_window=false}, eval('g:evpre')) eq({cwd=dirs[2], scope='global', changed_window=false}, eval('g:ev')) + eq(2, eval('g:cdprecount')) + eq(2, eval('g:cdcount')) local status, err = pcall(function() request('nvim_set_current_dir', '/doesnotexist') end) eq(false, status) eq('Failed to change directory', string.match(err, ': (.*)')) - eq({cwd=dirs[2], scope='global', changed_window=false}, eval('g:ev')) + eq({directory='/doesnotexist', scope='global', changed_window=false}, eval('g:evpre')) + eq(3, eval('g:cdprecount')) + eq(2, eval('g:cdcount')) end) - it('works when local to buffer', function() + it('work when local to buffer', function() + command('let g:triggeredpre = 0') command('let g:triggered = 0') + command('autocmd DirChangedPre <buffer> let g:triggeredpre = 1') command('autocmd DirChanged <buffer> let g:triggered = 1') command('cd '..dirs[1]) + eq(1, eval('g:triggeredpre')) eq(1, eval('g:triggered')) end) end) diff --git a/test/functional/autocmd/focus_spec.lua b/test/functional/autocmd/focus_spec.lua index 3f9a0ad09b..e3c9e1f9ee 100644 --- a/test/functional/autocmd/focus_spec.lua +++ b/test/functional/autocmd/focus_spec.lua @@ -56,7 +56,7 @@ describe('autoread TUI FocusGained/FocusLost', function() line 3 | line 4 | {5:xtest-foo }| - "xtest-foo" 4L, 28C | + "xtest-foo" 4L, 28B | {3:-- TERMINAL --} | ]]} end) diff --git a/test/functional/autocmd/show_spec.lua b/test/functional/autocmd/show_spec.lua new file mode 100644 index 0000000000..505bed834b --- /dev/null +++ b/test/functional/autocmd/show_spec.lua @@ -0,0 +1,183 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') + +local clear = helpers.clear +local command = helpers.command +local dedent = helpers.dedent +local eq = helpers.eq +local funcs = helpers.funcs +local eval = helpers.eval +local exec = helpers.exec +local feed = helpers.feed + +describe(":autocmd", function() + before_each(function() + clear({'-u', 'NONE'}) + end) + + it("should not segfault when you just do autocmd", function() + command ":autocmd" + end) + + it("should filter based on ++once", function() + command "autocmd! BufEnter" + command "autocmd BufEnter * :echo 'Hello'" + command [[augroup TestingOne]] + command [[ autocmd BufEnter * :echo "Line 1"]] + command [[ autocmd BufEnter * :echo "Line 2"]] + command [[augroup END]] + + eq(dedent([[ + + --- Autocommands --- + BufEnter + * :echo 'Hello' + TestingOne BufEnter + * :echo "Line 1" + :echo "Line 2"]]), + funcs.execute('autocmd BufEnter')) + end) + + 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:attach() + exec([[ + set more + autocmd! BufEnter + augroup test_1 + autocmd BufEnter A echo 'A' + autocmd BufEnter B echo 'B' + autocmd BufEnter C echo 'C' + autocmd BufEnter D echo 'D' + autocmd BufEnter E echo 'E' + autocmd BufEnter F echo 'F' + augroup END + autocmd! BufLeave + augroup test_1 + autocmd BufLeave A echo 'A' + autocmd BufLeave B echo 'B' + autocmd BufLeave C echo 'C' + autocmd BufLeave D echo 'D' + autocmd BufLeave E echo 'E' + autocmd BufLeave F echo 'F' + augroup END + ]]) + feed(':autocmd<CR>') + screen:expect([[ + :autocmd | + {3:--- Autocommands ---} | + {3:test_1} {3:BufEnter} | + A echo 'A' | + B echo 'B' | + {2:-- More --}^ | + ]]) + feed('q') + screen:expect([[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]) + end) + + it('should not show group information for deleted pattern', function() + exec([[ + autocmd! BufEnter + augroup test_1 + autocmd BufEnter A echo 'A' + autocmd BufEnter B echo 'B' + autocmd BufEnter C echo 'C' + augroup END + augroup test_2 + autocmd BufEnter foo echo 'foo' + augroup END + augroup test_3 + autocmd BufEnter D echo 'D' + autocmd BufEnter E echo 'E' + autocmd BufEnter F echo 'F' + augroup END + + func Func() + autocmd! test_2 BufEnter + let g:output = execute('autocmd BufEnter') + endfunc + + autocmd User foo call Func() + doautocmd User foo + ]]) + eq(dedent([[ + + --- Autocommands --- + test_1 BufEnter + A echo 'A' + B echo 'B' + C echo 'C' + test_3 BufEnter + D echo 'D' + E echo 'E' + F echo 'F']]), eval('g:output')) + end) + + it('can filter by pattern #17973', function() + exec([[ + autocmd! BufEnter + autocmd! User + augroup test_1 + autocmd BufEnter A echo "A1" + autocmd BufEnter B echo "B1" + autocmd User A echo "A1" + autocmd User B echo "B1" + augroup END + augroup test_2 + autocmd BufEnter A echo "A2" + autocmd BufEnter B echo "B2" + autocmd User A echo "A2" + autocmd User B echo "B2" + augroup END + augroup test_3 + autocmd BufEnter A echo "A3" + autocmd BufEnter B echo "B3" + autocmd User A echo "A3" + autocmd User B echo "B3" + augroup END + ]]) + eq(dedent([[ + + --- Autocommands --- + test_1 User + A echo "A1" + test_2 User + A echo "A2" + test_3 User + A echo "A3"]]), funcs.execute('autocmd User A')) + eq(dedent([[ + + --- Autocommands --- + test_1 BufEnter + B echo "B1" + test_2 BufEnter + B echo "B2" + test_3 BufEnter + B echo "B3" + test_1 User + B echo "B1" + test_2 User + B echo "B2" + test_3 User + B echo "B3"]]), funcs.execute('autocmd * B')) + eq(dedent([[ + + --- Autocommands --- + test_3 BufEnter + B echo "B3" + test_3 User + B echo "B3"]]), funcs.execute('autocmd test_3 * B')) + end) +end) diff --git a/test/functional/autocmd/signal_spec.lua b/test/functional/autocmd/signal_spec.lua index 719adeaf1b..d4f65cc61d 100644 --- a/test/functional/autocmd/signal_spec.lua +++ b/test/functional/autocmd/signal_spec.lua @@ -30,6 +30,12 @@ describe('autocmd Signal', function() eq({'notification', 'foo', {}}, next_msg()) end) + it('matches SIGWINCH', function() + command('autocmd Signal SIGWINCH call rpcnotify(1, "foo")') + posix_kill('WINCH', funcs.getpid()) + eq({'notification', 'foo', {}}, next_msg()) + end) + it('does not match unknown patterns', function() command('autocmd Signal SIGUSR2 call rpcnotify(1, "foo")') posix_kill('USR1', funcs.getpid()) diff --git a/test/functional/autocmd/termxx_spec.lua b/test/functional/autocmd/termxx_spec.lua index 1e8f981437..859c2ebf44 100644 --- a/test/functional/autocmd/termxx_spec.lua +++ b/test/functional/autocmd/termxx_spec.lua @@ -1,21 +1,41 @@ local luv = require('luv') local helpers = require('test.functional.helpers')(after_each) -local clear, command, nvim, nvim_dir = - helpers.clear, helpers.command, helpers.nvim, helpers.nvim_dir +local clear, command, nvim, testprg = + helpers.clear, helpers.command, helpers.nvim, helpers.testprg local eval, eq, neq, retry = helpers.eval, helpers.eq, helpers.neq, helpers.retry local ok = helpers.ok local feed = helpers.feed +local pcall_err = helpers.pcall_err +local assert_alive = helpers.assert_alive local iswin = helpers.iswin describe('autocmd TermClose', function() before_each(function() clear() - nvim('set_option', 'shell', nvim_dir .. '/shell-test') + nvim('set_option', 'shell', testprg('shell-test')) command('set shellcmdflag=EXE shellredir= shellpipe= shellquote= shellxquote=') end) + + local function test_termclose_delete_own_buf() + command('autocmd TermClose * bdelete!') + command('terminal') + eq('Vim(bdelete):E937: Attempt to delete a buffer that is in use', pcall_err(command, 'bdelete!')) + assert_alive() + end + + -- TODO: fixed after merging patches for `can_unload_buffer`? + pending('TermClose deleting its own buffer, altbuf = buffer 1 #10386', function() + test_termclose_delete_own_buf() + end) + + it('TermClose deleting its own buffer, altbuf NOT buffer 1 #10386', function() + command('edit foo1') + test_termclose_delete_own_buf() + end) + it('triggers when fast-exiting terminal job stops', function() command('autocmd TermClose * let g:test_termclose = 23') command('terminal') diff --git a/test/functional/autocmd/winscrolled_spec.lua b/test/functional/autocmd/winscrolled_spec.lua index 1ef5a37479..5c1b758961 100644 --- a/test/functional/autocmd/winscrolled_spec.lua +++ b/test/functional/autocmd/winscrolled_spec.lua @@ -3,60 +3,83 @@ local helpers = require('test.functional.helpers')(after_each) local clear = helpers.clear local eq = helpers.eq local eval = helpers.eval -local source = helpers.source +local command = helpers.command +local feed = helpers.feed +local meths = helpers.meths +local assert_alive = helpers.assert_alive + +before_each(clear) describe('WinScrolled', function() - before_each(clear) + local win_id + + before_each(function() + win_id = meths.get_current_win().id + command(string.format('autocmd WinScrolled %d let g:matched = v:true', win_id)) + command('let g:scrolled = 0') + command('autocmd WinScrolled * let g:scrolled += 1') + command([[autocmd WinScrolled * let g:amatch = str2nr(expand('<amatch>'))]]) + command([[autocmd WinScrolled * let g:afile = str2nr(expand('<afile>'))]]) + end) + + after_each(function() + eq(true, eval('g:matched')) + eq(win_id, eval('g:amatch')) + eq(win_id, eval('g:afile')) + end) it('is triggered by scrolling vertically', function() - source([[ - set nowrap - let width = winwidth(0) - let line = '123' . repeat('*', width * 2) - let lines = [line, line] - call nvim_buf_set_lines(0, 0, -1, v:true, lines) - - let g:scrolled = 0 - autocmd WinScrolled * let g:scrolled += 1 - execute "normal! \<C-e>" - ]]) + local lines = {'123', '123'} + meths.buf_set_lines(0, 0, -1, true, lines) + eq(0, eval('g:scrolled')) + feed('<C-E>') eq(1, eval('g:scrolled')) end) it('is triggered by scrolling horizontally', function() - source([[ - set nowrap - let width = winwidth(0) - let line = '123' . repeat('*', width * 2) - let lines = [line, line] - call nvim_buf_set_lines(0, 0, -1, v:true, lines) - - let g:scrolled = 0 - autocmd WinScrolled * let g:scrolled += 1 - execute "normal! zl" - ]]) + command('set nowrap') + local width = meths.win_get_width(0) + local line = '123' .. ('*'):rep(width * 2) + local lines = {line, line} + meths.buf_set_lines(0, 0, -1, true, lines) + eq(0, eval('g:scrolled')) + feed('zl') eq(1, eval('g:scrolled')) end) - it('is triggered when the window scrolls in insert mode', function() - source([[ - let height = winheight(0) - let lines = map(range(height * 2), {_, i -> string(i)}) - call nvim_buf_set_lines(0, 0, -1, v:true, lines) - - let g:scrolled = 0 - autocmd WinScrolled * let g:scrolled += 1 - call feedkeys("LA\<CR><Esc>", "n") - ]]) + it('is triggered by horizontal scrolling from cursor move', function() + command('set nowrap') + local lines = {'', '', 'Foo'} + meths.buf_set_lines(0, 0, -1, true, lines) + meths.win_set_cursor(0, {3, 0}) + eq(0, eval('g:scrolled')) + feed('zl') + eq(1, eval('g:scrolled')) + feed('zl') eq(2, eval('g:scrolled')) + feed('h') + eq(3, eval('g:scrolled')) end) - it('is triggered when the window is resized', function() - source([[ - let g:scrolled = 0 - autocmd WinScrolled * let g:scrolled += 1 - wincmd v - ]]) + it('is triggered when the window scrolls in Insert mode', function() + local height = meths.win_get_height(0) + local lines = {} + for i = 1, height * 2 do + lines[i] = tostring(i) + end + meths.buf_set_lines(0, 0, -1, true, lines) + feed('L') + eq(0, eval('g:scrolled')) + feed('A<CR><Esc>') eq(1, eval('g:scrolled')) end) end) + +it('closing window in WinScrolled does not cause use-after-free #13265', function() + local lines = {'aaa', 'bbb'} + meths.buf_set_lines(0, 0, -1, true, lines) + command('vsplit') + command('autocmd WinScrolled * close') + feed('<C-E>') + assert_alive() +end) diff --git a/test/functional/core/channels_spec.lua b/test/functional/core/channels_spec.lua index 93dec9fb35..ca52404d3b 100644 --- a/test/functional/core/channels_spec.lua +++ b/test/functional/core/channels_spec.lua @@ -10,6 +10,8 @@ local nvim_prog = helpers.nvim_prog local is_os = helpers.is_os local retry = helpers.retry local expect_twostreams = helpers.expect_twostreams +local assert_alive = helpers.assert_alive +local pcall_err = helpers.pcall_err describe('channels', function() local init = [[ @@ -100,6 +102,38 @@ describe('channels', function() eq({"notification", "exit", {3,0}}, next_msg()) end) + it('can use stdio channel and on_print callback', function() + source([[ + let g:job_opts = { + \ 'on_stdout': function('OnEvent'), + \ 'on_stderr': function('OnEvent'), + \ 'on_exit': function('OnEvent'), + \ } + ]]) + meths.set_var("nvim_prog", nvim_prog) + meths.set_var("code", [[ + function! OnStdin(id, data, event) dict + echo string([a:id, a:data, a:event]) + if a:data == [''] + quit + endif + endfunction + function! OnPrint(text) dict + call chansend(g:x, ['OnPrint:' .. a:text]) + endfunction + let g:x = stdioopen({'on_stdin': funcref('OnStdin'), 'on_print':'OnPrint'}) + call chansend(x, "hello") + ]]) + command("let g:id = jobstart([ g:nvim_prog, '-u', 'NONE', '-i', 'NONE', '--cmd', 'set noswapfile', '--headless', '--cmd', g:code], g:job_opts)") + local id = eval("g:id") + ok(id > 0) + + eq({ "notification", "stdout", {id, { "hello" } } }, next_msg()) + + command("call chansend(id, 'howdy')") + eq({"notification", "stdout", {id, {"OnPrint:[1, ['howdy'], 'stdin']"}}}, next_msg()) + end) + local function expect_twoline(id, stream, line1, line2, nobr) local msg = next_msg() local joined = nobr and {line1..line2} or {line1, line2} @@ -282,3 +316,22 @@ describe('channels', function() eq({"notification", "exit", {id, 1, {''}}}, next_msg()) end) end) + +describe('loopback', function() + before_each(function() + clear() + command("let chan = sockconnect('pipe', v:servername, {'rpc': v:true})") + end) + + it('does not crash when sending raw data', function() + eq("Vim(call):Can't send raw data to rpc channel", + pcall_err(command, "call chansend(chan, 'test')")) + assert_alive() + end) + + it('are released when closed', function() + local chans = eval('len(nvim_list_chans())') + command('call chanclose(chan)') + eq(chans - 1, eval('len(nvim_list_chans())')) + end) +end) diff --git a/test/functional/core/fileio_spec.lua b/test/functional/core/fileio_spec.lua index c68bc18eed..a4d22685e8 100644 --- a/test/functional/core/fileio_spec.lua +++ b/test/functional/core/fileio_spec.lua @@ -1,27 +1,34 @@ local helpers = require('test.functional.helpers')(after_each) +local assert_log = helpers.assert_log +local assert_nolog = helpers.assert_nolog local clear = helpers.clear local command = helpers.command local eq = helpers.eq +local ok = helpers.ok local feed = helpers.feed local funcs = helpers.funcs local nvim_prog = helpers.nvim_prog local request = helpers.request local retry = helpers.retry local rmdir = helpers.rmdir +local matches = helpers.matches local mkdir = helpers.mkdir local sleep = helpers.sleep local read_file = helpers.read_file +local tmpname = helpers.tmpname local trim = helpers.trim local currentdir = helpers.funcs.getcwd local iswin = helpers.iswin local assert_alive = helpers.assert_alive +local expect_exit = helpers.expect_exit +local write_file = helpers.write_file describe('fileio', function() before_each(function() end) after_each(function() - command(':qall!') + expect_exit(command, ':qall!') os.remove('Xtest_startup_shada') os.remove('Xtest_startup_file1') os.remove('Xtest_startup_file1~') @@ -139,3 +146,69 @@ describe('fileio', function() end) end) +describe('tmpdir', function() + local tmproot_pat = [=[.*[/\\]nvim%.[^/\\]+]=] + local testlog = 'Xtest_tmpdir_log' + local faketmp + + before_each(function() + -- Fake /tmp dir so that we can mess it up. + faketmp = tmpname() + os.remove(faketmp) + mkdir(faketmp) + end) + + after_each(function() + expect_exit(command, ':qall!') + os.remove(testlog) + end) + + it('failure modes', function() + clear({ env={ NVIM_LOG_FILE=testlog, TMPDIR=faketmp, } }) + assert_nolog('tempdir is not a directory', testlog) + assert_nolog('tempdir has invalid permissions', testlog) + + -- Tempfiles typically look like: "…/nvim.<user>/xxx/0". + -- - "…/nvim.<user>/xxx/" is the per-process tmpdir, not shared with other Nvims. + -- - "…/nvim.<user>/" is the tmpdir root, shared by all Nvims (normally). + local tmproot = (funcs.tempname()):match(tmproot_pat) + ok(tmproot:len() > 4, 'tmproot like "nvim.foo"', tmproot) + + -- Test how Nvim handles invalid tmpdir root (by hostile users or accidents). + -- + -- "…/nvim.<user>/" is not a directory: + expect_exit(command, ':qall!') + rmdir(tmproot) + write_file(tmproot, '') -- Not a directory, vim_mktempdir() should skip it. + clear({ env={ NVIM_LOG_FILE=testlog, TMPDIR=faketmp, } }) + matches(tmproot_pat, funcs.stdpath('run')) -- Tickle vim_mktempdir(). + -- Assert that broken tmpdir root was handled. + retry(nil, 1000, function() + assert_log('tempdir root not a directory', testlog, 100) + end) + + -- "…/nvim.<user>/" has wrong permissions: + if iswin() then + return -- TODO(justinmk): need setfperm/getfperm on Windows. #8244 + end + os.remove(testlog) + os.remove(tmproot) + mkdir(tmproot) + funcs.setfperm(tmproot, 'rwxr--r--') -- Invalid permissions, vim_mktempdir() should skip it. + clear({ env={ NVIM_LOG_FILE=testlog, TMPDIR=faketmp, } }) + matches(tmproot_pat, funcs.stdpath('run')) -- Tickle vim_mktempdir(). + -- Assert that broken tmpdir root was handled. + retry(nil, 1000, function() + assert_log('tempdir root has invalid permissions', testlog, 100) + end) + end) + + it('too long', function() + local bigname = ('%s/%s'):format(faketmp, ('x'):rep(666)) + mkdir(bigname) + clear({ env={ NVIM_LOG_FILE=testlog, TMPDIR=bigname, } }) + matches(tmproot_pat, funcs.stdpath('run')) -- Tickle vim_mktempdir(). + local len = (funcs.tempname()):len() + ok(len > 4 and len < 256, '4 < len < 256', tostring(len)) + end) +end) diff --git a/test/functional/core/job_spec.lua b/test/functional/core/job_spec.lua index 5e127bce26..04fbb807be 100644 --- a/test/functional/core/job_spec.lua +++ b/test/functional/core/job_spec.lua @@ -1,9 +1,9 @@ local helpers = require('test.functional.helpers')(after_each) local clear, eq, eval, exc_exec, feed_command, feed, insert, neq, next_msg, nvim, - nvim_dir, ok, source, write_file, mkdir, rmdir = helpers.clear, + testprg, ok, source, write_file, mkdir, rmdir = helpers.clear, helpers.eq, helpers.eval, helpers.exc_exec, helpers.feed_command, helpers.feed, helpers.insert, helpers.neq, helpers.next_msg, helpers.nvim, - helpers.nvim_dir, helpers.ok, helpers.source, + helpers.testprg, helpers.ok, helpers.source, helpers.write_file, helpers.mkdir, helpers.rmdir local assert_alive = helpers.assert_alive local command = helpers.command @@ -16,6 +16,7 @@ local poke_eventloop = helpers.poke_eventloop local iswin = helpers.iswin local get_pathsep = helpers.get_pathsep local pathroot = helpers.pathroot +local exec_lua = helpers.exec_lua local nvim_set = helpers.nvim_set local expect_twostreams = helpers.expect_twostreams local expect_msg_seq = helpers.expect_msg_seq @@ -72,12 +73,20 @@ describe('jobs', function() nvim('command', [[call jobstart('echo $TOTO $VAR', g:job_opts)]]) end - expect_msg_seq({ - {'notification', 'stdout', {0, {'hello world abc', ''}}}, - }) + expect_msg_seq( + { + {'notification', 'stdout', {0, {'hello world abc'}}}, + {'notification', 'stdout', {0, {'', ''}}}, + }, + { + {'notification', 'stdout', {0, {'hello world abc', ''}}}, + {'notification', 'stdout', {0, {''}}} + } + ) end) it('append environment with pty #env', function() + if helpers.pending_win32(pending) then return end nvim('command', "let $VAR = 'abc'") nvim('command', "let $TOTO = 'goodbye world'") nvim('command', "let g:job_opts.pty = v:true") @@ -87,9 +96,16 @@ describe('jobs', function() else nvim('command', [[call jobstart('echo $TOTO $VAR', g:job_opts)]]) end - expect_msg_seq({ - {'notification', 'stdout', {0, {'hello world abc', ''}}}, - }) + expect_msg_seq( + { + {'notification', 'stdout', {0, {'hello world abc'}}}, + {'notification', 'stdout', {0, {'', ''}}}, + }, + { + {'notification', 'stdout', {0, {'hello world abc', ''}}}, + {'notification', 'stdout', {0, {''}}} + } + ) end) it('replace environment #env', function() @@ -207,7 +223,7 @@ describe('jobs', function() ok(string.find(err, "E475: Invalid argument: expected valid directory$") ~= nil) end) - it('produces error when using non-executable `cwd`', function() + it('error on non-executable `cwd`', function() if iswin() then return end -- N/A for Windows local dir = 'Xtest_not_executable_dir' @@ -248,7 +264,7 @@ describe('jobs', function() eq({'notification', 'exit', {0, 0}}, next_msg()) end) - it('allows interactive commands', function() + it('interactive commands', function() nvim('command', "let j = jobstart(['cat', '-'], g:job_opts)") neq(0, eval('j')) nvim('command', 'call jobsend(j, "abc\\n")') @@ -294,13 +310,7 @@ describe('jobs', function() nvim('command', "call jobstop(j)") end) - it("will not buffer data if it doesn't end in newlines", function() - if helpers.isCI('travis') and os.getenv('CC') == 'gcc-4.9' - and helpers.is_os('mac') then - -- XXX: Hangs Travis macOS since e9061117a5b8f195c3f26a5cb94e18ddd7752d86. - pending("[Hangs on Travis macOS. #5002]") - end - + it("emits partial lines (does NOT buffer data lacking newlines)", function() nvim('command', "let j = jobstart(['cat', '-'], g:job_opts)") nvim('command', 'call jobsend(j, "abc\\nxyz")') eq({'notification', 'stdout', {0, {'abc', 'xyz'}}}, next_msg()) @@ -383,7 +393,7 @@ describe('jobs', function() eq(NIL, meths.get_proc(pid)) end) - it("do not survive the exit of nvim", function() + it("disposed on Nvim exit", function() -- use sleep, which doesn't die on stdin close nvim('command', "let g:j = jobstart(has('win32') ? ['ping', '-n', '1001', '127.0.0.1'] : ['sleep', '1000'], g:job_opts)") local pid = eval('jobpid(g:j)') @@ -651,6 +661,44 @@ describe('jobs', function() ) end) + it('jobstart() environment: $NVIM, $NVIM_LISTEN_ADDRESS #11009', function() + local function get_env_in_child_job(envname, env) + return exec_lua([[ + local envname, env = ... + local join = function(s) return vim.fn.join(s, '') end + local stdout = {} + local stderr = {} + local opt = { + env = env, + stdout_buffered = true, + stderr_buffered = true, + on_stderr = function(chan, data, name) stderr = data end, + on_stdout = function(chan, data, name) stdout = data end, + } + local j1 = vim.fn.jobstart({ vim.v.progpath, '-es', '-V1',( '+echo "%s="..getenv("%s")'):format(envname, envname), '+qa!' }, opt) + vim.fn.jobwait({ j1 }, 10000) + return join({ join(stdout), join(stderr) }) + ]], + envname, + env) + end + + local addr = eval('v:servername') + ok((addr):len() > 0) + -- $NVIM is _not_ defined in the top-level Nvim process. + eq('', eval('$NVIM')) + -- jobstart() shares its v:servername with the child via $NVIM. + eq('NVIM='..addr, get_env_in_child_job('NVIM')) + -- $NVIM_LISTEN_ADDRESS is unset by server_init in the child. + eq('NVIM_LISTEN_ADDRESS=null', get_env_in_child_job('NVIM_LISTEN_ADDRESS')) + eq('NVIM_LISTEN_ADDRESS=null', get_env_in_child_job('NVIM_LISTEN_ADDRESS', + { NVIM_LISTEN_ADDRESS='Xtest_jobstart_env' })) + -- User can explicitly set $NVIM_LOG_FILE, $VIM, $VIMRUNTIME. + eq('NVIM_LOG_FILE=Xtest_jobstart_env', + get_env_in_child_job('NVIM_LOG_FILE', { NVIM_LOG_FILE='Xtest_jobstart_env' })) + os.remove('Xtest_jobstart_env') + end) + describe('jobwait', function() before_each(function() if iswin() then @@ -1009,8 +1057,7 @@ describe('jobs', function() return a:data endfunction ]]) - local ext = iswin() and '.exe' or '' - insert(nvim_dir..'/tty-test'..ext) -- Full path to tty-test. + insert(testprg('tty-test')) nvim('command', 'let g:job_opts.pty = 1') nvim('command', 'let exec = [expand("<cfile>:p")]') nvim('command', "let j = jobstart(exec, g:job_opts)") @@ -1043,7 +1090,7 @@ describe('jobs', function() local other_jobid = eval("jobstart(['cat', '-'], g:job_opts)") local other_pid = eval('jobpid(' .. other_jobid .. ')') - -- Other job doesn't block first job from recieving SIGHUP on jobclose() + -- Other job doesn't block first job from receiving SIGHUP on jobclose() command('call jobclose(j)') -- Have to wait so that the SIGHUP can be processed by tty-test on time. -- Can't wait for the next message in case this test fails, if it fails diff --git a/test/functional/core/log_spec.lua b/test/functional/core/log_spec.lua new file mode 100644 index 0000000000..3b1ccd9559 --- /dev/null +++ b/test/functional/core/log_spec.lua @@ -0,0 +1,57 @@ +local helpers = require('test.functional.helpers')(after_each) +local assert_log = helpers.assert_log +local clear = helpers.clear +local command = helpers.command +local eq = helpers.eq +local exec_lua = helpers.exec_lua +local expect_exit = helpers.expect_exit +local request = helpers.request +local retry = helpers.retry + +describe('log', function() + local testlog = 'Xtest_logging' + + after_each(function() + expect_exit(command, 'qa!') + os.remove(testlog) + end) + + it('skipped before log_init', function() + -- This test is for _visibility_: adjust as needed, after checking for regression. + -- + -- During startup some components may try to log before logging is setup. + -- That should be uncommon (ideally never)--and if there are MANY such + -- calls, that needs investigation. + clear() + eq(0, request('nvim__stats').log_skip) + clear{env={CDPATH='~doesnotexist'}} + assert(request('nvim__stats').log_skip <= 13) + end) + + it('messages are formatted with 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 + + clear({env={ + NVIM_LOG_FILE=testlog, + -- TODO: remove this after nvim_log #7062 is merged. + __NVIM_TEST_LOG='1' + }}) + + local tid = _G._nvim_test_id + retry(nil, 1000, function() + assert_log(tid..'%.%d+%.%d +server_init:%d+: test log message', testlog, 100) + end) + + exec_lua([[ + local j1 = vim.fn.jobstart({ vim.v.progpath, '-es', '-V1', '+foochild', '+qa!' }, vim.empty_dict()) + vim.fn.jobwait({ j1 }, 10000) + ]]) + + -- Child Nvim spawned by jobstart() appends "/c" to parent name. + retry(nil, 1000, function() + assert_log('%.%d+%.%d/c +server_init:%d+: test log message', testlog, 100) + end) + end) +end) diff --git a/test/functional/core/main_spec.lua b/test/functional/core/main_spec.lua index 37a9f0b836..f6fb859ccc 100644 --- a/test/functional/core/main_spec.lua +++ b/test/functional/core/main_spec.lua @@ -52,11 +52,15 @@ describe('Command-line option', function() if helpers.pending_win32(pending) then return end local screen = Screen.new(40, 8) screen:attach() - funcs.termopen({ + local args = { nvim_prog_abs(), '-u', 'NONE', '-i', 'NONE', - '--cmd', 'set noswapfile shortmess+=IFW fileformats=unix', - '-s', '-' - }) + '--cmd', 'set noswapfile shortmess+=IFW fileformats=unix', + '-s', '-' + } + + -- Need to explicitly pipe to stdin so that the embedded Nvim instance doesn't try to read + -- data from the terminal #18181 + funcs.termopen(string.format([[echo "" | %s]], table.concat(args, " "))) screen:expect([[ ^ | {1:~ }| diff --git a/test/functional/core/remote_spec.lua b/test/functional/core/remote_spec.lua new file mode 100644 index 0000000000..d7bd075eb2 --- /dev/null +++ b/test/functional/core/remote_spec.lua @@ -0,0 +1,142 @@ +local helpers = require('test.functional.helpers')(after_each) + +local clear = helpers.clear +local command = helpers.command +local eq = helpers.eq +local expect = helpers.expect +local funcs = helpers.funcs +local insert = helpers.insert +local meths = helpers.meths +local new_argv = helpers.new_argv +local neq = helpers.neq +local set_session = helpers.set_session +local spawn = helpers.spawn +local tmpname = helpers.tmpname +local write_file = helpers.write_file + +describe('Remote', function() + local fname, other_fname + local contents = 'The call is coming from outside the process' + local other_contents = "A second file's contents" + + before_each(function() + fname = tmpname() .. ' with spaces in the filename' + other_fname = tmpname() + write_file(fname, contents) + write_file(other_fname, other_contents) + end) + + describe('connect to server and', function() + local server + before_each(function() + server = spawn(new_argv(), true) + set_session(server) + end) + + after_each(function() + server:close() + end) + + local function run_remote(...) + set_session(server) + local addr = funcs.serverlist()[1] + local client_argv = new_argv({args={'--server', addr, ...}}) + + -- Create an nvim instance just to run the remote-invoking nvim. We want + -- to wait for the remote instance to exit and calling jobwait blocks + -- the event loop. If the server event loop is blocked, it can't process + -- our incoming --remote calls. + local client_starter = spawn(new_argv(), false, nil, true) + set_session(client_starter) + local client_job_id = funcs.jobstart(client_argv) + eq({ 0 }, funcs.jobwait({client_job_id})) + client_starter:close() + set_session(server) + end + + it('edit a single file', function() + run_remote('--remote', fname) + expect(contents) + eq(2, #funcs.getbufinfo()) + end) + + it('tab edit a single file with a non-changed buffer', function() + run_remote('--remote-tab', fname) + expect(contents) + eq(1, #funcs.gettabinfo()) + end) + + it('tab edit a single file with a changed buffer', function() + insert('hello') + run_remote('--remote-tab', fname) + expect(contents) + eq(2, #funcs.gettabinfo()) + end) + + it('edit multiple files', function() + run_remote('--remote', fname, other_fname) + expect(contents) + command('next') + expect(other_contents) + eq(3, #funcs.getbufinfo()) + end) + + it('send keys', function() + run_remote('--remote-send', ':edit '..fname..'<CR><C-W>v') + expect(contents) + eq(2, #funcs.getwininfo()) + -- Only a single buffer as we're using edit and not drop like --remote does + eq(1, #funcs.getbufinfo()) + end) + + it('evaluate expressions', function() + run_remote('--remote-expr', 'setline(1, "Yo")') + expect('Yo') + end) + end) + + it('creates server if not found', function() + clear('--remote', fname) + expect(contents) + eq(1, #funcs.getbufinfo()) + -- Since we didn't pass silent, we should get a complaint + neq(nil, string.find(meths.exec('messages', true), 'E247')) + end) + + it('creates server if not found with tabs', function() + clear('--remote-tab-silent', fname, other_fname) + expect(contents) + eq(2, #funcs.gettabinfo()) + eq(2, #funcs.getbufinfo()) + -- We passed silent, so no message should be issued about the server not being found + eq(nil, string.find(meths.exec('messages', true), 'E247')) + end) + + pending('exits with error on', function() + local function run_and_check_exit_code(...) + local bogus_argv = new_argv(...) + + -- Create an nvim instance just to run the remote-invoking nvim. We want + -- to wait for the remote instance to exit and calling jobwait blocks + -- the event loop. If the server event loop is blocked, it can't process + -- our incoming --remote calls. + clear() + local bogus_job_id = funcs.jobstart(bogus_argv) + eq({2}, funcs.jobwait({bogus_job_id})) + end + it('bogus subcommand', function() + run_and_check_exit_code('--remote-bogus') + end) + + it('send without server', function() + run_and_check_exit_code('--remote-send', 'i') + end) + + it('expr without server', function() + run_and_check_exit_code('--remote-expr', 'setline(1, "Yo")') + end) + it('wait subcommand', function() + run_and_check_exit_code('--remote-wait', fname) + end) + end) +end) diff --git a/test/functional/core/startup_spec.lua b/test/functional/core/startup_spec.lua index 2fa84e8313..4f9df4010e 100644 --- a/test/functional/core/startup_spec.lua +++ b/test/functional/core/startup_spec.lua @@ -2,6 +2,7 @@ local helpers = require('test.functional.helpers')(after_each) local Screen = require('test.functional.ui.screen') local assert_alive = helpers.assert_alive +local assert_log = helpers.assert_log local clear = helpers.clear local command = helpers.command local ok = helpers.ok @@ -23,6 +24,29 @@ local iswin = helpers.iswin local startswith = helpers.startswith local write_file = helpers.write_file local meths = helpers.meths +local alter_slashes = helpers.alter_slashes + +local testfile = 'Xtest_startuptime' +after_each(function() + os.remove(testfile) +end) + +describe('startup', function() + it('--clean', function() + clear() + ok(string.find(alter_slashes(meths.get_option('runtimepath')), funcs.stdpath('config'), 1, true) ~= nil) + clear('--clean') + ok(string.find(alter_slashes(meths.get_option('runtimepath')), funcs.stdpath('config'), 1, true) == nil) + end) + + it('--startuptime', function() + clear({ args = {'--startuptime', testfile}}) + retry(nil, 1000, function() + assert_log('sourcing', testfile, 100) + assert_log("require%('vim%._editor'%)", testfile, 100) + end) + end) +end) describe('startup', function() before_each(function() @@ -255,6 +279,7 @@ describe('startup', function() it('does not crash when expanding cdpath during early_init', function() clear{env={CDPATH='~doesnotexist'}} + assert_alive() eq(',~doesnotexist', eval('&cdpath')) end) @@ -355,7 +380,7 @@ describe('startup', function() end) it("handles :packadd during startup", function() - -- control group: opt/bonus is not availabe by default + -- control group: opt/bonus is not available by default pack_clear [[ try let g:x = bonus#secret() @@ -398,7 +423,8 @@ describe('startup', function() eq({'ordinary', 'FANCY', 'mittel', 'FANCY after', 'ordinary after'}, exec_lua [[ return _G.test_loadorder ]]) local rtp = meths.get_option'rtp' - ok(startswith(rtp, 'test/functional/fixtures/nvim,test/functional/fixtures/pack/*/start/*,test/functional/fixtures/start/*,test/functional/fixtures,test/functional/fixtures/middle,'), 'rtp='..rtp) + ok(startswith(rtp, 'test/functional/fixtures/nvim,test/functional/fixtures/pack/*/start/*,test/functional/fixtures/start/*,test/functional/fixtures,test/functional/fixtures/middle,'), + 'startswith(…)', 'rtp='..rtp) end) it("handles the correct order with opt packages and after/", function() @@ -492,22 +518,24 @@ describe('sysinit', function() it('fixed hang issue with -D (#12647)', function() local screen - screen = Screen.new(60, 6) + screen = Screen.new(60, 7) screen:attach() command([[let g:id = termopen('"]]..nvim_prog.. [[" -u NONE -i NONE --cmd "set noruler" -D')]]) screen:expect([[ ^ | Entering Debug mode. Type "cont" to continue. | - cmd: augroup nvim_terminal | + nvim_exec() | + cmd: aunmenu * | > | - <" -u NONE -i NONE --cmd "set noruler" -D 1,0-1 All| + <" -u NONE -i NONE --cmd "set noruler" -D 1,1 All| | ]]) command([[call chansend(g:id, "cont\n")]]) screen:expect([[ ^ | ~ | + ~ | [No Name] | | <" -u NONE -i NONE --cmd "set noruler" -D 1,0-1 All| @@ -516,13 +544,6 @@ describe('sysinit', function() end) end) -describe('clean', function() - clear() - ok(string.find(meths.get_option('runtimepath'), funcs.stdpath('config'), 1, true) ~= nil) - clear('--clean') - ok(string.find(meths.get_option('runtimepath'), funcs.stdpath('config'), 1, true) == nil) -end) - describe('user config init', function() local xhome = 'Xhome' local pathsep = helpers.get_pathsep() @@ -577,7 +598,7 @@ describe('user config init', function() it('loads default lua config, but shows an error', function() clear{ args_rm={'-u'}, env=xenv } - feed('<cr>') -- confirm "Conflicting config ..." message + feed('<cr><c-c>') -- Dismiss "Conflicting config …" message. eq(1, eval('g:lua_rc')) matches('^E5422: Conflicting configs', meths.exec('messages', true)) end) @@ -629,13 +650,13 @@ describe('runtime:', function() eq(2, eval('g:lua_plugin')) -- Check if plugin_file_path is listed in :scriptname local scripts = meths.exec(':scriptnames', true) - assert.Truthy(scripts:find(plugin_file_path)) + assert(scripts:find(plugin_file_path)) -- Check if plugin_file_path is listed in startup profile local profile_reader = io.open(profiler_file, 'r') local profile_log = profile_reader:read('*a') profile_reader:close() - assert.Truthy(profile_log :find(plugin_file_path)) + assert(profile_log:find(plugin_file_path)) os.remove(profiler_file) rmdir(plugin_path) diff --git a/test/functional/editor/K_spec.lua b/test/functional/editor/K_spec.lua index 40f36491e4..8ad81ac3d6 100644 --- a/test/functional/editor/K_spec.lua +++ b/test/functional/editor/K_spec.lua @@ -33,6 +33,29 @@ describe('K', function() feed('i'..test_file..'<ESC>K') retry(nil, nil, function() eq(1, eval('filereadable("'..test_file..'")')) end) eq({'fnord'}, eval("readfile('"..test_file.."')")) + -- Confirm that Neovim is still in terminal mode after K is pressed (#16692). + helpers.sleep(500) + eq('t', eval('mode()')) + feed('<space>') -- Any key, not just <space>, can be used here to escape. + eq('n', eval('mode()')) + end) + + it("<esc> kills the buffer for a running 'keywordprg' command", function() + helpers.source('set keywordprg=less') + eval('writefile(["hello", "world"], "' .. test_file .. '")') + feed('i' .. test_file .. '<esc>K') + eq('t', eval('mode()')) + -- Confirm that an arbitrary keypress doesn't escape (i.e., the process is + -- still running). If the process were no longer running, an arbitrary + -- keypress would escape. + helpers.sleep(500) + feed('<space>') + eq('t', eval('mode()')) + -- Confirm that <esc> kills the buffer for the running command. + local bufnr = eval('bufnr()') + feed('<esc>') + eq('n', eval('mode()')) + helpers.neq(bufnr, eval('bufnr()')) end) end) diff --git a/test/functional/editor/completion_spec.lua b/test/functional/editor/completion_spec.lua index befad29922..e27da0947f 100644 --- a/test/functional/editor/completion_spec.lua +++ b/test/functional/editor/completion_spec.lua @@ -1193,4 +1193,64 @@ describe('completion', function() eq('foobar', eval('g:word')) feed('<esc>') end) + + it('is stopped by :stopinsert from timer #12976', function() + screen:try_resize(32,14) + command([[call setline(1, ['hello', 'hullo', 'heeee', ''])]]) + feed('Gah<c-x><c-n>') + screen:expect([[ + hello | + hullo | + heeee | + hello^ | + {2:hello }{0: }| + {1:hullo }{0: }| + {1:heeee }{0: }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {3:-- }{4:match 1 of 3} | + ]]) + command([[call timer_start(100, { -> execute('stopinsert') })]]) + helpers.sleep(200) + feed('k') -- cursor should move up in Normal mode + screen:expect([[ + hello | + hullo | + heee^e | + hello | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]) + end) + + it('does not crash if text is changed by first call to complete function #17489', function() + source([[ + func Complete(findstart, base) abort + if a:findstart + let col = col('.') + call complete_add('#') + return col - 1 + else + return [] + endif + endfunc + + set completeopt=longest + set completefunc=Complete + ]]) + feed('ifoo#<C-X><C-U>') + assert_alive() + end) end) diff --git a/test/functional/ex_cmds/ctrl_c_spec.lua b/test/functional/editor/ctrl_c_spec.lua index f65d9f0d01..60131bf2a4 100644 --- a/test/functional/ex_cmds/ctrl_c_spec.lua +++ b/test/functional/editor/ctrl_c_spec.lua @@ -2,16 +2,20 @@ local helpers = require('test.functional.helpers')(after_each) local Screen = require('test.functional.ui.screen') local clear, feed, source = helpers.clear, helpers.feed, helpers.source local command = helpers.command +local sleep = helpers.sleep describe("CTRL-C (mapped)", function() + local screen + before_each(function() clear() + screen = Screen.new(52, 6) + screen:attach() end) it("interrupts :global", function() -- Crashes luajit. - if helpers.skip_fragile(pending, - helpers.isCI('travis') or helpers.isCI('appveyor')) then + if helpers.skip_fragile(pending) then return end @@ -21,14 +25,6 @@ describe("CTRL-C (mapped)", function() ]]) command("silent edit! test/functional/fixtures/bigfile.txt") - local screen = Screen.new(52, 6) - screen:attach() - screen:set_default_attr_ids({ - [0] = {foreground = Screen.colors.White, - background = Screen.colors.Red}, - [1] = {bold = true, - foreground = Screen.colors.SeaGreen} - }) screen:expect([[ ^0000;<control>;Cc;0;BN;;;;;N;NULL;;;; | @@ -57,4 +53,42 @@ describe("CTRL-C (mapped)", function() end end end) + + it('interrupts :sleep', function() + command('nnoremap <C-C> <Nop>') + feed(':sleep 100<CR>') + -- wait for :sleep to start + sleep(10) + feed('foo<C-C>') + -- wait for input buffer to be flushed + sleep(10) + feed('i') + screen:expect([[ + ^ | + ~ | + ~ | + ~ | + ~ | + -- INSERT -- | + ]]) + end) + + it('interrupts recursive mapping', function() + command('nnoremap <C-C> <Nop>') + command('nmap <F2> <Ignore><F2>') + feed('<F2>') + sleep(10) + feed('foo<C-C>') + -- wait for input buffer to be flushed + sleep(10) + feed('i') + screen:expect([[ + ^ | + ~ | + ~ | + ~ | + ~ | + -- INSERT -- | + ]]) + end) end) diff --git a/test/functional/editor/jump_spec.lua b/test/functional/editor/jump_spec.lua index d09c20f226..63f522fe6e 100644 --- a/test/functional/editor/jump_spec.lua +++ b/test/functional/editor/jump_spec.lua @@ -1,4 +1,5 @@ local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') local clear = helpers.clear local command = helpers.command @@ -7,6 +8,7 @@ local funcs = helpers.funcs local feed = helpers.feed local exec_capture = helpers.exec_capture local write_file = helpers.write_file +local curbufmeths = helpers.curbufmeths describe('jumplist', function() local fname1 = 'Xtest-functional-normal-jump' @@ -137,3 +139,144 @@ describe("jumpoptions=stack behaves like 'tagstack'", function() exec_capture('jumps')) end) end) + +describe("jumpoptions=view", function() + local file1 = 'Xtestfile-functional-editor-jumps' + local file2 = 'Xtestfile-functional-editor-jumps-2' + local function content() + local c = {} + for i=1,30 do + c[i] = i .. " line" + end + return table.concat(c, "\n") + end + before_each(function() + clear() + write_file(file1, content(), false, false) + write_file(file2, content(), false, false) + command('set jumpoptions=view') + end) + after_each(function() + os.remove(file1) + os.remove(file2) + end) + + it('restores the view', function() + local screen = Screen.new(5, 8) + screen:attach() + command("edit " .. file1) + feed("12Gztj") + feed("gg<C-o>") + screen:expect([[ + 12 line | + ^13 line | + 14 line | + 15 line | + 16 line | + 17 line | + 18 line | + | + ]]) + end) + + it('restores the view across files', function() + local screen = Screen.new(5, 5) + screen:attach() + command("args " .. file1 .. " " .. file2) + feed("12Gzt") + command("next") + feed("G") + screen:expect([[ + 27 line | + 28 line | + 29 line | + ^30 line | + | + ]]) + feed("<C-o><C-o>") + screen:expect([[ + ^12 line | + 13 line | + 14 line | + 15 line | + | + ]]) + end) + + it('restores the view across files with <C-^>', function() + local screen = Screen.new(5, 5) + screen:attach() + command("args " .. file1 .. " " .. file2) + feed("12Gzt") + command("next") + feed("G") + screen:expect([[ + 27 line | + 28 line | + 29 line | + ^30 line | + | + ]]) + feed("<C-^>") + screen:expect([[ + ^12 line | + 13 line | + 14 line | + 15 line | + | + ]]) + end) + + it('falls back to standard behavior when view can\'t be recovered', function() + local screen = Screen.new(5, 8) + screen:attach() + command("edit " .. file1) + feed("7GzbG") + curbufmeths.set_lines(0, 2, true, {}) + -- Move to line 7, and set it as the last line visible on the view with zb, meaning to recover + -- the view it needs to put the cursor 7 lines from the top line. Then go to the end of the + -- file, delete 2 lines before line 7, meaning the jump/mark is moved 2 lines up to line 5. + -- Therefore when trying to jump back to it it's not possible to set a 7 line offset from the + -- mark position to the top line, since there's only 5 lines from the mark position to line 0. + -- Therefore falls back to standard behavior which is centering the view/line. + feed("<C-o>") + screen:expect([[ + 4 line | + 5 line | + 6 line | + ^7 line | + 8 line | + 9 line | + 10 line | + | + ]]) + end) + + it('falls back to standard behavior for a mark without a view', function() + local screen = Screen.new(5, 8) + screen:attach() + command('edit ' .. file1) + feed('10ggzzvwy') + screen:expect([[ + 7 line | + 8 line | + 9 line | + ^10 line | + 11 line | + 12 line | + 13 line | + | + ]]) + feed('`]') + screen:expect([[ + 7 line | + 8 line | + 9 line | + 10 ^line | + 11 line | + 12 line | + 13 line | + | + ]]) + end) +end) diff --git a/test/functional/editor/langmap_spec.lua b/test/functional/editor/langmap_spec.lua index e4349a22e7..b1070ecddc 100644 --- a/test/functional/editor/langmap_spec.lua +++ b/test/functional/editor/langmap_spec.lua @@ -30,7 +30,7 @@ describe("'langmap'", function() command('nmapclear') end) it("'langnoremap' is by default ON", function() - eq(eval('&langnoremap'), 1) + eq(1, eval('&langnoremap')) end) it("Results of maps are not converted when 'langnoremap' ON.", function() @@ -71,19 +71,19 @@ describe("'langmap'", function() feed('<C-w>s') local origwin = curwin() feed('<C-w>i') - neq(curwin(), origwin) + neq(origwin, curwin()) -- Works when setting a mark feed('yy3p3gg0mwgg0mi') - eq(call('getpos', "'i"), {0, 3, 1, 0}) - eq(call('getpos', "'w"), {0, 1, 1, 0}) + eq({0, 3, 1, 0}, call('getpos', "'i")) + eq({0, 1, 1, 0}, call('getpos', "'w")) feed('3dd') -- Works when moving to a mark feed("'i") - eq(call('getpos', '.'), {0, 1, 1, 0}) + eq({0, 1, 1, 0}, call('getpos', '.')) -- Works when selecting a register feed('qillqqwhhq') - eq(eval('@i'), 'hh') - eq(eval('@w'), 'll') + eq('hh', eval('@i')) + eq('ll', eval('@w')) feed('a<C-r>i<esc>') expect('illii www') feed('"ip') @@ -107,7 +107,7 @@ describe("'langmap'", function() expect('wwi www') feed('u@a') expect('wwi www') - eq(eval('@a'), ':s/i/w/gc\ryyn') + eq(':s/i/w/gc\ryyn', eval('@a')) end) it('insert-mode CTRL-G', function() command('set langmap=jk,kj') @@ -127,7 +127,7 @@ describe("'langmap'", function() helhellolo helxlo hello]]) - eq(eval('@a'), 'gg3|ahellojx') + eq('gg3|ahellojx', eval('@a')) end) it('command-line CTRL-\\', function() command('set langmap=en,ne') @@ -145,8 +145,8 @@ describe("'langmap'", function() set langmap=ij,ji ]]) feed(':let <C-R>i=1<CR>') - eq(eval('i_value'), 1) - eq(eval('j_value'), 0) + eq(1, eval('i_value')) + eq(0, eval('j_value')) end) -- it('-- More -- prompt', function() -- -- The 'b' 'j' 'd' 'f' commands at the -- More -- prompt @@ -186,17 +186,17 @@ describe("'langmap'", function() nnoremap x :call Map()<CR> ]]) feed('x1<CR>') - eq(eval('gotten_one'), 1) + eq(1, eval('gotten_one')) command('let g:gotten_one = 0') feed_command('call Map()') feed('1<CR>') - eq(eval('gotten_one'), 1) + eq(1, eval('gotten_one')) end) end) it('conversions are not applied during setreg()', function() call('setreg', 'i', 'ww') - eq(eval('@i'), 'ww') + eq('ww', eval('@i')) end) it('conversions not applied in insert mode', function() feed('aiiiwww') @@ -213,11 +213,11 @@ describe("'langmap'", function() iii]]) end) - local function testrecording(command_string, expect_string, setup_function) + local function testrecording(command_string, expect_string, setup_function, expect_macro) if setup_function then setup_function() end feed('qa' .. command_string .. 'q') expect(expect_string) - eq(helpers.funcs.nvim_replace_termcodes(command_string, true, true, true), + eq(expect_macro or helpers.funcs.nvim_replace_termcodes(command_string, true, true, true), eval('@a')) if setup_function then setup_function() end -- n.b. may need nvim_replace_termcodes() here. @@ -273,8 +273,8 @@ describe("'langmap'", function() it('treats control modified keys as characters', function() command('nnoremap <C-w> iw<esc>') command('nnoremap <C-i> ii<esc>') - testrecording('<C-w>', 'whello', local_setup) - testrecording('<C-i>', 'ihello', local_setup) + testrecording('<C-w>', 'whello', local_setup, eval([["\<*C-w>"]])) + testrecording('<C-i>', 'ihello', local_setup, eval([["\<*C-i>"]])) end) end) diff --git a/test/functional/editor/macro_spec.lua b/test/functional/editor/macro_spec.lua index c0c9256af2..53be7dcc62 100644 --- a/test/functional/editor/macro_spec.lua +++ b/test/functional/editor/macro_spec.lua @@ -6,28 +6,31 @@ local feed = helpers.feed local clear = helpers.clear local expect = helpers.expect local command = helpers.command +local funcs = helpers.funcs +local meths = helpers.meths local insert = helpers.insert local curbufmeths = helpers.curbufmeths +before_each(clear) + describe('macros', function() - before_each(clear) it('can be recorded and replayed', function() feed('qiahello<esc>q') expect('hello') - eq(eval('@i'), 'ahello') + eq('ahello', eval('@i')) feed('@i') expect('hellohello') - eq(eval('@i'), 'ahello') + eq('ahello', eval('@i')) end) it('applies maps', function() command('imap x l') command('nmap l a') feed('qilxxx<esc>q') expect('lll') - eq(eval('@i'), 'lxxx') + eq('lxxx', eval('@i')) feed('@i') expect('llllll') - eq(eval('@i'), 'lxxx') + eq('lxxx', eval('@i')) end) it('can be replayed with Q', function() @@ -47,9 +50,47 @@ hello]] end) end) -describe('reg_recorded()', function() - before_each(clear) +describe('immediately after a macro has finished executing,', function() + before_each(function() + command([[let @a = 'gg0']]) + end) + + describe('reg_executing() from RPC returns an empty string', function() + it('if the macro does not end with a <Nop> mapping', function() + feed('@a') + eq('', funcs.reg_executing()) + end) + + it('if the macro ends with a <Nop> mapping', function() + command('nnoremap 0 <Nop>') + feed('@a') + eq('', funcs.reg_executing()) + end) + end) + describe('characters from a mapping are not treated as a part of the macro #18015', function() + before_each(function() + command('nnoremap s qa') + end) + + it('if the macro does not end with a <Nop> mapping', function() + feed('@asq') -- "q" from "s" mapping should start recording a macro instead of being no-op + eq({mode = 'n', blocking = false}, meths.get_mode()) + expect('') + eq('', eval('@a')) + end) + + it('if the macro ends with a <Nop> mapping', function() + command('nnoremap 0 <Nop>') + feed('@asq') -- "q" from "s" mapping should start recording a macro instead of being no-op + eq({mode = 'n', blocking = false}, meths.get_mode()) + expect('') + eq('', eval('@a')) + end) + end) +end) + +describe('reg_recorded()', function() it('returns the correct value', function() feed [[qqyyq]] eq('q', eval('reg_recorded()')) diff --git a/test/functional/editor/mark_spec.lua b/test/functional/editor/mark_spec.lua new file mode 100644 index 0000000000..1eb76aa628 --- /dev/null +++ b/test/functional/editor/mark_spec.lua @@ -0,0 +1,401 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') +local meths = helpers.meths +local curbufmeths = helpers.curbufmeths +local clear = helpers.clear +local command = helpers.command +local funcs = helpers.funcs +local eq = helpers.eq +local feed = helpers.feed +local write_file = helpers.write_file +local pcall_err = helpers.pcall_err +local cursor = function() return helpers.meths.win_get_cursor(0) end + +describe('named marks', function() + local file1 = 'Xtestfile-functional-editor-marks' + local file2 = 'Xtestfile-functional-editor-marks-2' + before_each(function() + clear() + write_file(file1, '1test1\n1test2\n1test3\n1test4', false, false) + write_file(file2, '2test1\n2test2\n2test3\n2test4', false, false) + end) + after_each(function() + os.remove(file1) + os.remove(file2) + end) + + + it("can be set", function() + command("edit " .. file1) + command("mark a") + eq({1, 0}, curbufmeths.get_mark("a")) + feed("jmb") + eq({2, 0}, curbufmeths.get_mark("b")) + feed("jmB") + eq({3, 0}, curbufmeths.get_mark("B")) + command("4kc") + eq({4, 0}, curbufmeths.get_mark("c")) + end) + + it("errors when set out of range with :mark", function() + command("edit " .. file1) + local err = pcall_err(helpers.exec_capture, "1000mark x") + eq("Vim(mark):E16: Invalid range: 1000mark x", err) + end) + + it("errors when set out of range with :k", function() + command("edit " .. file1) + local err = pcall_err(helpers.exec_capture, "1000kx") + eq("Vim(k):E16: Invalid range: 1000kx", err) + end) + + it("errors on unknown mark name with :mark", function() + command("edit " .. file1) + local err = pcall_err(helpers.exec_capture, "mark #") + eq("Vim(mark):E191: Argument must be a letter or forward/backward quote", err) + end) + + it("errors on unknown mark name with '", function() + command("edit " .. file1) + local err = pcall_err(helpers.exec_capture, "normal! '#") + eq("Vim(normal):E78: Unknown mark", err) + end) + + it("errors on unknown mark name with `", function() + command("edit " .. file1) + local err = pcall_err(helpers.exec_capture, "normal! `#") + eq("Vim(normal):E78: Unknown mark", err) + end) + + it("errors when moving to a mark that is not set with '", function() + command("edit " .. file1) + local err = pcall_err(helpers.exec_capture, "normal! 'z") + eq("Vim(normal):E20: Mark not set", err) + err = pcall_err(helpers.exec_capture, "normal! '.") + eq("Vim(normal):E20: Mark not set", err) + end) + + it("errors when moving to a mark that is not set with `", function() + command("edit " .. file1) + local err = pcall_err(helpers.exec_capture, "normal! `z") + eq("Vim(normal):E20: Mark not set", err) + err = pcall_err(helpers.exec_capture, "normal! `>") + eq("Vim(normal):E20: Mark not set", err) + end) + + it("errors when moving to a global mark that is not set with '", function() + command("edit " .. file1) + local err = pcall_err(helpers.exec_capture, "normal! 'Z") + eq("Vim(normal):E20: Mark not set", err) + end) + + it("errors when moving to a global mark that is not set with `", function() + command("edit " .. file1) + local err = pcall_err(helpers.exec_capture, "normal! `Z") + eq("Vim(normal):E20: Mark not set", err) + end) + + it("can move to them using '", function() + command("args " .. file1 .. " " .. file2) + feed("j") + feed("ma") + feed("G'a") + eq({2, 0}, cursor()) + feed("mA") + command("next") + feed("'A") + eq(1, meths.get_current_buf().id) + eq({2, 0}, cursor()) + end) + + it("can move to them using `", function() + command("args " .. file1 .. " " .. file2) + feed("jll") + feed("ma") + feed("G`a") + eq({2, 2}, cursor()) + feed("mA") + command("next") + feed("`A") + eq(1, meths.get_current_buf().id) + eq({2, 2}, cursor()) + end) + + it("can move to them using g'", function() + command("args " .. file1 .. " " .. file2) + feed("jll") + feed("ma") + feed("Gg'a") + eq({2, 0}, cursor()) + feed("mA") + command("next") + feed("g'A") + eq(1, meths.get_current_buf().id) + eq({2, 0}, cursor()) + end) + + it("can move to them using g`", function() + command("args " .. file1 .. " " .. file2) + feed("jll") + feed("ma") + feed("Gg`a") + eq({2, 2}, cursor()) + feed("mA") + command("next") + feed("g`A") + eq(1, meths.get_current_buf().id) + eq({2, 2}, cursor()) + end) + + it("errors when it can't find the buffer", function() + command("args " .. file1 .. " " .. file2) + feed("mA") + command("next") + command("bw! " .. file1 ) + local err = pcall_err(helpers.exec_capture, "normal! 'A") + eq("Vim(normal):E92: Buffer 1 not found", err) + os.remove(file1) + end) + + it("leave a context mark when moving with '", function() + command("edit " .. file1) + feed("llmamA") + feed("10j0") -- first col, last line + local pos = cursor() + feed("'a") + feed("<C-o>") + eq(pos, cursor()) + feed("'A") + feed("<C-o>") + eq(pos, cursor()) + end) + + it("leave a context mark when moving with `", function() + command("edit " .. file1) + feed("llmamA") + feed("10j0") -- first col, last line + local pos = cursor() + feed("`a") + feed("<C-o>") + eq(pos, cursor()) + feed("`A") + feed("<C-o>") + eq(pos, cursor()) + end) + + it("leave a context mark when the mark changes buffer with g'", function() + command("args " .. file1 .. " " .. file2) + local pos + feed("GmA") + command("next") + pos = cursor() + command("clearjumps") + feed("g'A") -- since the mark is in another buffer, it leaves a context mark + feed("<C-o>") + eq(pos, cursor()) + end) + + it("leave a context mark when the mark changes buffer with g`", function() + command("args " .. file1 .. " " .. file2) + local pos + feed("GmA") + command("next") + pos = cursor() + command("clearjumps") + feed("g`A") -- since the mark is in another buffer, it leaves a context mark + feed("<C-o>") + eq(pos, cursor()) + end) + + it("do not leave a context mark when moving with g'", function() + command("edit " .. file1) + local pos + feed("ma") + pos = cursor() -- Mark pos + feed("10j0") -- first col, last line + feed("g'a") + feed("<C-o>") -- should do nothing + eq(pos, cursor()) + feed("mA") + pos = cursor() -- Mark pos + feed("10j0") -- first col, last line + feed("g'a") + feed("<C-o>") -- should do nothing + eq(pos, cursor()) + end) + + it("do not leave a context mark when moving with g`", function() + command("edit " .. file1) + local pos + feed("ma") + pos = cursor() -- Mark pos + feed("10j0") -- first col, last line + feed("g`a") + feed("<C-o>") -- should do nothing + eq(pos, cursor()) + feed("mA") + pos = cursor() -- Mark pos + feed("10j0") -- first col, last line + feed("g'a") + feed("<C-o>") -- should do nothing + eq(pos, cursor()) + end) + + it("open folds when moving to them", function() + command("edit " .. file1) + feed("jzfG") -- Fold from the second line to the end + command("3mark a") + feed("G") -- On top of the fold + assert(funcs.foldclosed('.') ~= -1) -- folded + feed("'a") + eq(-1, funcs.foldclosed('.')) + + feed("zc") + assert(funcs.foldclosed('.') ~= -1) -- folded + -- TODO: remove this workaround after fixing #15873 + feed("k`a") + eq(-1, funcs.foldclosed('.')) + + feed("zc") + assert(funcs.foldclosed('.') ~= -1) -- folded + feed("kg'a") + eq(-1, funcs.foldclosed('.')) + + feed("zc") + assert(funcs.foldclosed('.') ~= -1) -- folded + feed("kg`a") + eq(-1, funcs.foldclosed('.')) + end) + + it("do not open folds when moving to them doesn't move the cursor", function() + command("edit " .. file1) + feed("jzfG") -- Fold from the second line to the end + assert(funcs.foldclosed('.') == 2) -- folded + feed("ma") + feed("'a") + feed("`a") + feed("g'a") + feed("g`a") + -- should still be folded + eq(2, funcs.foldclosed('.')) + end) + + it("getting '{ '} '( ') does not move cursor", function() + meths.buf_set_lines(0, 0, 0, true, {'aaaaa', 'bbbbb', 'ccccc', 'ddddd', 'eeeee'}) + meths.win_set_cursor(0, {2, 0}) + funcs.getpos("'{") + eq({2, 0}, meths.win_get_cursor(0)) + funcs.getpos("'}") + eq({2, 0}, meths.win_get_cursor(0)) + funcs.getpos("'(") + eq({2, 0}, meths.win_get_cursor(0)) + funcs.getpos("')") + eq({2, 0}, meths.win_get_cursor(0)) + end) + + it('in command range does not move cursor #19248', function() + meths.create_user_command('Test', ':', {range = true}) + meths.buf_set_lines(0, 0, 0, true, {'aaaaa', 'bbbbb', 'ccccc', 'ddddd', 'eeeee'}) + meths.win_set_cursor(0, {2, 0}) + command([['{,'}Test]]) + eq({2, 0}, meths.win_get_cursor(0)) + end) +end) + +describe('named marks view', function() + local file1 = 'Xtestfile-functional-editor-marks' + local file2 = 'Xtestfile-functional-editor-marks-2' + local function content() + local c = {} + for i=1,30 do + c[i] = i .. " line" + end + return table.concat(c, "\n") + end + before_each(function() + clear() + write_file(file1, content(), false, false) + write_file(file2, content(), false, false) + command("set jumpoptions+=view") + end) + after_each(function() + os.remove(file1) + os.remove(file2) + end) + + it('is restored', function() + local screen = Screen.new(5, 8) + screen:attach() + command("edit " .. file1) + feed("<C-e>jWma") + feed("G'a") + local expected = [[ + 2 line | + ^3 line | + 4 line | + 5 line | + 6 line | + 7 line | + 8 line | + | + ]] + screen:expect({grid=expected}) + feed("G`a") + screen:expect([[ + 2 line | + 3 ^line | + 4 line | + 5 line | + 6 line | + 7 line | + 8 line | + | + ]]) + end) + + it('is restored across files', function() + local screen = Screen.new(5, 5) + screen:attach() + command("args " .. file1 .. " " .. file2) + feed("<C-e>mA") + local mark_view = [[ + ^2 line | + 3 line | + 4 line | + 5 line | + | + ]] + screen:expect(mark_view) + command("next") + screen:expect([[ + ^1 line | + 2 line | + 3 line | + 4 line | + | + ]]) + feed("'A") + screen:expect(mark_view) + end) + + it('fallback to standard behavior when view can\'t be recovered', function() + local screen = Screen.new(10, 10) + screen:attach() + command("edit " .. file1) + feed("7GzbmaG") -- Seven lines from the top + command("new") -- Screen size for window is now half the height can't be restored + feed("<C-w>p'a") + screen:expect([[ + | + ~ | + ~ | + ~ | + [No Name] | + 6 line | + ^7 line | + 8 line | + {MATCH:.*} | + | + ]]) + end) +end) diff --git a/test/functional/editor/meta_key_spec.lua b/test/functional/editor/meta_key_spec.lua index 2280f5bb24..23964ca10f 100644 --- a/test/functional/editor/meta_key_spec.lua +++ b/test/functional/editor/meta_key_spec.lua @@ -27,6 +27,14 @@ describe('meta-keys #8226 #13042', function() command('nnoremap <A-j> Aalt-j<Esc>') feed('<A-j><M-l>') expect('llo<ESC>;<ESC>;alt-jmeta-l') + -- Unmapped ALT-chord with characters containing K_SPECIAL bytes + command('nnoremap … A…<Esc>') + feed('<A-…><M-…>') + expect('llo<ESC>;<ESC>;alt-jmeta-l<ESC>…<ESC>…') + command("execute 'nnoremap' nr2char(0x40000000) 'AMAX<Esc>'") + command("call nvim_input('<A-'.nr2char(0x40000000).'>')") + command("call nvim_input('<M-'.nr2char(0x40000000).'>')") + expect('llo<ESC>;<ESC>;alt-jmeta-l<ESC>…<ESC>…<ESC>MAX<ESC>MAX') end) it('ALT/META, visual-mode', function() @@ -36,13 +44,20 @@ describe('meta-keys #8226 #13042', function() expect('peach') -- Unmapped ALT-chord resolves isolated (non-ALT) ESC mapping. #13086 #15869 command('vnoremap <ESC> A<lt>ESC>') - feed('viw<A-;><ESC>viw<M-;><ESC>') + feed('viw<A-;><Esc>viw<M-;><Esc>') expect('peach<ESC>;<ESC>;') -- Mapped ALT-chord behaves as mapped. command('vnoremap <M-l> Ameta-l<Esc>') command('vnoremap <A-j> Aalt-j<Esc>') feed('viw<A-j>viw<M-l>') expect('peach<ESC>;<ESC>;alt-jmeta-l') + -- Unmapped ALT-chord with characters containing K_SPECIAL bytes + feed('viw<A-…><Esc>viw<M-…><Esc>') + expect('peach<ESC>;<ESC>;alt-jmeta-l<ESC>…<ESC>…') + command("execute 'inoremap' nr2char(0x40000000) 'MAX'") + command("call nvim_input('viw<A-'.nr2char(0x40000000).'><Esc>')") + command("call nvim_input('viw<M-'.nr2char(0x40000000).'><Esc>')") + expect('peach<ESC>;<ESC>;alt-jmeta-l<ESC>…<ESC>…<ESC>MAX<ESC>MAX') end) it('ALT/META insert-mode', function() @@ -89,4 +104,41 @@ describe('meta-keys #8226 #13042', function() eq({ 0, 2, 1, 0, }, funcs.getpos('.')) eq('nt', eval('mode(1)')) end) + + it('ALT/META when recording a macro #13235', function() + command('inoremap <M-Esc> <lt>M-ESC>') + feed('ifoo<CR>bar<CR>baz<Esc>gg0') + -- <M-"> is reinterpreted as <Esc>" + feed('qrviw"ayC// This is some text: <M-">apq') + expect([[ + // This is some text: foo + bar + baz]]) + -- Should not insert an extra double quote or trigger <M-Esc> when replaying + feed('j0@rj0@@') + expect([[ + // This is some text: foo + // This is some text: bar + // This is some text: baz]]) + command('%delete') + end) + + it('ALT/META with special key when recording a macro', function() + command('inoremap <M-Esc> <lt>M-ESC>') + command('noremap <S-Tab> "') + command('noremap! <S-Tab> "') + feed('ifoo<CR>bar<CR>baz<Esc>gg0') + -- <M-S-Tab> is reinterpreted as <Esc><S-Tab> + feed('qrviw<S-Tab>ayC// This is some text: <M-S-Tab>apq') + expect([[ + // This is some text: foo + bar + baz]]) + -- Should not insert an extra double quote or trigger <M-Esc> when replaying + feed('j0@rj0@@') + expect([[ + // This is some text: foo + // This is some text: bar + // This is some text: baz]]) + end) end) diff --git a/test/functional/editor/mode_cmdline_spec.lua b/test/functional/editor/mode_cmdline_spec.lua index 0f7d821bb5..50cc5e17ee 100644 --- a/test/functional/editor/mode_cmdline_spec.lua +++ b/test/functional/editor/mode_cmdline_spec.lua @@ -3,67 +3,74 @@ local helpers = require('test.functional.helpers')(after_each) local clear, insert, funcs, eq, feed = helpers.clear, helpers.insert, helpers.funcs, helpers.eq, helpers.feed +local eval = helpers.eval local meths = helpers.meths -describe('cmdline CTRL-R', function() +describe('cmdline', function() before_each(clear) - it('pasting non-special register inserts <CR> *between* lines', function() - insert([[ - line1abc - line2somemoretext - ]]) - -- Yank 2 lines linewise, then paste to cmdline. - feed([[<C-\><C-N>gg0yj:<C-R>0]]) - -- <CR> inserted between lines, NOT after the final line. - eq('line1abc\rline2somemoretext', funcs.getcmdline()) + describe('Ctrl-R', function() + it('pasting non-special register inserts <CR> *between* lines', function() + insert([[ + line1abc + line2somemoretext + ]]) + -- Yank 2 lines linewise, then paste to cmdline. + feed([[<C-\><C-N>gg0yj:<C-R>0]]) + -- <CR> inserted between lines, NOT after the final line. + eq('line1abc\rline2somemoretext', funcs.getcmdline()) - -- Yank 2 lines charwise, then paste to cmdline. - feed([[<C-\><C-N>gg05lyvj:<C-R>0]]) - -- <CR> inserted between lines, NOT after the final line. - eq('abc\rline2', funcs.getcmdline()) + -- Yank 2 lines charwise, then paste to cmdline. + feed([[<C-\><C-N>gg05lyvj:<C-R>0]]) + -- <CR> inserted between lines, NOT after the final line. + eq('abc\rline2', funcs.getcmdline()) - -- Yank 1 line linewise, then paste to cmdline. - feed([[<C-\><C-N>ggyy:<C-R>0]]) - -- No <CR> inserted. - eq('line1abc', funcs.getcmdline()) - end) + -- Yank 1 line linewise, then paste to cmdline. + feed([[<C-\><C-N>ggyy:<C-R>0]]) + -- No <CR> inserted. + eq('line1abc', funcs.getcmdline()) + end) - it('pasting special register inserts <CR>, <NL>', function() - feed([[:<C-R>="foo\nbar\rbaz"<CR>]]) - eq('foo\nbar\rbaz', funcs.getcmdline()) + it('pasting special register inserts <CR>, <NL>', function() + feed([[:<C-R>="foo\nbar\rbaz"<CR>]]) + eq('foo\nbar\rbaz', funcs.getcmdline()) + end) end) -end) -describe('cmdline history', function() - before_each(clear) + it('Ctrl-Shift-V supports entering unsimplified key notations', function() + feed(':"<C-S-V><C-J><C-S-V><C-@><C-S-V><C-[><C-S-V><C-S-M><C-S-V><M-C-I><C-S-V><C-D-J><CR>') - it('correctly clears start of the history', function() - -- Regression test: check absence of the memory leak when clearing start of - -- the history using ex_getln.c/clr_history(). - eq(1, funcs.histadd(':', 'foo')) - eq(1, funcs.histdel(':')) - eq('', funcs.histget(':', -1)) + eq('"<C-J><C-@><C-[><C-S-M><M-C-I><C-D-J>', eval('@:')) end) - it('correctly clears end of the history', function() - -- Regression test: check absence of the memory leak when clearing end of - -- the history using ex_getln.c/clr_history(). - meths.set_option('history', 1) - eq(1, funcs.histadd(':', 'foo')) - eq(1, funcs.histdel(':')) - eq('', funcs.histget(':', -1)) - end) + describe('history', function() + it('correctly clears start of the history', function() + -- Regression test: check absence of the memory leak when clearing start of + -- the history using ex_getln.c/clr_history(). + eq(1, funcs.histadd(':', 'foo')) + eq(1, funcs.histdel(':')) + eq('', funcs.histget(':', -1)) + end) + + it('correctly clears end of the history', function() + -- Regression test: check absence of the memory leak when clearing end of + -- the history using ex_getln.c/clr_history(). + meths.set_option('history', 1) + eq(1, funcs.histadd(':', 'foo')) + eq(1, funcs.histdel(':')) + eq('', funcs.histget(':', -1)) + end) - it('correctly removes item from history', function() - -- Regression test: check that ex_getln.c/del_history_idx() correctly clears - -- history index after removing history entry. If it does not then deleting - -- history will result in a double free. - eq(1, funcs.histadd(':', 'foo')) - eq(1, funcs.histadd(':', 'bar')) - eq(1, funcs.histadd(':', 'baz')) - eq(1, funcs.histdel(':', -2)) - eq(1, funcs.histdel(':')) - eq('', funcs.histget(':', -1)) + it('correctly removes item from history', function() + -- Regression test: check that ex_getln.c/del_history_idx() correctly clears + -- history index after removing history entry. If it does not then deleting + -- history will result in a double free. + eq(1, funcs.histadd(':', 'foo')) + eq(1, funcs.histadd(':', 'bar')) + eq(1, funcs.histadd(':', 'baz')) + eq(1, funcs.histdel(':', -2)) + eq(1, funcs.histdel(':')) + eq('', funcs.histget(':', -1)) + end) end) end) diff --git a/test/functional/editor/mode_insert_spec.lua b/test/functional/editor/mode_insert_spec.lua index f03508035d..e3d3cdbd85 100644 --- a/test/functional/editor/mode_insert_spec.lua +++ b/test/functional/editor/mode_insert_spec.lua @@ -75,4 +75,62 @@ describe('insert-mode', function() expect('hello oooworld') end) end) + + describe('Ctrl-V', function() + it('supports entering the decimal value of a character', function() + feed('i<C-V>076<C-V>167') + expect('L§') + end) + + it('supports entering the octal value of a character with "o"', function() + feed('i<C-V>o114<C-V>o247<Esc>') + expect('L§') + end) + + it('supports entering the octal value of a character with "O"', function() + feed('i<C-V>O114<C-V>O247<Esc>') + expect('L§') + end) + + it('supports entering the hexadecimal value of a character with "x"', function() + feed('i<C-V>x4c<C-V>xA7<Esc>') + expect('L§') + end) + + it('supports entering the hexadecimal value of a character with "X"', function() + feed('i<C-V>X4c<C-V>XA7<Esc>') + expect('L§') + end) + + it('supports entering the hexadecimal value of a character with "u"', function() + feed('i<C-V>u25ba<C-V>u25C7<Esc>') + expect('►◇') + end) + + it('supports entering the hexadecimal value of a character with "U"', function() + feed('i<C-V>U0001f600<C-V>U0001F601<Esc>') + expect('😀😁') + end) + + it('entering character by value is interrupted by invalid character', function() + feed('i<C-V>76c<C-V>76<C-F2><C-V>u3c0j<C-V>u3c0<M-F3><C-V>U1f600j<C-V>U1f600<D-F4><Esc>') + expect('LcL<C-F2>πjπ<M-F3>😀j😀<D-F4>') + end) + + it('shows o, O, u, U, x, X, and digits with modifiers', function() + feed('i<C-V><M-o><C-V><D-o><C-V><M-O><C-V><D-O><Esc>') + expect('<M-o><D-o><M-O><D-O>') + feed('cc<C-V><M-u><C-V><D-u><C-V><M-U><C-V><D-U><Esc>') + expect('<M-u><D-u><M-U><D-U>') + feed('cc<C-V><M-x><C-V><D-x><C-V><M-X><C-V><D-X><Esc>') + expect('<M-x><D-x><M-X><D-X>') + feed('cc<C-V><M-1><C-V><D-2><C-V><M-7><C-V><D-8><Esc>') + expect('<M-1><D-2><M-7><D-8>') + end) + end) + + it('Ctrl-Shift-V supports entering unsimplified key notations', function() + feed('i<C-S-V><C-J><C-S-V><C-@><C-S-V><C-[><C-S-V><C-S-M><C-S-V><M-C-I><C-S-V><C-D-J><Esc>') + expect('<C-J><C-@><C-[><C-S-M><M-C-I><C-D-J>') + end) end) diff --git a/test/functional/editor/put_spec.lua b/test/functional/editor/put_spec.lua index 26967ecbba..cc9fce8f67 100644 --- a/test/functional/editor/put_spec.lua +++ b/test/functional/editor/put_spec.lua @@ -64,7 +64,7 @@ describe('put command', function() -- one place to the right (unless we were at the end of the -- line when we pasted). if not (exception_table.undo_position and after_undo) then - eq(funcs.getcurpos(), init_cursorpos) + eq(init_cursorpos, funcs.getcurpos()) end end @@ -86,7 +86,7 @@ describe('put command', function() -- If we paste the ". register with a count we can't avoid -- changing this register, hence avoid this check. if not test.exception_table.dot_reg_changed then - eq(funcs.getreg('.'), orig_dotstr) + eq(orig_dotstr, funcs.getreg('.')) end -- Doing something, undoing it, and then redoing it should @@ -138,9 +138,9 @@ describe('put command', function() end -- create_test_defs() }}} local function find_cursor_position(expect_string) -- {{{ - -- There must only be one occurance of the character 'x' in + -- There must only be one occurrence of the character 'x' in -- expect_string. - -- This function removes that occurance, and returns the position that + -- This function removes that occurrence, and returns the position that -- it was in. -- This returns the cursor position that would leave the 'x' in that -- place if we feed 'ix<esc>' and the string existed before it. @@ -507,9 +507,11 @@ describe('put command', function() return function(exception_table, after_redo) test_expect(exception_table, after_redo) if selection_string then - eq(getreg('"'), selection_string) + if not conversion_table.put_backwards then + eq(selection_string, getreg('"')) + end else - eq(getreg('"'), 'test_string"') + eq('test_string"', getreg('"')) end end end @@ -714,7 +716,9 @@ describe('put command', function() expect_base, conversion_table) return function(exception_table, after_redo) test_expect(exception_table, after_redo) - eq(getreg('"'), 'Line of words 1\n') + if not conversion_table.put_backwards then + eq('Line of words 1\n', getreg('"')) + end end end local base_expect_string = [[ @@ -748,7 +752,9 @@ describe('put command', function() end, expect_base, conversion_table) return function(e,c) test_expect(e,c) - eq(getreg('"'), 'Lin\nLin') + if not conversion_table.put_backwards then + eq('Lin\nLin', getreg('"')) + end end end @@ -800,9 +806,9 @@ describe('put command', function() feed('u') -- Have to use feed('u') here to set curswant, because -- ex_undo() doesn't do that. - eq(funcs.getcurpos(), {0, 1, 1, 0, 1}) + eq({0, 1, 1, 0, 1}, funcs.getcurpos()) feed('<C-r>') - eq(funcs.getcurpos(), {0, 1, 1, 0, 1}) + eq({0, 1, 1, 0, 1}, funcs.getcurpos()) end end diff --git a/test/functional/editor/tabpage_spec.lua b/test/functional/editor/tabpage_spec.lua index d1d6854b07..3b2c1db350 100644 --- a/test/functional/editor/tabpage_spec.lua +++ b/test/functional/editor/tabpage_spec.lua @@ -3,8 +3,11 @@ local helpers = require('test.functional.helpers')(after_each) local clear = helpers.clear local command = helpers.command local eq = helpers.eq +local neq = helpers.neq local feed = helpers.feed local eval = helpers.eval +local exec = helpers.exec +local funcs = helpers.funcs describe('tabpage', function() before_each(clear) @@ -34,5 +37,28 @@ describe('tabpage', function() eq(3, eval('tabpagenr()')) end) -end) + it('does not crash or loop 999 times if BufWipeout autocommand switches window #17868', function() + exec([[ + tabedit + let s:window_id = win_getid() + botright new + setlocal bufhidden=wipe + let g:win_closed = 0 + autocmd WinClosed * let g:win_closed += 1 + autocmd BufWipeout <buffer> call win_gotoid(s:window_id) + tabprevious + +tabclose + ]]) + neq(999, eval('g:win_closed')) + end) + + it(":tabmove handles modifiers and addr", function() + command('tabnew | tabnew | tabnew') + eq(4, funcs.nvim_tabpage_get_number(0)) + command(' silent :keepalt :: ::: silent! - tabmove') + eq(3, funcs.nvim_tabpage_get_number(0)) + command(' silent :keepalt :: ::: silent! -2 tabmove') + eq(1, funcs.nvim_tabpage_get_number(0)) + end) +end) diff --git a/test/functional/editor/undo_spec.lua b/test/functional/editor/undo_spec.lua index a023ca3d90..a041428cdc 100644 --- a/test/functional/editor/undo_spec.lua +++ b/test/functional/editor/undo_spec.lua @@ -2,9 +2,18 @@ local helpers = require('test.functional.helpers')(after_each) local clear = helpers.clear local command = helpers.command +local eval = helpers.eval local expect = helpers.expect +local eq = helpers.eq local feed = helpers.feed +local feed_command = helpers.feed_command local insert = helpers.insert +local funcs = helpers.funcs + +local function lastmessage() + local messages = funcs.split(funcs.execute('messages'), '\n') + return messages[#messages] +end describe('u CTRL-R g- g+', function() before_each(clear) @@ -59,3 +68,61 @@ describe('u CTRL-R g- g+', function() undo_and_redo(4, 'g-', 'g+', '1') end) end) + +describe(':undo! command', function() + before_each(function() + clear() + feed('i1 little bug in the code<Esc>') + feed('o1 little bug in the code<Esc>') + feed('oTake 1 down, patch it around<Esc>') + feed('o99 little bugs in the code<Esc>') + end) + it('works', function() + feed_command('undo!') + expect([[ + 1 little bug in the code + 1 little bug in the code + Take 1 down, patch it around]]) + feed('<C-r>') + eq('Already at newest change', lastmessage()) + end) + it('works with arguments', function() + feed_command('undo! 2') + expect([[ + 1 little bug in the code + 1 little bug in the code]]) + feed('<C-r>') + eq('Already at newest change', lastmessage()) + end) + it('correctly sets alternative redo', function() + feed('uo101 little bugs in the code<Esc>') + feed_command('undo!') + feed('<C-r>') + expect([[ + 1 little bug in the code + 1 little bug in the code + Take 1 down, patch it around + 99 little bugs in the code]]) + + feed('uuoTake 2 down, patch them around<Esc>') + feed('o101 little bugs in the code<Esc>') + feed_command('undo! 2') + feed('<C-r><C-r>') + expect([[ + 1 little bug in the code + 1 little bug in the code + Take 1 down, patch it around + 99 little bugs in the code]]) + end) + it('fails when attempting to redo or move to different undo branch', function() + feed_command('undo! 4') + eq('E5767: Cannot use :undo! to redo or move to a different undo branch', eval('v:errmsg')) + feed('u') + feed_command('undo! 4') + eq('E5767: Cannot use :undo! to redo or move to a different undo branch', eval('v:errmsg')) + feed('o101 little bugs in the code<Esc>') + feed('o101 little bugs in the code<Esc>') + feed_command('undo! 4') + eq('E5767: Cannot use :undo! to redo or move to a different undo branch', eval('v:errmsg')) + end) +end) diff --git a/test/functional/ex_cmds/cd_spec.lua b/test/functional/ex_cmds/cd_spec.lua index f9cce0deb6..42a811f5da 100644 --- a/test/functional/ex_cmds/cd_spec.lua +++ b/test/functional/ex_cmds/cd_spec.lua @@ -173,7 +173,7 @@ for _, cmd in ipairs {'cd', 'chdir'} do -- Change tab-local working directory and verify it is different command('silent t' .. cmd .. ' ' .. directories.tab) eq(globalDir .. pathsep .. directories.tab, cwd()) - eq(cwd(), tcwd()) -- working directory maches tab directory + eq(cwd(), tcwd()) -- working directory matches tab directory eq(1, tlwd()) eq(cwd(), wcwd()) -- still no window-directory eq(0, wlwd()) diff --git a/test/functional/ex_cmds/cmd_map_spec.lua b/test/functional/ex_cmds/cmd_map_spec.lua index 0b2190bbcf..919d167712 100644 --- a/test/functional/ex_cmds/cmd_map_spec.lua +++ b/test/functional/ex_cmds/cmd_map_spec.lua @@ -1,21 +1,23 @@ local helpers = require('test.functional.helpers')(after_each) local clear = helpers.clear -local feed_command = helpers.feed_command local feed = helpers.feed local eq = helpers.eq local expect = helpers.expect local eval = helpers.eval local funcs = helpers.funcs local insert = helpers.insert +local write_file = helpers.write_file local exc_exec = helpers.exc_exec -local source = helpers.source +local command = helpers.command local Screen = require('test.functional.ui.screen') describe('mappings with <Cmd>', function() local screen + local tmpfile = 'X_ex_cmds_cmd_map' + local function cmdmap(lhs, rhs) - feed_command('noremap '..lhs..' <Cmd>'..rhs..'<cr>') - feed_command('noremap! '..lhs..' <Cmd>'..rhs..'<cr>') + command('noremap '..lhs..' <Cmd>'..rhs..'<cr>') + command('noremap! '..lhs..' <Cmd>'..rhs..'<cr>') end before_each(function() @@ -39,7 +41,7 @@ describe('mappings with <Cmd>', function() cmdmap('<F4>', 'normal! ww') cmdmap('<F5>', 'normal! "ay') cmdmap('<F6>', 'throw "very error"') - feed_command([[ + command([[ function! TextObj() if mode() !=# "v" normal! v @@ -55,11 +57,15 @@ describe('mappings with <Cmd>', function() feed('gg') cmdmap('<F8>', 'startinsert') cmdmap('<F9>', 'stopinsert') - feed_command("abbr foo <Cmd>let g:y = 17<cr>bar") + command("abbr foo <Cmd>let g:y = 17<cr>bar") + end) + + after_each(function() + os.remove(tmpfile) end) it('can be displayed', function() - feed_command('map <F3>') + command('map <F3>') screen:expect([[ ^some short lines | of test text | @@ -73,8 +79,8 @@ describe('mappings with <Cmd>', function() end) it('handles invalid mappings', function() - feed_command('let x = 0') - feed_command('noremap <F3> <Cmd><Cmd>let x = 1<cr>') + command('let x = 0') + command('noremap <F3> <Cmd><Cmd>let x = 1<cr>') feed('<F3>') screen:expect([[ ^some short lines | @@ -87,7 +93,7 @@ describe('mappings with <Cmd>', function() {2:E5521: <Cmd> mapping must end with <CR> before second <Cmd>} | ]]) - feed_command('noremap <F3> <Cmd><F3>let x = 2<cr>') + command('noremap <F3> <Cmd>let x = 3') feed('<F3>') screen:expect([[ ^some short lines | @@ -97,22 +103,50 @@ describe('mappings with <Cmd>', function() {1:~ }| {1:~ }| {1:~ }| - {2:E5522: <Cmd> mapping must not include <F3> key} | + {2:E5520: <Cmd> mapping must end with <CR>} | ]]) + eq(0, eval('x')) + end) - feed_command('noremap <F3> <Cmd>let x = 3') + it('allows special keys and modifiers', function() + command('noremap <F3> <Cmd>normal! <Down><CR>') feed('<F3>') screen:expect([[ - ^some short lines | - of test text | + some short lines | + ^of test text | {1:~ }| {1:~ }| {1:~ }| {1:~ }| {1:~ }| - {2:E5520: <Cmd> mapping must end with <CR>} | + | ]]) - eq(0, eval('x')) + + command('noremap <F3> <Cmd>normal! <C-Right><CR>') + feed('<F3>') + screen:expect([[ + some short lines | + of ^test text | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]) + end) + + it('handles string containing K_SPECIAL (0x80) bytes correctly', function() + command([[noremap <F3> <Cmd>let g:str = 'foo…bar'<CR>]]) + feed('<F3>') + eq('foo…bar', eval('g:str')) + local str = eval([["foo\<D-…>bar"]]) + command([[noremap <F3> <Cmd>let g:str = ']]..str..[['<CR>]]) + feed('<F3>') + eq(str, eval('g:str')) + command([[noremap <F3> <Cmd>let g:str = 'foo<D-…>bar'<CR>]]) + feed('<F3>') + eq(str, eval('g:str')) end) it('works in various modes and sees correct `mode()` value', function() @@ -137,7 +171,7 @@ describe('mappings with <Cmd>', function() eq('n', eval('mode(1)')) -- select mode mapping - feed_command('snoremap <F3> <Cmd>let m = mode(1)<cr>') + command('snoremap <F3> <Cmd>let m = mode(1)<cr>') feed('gh<F3>') eq('s', eval('m')) -- didn't leave select mode @@ -184,8 +218,8 @@ describe('mappings with <Cmd>', function() eq('n', eval('mode(1)')) -- terminal mode - feed_command('tnoremap <F3> <Cmd>let m = mode(1)<cr>') - feed_command('split | terminal') + command('tnoremap <F3> <Cmd>let m = mode(1)<cr>') + command('split | terminal') feed('i') eq('t', eval('mode(1)')) feed('<F3>') @@ -264,11 +298,11 @@ describe('mappings with <Cmd>', function() end) it('works in :normal command', function() - feed_command('noremap ,x <Cmd>call append(1, "xx")\\| call append(1, "aa")<cr>') - feed_command('noremap ,f <Cmd>nosuchcommand<cr>') - feed_command('noremap ,e <Cmd>throw "very error"\\| call append(1, "yy")<cr>') - feed_command('noremap ,m <Cmd>echoerr "The message."\\| call append(1, "zz")<cr>') - feed_command('noremap ,w <Cmd>for i in range(5)\\|if i==1\\|echoerr "Err"\\|endif\\|call append(1, i)\\|endfor<cr>') + command('noremap ,x <Cmd>call append(1, "xx")\\| call append(1, "aa")<cr>') + command('noremap ,f <Cmd>nosuchcommand<cr>') + command('noremap ,e <Cmd>throw "very error"\\| call append(1, "yy")<cr>') + command('noremap ,m <Cmd>echoerr "The message."\\| call append(1, "zz")<cr>') + command('noremap ,w <Cmd>for i in range(5)\\|if i==1\\|echoerr "Err"\\|endif\\|call append(1, i)\\|endfor<cr>') feed(":normal ,x<cr>") screen:expect([[ @@ -297,7 +331,7 @@ describe('mappings with <Cmd>', function() :normal ,x | ]]) - feed_command(':%d') + command(':%d') eq('Vim(echoerr):Err', exc_exec("normal ,w")) screen:expect([[ ^ | @@ -310,8 +344,8 @@ describe('mappings with <Cmd>', function() --No lines in buffer-- | ]]) - feed_command(':%d') - feed_command(':normal ,w') + command(':%d') + feed(':normal ,w<cr>') screen:expect([[ ^ | 4 | @@ -401,8 +435,8 @@ describe('mappings with <Cmd>', function() end) it('works in select mode', function() - feed_command('snoremap <F1> <cmd>throw "very error"<cr>') - feed_command('snoremap <F2> <cmd>normal! <c-g>"by<cr>') + command('snoremap <F1> <cmd>throw "very error"<cr>') + command('snoremap <F2> <cmd>normal! <c-g>"by<cr>') -- can extend select mode feed('gh<F4>') screen:expect([[ @@ -830,12 +864,14 @@ describe('mappings with <Cmd>', function() end) it("works with <SID> mappings", function() - source([[ + command('new!') + write_file(tmpfile, [[ map <f2> <Cmd>call <SID>do_it()<Cr> function! s:do_it() let g:x = 10 endfunction ]]) + command('source '..tmpfile) feed('<f2>') eq('', eval('v:errmsg')) eq(10, eval('g:x')) diff --git a/test/functional/ex_cmds/drop_spec.lua b/test/functional/ex_cmds/drop_spec.lua index 9d84a2d4f6..2537ab9cdc 100644 --- a/test/functional/ex_cmds/drop_spec.lua +++ b/test/functional/ex_cmds/drop_spec.lua @@ -41,14 +41,14 @@ describe(":drop", function() feed_command("edit tmp2") feed_command("drop tmp1") screen:expect([[ - {2:│}^ | - {0:~ }{2:│}{0:~ }| - {0:~ }{2:│}{0:~ }| - {0:~ }{2:│}{0:~ }| - {0:~ }{2:│}{0:~ }| - {0:~ }{2:│}{0:~ }| - {0:~ }{2:│}{0:~ }| - {0:~ }{2:│}{0:~ }| + │^ | + {0:~ }│{0:~ }| + {0:~ }│{0:~ }| + {0:~ }│{0:~ }| + {0:~ }│{0:~ }| + {0:~ }│{0:~ }| + {0:~ }│{0:~ }| + {0:~ }│{0:~ }| {2:tmp2 }{1:tmp1 }| :drop tmp1 | ]]) @@ -62,14 +62,14 @@ describe(":drop", function() feed("iABC<esc>") feed_command("drop tmp3") screen:expect([[ - ^ {2:│} | - {0:~ }{2:│}{0:~ }| - {0:~ }{2:│}{0:~ }| - {0:~ }{2:│}{0:~ }| - {1:tmp3 }{2:│}{0:~ }| - ABC {2:│}{0:~ }| - {0:~ }{2:│}{0:~ }| - {0:~ }{2:│}{0:~ }| + ^ │ | + {0:~ }│{0:~ }| + {0:~ }│{0:~ }| + {0:~ }│{0:~ }| + {1:tmp3 }│{0:~ }| + ABC │{0:~ }| + {0:~ }│{0:~ }| + {0:~ }│{0:~ }| {2:tmp2 [+] tmp1 }| "tmp3" [New] | ]]) diff --git a/test/functional/ex_cmds/echo_spec.lua b/test/functional/ex_cmds/echo_spec.lua index d320425de1..a6be04138b 100644 --- a/test/functional/ex_cmds/echo_spec.lua +++ b/test/functional/ex_cmds/echo_spec.lua @@ -181,9 +181,9 @@ describe(':echo :echon :echomsg :echoerr', function() end) it('dumps references to script functions', function() - eq('<SNR>2_Test2', eval('String(Test2_f)')) - eq("function('<SNR>2_Test2')", eval('StringMsg(Test2_f)')) - eq("function('<SNR>2_Test2')", eval('StringErr(Test2_f)')) + eq('<SNR>1_Test2', eval('String(Test2_f)')) + eq("function('<SNR>1_Test2')", eval('StringMsg(Test2_f)')) + eq("function('<SNR>1_Test2')", eval('StringErr(Test2_f)')) end) it('dump references to lambdas', function() @@ -205,11 +205,11 @@ describe(':echo :echon :echomsg :echoerr', function() it('dumps automatically created partials', function() assert_same_echo_dump( - "function('<SNR>2_Test2', {'f': function('<SNR>2_Test2')})", + "function('<SNR>1_Test2', {'f': function('<SNR>1_Test2')})", '{"f": Test2_f}.f', true) assert_same_echo_dump( - "function('<SNR>2_Test2', [1], {'f': function('<SNR>2_Test2', [1])})", + "function('<SNR>1_Test2', [1], {'f': function('<SNR>1_Test2', [1])})", '{"f": function(Test2_f, [1])}.f', true) end) @@ -227,7 +227,7 @@ describe(':echo :echon :echomsg :echoerr', function() function() meths.set_var('d', {v=true}) eq(dedent([[ - {'p': function('<SNR>2_Test2', {...@0}), 'f': function('<SNR>2_Test2'), 'v': v:true}]]), + {'p': function('<SNR>1_Test2', {...@0}), 'f': function('<SNR>1_Test2'), 'v': v:true}]]), exec_capture('echo String(extend(extend(g:d, {"f": g:Test2_f}), {"p": g:d.f}))')) end) @@ -264,7 +264,7 @@ describe(':echo :echon :echomsg :echoerr', function() eval('add(l, function("Test1", l))') eval('add(l, function("Test1", d))') eq(dedent([=[ - {'p': function('<SNR>2_Test2', [[[...@3], function('Test1', [[...@3]]), function('Test1', {...@0})], function('Test1', [[[...@5], function('Test1', [[...@5]]), function('Test1', {...@0})]]), function('Test1', {...@0})], {...@0}), 'f': function('<SNR>2_Test2'), 'v': v:true}]=]), + {'p': function('<SNR>1_Test2', [[[...@3], function('Test1', [[...@3]]), function('Test1', {...@0})], function('Test1', [[[...@5], function('Test1', [[...@5]]), function('Test1', {...@0})]]), function('Test1', {...@0})], {...@0}), 'f': function('<SNR>1_Test2'), 'v': v:true}]=]), exec_capture('echo String(extend(extend(g:d, {"f": g:Test2_f}), {"p": function(g:d.f, l)}))')) end) end) diff --git a/test/functional/ex_cmds/ls_spec.lua b/test/functional/ex_cmds/ls_spec.lua index 9853084c47..2583d80269 100644 --- a/test/functional/ex_cmds/ls_spec.lua +++ b/test/functional/ex_cmds/ls_spec.lua @@ -5,7 +5,7 @@ local eq = helpers.eq local eval = helpers.eval local feed = helpers.feed local nvim = helpers.nvim -local nvim_dir = helpers.nvim_dir +local testprg = helpers.testprg local retry = helpers.retry describe(':ls', function() @@ -14,7 +14,7 @@ describe(':ls', function() end) it('R, F for :terminal buffers', function() - nvim('set_option', 'shell', string.format('"%s" INTERACT', nvim_dir..'/shell-test')) + nvim('set_option', 'shell', string.format('"%s" INTERACT', testprg('shell-test'))) command('edit foo') command('set hidden') diff --git a/test/functional/ex_cmds/make_spec.lua b/test/functional/ex_cmds/make_spec.lua index 3b4d22ab38..bf585ee44c 100644 --- a/test/functional/ex_cmds/make_spec.lua +++ b/test/functional/ex_cmds/make_spec.lua @@ -4,7 +4,7 @@ local eval = helpers.eval local has_powershell = helpers.has_powershell local matches = helpers.matches local nvim = helpers.nvim -local nvim_dir = helpers.nvim_dir +local testprg = helpers.testprg describe(':make', function() clear() @@ -22,7 +22,7 @@ describe(':make', function() end) it('captures stderr & non zero exit code #14349', function () - nvim('set_option', 'makeprg', nvim_dir..'/shell-test foo') + nvim('set_option', 'makeprg', testprg('shell-test')..' foo') local out = eval('execute("make")') -- Make program exit code correctly captured matches('\nshell returned 3', out) @@ -31,7 +31,7 @@ describe(':make', function() end) it('captures stderr & zero exit code #14349', function () - nvim('set_option', 'makeprg', nvim_dir..'/shell-test') + nvim('set_option', 'makeprg', testprg('shell-test')) local out = eval('execute("make")') -- Ensure there are no "shell returned X" messages between -- command and last line (indicating zero exit) diff --git a/test/functional/ex_cmds/map_spec.lua b/test/functional/ex_cmds/map_spec.lua index 84d5bc2335..c6bdd017bd 100644 --- a/test/functional/ex_cmds/map_spec.lua +++ b/test/functional/ex_cmds/map_spec.lua @@ -1,11 +1,15 @@ local helpers = require("test.functional.helpers")(after_each) +local Screen = require('test.functional.ui.screen') local eq = helpers.eq +local exec = helpers.exec local feed = helpers.feed local meths = helpers.meths local clear = helpers.clear local command = helpers.command local expect = helpers.expect +local insert = helpers.insert +local eval = helpers.eval describe(':*map', function() before_each(clear) @@ -25,4 +29,233 @@ describe(':*map', function() feed('i-<M-">-') expect('-foo-') end) + + it('shows <nop> as mapping rhs', function() + command('nmap asdf <Nop>') + eq([[ + +n asdf <Nop>]], + helpers.exec_capture('nmap asdf')) + end) + + it('mappings with description can be filtered', function() + meths.set_keymap('n', 'asdf1', 'qwert', {desc='do the one thing'}) + meths.set_keymap('n', 'asdf2', 'qwert', {desc='doesnot really do anything'}) + meths.set_keymap('n', 'asdf3', 'qwert', {desc='do the other thing'}) + eq([[ + +n asdf3 qwert + do the other thing +n asdf1 qwert + do the one thing]], + helpers.exec_capture('filter the nmap')) + end) + + it('<Plug> mappings ignore nore', function() + command('let x = 0') + eq(0, meths.eval('x')) + command [[ + nnoremap <Plug>(Increase_x) <cmd>let x+=1<cr> + nmap increase_x_remap <Plug>(Increase_x) + nnoremap increase_x_noremap <Plug>(Increase_x) + ]] + feed('increase_x_remap') + eq(1, meths.eval('x')) + feed('increase_x_noremap') + eq(2, meths.eval('x')) + end) + + it("Doesn't auto ignore nore for keys before or after <Plug> mapping", function() + command('let x = 0') + eq(0, meths.eval('x')) + command [[ + nnoremap x <nop> + nnoremap <Plug>(Increase_x) <cmd>let x+=1<cr> + nmap increase_x_remap x<Plug>(Increase_x)x + nnoremap increase_x_noremap x<Plug>(Increase_x)x + ]] + insert("Some text") + eq('Some text', eval("getline('.')")) + + feed('increase_x_remap') + eq(1, meths.eval('x')) + eq('Some text', eval("getline('.')")) + feed('increase_x_noremap') + eq(2, meths.eval('x')) + eq('Some te', eval("getline('.')")) + end) +end) + +describe('Screen', function() + local screen + before_each(function() + clear() + screen = Screen.new(20, 5) + screen:attach() + end) + + it('cursor is restored after :map <expr> which calls input()', function() + command('map <expr> x input("> ")') + screen:expect([[ + ^ | + ~ | + ~ | + ~ | + | + ]]) + feed('x') + screen:expect([[ + | + ~ | + ~ | + ~ | + > ^ | + ]]) + feed('\n') + screen:expect([[ + ^ | + ~ | + ~ | + ~ | + > | + ]]) + end) + + it('cursor is restored after :imap <expr> which calls input()', function() + command('imap <expr> x input("> ")') + feed('i') + screen:expect([[ + ^ | + ~ | + ~ | + ~ | + -- INSERT -- | + ]]) + feed('x') + screen:expect([[ + | + ~ | + ~ | + ~ | + > ^ | + ]]) + feed('\n') + screen:expect([[ + ^ | + ~ | + ~ | + ~ | + > | + ]]) + end) + + it('cursor position does not move after empty-string :cmap <expr> #19046', function() + command([[cnoremap <expr> <F2> '']]) + feed(':<F2>') + screen:expect([[ + | + ~ | + ~ | + ~ | + :^ | + ]]) + end) + + it('cursor is restored after :map <expr> which redraws statusline vim-patch:8.1.2336', function() + exec([[ + call setline(1, ['one', 'two', 'three']) + 2 + set ls=2 + hi! link StatusLine ErrorMsg + noremap <expr> <C-B> Func() + func Func() + let g:on = !get(g:, 'on', 0) + redraws + return '' + endfunc + func Status() + return get(g:, 'on', 0) ? '[on]' : '' + endfunc + set stl=%{Status()} + ]]) + feed('<C-B>') + screen:expect([[ + one | + ^two | + three | + [on] | + | + ]]) + end) + + it('error in :nmap <expr> does not mess up display vim-patch:4.2.4338', function() + screen:try_resize(40, 5) + command('nmap <expr> <F2> execute("throw 42")') + feed('<F2>') + screen:expect([[ + | + | + Error detected while processing : | + E605: Exception not caught: 42 | + Press ENTER or type command to continue^ | + ]]) + feed('<CR>') + screen:expect([[ + ^ | + ~ | + ~ | + ~ | + | + ]]) + end) + + it('error in :cmap <expr> handled correctly vim-patch:4.2.4338', function() + screen:try_resize(40, 5) + command('cmap <expr> <F2> execute("throw 42")') + feed(':echo "foo') + screen:expect([[ + | + ~ | + ~ | + ~ | + :echo "foo^ | + ]]) + feed('<F2>') + screen:expect([[ + | + :echo "foo | + Error detected while processing : | + E605: Exception not caught: 42 | + :echo "foo^ | + ]]) + feed('"') + screen:expect([[ + | + :echo "foo | + Error detected while processing : | + E605: Exception not caught: 42 | + :echo "foo"^ | + ]]) + feed('\n') + screen:expect([[ + :echo "foo | + Error detected while processing : | + E605: Exception not caught: 42 | + foo | + Press ENTER or type command to continue^ | + ]]) + end) + + it('listing mappings clears command line vim-patch:8.2.4401', function() + screen:try_resize(40, 5) + command('nmap a b') + feed(': nmap a<CR>') + screen:expect([[ + ^ | + ~ | + ~ | + ~ | + n a b | + ]]) + end) end) diff --git a/test/functional/ex_cmds/menu_spec.lua b/test/functional/ex_cmds/menu_spec.lua index 0cd32df27c..b9ed32c328 100644 --- a/test/functional/ex_cmds/menu_spec.lua +++ b/test/functional/ex_cmds/menu_spec.lua @@ -64,6 +64,8 @@ describe('menu_get', function() before_each(function() clear() command([=[ + aunmenu * + nnoremenu &Test.Test inormal<ESC> inoremenu Test.Test insert vnoremenu Test.Test x @@ -396,6 +398,7 @@ describe('menu_get', function() before_each(function() clear() + command('aunmenu *') end) it('returns <keycode> representation of special keys', function() diff --git a/test/functional/ex_cmds/mksession_spec.lua b/test/functional/ex_cmds/mksession_spec.lua index 09eaa36686..1553de4432 100644 --- a/test/functional/ex_cmds/mksession_spec.lua +++ b/test/functional/ex_cmds/mksession_spec.lua @@ -1,14 +1,20 @@ local lfs = require('lfs') local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') local clear = helpers.clear local command = helpers.command local get_pathsep = helpers.get_pathsep +local iswin = helpers.iswin local eq = helpers.eq +local neq = helpers.neq local funcs = helpers.funcs local matches = helpers.matches local pesc = helpers.pesc local rmdir = helpers.rmdir +local sleep = helpers.sleep +local meths = helpers.meths +local expect_exit = helpers.expect_exit local file_prefix = 'Xtest-functional-ex_cmds-mksession_spec' @@ -30,7 +36,8 @@ describe(':mksession', function() -- If the same :terminal is displayed in multiple windows, :mksession -- should restore it as such. - -- Create two windows showing the same :terminal buffer. + -- Create three windows: first two from top show same terminal, third - + -- another one (created earlier). command('terminal') command('split') command('terminal') @@ -38,13 +45,13 @@ describe(':mksession', function() command('mksession '..session_file) -- Create a new test instance of Nvim. - command('qall!') + expect_exit(command, 'qall!') clear() -- Restore session. command('source '..session_file) - eq({2,2,4}, - {funcs.winbufnr(1), funcs.winbufnr(2), funcs.winbufnr(3)}) + eq(funcs.winbufnr(1), funcs.winbufnr(2)) + neq(funcs.winbufnr(1), funcs.winbufnr(3)) end) it('restores tab-local working directories', function() @@ -91,12 +98,7 @@ describe(':mksession', function() command('tabnext 1') eq(cwd_dir .. get_pathsep() .. tmpfile_base .. '1', funcs.expand('%:p')) command('tabnext 2') - -- :mksession stores paths using unix slashes, but Nvim doesn't adjust these - -- for absolute paths in all cases yet. Absolute paths are used in the - -- session file after :tcd, so we need to expect unix slashes here for now - -- eq(cwd_dir .. get_pathsep() .. tmpfile_base .. '2', funcs.expand('%:p')) - eq(cwd_dir:gsub([[\]], '/') .. '/' .. tmpfile_base .. '2', - funcs.expand('%:p')) + eq(cwd_dir .. get_pathsep() .. tmpfile_base .. '2', funcs.expand('%:p')) end) it('restores CWD for :terminal buffers #11288', function() @@ -105,10 +107,14 @@ describe(':mksession', function() local session_path = cwd_dir..'/'..session_file command('cd '..tab_dir) - command('terminal echo $PWD') + command('terminal') command('cd '..cwd_dir) command('mksession '..session_path) - command('qall!') + command('bdelete!') + if iswin() then + sleep(100) -- Make sure all child processes have exited. + end + expect_exit(command, 'qall!') -- Create a new test instance of Nvim. clear() @@ -116,6 +122,83 @@ describe(':mksession', function() local expected_cwd = cwd_dir..'/'..tab_dir matches('^term://'..pesc(expected_cwd)..'//%d+:', funcs.expand('%')) - command('qall!') + command('bdelete!') + if iswin() then + sleep(100) -- Make sure all child processes have exited. + end + end) + + it('restores CWD for :terminal buffer at root directory #16988', function() + if iswin() then + pending('N/A for Windows') + return + end + + local screen + local cwd_dir = funcs.fnamemodify('.', ':p:~'):gsub([[[\/]*$]], '') + local session_path = cwd_dir..'/'..session_file + + screen = Screen.new(50, 6) + screen:attach({rgb=false}) + local expected_screen = [[ + ^/ | + | + [Process exited 0] | + | + | + | + ]] + + command('cd /') + command('terminal echo $PWD') + + -- Verify that the terminal's working directory is "/". + screen:expect(expected_screen) + + command('cd '..cwd_dir) + command('mksession '..session_path) + expect_exit(command, 'qall!') + + -- Create a new test instance of Nvim. + clear() + screen = Screen.new(50, 6) + screen:attach({rgb=false}) + command('silent source '..session_path) + + -- Verify that the terminal's working directory is "/". + screen:expect(expected_screen) + end) + + it('restores a session when there is a float #18432', function() + local tmpfile = file_prefix .. '-tmpfile-float' + + command('edit ' .. tmpfile) + local buf = meths.create_buf(false, true) + local config = { + relative = 'editor', + focusable = false, + width = 10, + height = 3, + row = 0, + col = 1, + style = 'minimal' + } + meths.open_win(buf, false, config) + local cmdheight = meths.get_option('cmdheight') + command('mksession ' .. session_file) + + -- Create a new test instance of Nvim. + clear() + + command('source ' .. session_file) + + eq(tmpfile, funcs.expand('%')) + -- Check that there is only a single window, which indicates the floating + -- window was not restored. + eq(1, funcs.winnr('$')) + -- The command-line height should remain the same as it was. + eq(cmdheight, meths.get_option('cmdheight')) + + os.remove(tmpfile) end) end) diff --git a/test/functional/ex_cmds/normal_spec.lua b/test/functional/ex_cmds/normal_spec.lua new file mode 100644 index 0000000000..f6e7dd2b3a --- /dev/null +++ b/test/functional/ex_cmds/normal_spec.lua @@ -0,0 +1,27 @@ +local helpers = require('test.functional.helpers')(after_each) +local clear = helpers.clear +local command = helpers.command +local feed = helpers.feed +local expect = helpers.expect +local eq = helpers.eq +local eval = helpers.eval + +before_each(clear) + +describe(':normal', function() + it('can get out of Insert mode if called from Ex mode #17924', function() + feed('gQnormal! Ifoo<CR>') + expect('foo') + end) + + it('normal! does not execute command in Ex mode when running out of characters', function() + command('let g:var = 0') + command('normal! gQlet g:var = 1') + eq(0, eval('g:var')) + end) + + it('normal! gQinsert does not hang #17980', function() + command('normal! gQinsert') + expect('') + end) +end) diff --git a/test/functional/ex_cmds/quickfix_commands_spec.lua b/test/functional/ex_cmds/quickfix_commands_spec.lua index c956a2df2d..94b7fa1a84 100644 --- a/test/functional/ex_cmds/quickfix_commands_spec.lua +++ b/test/functional/ex_cmds/quickfix_commands_spec.lua @@ -109,4 +109,17 @@ describe('quickfix', function() ]]) eq({0, 6, 1, 0, 1}, funcs.getcurpos()) end) + + 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') + source([[ + set grepprg=internal + autocmd BufAdd * call and(0, 0) + autocmd QuickFixCmdPost grep ++nested cclose | cwindow + ]]) + command('grep foo ' .. file) + command('grep foo ' .. file) + os.remove(file) + end) end) diff --git a/test/functional/ex_cmds/script_spec.lua b/test/functional/ex_cmds/script_spec.lua index 0a772c559b..bf69ada820 100644 --- a/test/functional/ex_cmds/script_spec.lua +++ b/test/functional/ex_cmds/script_spec.lua @@ -2,18 +2,30 @@ local helpers = require('test.functional.helpers')(after_each) local eq = helpers.eq local neq = helpers.neq +local command = helpers.command +local write_file = helpers.write_file local meths = helpers.meths local clear = helpers.clear local dedent = helpers.dedent -local source = helpers.source local exc_exec = helpers.exc_exec local missing_provider = helpers.missing_provider +local tmpfile = 'X_ex_cmds_script' + before_each(clear) +local function source(code) + write_file(tmpfile, code) + command('source '..tmpfile) +end + describe('script_get-based command', function() local garbage = ')}{+*({}]*[;(+}{&[]}{*])(' + after_each(function() + os.remove(tmpfile) + end) + local function test_garbage_exec(cmd, check_neq) describe(cmd, function() it('works correctly when skipping oneline variant', function() @@ -62,10 +74,10 @@ describe('script_get-based command', function() -- Provider-based scripts test_garbage_exec('ruby', not missing_provider('ruby')) - test_garbage_exec('python', not missing_provider('python')) test_garbage_exec('python3', not missing_provider('python3')) -- Missing scripts + test_garbage_exec('python', false) test_garbage_exec('tcl', false) test_garbage_exec('mzscheme', false) test_garbage_exec('perl', false) diff --git a/test/functional/ex_cmds/sign_spec.lua b/test/functional/ex_cmds/sign_spec.lua index 891cfe1670..f280a45174 100644 --- a/test/functional/ex_cmds/sign_spec.lua +++ b/test/functional/ex_cmds/sign_spec.lua @@ -1,5 +1,5 @@ local helpers = require('test.functional.helpers')(after_each) -local clear, nvim, eq = helpers.clear, helpers.nvim, helpers.eq +local clear, nvim, eq, assert_alive = helpers.clear, helpers.nvim, helpers.eq, helpers.assert_alive describe('sign', function() before_each(clear) @@ -21,4 +21,11 @@ describe('sign', function() end) end) end) + + describe('define {id}', function() + it ('does not leak memory when specifying multiple times the same argument', function() + nvim('command', 'sign define Foo culhl=Normal culhl=Normal') + assert_alive() + end) + end) end) diff --git a/test/functional/ex_cmds/source_spec.lua b/test/functional/ex_cmds/source_spec.lua index fa650d611b..13a40fcc53 100644 --- a/test/functional/ex_cmds/source_spec.lua +++ b/test/functional/ex_cmds/source_spec.lua @@ -19,6 +19,26 @@ describe(':source', function() clear() end) + it('sourcing a file that is deleted and recreated is consistent vim-patch:8.1.0151', function() + local test_file = 'Xfile.vim' + local other_file = 'Xfoobar' + local script = [[ + func Func() + endfunc + ]] + write_file(test_file, script) + command('source ' .. test_file) + os.remove(test_file) + write_file(test_file, script) + command('source ' .. test_file) + os.remove(test_file) + write_file(other_file, '') + write_file(test_file, script) + command('source ' .. test_file) + os.remove(other_file) + os.remove(test_file) + end) + it('current buffer', function() insert([[ let a = 2 diff --git a/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua b/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua index d91feb4bc1..4d984af41e 100644 --- a/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua +++ b/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua @@ -10,6 +10,7 @@ local feed = helpers.feed local nvim_prog = helpers.nvim_prog local ok = helpers.ok local rmdir = helpers.rmdir +local os_kill = helpers.os_kill local set_session = helpers.set_session local spawn = helpers.spawn local nvim_async = helpers.nvim_async @@ -62,6 +63,7 @@ describe(':preserve', function() local swappath1 = eval('g:swapname') + os_kill(eval('getpid()')) -- Start another Nvim instance. local nvim2 = spawn({nvim_prog, '-u', 'NONE', '-i', 'NONE', '--embed'}, true) @@ -122,6 +124,7 @@ describe('swapfile detection', function() feed('isometext<esc>') command('preserve') + os_kill(eval('getpid()')) -- Start another Nvim instance. local nvim2 = spawn({nvim_prog, '-u', 'NONE', '-i', 'NONE', '--embed'}, true) diff --git a/test/functional/ex_cmds/verbose_spec.lua b/test/functional/ex_cmds/verbose_spec.lua new file mode 100644 index 0000000000..e6f67ef18e --- /dev/null +++ b/test/functional/ex_cmds/verbose_spec.lua @@ -0,0 +1,168 @@ +local helpers = require('test.functional.helpers')(after_each) + +local clear = helpers.clear +local eq = helpers.eq +local exec = helpers.exec +local exec_capture = helpers.exec_capture +local write_file = helpers.write_file +local call_viml_function = helpers.meths.call_function + +describe('lua :verbose', function() + local script_location, script_file + -- All test cases below use the same nvim instance. + setup(function() + clear{args={'-V1'}} + script_file = 'test_verbose.lua' + local current_dir = call_viml_function('getcwd', {}) + current_dir = call_viml_function('fnamemodify', {current_dir, ':~'}) + script_location = table.concat{current_dir, helpers.get_pathsep(), script_file} + + write_file(script_file, [[ +vim.api.nvim_set_option('hlsearch', false) +vim.bo.expandtab = true +vim.opt.number = true +vim.api.nvim_set_keymap('n', '<leader>key1', ':echo "test"<cr>', {noremap = true}) +vim.keymap.set('n', '<leader>key2', ':echo "test"<cr>') + +vim.api.nvim_exec("augroup test_group\ + autocmd!\ + autocmd FileType c setl cindent\ + augroup END\ + ", false) + +vim.api.nvim_command("command Bdelete :bd") +vim.api.nvim_create_user_command("TestCommand", ":echo 'Hello'", {}) + +vim.api.nvim_exec ("\ +function Close_Window() abort\ + wincmd -\ +endfunction\ +", false) + +local ret = vim.api.nvim_exec ("\ +function! s:return80()\ + return 80\ +endfunction\ +let &tw = s:return80()\ +", true) +]]) + exec(':source '..script_file) + end) + + teardown(function() + os.remove(script_file) + end) + + it('"Last set" for option set by Lua', function() + local result = exec_capture(':verbose set hlsearch?') + eq(string.format([[ +nohlsearch + Last set from %s line 1]], + script_location), result) + end) + + it('"Last set" for option set by vim.o', function() + local result = exec_capture(':verbose set expandtab?') + eq(string.format([[ + expandtab + Last set from %s line 2]], + script_location), result) + end) + + it('"Last set" for option set by vim.opt', function() + local result = exec_capture(':verbose set number?') + eq(string.format([[ + number + Last set from %s line 3]], + script_location), result) + end) + + it('"Last set" for keymap set by Lua', function() + local result = exec_capture(':verbose map <leader>key1') + eq(string.format([[ + +n \key1 * :echo "test"<CR> + Last set from %s line 4]], + script_location), result) + end) + + it('"Last set" for keymap set by vim.keymap', function() + local result = exec_capture(':verbose map <leader>key2') + eq(string.format([[ + +n \key2 * :echo "test"<CR> + Last set from %s line 5]], + script_location), result) + end) + + it('"Last set" for autocmd by vim.api.nvim_exec', function() + local result = exec_capture(':verbose autocmd test_group Filetype c') + eq(string.format([[ +--- Autocommands --- +test_group FileType + c setl cindent + Last set from %s line 7]], + script_location), result) + end) + + it('"Last set" for command defined by nvim_command', function() + local result = exec_capture(':verbose command Bdelete') + eq(string.format([[ + Name Args Address Complete Definition + Bdelete 0 :bd + Last set from %s line 13]], + script_location), result) + end) + + it('"Last set" for command defined by nvim_create_user_command', function() + local result = exec_capture(':verbose command TestCommand') + eq(string.format([[ + Name Args Address Complete Definition + TestCommand 0 :echo 'Hello' + Last set from %s line 14]], + script_location), result) + end) + + it('"Last set for function', function() + local result = exec_capture(':verbose function Close_Window') + eq(string.format([[ + function Close_Window() abort + Last set from %s line 16 +1 wincmd - + endfunction]], + script_location), result) + end) + + it('"Last set" works with anonymous sid', function() + local result = exec_capture(':verbose set tw?') + eq(string.format([[ + textwidth=80 + Last set from %s line 22]], + script_location), result) + end) +end) + +describe('lua verbose:', function() + local script_file + + setup(function() + clear() + script_file = 'test_luafile.lua' + write_file(script_file, [[ + vim.api.nvim_set_option('hlsearch', false) + ]]) + exec(':source '..script_file) + end) + + teardown(function() + os.remove(script_file) + end) + + it('is disabled when verbose = 0', function() + local result = exec_capture(':verbose set hlsearch?') + eq([[ +nohlsearch + Last set from Lua]], result) + end) +end) + diff --git a/test/functional/fixtures/CMakeLists.txt b/test/functional/fixtures/CMakeLists.txt index 270540de2e..a5410c2f8c 100644 --- a/test/functional/fixtures/CMakeLists.txt +++ b/test/functional/fixtures/CMakeLists.txt @@ -2,9 +2,11 @@ add_executable(tty-test EXCLUDE_FROM_ALL tty-test.c) target_link_libraries(tty-test ${LIBUV_LIBRARIES}) add_executable(shell-test EXCLUDE_FROM_ALL shell-test.c) +# Fake pwsh (powershell) for testing make_filter_cmd(). #16271 +add_executable(pwsh-test EXCLUDE_FROM_ALL shell-test.c) add_executable(printargs-test EXCLUDE_FROM_ALL printargs-test.c) add_executable(printenv-test EXCLUDE_FROM_ALL printenv-test.c) -if(WIN32) +if(MINGW) set_target_properties(printenv-test PROPERTIES LINK_FLAGS -municode) endif() diff --git a/test/functional/fixtures/api_level_9.mpack b/test/functional/fixtures/api_level_9.mpack Binary files differnew file mode 100644 index 0000000000..650d7a6a4d --- /dev/null +++ b/test/functional/fixtures/api_level_9.mpack diff --git a/test/functional/fixtures/autoload/provider/python.vim b/test/functional/fixtures/autoload/provider/python3.vim index d68360ac30..8ed4330a35 100644 --- a/test/functional/fixtures/autoload/provider/python.vim +++ b/test/functional/fixtures/autoload/provider/python3.vim @@ -1,6 +1,6 @@ " Dummy test provider, missing this required variable: " let g:loaded_brokenenabled_provider = 0 -function! provider#python#Call(method, args) +function! provider#python3#Call(method, args) return 42 endfunction diff --git a/test/functional/fixtures/fake-lsp-server.lua b/test/functional/fixtures/fake-lsp-server.lua index 5c0de50731..0dc0c8c2db 100644 --- a/test/functional/fixtures/fake-lsp-server.lua +++ b/test/functional/fixtures/fake-lsp-server.lua @@ -28,7 +28,10 @@ local function assert_eq(a, b, ...) if not vim.deep_equal(a, b) then error(message_parts(": ", ..., "assert_eq failed", - string.format("left == %q, right == %q", vim.inspect(a), vim.inspect(b)) + string.format("left == %q, right == %q", + table.concat(vim.split(vim.inspect(a), "\n"), ""), + table.concat(vim.split(vim.inspect(b), "\n"), "") + ) )) end end @@ -100,8 +103,12 @@ local tests = {} function tests.basic_init() skeleton { - on_init = function(_params) - return { capabilities = {} } + on_init = function(_) + return { + capabilities = { + textDocumentSync = protocol.TextDocumentSyncKind.None; + } + } end; body = function() notify('test') @@ -109,6 +116,19 @@ function tests.basic_init() } end +function tests.basic_init_did_change_configuration() + skeleton({ + on_init = function(_) + return { + capabilities = {}, + } + end, + body = function() + expect_notification('workspace/didChangeConfiguration', { settings = { dummy = 1 } }) + end, + }) +end + function tests.check_workspace_configuration() skeleton { on_init = function(_params) @@ -119,8 +139,10 @@ function tests.check_workspace_configuration() notify('workspace/configuration', { items = { { section = "testSetting1" }; { section = "testSetting2" }; + { section = "test.Setting3" }; + { section = "test.Setting4" }; } }) - expect_notification('workspace/configuration', { true; vim.NIL}) + expect_notification('workspace/configuration', { true; false; 'nested'; vim.NIL}) notify('shutdown') end; } @@ -130,8 +152,11 @@ function tests.prepare_rename_nil() skeleton { on_init = function() return { capabilities = { - renameProvider = true, - } } + renameProvider = { + prepareProvider = true + } + } + } end; body = function() notify('start') @@ -147,8 +172,11 @@ function tests.prepare_rename_placeholder() skeleton { on_init = function() return { capabilities = { - renameProvider = true, - } } + renameProvider = { + prepareProvider = true + } + } + } end; body = function() notify('start') @@ -168,8 +196,11 @@ function tests.prepare_rename_range() skeleton { on_init = function() return { capabilities = { - renameProvider = true, - } } + renameProvider = { + prepareProvider = true + } + } + } end; body = function() notify('start') @@ -191,19 +222,19 @@ end function tests.prepare_rename_error() skeleton { on_init = function() - return { capabilities = { - renameProvider = true, - } } + return { + capabilities = { + renameProvider = { + prepareProvider = true + }, + } + } end; body = function() notify('start') expect_request('textDocument/prepareRename', function() return {}, nil end) - expect_request('textDocument/rename', function(params) - assert_eq(params.newName, 'renameto') - return nil, nil - end) notify('shutdown') end; } @@ -217,6 +248,7 @@ function tests.basic_check_capabilities() return { capabilities = { textDocumentSync = protocol.TextDocumentSyncKind.Full; + codeLensProvider = false } } end; @@ -225,6 +257,51 @@ function tests.basic_check_capabilities() } end +function tests.text_document_sync_save_bool() + skeleton { + on_init = function() + return { + capabilities = { + textDocumentSync = { + save = true + } + } + } + end; + body = function() + notify('start') + expect_notification('textDocument/didSave', {textDocument = { uri = "file://" }}) + notify('shutdown') + end; + } +end + +function tests.text_document_sync_save_includeText() + skeleton { + on_init = function() + return { + capabilities = { + textDocumentSync = { + save = { + includeText = true + } + } + } + } + end; + body = function() + notify('start') + expect_notification('textDocument/didSave', { + textDocument = { + uri = "file://" + }, + text = "help me\n" + }) + notify('shutdown') + end; + } +end + function tests.capabilities_for_client_supports_method() skeleton { on_init = function(params) @@ -235,6 +312,7 @@ function tests.capabilities_for_client_supports_method() textDocumentSync = protocol.TextDocumentSyncKind.Full; completionProvider = true; hoverProvider = true; + renameProvider = false; definitionProvider = false; referencesProvider = false; codeLensProvider = { resolveProvider = true; }; @@ -542,7 +620,15 @@ function tests.basic_check_buffer_open_and_change_incremental() assert_eq(params.capabilities, expected_capabilities) return { capabilities = { - textDocumentSync = protocol.TextDocumentSyncKind.Incremental; + textDocumentSync = { + openClose = true, + change = protocol.TextDocumentSyncKind.Incremental, + willSave = true, + willSaveWaitUntil = true, + save = { + includeText = true, + } + } } } end; @@ -671,6 +757,78 @@ function tests.code_action_with_resolve() } end +function tests.code_action_server_side_command() + skeleton({ + on_init = function() + return { + capabilities = { + codeActionProvider = { + resolveProvider = false, + }, + }, + } + end, + body = function() + notify('start') + local cmd = { + title = 'Command 1', + command = 'dummy1', + } + expect_request('textDocument/codeAction', function() + return nil, { cmd } + end) + expect_request('workspace/executeCommand', function() + return nil, cmd + end) + notify('shutdown') + end, + }) +end + + +function tests.code_action_filter() + skeleton { + on_init = function() + return { + capabilities = { + codeActionProvider = { + resolveProvider = false + } + } + } + end; + body = function() + notify('start') + local action = { + title = 'Action 1', + command = 'command' + } + local preferred_action = { + title = 'Action 2', + isPreferred = true, + command = 'preferred_command', + } + local quickfix_action = { + title = 'Action 3', + kind = 'quickfix', + command = 'quickfix_command', + } + local quickfix_foo_action = { + title = 'Action 4', + kind = 'quickfix.foo', + command = 'quickfix_foo_command', + } + expect_request('textDocument/codeAction', function() + return nil, { action, preferred_action, quickfix_action, quickfix_foo_action, } + end) + expect_request('textDocument/codeAction', function() + return nil, { action, preferred_action, quickfix_action, quickfix_foo_action, } + end) + notify('shutdown') + end; + } +end + function tests.clientside_commands() skeleton { on_init = function() @@ -685,6 +843,68 @@ function tests.clientside_commands() } end +function tests.codelens_refresh_lock() + skeleton { + on_init = function() + return { + capabilities = { + codeLensProvider = { resolveProvider = true; }; + } + } + end; + body = function() + notify('start') + expect_request("textDocument/codeLens", function () + return {code = -32002, message = "ServerNotInitialized"}, nil + end) + expect_request("textDocument/codeLens", function () + local lenses = { + { + range = { + start = { line = 0, character = 0, }, + ['end'] = { line = 0, character = 3 } + }, + command = { title = 'Lens1', command = 'Dummy' } + }, + } + return nil, lenses + end) + expect_request("textDocument/codeLens", function () + local lenses = { + { + range = { + start = { line = 0, character = 0, }, + ['end'] = { line = 0, character = 3 } + }, + command = { title = 'Lens2', command = 'Dummy' } + }, + } + return nil, lenses + end) + notify('shutdown') + end; + } +end + +function tests.basic_formatting() + skeleton { + on_init = function() + return { + capabilities = { + documentFormattingProvider = true, + } + } + end; + body = function() + notify('start') + expect_request('textDocument/formatting', function() + return nil, {} + end) + notify('shutdown') + end; + } +end + -- Tests will be indexed by TEST_NAME local kill_timer = vim.loop.new_timer() diff --git a/test/functional/fixtures/lua/test_plug/health/init.lua b/test/functional/fixtures/lua/test_plug/health/init.lua index d07632cff4..58162d4515 100644 --- a/test/functional/fixtures/lua/test_plug/health/init.lua +++ b/test/functional/fixtures/lua/test_plug/health/init.lua @@ -1,11 +1,10 @@ local M = {} -local health = require("health") M.check = function() - health.report_start("report 1") - health.report_ok("everything is fine") - health.report_start("report 2") - health.report_ok("nothing to see here") + vim.health.report_start("report 1") + vim.health.report_ok("everything is fine") + vim.health.report_start("report 2") + vim.health.report_ok("nothing to see here") end return M diff --git a/test/functional/fixtures/lua/test_plug/submodule/health.lua b/test/functional/fixtures/lua/test_plug/submodule/health.lua index d07632cff4..58162d4515 100644 --- a/test/functional/fixtures/lua/test_plug/submodule/health.lua +++ b/test/functional/fixtures/lua/test_plug/submodule/health.lua @@ -1,11 +1,10 @@ local M = {} -local health = require("health") M.check = function() - health.report_start("report 1") - health.report_ok("everything is fine") - health.report_start("report 2") - health.report_ok("nothing to see here") + vim.health.report_start("report 1") + vim.health.report_ok("everything is fine") + vim.health.report_start("report 2") + vim.health.report_ok("nothing to see here") end return M diff --git a/test/functional/fixtures/lua/test_plug/submodule_empty/health.lua b/test/functional/fixtures/lua/test_plug/submodule_empty/health.lua new file mode 100644 index 0000000000..d2cf86e4f0 --- /dev/null +++ b/test/functional/fixtures/lua/test_plug/submodule_empty/health.lua @@ -0,0 +1,7 @@ +local M = {} + +M.check = function() + return {} +end + +return M diff --git a/test/functional/fixtures/lua/test_plug/submodule_failed/health.lua b/test/functional/fixtures/lua/test_plug/submodule_failed/health.lua index 3a8af6ebb2..ee5f4e404b 100644 --- a/test/functional/fixtures/lua/test_plug/submodule_failed/health.lua +++ b/test/functional/fixtures/lua/test_plug/submodule_failed/health.lua @@ -1,10 +1,9 @@ local M = {} -local health = require("health") M.check = function() - health.report_start("report 1") - health.report_ok("everything is fine") - health.report_warn("About to add a number to nil") + vim.health.report_start("report 1") + vim.health.report_ok("everything is fine") + vim.health.report_warn("About to add a number to nil") local a = nil + 2 return a end diff --git a/test/functional/fixtures/pack/foo/start/bar/lua/baz-quux.lua b/test/functional/fixtures/pack/foo/start/bar/lua/baz-quux.lua new file mode 100644 index 0000000000..c1c33d787e --- /dev/null +++ b/test/functional/fixtures/pack/foo/start/bar/lua/baz-quux.lua @@ -0,0 +1 @@ +return {doit=function() return 9004 end} diff --git a/test/functional/helpers.lua b/test/functional/helpers.lua index 1845786c4b..0c616e73fb 100644 --- a/test/functional/helpers.lua +++ b/test/functional/helpers.lua @@ -21,7 +21,6 @@ local map = global_helpers.tbl_map local ok = global_helpers.ok local sleep = global_helpers.sleep local tbl_contains = global_helpers.tbl_contains -local write_file = global_helpers.write_file local fail = global_helpers.fail local module = { @@ -42,10 +41,8 @@ module.nvim_set = ( module.nvim_argv = { module.nvim_prog, '-u', 'NONE', '-i', 'NONE', '--cmd', module.nvim_set, - '--cmd', 'unmap Y', - '--cmd', 'unmap <C-L>', - '--cmd', 'iunmap <C-U>', - '--cmd', 'iunmap <C-W>', + '--cmd', 'mapclear', + '--cmd', 'mapclear!', '--embed'} -- Directory containing nvim. @@ -54,7 +51,6 @@ if module.nvim_dir == module.nvim_prog then module.nvim_dir = "." end -local tmpname = global_helpers.tmpname local iswin = global_helpers.iswin local prepend_argv @@ -274,6 +270,13 @@ function module.command(cmd) module.request('nvim_command', cmd) end + +-- use for commands which expect nvim to quit +function module.expect_exit(...) + eq("EOF was received from Nvim. Likely the Nvim process crashed.", + module.pcall_err(...)) +end + -- Evaluates a VimL expression. -- Fails on VimL error, but does not update v:errmsg. function module.eval(expr) @@ -363,14 +366,15 @@ local function remove_args(args, args_rm) return new_args end -function module.spawn(argv, merge, env, keep) +--- @param io_extra used for stdin_fd, see :help ui-option +function module.spawn(argv, merge, env, keep, io_extra) if session and not keep then session:close() end local child_stream = ChildProcessStream.spawn( merge and module.merge_args(prepend_argv, argv) or argv, - env) + env, io_extra) return Session.new(child_stream) end @@ -417,8 +421,8 @@ end -- clear('-e') -- clear{args={'-e'}, args_rm={'-i'}, env={TERM=term}} function module.clear(...) - local argv, env = module.new_argv(...) - module.set_session(module.spawn(argv, nil, env)) + local argv, env, io_extra = module.new_argv(...) + module.set_session(module.spawn(argv, nil, env, nil, io_extra)) end -- Builds an argument list for use in clear(). @@ -427,17 +431,25 @@ end function module.new_argv(...) local args = {unpack(module.nvim_argv)} table.insert(args, '--headless') + if _G._nvim_test_id then + -- Set the server name to the test-id for logging. #8519 + table.insert(args, '--listen') + table.insert(args, _G._nvim_test_id) + end local new_args + local io_extra local env = nil local opts = select(1, ...) - if type(opts) == 'table' then + if type(opts) ~= 'table' then + new_args = {...} + else args = remove_args(args, opts.args_rm) if opts.env then - local env_tbl = {} + local env_opt = {} for k, v in pairs(opts.env) do assert(type(k) == 'string') assert(type(v) == 'string') - env_tbl[k] = v + env_opt[k] = v end for _, k in ipairs({ 'HOME', @@ -453,23 +465,23 @@ function module.new_argv(...) 'TMPDIR', 'VIMRUNTIME', }) do - if not env_tbl[k] then - env_tbl[k] = os.getenv(k) + -- Set these from the environment unless the caller defined them. + if not env_opt[k] then + env_opt[k] = os.getenv(k) end end env = {} - for k, v in pairs(env_tbl) do + for k, v in pairs(env_opt) do env[#env + 1] = k .. '=' .. v end end new_args = opts.args or {} - else - new_args = {...} + io_extra = opts.io_extra end for _, arg in ipairs(new_args) do table.insert(args, arg) end - return args, env + return args, env, io_extra end function module.insert(...) @@ -494,44 +506,38 @@ function module.feed_command(...) end end -local sourced_fnames = {} +-- @deprecated use nvim_exec() function module.source(code) - local fname = tmpname() - write_file(fname, code) - module.command('source '..fname) - -- DO NOT REMOVE FILE HERE. - -- do_source() has a habit of checking whether files are “same” by using inode - -- and device IDs. If you run two source() calls in quick succession there is - -- a good chance that underlying filesystem will reuse the inode, making files - -- appear as “symlinks” to do_source when it checks FileIDs. With current - -- setup linux machines (both QB, travis and mine(ZyX-I) with XFS) do reuse - -- inodes, Mac OS machines (again, both QB and travis) do not. - -- - -- Files appearing as “symlinks” mean that both the first and the second - -- source() calls will use same SID, which may fail some tests which check for - -- exact numbers after `<SNR>` in e.g. function names. - sourced_fnames[#sourced_fnames + 1] = fname - return fname + module.exec(dedent(code)) end function module.has_powershell() return module.eval('executable("'..(iswin() and 'powershell' or 'pwsh')..'")') == 1 end -function module.set_shell_powershell() - local shell = iswin() and 'powershell' or 'pwsh' - assert(module.has_powershell()) +--- Sets Nvim shell to powershell. +--- +--- @param fake (boolean) If true, a fake will be used if powershell is not +--- found on the system. +--- @returns true if powershell was found on the system, else false. +function module.set_shell_powershell(fake) + local found = module.has_powershell() + if not fake then + assert(found) + end + local shell = found and (iswin() and 'powershell' or 'pwsh') or module.testprg('pwsh-test') local set_encoding = '[Console]::InputEncoding=[Console]::OutputEncoding=[System.Text.Encoding]::UTF8;' local cmd = set_encoding..'Remove-Item -Force '..table.concat(iswin() and {'alias:cat', 'alias:echo', 'alias:sleep'} or {'alias:echo'}, ',')..';' - module.source([[ + module.exec([[ let &shell = ']]..shell..[[' set shellquote= shellxquote= - let &shellpipe = '2>&1 | Out-File -Encoding UTF8 %s; exit $LastExitCode' - let &shellredir = '2>&1 | Out-File -Encoding UTF8 %s; exit $LastExitCode' let &shellcmdflag = '-NoLogo -NoProfile -ExecutionPolicy RemoteSigned -Command ]]..cmd..[[' + let &shellpipe = '2>&1 | Out-File -Encoding UTF8 %s; exit $LastExitCode' + let &shellredir = '-RedirectStandardOutput %s -NoNewWindow -Wait' ]]) + return found end function module.nvim(method, ...) @@ -736,14 +742,10 @@ function module.pending_win32(pending_fn) end function module.pending_c_parser(pending_fn) - local status, msg = unpack(module.exec_lua([[ return {pcall(vim.treesitter.require_language, 'c')} ]])) + local status, _ = unpack(module.exec_lua([[ return {pcall(vim.treesitter.require_language, 'c')} ]])) if not status then - if module.isCI() then - error("treesitter C parser not found, required on CI: " .. msg) - else - pending_fn 'no C parser, skipping' - return true - end + pending_fn 'no C parser, skipping' + return true end return false end @@ -791,12 +793,22 @@ function module.get_pathsep() return iswin() and '\\' or '/' end +--- Gets the filesystem root dir, namely "/" or "C:/". function module.pathroot() local pathsep = package.config:sub(1,1) return iswin() and (module.nvim_dir:sub(1,2)..pathsep) or '/' end --- Returns a valid, platform-independent $NVIM_LISTEN_ADDRESS. +--- Gets the full `…/build/bin/{name}` path of a test program produced by +--- `test/functional/fixtures/CMakeLists.txt`. +--- +--- @param name (string) Name of the test program. +function module.testprg(name) + local ext = module.iswin() and '.exe' or '' + return ('%s/%s%s'):format(module.nvim_dir, name, ext) +end + +-- Returns a valid, platform-independent Nvim listen address. -- Useful for communicating with child instances. function module.new_pipename() -- HACK: Start a server temporarily, get the name, then stop it. @@ -887,9 +899,6 @@ module = global_helpers.tbl_extend('error', module, global_helpers) return function(after_each) if after_each then after_each(function() - for _, fname in ipairs(sourced_fnames) do - os.remove(fname) - end check_logs() check_cores('build/bin/nvim') if session then diff --git a/test/functional/legacy/003_cindent_spec.lua b/test/functional/legacy/003_cindent_spec.lua deleted file mode 100644 index 061904c42f..0000000000 --- a/test/functional/legacy/003_cindent_spec.lua +++ /dev/null @@ -1,4774 +0,0 @@ --- Test for 'cindent'. --- For new tests, consider putting them in test_cindent.vim. --- --- There are 50+ test command blocks (the stuff between STARTTEST and ENDTEST) --- in the original test. These have been converted to "it" test cases here. - -local helpers = require('test.functional.helpers')(after_each) -local feed, insert = helpers.feed, helpers.insert -local clear, feed_command, expect = helpers.clear, helpers.feed_command, helpers.expect - --- Inserts text as usual, and additionally positions the cursor on line 1 and --- sets 'cindent' and tab settings. (In the original "test3.in" the modeline at --- the top of the file takes care of this.) -local function insert_(content) - insert(content) - feed_command('1', 'set cin ts=4 sw=4') -end - --- luacheck: ignore 621 (Indentation) --- luacheck: ignore 613 (Trailing whitespace in a string) -describe('cindent', function() - before_each(clear) - - it('1 is working', function() - insert_([=[ - - /* start of AUTO matically checked vim: set ts=4 : */ - { - if (test) - cmd1; - cmd2; - } - - { - if (test) - cmd1; - else - cmd2; - } - - { - if (test) - { - cmd1; - cmd2; - } - } - - { - if (test) - { - cmd1; - else - } - } - - { - while (this) - if (test) - cmd1; - cmd2; - } - - { - while (this) - if (test) - cmd1; - else - cmd2; - } - - { - if (test) - { - cmd; - } - - if (test) - cmd; - } - - { - if (test) { - cmd; - } - - if (test) cmd; - } - - { - cmd1; - for (blah) - while (this) - if (test) - cmd2; - cmd3; - } - - { - cmd1; - for (blah) - while (this) - if (test) - cmd2; - cmd3; - - if (test) - { - cmd1; - cmd2; - cmd3; - } - } - - - /* Test for 'cindent' do/while mixed with if/else: */ - - { - do - if (asdf) - asdfasd; - while (cond); - - do - if (asdf) - while (asdf) - asdf; - while (asdf); - } - - /* Test for 'cindent' with two ) on a continuation line */ - { - if (asdfasdf;asldkfj asdlkfj as;ldkfj sal;d - aal;sdkjf ( ;asldfkja;sldfk - al;sdjfka ;slkdf ) sa;ldkjfsa dlk;) - line up here; - } - - - /* C++ tests: */ - - // foo() these three lines should remain in column 0 - // { - // } - - /* Test for continuation and unterminated lines: */ - { - i = 99 + 14325 + - 21345 + - 21345 + - 21345 + ( 21345 + - 21345) + - 2345 + - 1234; - c = 1; - } - - /* - testje for indent with empty line - - here */ - - { - if (testing && - not a joke || - line up here) - hay; - if (testing && - (not a joke || testing - )line up here) - hay; - if (testing && - (not a joke || testing - line up here)) - hay; - } - - - { - switch (c) - { - case xx: - do - if (asdf) - do - asdfasdf; - while (asdf); - else - asdfasdf; - while (cond); - case yy: - case xx: - case zz: - testing; - } - } - - { - if (cond) { - foo; - } - else - { - bar; - } - } - - { - if (alskdfj ;alsdkfjal;skdjf (;sadlkfsa ;dlkf j;alksdfj ;alskdjf - alsdkfj (asldk;fj - awith cino=(0 ;lf this one goes to below the paren with == - ;laksjfd ;lsakdjf ;alskdf asd) - asdfasdf;))) - asdfasdf; - } - - int - func(a, b) - int a; - int c; - { - if (c1 && (c2 || - c3)) - foo; - if (c1 && - (c2 || c3) - ) - } - - { - while (asd) - { - if (asdf) - if (test) - if (that) - { - if (asdf) - do - cdasd; - while (as - df); - } - else - if (asdf) - asdf; - else - asdf; - asdf; - } - } - - { - s = "/*"; b = ';' - s = "/*"; b = ';'; - a = b; - } - - { - switch (a) - { - case a: - switch (t) - { - case 1: - cmd; - break; - case 2: - cmd; - break; - } - cmd; - break; - case b: - { - int i; - cmd; - } - break; - case c: { - int i; - cmd; - } - case d: if (cond && - test) { /* this line doesn't work right */ - int i; - cmd; - } - break; - } - } - - { - if (!(vim_strchr(p_cpo, CPO_BUFOPTGLOB) != NULL && entering) && - (bp_to->b_p_initialized || - (!entering && vim_strchr(p_cpo, CPO_BUFOPT) != NULL))) - return; - label : - asdf = asdf ? - asdf : asdf; - asdf = asdf ? - asdf: asdf; - } - - /* Special Comments : This function has the added complexity (compared */ - /* : to addtolist) of having to check for a detail */ - /* : texture and add that to the list first. */ - - char *(array[100]) = { - "testje", - "foo", - "bar", - } - - enum soppie - { - yes = 0, - no, - maybe - }; - - typedef enum soppie - { - yes = 0, - no, - maybe - }; - - static enum - { - yes = 0, - no, - maybe - } soppie; - - public static enum - { - yes = 0, - no, - maybe - } soppie; - - static private enum - { - yes = 0, - no, - maybe - } soppie; - - { - int a, - b; - } - - { - struct Type - { - int i; - char *str; - } var[] = - { - 0, "zero", - 1, "one", - 2, "two", - 3, "three" - }; - - float matrix[3][3] = - { - { - 0, - 1, - 2 - }, - { - 3, - 4, - 5 - }, - { - 6, - 7, - 8 - } - }; - } - - { - /* blah ( blah */ - /* where does this go? */ - - /* blah ( blah */ - cmd; - - func(arg1, - /* comment */ - arg2); - a; - { - b; - { - c; /* Hey, NOW it indents?! */ - } - } - - { - func(arg1, - arg2, - arg3); - /* Hey, what am I doing here? Is this coz of the ","? */ - } - } - - main () - { - if (cond) - { - a = b; - } - if (cond) { - a = c; - } - if (cond) - a = d; - return; - } - - { - case 2: if (asdf && - asdfasdf) - aasdf; - a = 9; - case 3: if (asdf) - aasdf; - a = 9; - case 4: x = 1; - y = 2; - - label: if (asdf) - here; - - label: if (asdf && - asdfasdf) - { - } - - label: if (asdf && - asdfasdf) { - there; - } - - label: if (asdf && - asdfasdf) - there; - } - - { - /* - hello with ":set comments= cino=c5" - */ - - /* - hello with ":set comments= cino=" - */ - } - - - { - if (a < b) { - a = a + 1; - } else - a = a + 2; - - if (a) - do { - testing; - } while (asdfasdf); - a = b + 1; - asdfasdf - } - - { - for ( int i = 0; - i < 10; i++ ) - { - } - i = 0; - } - - class bob - { - int foo() {return 1;} - int bar; - } - - main() - { - while(1) - if (foo) - { - bar; - } - else { - asdf; - } - misplacedline; - } - - { - if (clipboard.state == SELECT_DONE - && ((row == clipboard.start.lnum - && col >= clipboard.start.col) - || row > clipboard.start.lnum)) - } - - { - if (1) {i += 4;} - where_am_i; - return 0; - } - - { - { - } // sdf(asdf - if (asdf) - asd; - } - - { - label1: - label2: - } - - { - int fooRet = foo(pBar1, false /*fKB*/, - true /*fPTB*/, 3 /*nT*/, false /*fDF*/); - f() { - for ( i = 0; - i < m; - /* c */ i++ ) { - a = b; - } - } - } - - { - f1(/*comment*/); - f2(); - } - - { - do { - if (foo) { - } else - ; - } while (foo); - foo(); // was wrong - } - - int x; // no extra indent because of the ; - void func() - { - } - - char *tab[] = {"aaa", - "};", /* }; */ NULL} - int indented; - {} - - char *a[] = {"aaa", "bbb", - "ccc", NULL}; - // here - - char *tab[] = {"aaa", - "xx", /* xx */}; /* asdf */ - int not_indented; - - { - do { - switch (bla) - { - case 1: if (foo) - bar; - } - } while (boo); - wrong; - } - - int foo, - bar; - int foo; - - #if defined(foo) \ - && defined(bar) - char * xx = "asdf\ - foo\ - bor"; - int x; - - char *foo = "asdf\ - asdf\ - asdf", - *bar; - - void f() - { - #if defined(foo) \ - && defined(bar) - char *foo = "asdf\ - asdf\ - asdf", - *bar; - { - int i; - char *foo = "asdf\ - asdf\ - asdf", - *bar; - } - #endif - } - #endif - - int y; // comment - // comment - - // comment - - { - Constructor(int a, - int b ) : BaseClass(a) - { - } - } - - void foo() - { - char one, - two; - struct bla piet, - jan; - enum foo kees, - jannie; - static unsigned sdf, - krap; - unsigned int piet, - jan; - int - kees, - jan; - } - - { - t(int f, - int d); // ) - d(); - } - - Constructor::Constructor(int a, - int b - ) : - BaseClass(a, - b, - c), - mMember(b), - { - } - - Constructor::Constructor(int a, - int b ) : - BaseClass(a) - { - } - - Constructor::Constructor(int a, - int b ) /*x*/ : /*x*/ BaseClass(a), - member(b) - { - } - - A::A(int a, int b) - : aa(a), - bb(b), - cc(c) - { - } - - class CAbc : - public BaseClass1, - protected BaseClass2 - { - int Test() { return FALSE; } - int Test1() { return TRUE; } - - CAbc(int a, int b ) : - BaseClass(a) - { - switch(xxx) - { - case abc: - asdf(); - break; - - case 999: - baer(); - break; - } - } - - public: // <-- this was incoreectly indented before!! - void testfall(); - protected: - void testfall(); - }; - - class CAbc : public BaseClass1, - protected BaseClass2 - { - }; - - static struct - { - int a; - int b; - } variable[COUNT] = - { - { - 123, - 456 - }, - { - 123, - 456 - } - }; - - static struct - { - int a; - int b; - } variable[COUNT] = - { - { 123, 456 }, - { 123, 456 } - }; - - void asdf() /* ind_maxparen may cause trouble here */ - { - if ((0 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1)) break; - } - - foo() - { - a = cond ? foo() : asdf - + asdf; - - a = cond ? - foo() : asdf - + asdf; - } - - int main(void) - { - if (a) - if (b) - 2; - else 3; - next_line_of_code(); - } - - barry() - { - Foo::Foo (int one, - int two) - : something(4) - {} - } - - barry() - { - Foo::Foo (int one, int two) - : something(4) - {} - } - - Constructor::Constructor(int a, - int b - ) : - BaseClass(a, - b, - c), - mMember(b) - { - } - int main () - { - if (lala) - do - ++(*lolo); - while (lili - && lele); - lulu; - } - - int main () - { - switch (c) - { - case 'c': if (cond) - { - } - } - } - - main() - { - (void) MyFancyFuasdfadsfnction( - argument); - } - - main() - { - char foo[] = "/*"; - /* as - df */ - hello - } - - /* valid namespaces with normal indent */ - namespace - { - { - 111111111111; - } - } - namespace /* test */ - { - 11111111111111111; - } - namespace // test - { - 111111111111111111; - } - namespace - { - 111111111111111111; - } - namespace test - { - 111111111111111111; - } - namespace{ - 111111111111111111; - } - namespace test{ - 111111111111111111; - } - namespace { - 111111111111111111; - } - namespace test { - 111111111111111111; - namespace test2 { - 22222222222222222; - } - } - - /* invalid namespaces use block indent */ - namespace test test2 { - 111111111111111111111; - } - namespace11111111111 { - 111111111111; - } - namespace() { - 1111111111111; - } - namespace() - { - 111111111111111111; - } - namespace test test2 - { - 1111111111111111111; - } - namespace111111111 - { - 111111111111111111; - } - void getstring() { - /* Raw strings */ - const char* s = R"( - test { - # comment - field: 123 - } - )"; - } - void getstring() { - const char* s = R"foo( - test { - # comment - field: 123 - } - )foo"; - } - - { - int a[4] = { - [0] = 0, - [1] = 1, - [2] = 2, - [3] = 3, - }; - } - - { - a = b[2] - + 3; - } - - { - if (1) - /* aaaaa - * bbbbb - */ - a = 1; - } - - void func() - { - switch (foo) - { - case (bar): - if (baz()) - quux(); - break; - case (shmoo): - if (!bar) - { - } - case (foo1): - switch (bar) - { - case baz: - baz_f(); - break; - } - break; - default: - baz(); - baz(); - break; - } - } - - /* end of AUTO */ - ]=]) - - feed_command('/start of AUTO') - feed('=/end of AUTO<cr>') - - expect([=[ - - /* start of AUTO matically checked vim: set ts=4 : */ - { - if (test) - cmd1; - cmd2; - } - - { - if (test) - cmd1; - else - cmd2; - } - - { - if (test) - { - cmd1; - cmd2; - } - } - - { - if (test) - { - cmd1; - else - } - } - - { - while (this) - if (test) - cmd1; - cmd2; - } - - { - while (this) - if (test) - cmd1; - else - cmd2; - } - - { - if (test) - { - cmd; - } - - if (test) - cmd; - } - - { - if (test) { - cmd; - } - - if (test) cmd; - } - - { - cmd1; - for (blah) - while (this) - if (test) - cmd2; - cmd3; - } - - { - cmd1; - for (blah) - while (this) - if (test) - cmd2; - cmd3; - - if (test) - { - cmd1; - cmd2; - cmd3; - } - } - - - /* Test for 'cindent' do/while mixed with if/else: */ - - { - do - if (asdf) - asdfasd; - while (cond); - - do - if (asdf) - while (asdf) - asdf; - while (asdf); - } - - /* Test for 'cindent' with two ) on a continuation line */ - { - if (asdfasdf;asldkfj asdlkfj as;ldkfj sal;d - aal;sdkjf ( ;asldfkja;sldfk - al;sdjfka ;slkdf ) sa;ldkjfsa dlk;) - line up here; - } - - - /* C++ tests: */ - - // foo() these three lines should remain in column 0 - // { - // } - - /* Test for continuation and unterminated lines: */ - { - i = 99 + 14325 + - 21345 + - 21345 + - 21345 + ( 21345 + - 21345) + - 2345 + - 1234; - c = 1; - } - - /* - testje for indent with empty line - - here */ - - { - if (testing && - not a joke || - line up here) - hay; - if (testing && - (not a joke || testing - )line up here) - hay; - if (testing && - (not a joke || testing - line up here)) - hay; - } - - - { - switch (c) - { - case xx: - do - if (asdf) - do - asdfasdf; - while (asdf); - else - asdfasdf; - while (cond); - case yy: - case xx: - case zz: - testing; - } - } - - { - if (cond) { - foo; - } - else - { - bar; - } - } - - { - if (alskdfj ;alsdkfjal;skdjf (;sadlkfsa ;dlkf j;alksdfj ;alskdjf - alsdkfj (asldk;fj - awith cino=(0 ;lf this one goes to below the paren with == - ;laksjfd ;lsakdjf ;alskdf asd) - asdfasdf;))) - asdfasdf; - } - - int - func(a, b) - int a; - int c; - { - if (c1 && (c2 || - c3)) - foo; - if (c1 && - (c2 || c3) - ) - } - - { - while (asd) - { - if (asdf) - if (test) - if (that) - { - if (asdf) - do - cdasd; - while (as - df); - } - else - if (asdf) - asdf; - else - asdf; - asdf; - } - } - - { - s = "/*"; b = ';' - s = "/*"; b = ';'; - a = b; - } - - { - switch (a) - { - case a: - switch (t) - { - case 1: - cmd; - break; - case 2: - cmd; - break; - } - cmd; - break; - case b: - { - int i; - cmd; - } - break; - case c: { - int i; - cmd; - } - case d: if (cond && - test) { /* this line doesn't work right */ - int i; - cmd; - } - break; - } - } - - { - if (!(vim_strchr(p_cpo, CPO_BUFOPTGLOB) != NULL && entering) && - (bp_to->b_p_initialized || - (!entering && vim_strchr(p_cpo, CPO_BUFOPT) != NULL))) - return; - label : - asdf = asdf ? - asdf : asdf; - asdf = asdf ? - asdf: asdf; - } - - /* Special Comments : This function has the added complexity (compared */ - /* : to addtolist) of having to check for a detail */ - /* : texture and add that to the list first. */ - - char *(array[100]) = { - "testje", - "foo", - "bar", - } - - enum soppie - { - yes = 0, - no, - maybe - }; - - typedef enum soppie - { - yes = 0, - no, - maybe - }; - - static enum - { - yes = 0, - no, - maybe - } soppie; - - public static enum - { - yes = 0, - no, - maybe - } soppie; - - static private enum - { - yes = 0, - no, - maybe - } soppie; - - { - int a, - b; - } - - { - struct Type - { - int i; - char *str; - } var[] = - { - 0, "zero", - 1, "one", - 2, "two", - 3, "three" - }; - - float matrix[3][3] = - { - { - 0, - 1, - 2 - }, - { - 3, - 4, - 5 - }, - { - 6, - 7, - 8 - } - }; - } - - { - /* blah ( blah */ - /* where does this go? */ - - /* blah ( blah */ - cmd; - - func(arg1, - /* comment */ - arg2); - a; - { - b; - { - c; /* Hey, NOW it indents?! */ - } - } - - { - func(arg1, - arg2, - arg3); - /* Hey, what am I doing here? Is this coz of the ","? */ - } - } - - main () - { - if (cond) - { - a = b; - } - if (cond) { - a = c; - } - if (cond) - a = d; - return; - } - - { - case 2: if (asdf && - asdfasdf) - aasdf; - a = 9; - case 3: if (asdf) - aasdf; - a = 9; - case 4: x = 1; - y = 2; - - label: if (asdf) - here; - - label: if (asdf && - asdfasdf) - { - } - - label: if (asdf && - asdfasdf) { - there; - } - - label: if (asdf && - asdfasdf) - there; - } - - { - /* - hello with ":set comments= cino=c5" - */ - - /* - hello with ":set comments= cino=" - */ - } - - - { - if (a < b) { - a = a + 1; - } else - a = a + 2; - - if (a) - do { - testing; - } while (asdfasdf); - a = b + 1; - asdfasdf - } - - { - for ( int i = 0; - i < 10; i++ ) - { - } - i = 0; - } - - class bob - { - int foo() {return 1;} - int bar; - } - - main() - { - while(1) - if (foo) - { - bar; - } - else { - asdf; - } - misplacedline; - } - - { - if (clipboard.state == SELECT_DONE - && ((row == clipboard.start.lnum - && col >= clipboard.start.col) - || row > clipboard.start.lnum)) - } - - { - if (1) {i += 4;} - where_am_i; - return 0; - } - - { - { - } // sdf(asdf - if (asdf) - asd; - } - - { - label1: - label2: - } - - { - int fooRet = foo(pBar1, false /*fKB*/, - true /*fPTB*/, 3 /*nT*/, false /*fDF*/); - f() { - for ( i = 0; - i < m; - /* c */ i++ ) { - a = b; - } - } - } - - { - f1(/*comment*/); - f2(); - } - - { - do { - if (foo) { - } else - ; - } while (foo); - foo(); // was wrong - } - - int x; // no extra indent because of the ; - void func() - { - } - - char *tab[] = {"aaa", - "};", /* }; */ NULL} - int indented; - {} - - char *a[] = {"aaa", "bbb", - "ccc", NULL}; - // here - - char *tab[] = {"aaa", - "xx", /* xx */}; /* asdf */ - int not_indented; - - { - do { - switch (bla) - { - case 1: if (foo) - bar; - } - } while (boo); - wrong; - } - - int foo, - bar; - int foo; - - #if defined(foo) \ - && defined(bar) - char * xx = "asdf\ - foo\ - bor"; - int x; - - char *foo = "asdf\ - asdf\ - asdf", - *bar; - - void f() - { - #if defined(foo) \ - && defined(bar) - char *foo = "asdf\ - asdf\ - asdf", - *bar; - { - int i; - char *foo = "asdf\ - asdf\ - asdf", - *bar; - } - #endif - } - #endif - - int y; // comment - // comment - - // comment - - { - Constructor(int a, - int b ) : BaseClass(a) - { - } - } - - void foo() - { - char one, - two; - struct bla piet, - jan; - enum foo kees, - jannie; - static unsigned sdf, - krap; - unsigned int piet, - jan; - int - kees, - jan; - } - - { - t(int f, - int d); // ) - d(); - } - - Constructor::Constructor(int a, - int b - ) : - BaseClass(a, - b, - c), - mMember(b), - { - } - - Constructor::Constructor(int a, - int b ) : - BaseClass(a) - { - } - - Constructor::Constructor(int a, - int b ) /*x*/ : /*x*/ BaseClass(a), - member(b) - { - } - - A::A(int a, int b) - : aa(a), - bb(b), - cc(c) - { - } - - class CAbc : - public BaseClass1, - protected BaseClass2 - { - int Test() { return FALSE; } - int Test1() { return TRUE; } - - CAbc(int a, int b ) : - BaseClass(a) - { - switch(xxx) - { - case abc: - asdf(); - break; - - case 999: - baer(); - break; - } - } - - public: // <-- this was incoreectly indented before!! - void testfall(); - protected: - void testfall(); - }; - - class CAbc : public BaseClass1, - protected BaseClass2 - { - }; - - static struct - { - int a; - int b; - } variable[COUNT] = - { - { - 123, - 456 - }, - { - 123, - 456 - } - }; - - static struct - { - int a; - int b; - } variable[COUNT] = - { - { 123, 456 }, - { 123, 456 } - }; - - void asdf() /* ind_maxparen may cause trouble here */ - { - if ((0 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1 - && 1)) break; - } - - foo() - { - a = cond ? foo() : asdf - + asdf; - - a = cond ? - foo() : asdf - + asdf; - } - - int main(void) - { - if (a) - if (b) - 2; - else 3; - next_line_of_code(); - } - - barry() - { - Foo::Foo (int one, - int two) - : something(4) - {} - } - - barry() - { - Foo::Foo (int one, int two) - : something(4) - {} - } - - Constructor::Constructor(int a, - int b - ) : - BaseClass(a, - b, - c), - mMember(b) - { - } - int main () - { - if (lala) - do - ++(*lolo); - while (lili - && lele); - lulu; - } - - int main () - { - switch (c) - { - case 'c': if (cond) - { - } - } - } - - main() - { - (void) MyFancyFuasdfadsfnction( - argument); - } - - main() - { - char foo[] = "/*"; - /* as - df */ - hello - } - - /* valid namespaces with normal indent */ - namespace - { - { - 111111111111; - } - } - namespace /* test */ - { - 11111111111111111; - } - namespace // test - { - 111111111111111111; - } - namespace - { - 111111111111111111; - } - namespace test - { - 111111111111111111; - } - namespace{ - 111111111111111111; - } - namespace test{ - 111111111111111111; - } - namespace { - 111111111111111111; - } - namespace test { - 111111111111111111; - namespace test2 { - 22222222222222222; - } - } - - /* invalid namespaces use block indent */ - namespace test test2 { - 111111111111111111111; - } - namespace11111111111 { - 111111111111; - } - namespace() { - 1111111111111; - } - namespace() - { - 111111111111111111; - } - namespace test test2 - { - 1111111111111111111; - } - namespace111111111 - { - 111111111111111111; - } - void getstring() { - /* Raw strings */ - const char* s = R"( - test { - # comment - field: 123 - } - )"; - } - void getstring() { - const char* s = R"foo( - test { - # comment - field: 123 - } - )foo"; - } - - { - int a[4] = { - [0] = 0, - [1] = 1, - [2] = 2, - [3] = 3, - }; - } - - { - a = b[2] - + 3; - } - - { - if (1) - /* aaaaa - * bbbbb - */ - a = 1; - } - - void func() - { - switch (foo) - { - case (bar): - if (baz()) - quux(); - break; - case (shmoo): - if (!bar) - { - } - case (foo1): - switch (bar) - { - case baz: - baz_f(); - break; - } - break; - default: - baz(); - baz(); - break; - } - } - - /* end of AUTO */ - ]=]) - end) - - it('2 is working', function() - insert_([=[ - - { - - /* this is - * a real serious important big - * comment - */ - /* insert " about life, the universe, and the rest" after "serious" */ - } - ]=]) - - feed_command('set tw=0 noai fo=croq') - feed_command('let &wm = &columns - 20') - feed_command('/serious/e') - feed('a about life, the universe, and the rest<esc>') - - expect([=[ - - { - - /* this is - * a real serious - * about life, the - * universe, and the - * rest important big - * comment - */ - /* insert " about life, the universe, and the rest" after "serious" */ - } - ]=]) - end) - - it('3 is working', function() - insert_([=[ - - { - /* - * Testing for comments, without 'cin' set - */ - - /* - * what happens here? - */ - - /* - the end of the comment, try inserting a line below */ - - /* how about - this one */ - } - ]=]) - - feed_command('set nocin') - feed_command('/comments') - feed('joabout life<esc>/happens<cr>') - feed('jothere<esc>/below<cr>') - feed('oline<esc>/this<cr>') - feed('Ohello<esc>') - - expect([=[ - - { - /* - * Testing for comments, without 'cin' set - */ - about life - - /* - * what happens here? - */ - there - - /* - the end of the comment, try inserting a line below */ - line - - /* how about - hello - this one */ - } - ]=]) - end) - - it('4 is working', function() - insert_([=[ - - { - var = this + that + vec[0] * vec[0] - + vec[1] * vec[1] - + vec2[2] * vec[2]; - } - ]=]) - feed_command('set cin') - feed_command('/vec2') - feed('==<cr>') - - expect([=[ - - { - var = this + that + vec[0] * vec[0] - + vec[1] * vec[1] - + vec2[2] * vec[2]; - } - ]=]) - end) - - it('5 is working', function() - insert_([=[ - - { - asdf asdflkajds f; - if (tes & ting) { - asdf asdf asdf ; - asdfa sdf asdf; - } - testing1; - if (tes & ting) - { - asdf asdf asdf ; - asdfa sdf asdf; - } - testing2; - } - ]=]) - - feed_command('set cin') - feed_command('set cino=}4') - feed_command('/testing1') - feed('k2==/testing2<cr>') - feed('k2==<cr>') - - expect([=[ - - { - asdf asdflkajds f; - if (tes & ting) { - asdf asdf asdf ; - asdfa sdf asdf; - } - testing1; - if (tes & ting) - { - asdf asdf asdf ; - asdfa sdf asdf; - } - testing2; - } - ]=]) - end) - - it('6 is working', function() - insert_([=[ - - main ( int first_par, /* - * Comment for - * first par - */ - int second_par /* - * Comment for - * second par - */ - ) - { - func( first_par, /* - * Comment for - * first par - */ - second_par /* - * Comment for - * second par - */ - ); - - } - ]=]) - - feed_command('set cin') - feed_command('set cino=(0,)20') - feed_command('/main') - feed('=][<cr>') - - expect([=[ - - main ( int first_par, /* - * Comment for - * first par - */ - int second_par /* - * Comment for - * second par - */ - ) - { - func( first_par, /* - * Comment for - * first par - */ - second_par /* - * Comment for - * second par - */ - ); - - } - ]=]) - end) - - it('7 is working', function() - insert_([=[ - - main(void) - { - /* Make sure that cino=X0s is not parsed like cino=Xs. */ - if (cond) - foo(); - else - { - bar(); - } - } - ]=]) - - feed_command('set cin') - feed_command('set cino=es,n0s') - feed_command('/main') - feed('=][<cr>') - - expect([=[ - - main(void) - { - /* Make sure that cino=X0s is not parsed like cino=Xs. */ - if (cond) - foo(); - else - { - bar(); - } - } - ]=]) - end) - - it('8 is working', function() - insert_([=[ - - { - do - { - if () - { - if () - asdf; - else - asdf; - } - } while (); - cmd; /* this should go under the } */ - } - ]=]) - - feed_command('set cin') - feed_command('set cino=') - feed(']]=][<cr>') - - expect([=[ - - { - do - { - if () - { - if () - asdf; - else - asdf; - } - } while (); - cmd; /* this should go under the } */ - } - ]=]) - end) - - it('9 is working', function() - insert_([=[ - - void f() - { - if ( k() ) { - l(); - - } else { /* Start (two words) end */ - m(); - } - - n(); - } - ]=]) - - feed(']]=][<cr>') - - expect([=[ - - void f() - { - if ( k() ) { - l(); - - } else { /* Start (two words) end */ - m(); - } - - n(); - } - ]=]) - end) - - it('10 is working', function() - -- This is nasty. This is the only test case where the buffer content - -- differs from the original. Why? Proper behaviour of this test depends on - -- the fact that the setup code contains an (unbalanced) opening curly - -- bracket in "set cino={s,e-s". This bracket actually affects the outcome - -- of the test: without it the curly bracket under "void f()" would not be - -- indented properly. And that's why we've had to add one explicitly. - insert_([=[ - { <= THIS IS THE CURLY BRACKET EXPLAINED IN THE COMMENT. - - void f() - { - if ( k() ) - { - l(); - } else { /* Start (two words) end */ - m(); - } - n(); /* should be under the if () */ - } - ]=]) - - feed_command('set cino={s,e-s') - feed(']]=][<cr>') - - expect([=[ - { <= THIS IS THE CURLY BRACKET EXPLAINED IN THE COMMENT. - - void f() - { - if ( k() ) - { - l(); - } else { /* Start (two words) end */ - m(); - } - n(); /* should be under the if () */ - } - ]=]) - end) - - it('11 is working', function() - insert_([=[ - - void bar(void) - { - static array[2][2] = - { - { 1, 2 }, - { 3, 4 }, - } - - while (a) - { - foo(&a); - } - - { - int a; - { - a = a + 1; - } - } - b = a; - } - - void func(void) - { - a = 1; - { - b = 2; - } - c = 3; - d = 4; - } - /* foo */ - ]=]) - - feed_command('set cino={s,fs') - feed(']]=/ foo<cr>') - - expect([=[ - - void bar(void) - { - static array[2][2] = - { - { 1, 2 }, - { 3, 4 }, - } - - while (a) - { - foo(&a); - } - - { - int a; - { - a = a + 1; - } - } - b = a; - } - - void func(void) - { - a = 1; - { - b = 2; - } - c = 3; - d = 4; - } - /* foo */ - ]=]) - end) - - it('12 is working', function() - insert_([=[ - - a() - { - do { - a = a + - a; - } while ( a ); /* add text under this line */ - if ( a ) - a; - } - ]=]) - - feed_command('set cino=') - feed_command('/while') - feed('ohere<esc>') - - expect([=[ - - a() - { - do { - a = a + - a; - } while ( a ); /* add text under this line */ - here - if ( a ) - a; - } - ]=]) - end) - - it('13 is working', function() - insert_([=[ - - a() - { - label1: - /* hmm */ - // comment - } - ]=]) - - feed_command('set cino= com=') - feed_command('/comment') - feed('olabel2: b();<cr>label3 /* post */:<cr>/* pre */ label4:<cr>f(/*com*/);<cr>if (/*com*/)<cr>cmd();<esc>') - - expect([=[ - - a() - { - label1: - /* hmm */ - // comment - label2: b(); - label3 /* post */: - /* pre */ label4: - f(/*com*/); - if (/*com*/) - cmd(); - } - ]=]) - end) - - it('14 is working', function() - insert_([=[ - - /* - * A simple comment - */ - - /* - ** A different comment - */ - ]=]) - - feed_command('set comments& comments^=s:/*,m:**,ex:*/') - feed_command('/simple') - feed('=5j<cr>') - - expect([=[ - - /* - * A simple comment - */ - - /* - ** A different comment - */ - ]=]) - end) - - it('15 is working', function() - insert_([=[ - - - void f() - { - - /********* - A comment. - *********/ - } - ]=]) - - feed_command('set cino=c0') - feed_command('set comments& comments-=s1:/* comments^=s0:/*') - feed('2kdd]]=][<cr>') - - expect([=[ - - void f() - { - - /********* - A comment. - *********/ - } - ]=]) - end) - - it('16 is working', function() - insert_([=[ - - - void f() - { - - /********* - A comment. - *********/ - } - ]=]) - - feed_command('set cino=c0,C1') - feed_command('set comments& comments-=s1:/* comments^=s0:/*') - feed('2kdd]]=][<cr>') - - expect([=[ - - void f() - { - - /********* - A comment. - *********/ - } - ]=]) - end) - - it('17 is working', function() - insert_([=[ - - void f() - { - c = c1 && - ( - c2 || - c3 - ) && c4; - } - ]=]) - - feed_command('set cino=') - feed(']]=][<cr>') - - expect([=[ - - void f() - { - c = c1 && - ( - c2 || - c3 - ) && c4; - } - ]=]) - end) - - it('18 is working', function() - insert_([=[ - - - void f() - { - c = c1 && - ( - c2 || - c3 - ) && c4; - } - ]=]) - - feed_command('set cino=(s') - feed('2kdd]]=][<cr>') - - expect([=[ - - void f() - { - c = c1 && - ( - c2 || - c3 - ) && c4; - } - ]=]) - end) - - it('19 is working', function() - insert_([=[ - - - void f() - { - c = c1 && - ( - c2 || - c3 - ) && c4; - } - ]=]) - - feed_command('set cino=(s,U1 ') - feed('2kdd]]=][<cr>') - - expect([=[ - - void f() - { - c = c1 && - ( - c2 || - c3 - ) && c4; - } - ]=]) - end) - - it('20 is working', function() - insert_([=[ - - - void f() - { - if ( c1 - && ( c2 - || c3)) - foo; - } - ]=]) - - feed_command('set cino=(0') - feed('2kdd]]=][<cr>') - - expect([=[ - - void f() - { - if ( c1 - && ( c2 - || c3)) - foo; - } - ]=]) - end) - - it('21 is working', function() - insert_([=[ - - - void f() - { - if ( c1 - && ( c2 - || c3)) - foo; - } - ]=]) - - feed_command('set cino=(0,w1 ') - feed('2kdd]]=][<cr>') - - expect([=[ - - void f() - { - if ( c1 - && ( c2 - || c3)) - foo; - } - ]=]) - end) - - it('22 is working', function() - insert_([=[ - - - void f() - { - c = c1 && ( - c2 || - c3 - ) && c4; - if ( - c1 && c2 - ) - foo; - } - ]=]) - - feed_command('set cino=(s') - feed('2kdd]]=][<cr>') - - expect([=[ - - void f() - { - c = c1 && ( - c2 || - c3 - ) && c4; - if ( - c1 && c2 - ) - foo; - } - ]=]) - end) - - it('23 is working', function() - insert_([=[ - - - void f() - { - c = c1 && ( - c2 || - c3 - ) && c4; - if ( - c1 && c2 - ) - foo; - } - ]=]) - - feed_command('set cino=(s,m1 ') - feed('2kdd]]=][<cr>') - - expect([=[ - - void f() - { - c = c1 && ( - c2 || - c3 - ) && c4; - if ( - c1 && c2 - ) - foo; - } - ]=]) - end) - - it('24 is working', function() - insert_([=[ - - - void f() - { - switch (x) - { - case 1: - a = b; - break; - default: - a = 0; - break; - } - } - ]=]) - - feed_command('set cino=b1') - feed('2kdd]]=][<cr>') - - expect([=[ - - void f() - { - switch (x) - { - case 1: - a = b; - break; - default: - a = 0; - break; - } - } - ]=]) - end) - - it('25 is working', function() - insert_([=[ - - - void f() - { - invokeme( - argu, - ment); - invokeme( - argu, - ment - ); - invokeme(argu, - ment - ); - } - ]=]) - - feed_command('set cino=(0,W5') - feed('2kdd]]=][<cr>') - - expect([=[ - - void f() - { - invokeme( - argu, - ment); - invokeme( - argu, - ment - ); - invokeme(argu, - ment - ); - } - ]=]) - end) - - it('26 is working', function() - insert_([=[ - - - void f() - { - statement; - // comment 1 - // comment 2 - } - ]=]) - - feed_command('set cino=/6') - feed('2kdd]]=][<cr>') - - expect([=[ - - void f() - { - statement; - // comment 1 - // comment 2 - } - ]=]) - end) - - it('27 is working', function() - insert_([=[ - - - void f() - { - statement; - // comment 1 - // comment 2 - } - ]=]) - - feed_command('set cino=') - feed('2kdd]]/comment 1/+1<cr>') - feed('==<cr>') - - expect([=[ - - void f() - { - statement; - // comment 1 - // comment 2 - } - ]=]) - end) - - it('28 is working', function() - insert_([=[ - - - class CAbc - { - int Test() { return FALSE; } - - public: // comment - void testfall(); - protected: - void testfall(); - }; - ]=]) - - feed_command('set cino=g0') - feed('2kdd]]=][<cr>') - - expect([=[ - - class CAbc - { - int Test() { return FALSE; } - - public: // comment - void testfall(); - protected: - void testfall(); - }; - ]=]) - end) - - it('29 is working', function() - insert_([=[ - - - class Foo : public Bar - { - public: - virtual void method1(void) = 0; - virtual void method2(int arg1, - int arg2, - int arg3) = 0; - }; - ]=]) - - feed_command('set cino=(0,gs,hs') - feed('2kdd]]=][<cr>') - - expect([=[ - - class Foo : public Bar - { - public: - virtual void method1(void) = 0; - virtual void method2(int arg1, - int arg2, - int arg3) = 0; - }; - ]=]) - end) - - it('30 is working', function() - insert_([=[ - - - void - foo() - { - if (a) - { - } else - asdf; - } - ]=]) - - feed_command('set cino=+20') - feed('2kdd]]=][<cr>') - - expect([=[ - - void - foo() - { - if (a) - { - } else - asdf; - } - ]=]) - end) - - it('31 is working', function() - insert_([=[ - - - { - averylongfunctionnamelongfunctionnameaverylongfunctionname()->asd( - asdasdf, - func(asdf, - asdfadsf), - asdfasdf - ); - - /* those are ugly, but consequent */ - - func()->asd(asdasdf, - averylongfunctionname( - abc, - dec)->averylongfunctionname( - asdfadsf, - asdfasdf, - asdfasdf, - ), - func(asdfadf, - asdfasdf - ), - asdasdf - ); - - averylongfunctionnameaverylongfunctionnameavery()->asd(fasdf( - abc, - dec)->asdfasdfasdf( - asdfadsf, - asdfasdf, - asdfasdf, - ), - func(asdfadf, - asdfasdf), - asdasdf - ); - } - ]=]) - - feed_command('set cino=(0,W2s') - feed('2kdd]]=][<cr>') - - expect([=[ - - { - averylongfunctionnamelongfunctionnameaverylongfunctionname()->asd( - asdasdf, - func(asdf, - asdfadsf), - asdfasdf - ); - - /* those are ugly, but consequent */ - - func()->asd(asdasdf, - averylongfunctionname( - abc, - dec)->averylongfunctionname( - asdfadsf, - asdfasdf, - asdfasdf, - ), - func(asdfadf, - asdfasdf - ), - asdasdf - ); - - averylongfunctionnameaverylongfunctionnameavery()->asd(fasdf( - abc, - dec)->asdfasdfasdf( - asdfadsf, - asdfasdf, - asdfasdf, - ), - func(asdfadf, - asdfasdf), - asdasdf - ); - } - ]=]) - end) - - it('32 is working', function() - insert_([=[ - - - int main () - { - if (cond1 && - cond2 - ) - foo; - } - ]=]) - - feed_command('set cino=M1') - feed('2kdd]]=][<cr>') - - expect([=[ - - int main () - { - if (cond1 && - cond2 - ) - foo; - } - ]=]) - end) - - it('33 is working', function() - insert_([=[ - - - void func(int a - #if defined(FOO) - , int b - , int c - #endif - ) - { - } - ]=]) - - feed_command('set cino=(0,ts') - feed('2kdd2j=][<cr>') - - expect([=[ - - void func(int a - #if defined(FOO) - , int b - , int c - #endif - ) - { - } - ]=]) - end) - - it('34 is working', function() - insert_([=[ - - - - void - func(int a - #if defined(FOO) - , int b - , int c - #endif - ) - { - } - ]=]) - - feed_command('set cino=(0') - feed('2kdd2j=][<cr>') - - expect([=[ - - - void - func(int a - #if defined(FOO) - , int b - , int c - #endif - ) - { - } - ]=]) - end) - - it('35 is working', function() - insert_([=[ - - - void func(void) - { - if(x==y) - if(y==z) - foo=1; - else { bar=1; - baz=2; - } - printf("Foo!\n"); - } - - void func1(void) - { - char* tab[] = {"foo", "bar", - "baz", "quux", - "this line used", "to be indented incorrectly"}; - foo(); - } - - void func2(void) - { - int tab[] = - {1, 2, - 3, 4, - 5, 6}; - - printf("This line used to be indented incorrectly.\n"); - } - - int foo[] - #ifdef BAR - - = { 1, 2, 3, - 4, 5, 6 } - - #endif - ; - int baz; - - void func3(void) - { - int tab[] = { - 1, 2, - 3, 4, - 5, 6}; - - printf("Don't you dare indent this line incorrectly!\n"); - } - - void - func4(a, b, - c) - int a; - int b; - int c; - { - } - - void - func5( - int a, - int b) - { - } - - void - func6( - int a) - { - } - ]=]) - - feed_command('set cino&') - feed('2kdd2j=7][<cr>') - - expect([=[ - - void func(void) - { - if(x==y) - if(y==z) - foo=1; - else { bar=1; - baz=2; - } - printf("Foo!\n"); - } - - void func1(void) - { - char* tab[] = {"foo", "bar", - "baz", "quux", - "this line used", "to be indented incorrectly"}; - foo(); - } - - void func2(void) - { - int tab[] = - {1, 2, - 3, 4, - 5, 6}; - - printf("This line used to be indented incorrectly.\n"); - } - - int foo[] - #ifdef BAR - - = { 1, 2, 3, - 4, 5, 6 } - - #endif - ; - int baz; - - void func3(void) - { - int tab[] = { - 1, 2, - 3, 4, - 5, 6}; - - printf("Don't you dare indent this line incorrectly!\n"); - } - - void - func4(a, b, - c) - int a; - int b; - int c; - { - } - - void - func5( - int a, - int b) - { - } - - void - func6( - int a) - { - } - ]=]) - end) - - it('36 is working', function() - insert_([=[ - - - void func(void) - { - int tab[] = - { - 1, 2, 3, - 4, 5, 6}; - - printf("Indent this line correctly!\n"); - - switch (foo) - { - case bar: - printf("bar"); - break; - case baz: { - printf("baz"); - break; - } - case quux: - printf("But don't break the indentation of this instruction\n"); - break; - } - } - ]=]) - - feed_command('set cino&') - feed_command('set cino+=l1') - feed('2kdd2j=][<cr>') - - expect([=[ - - void func(void) - { - int tab[] = - { - 1, 2, 3, - 4, 5, 6}; - - printf("Indent this line correctly!\n"); - - switch (foo) - { - case bar: - printf("bar"); - break; - case baz: { - printf("baz"); - break; - } - case quux: - printf("But don't break the indentation of this instruction\n"); - break; - } - } - ]=]) - end) - - it('37 is working', function() - insert_([=[ - - - void func(void) - { - cout << "a" - << "b" - << ") :" - << "c"; - } - ]=]) - - feed_command('set cino&') - feed('2kdd2j=][<cr>') - - expect([=[ - - void func(void) - { - cout << "a" - << "b" - << ") :" - << "c"; - } - ]=]) - end) - - it('38 is working', function() - insert_([=[ - - void func(void) - { - /* - * This is a comment. - */ - } - ]=]) - - feed_command('set com=s1:/*,m:*,ex:*/') - feed(']]3jofoo();<esc>') - - expect([=[ - - void func(void) - { - /* - * This is a comment. - */ - foo(); - } - ]=]) - end) - - it('39 is working', function() - insert_([=[ - - - void func(void) - { - for (int i = 0; i < 10; ++i) - if (i & 1) { - foo(1); - } else - foo(0); - baz(); - } - ]=]) - - feed_command('set cino&') - feed('2kdd2j=][<cr>') - - expect([=[ - - void func(void) - { - for (int i = 0; i < 10; ++i) - if (i & 1) { - foo(1); - } else - foo(0); - baz(); - } - ]=]) - end) - - it('40 is working', function() - insert_([=[ - - - void func(void) - { - if (condition1 - && condition2) - action(); - function(argument1 - && argument2); - - if (c1 && (c2 || - c3)) - foo; - if (c1 && - (c2 || c3)) - { - } - - if ( c1 - && ( c2 - || c3)) - foo; - func( c1 - && ( c2 - || c3)) - foo; - } - ]=]) - - feed_command('set cino=k2s,(0') - feed('2kdd3j=][<cr>') - - expect([=[ - - void func(void) - { - if (condition1 - && condition2) - action(); - function(argument1 - && argument2); - - if (c1 && (c2 || - c3)) - foo; - if (c1 && - (c2 || c3)) - { - } - - if ( c1 - && ( c2 - || c3)) - foo; - func( c1 - && ( c2 - || c3)) - foo; - } - ]=]) - end) - - it('41 is working', function() - insert_([=[ - - - void func(void) - { - if (condition1 - && condition2) - action(); - function(argument1 - && argument2); - - if (c1 && (c2 || - c3)) - foo; - if (c1 && - (c2 || c3)) - { - } - - if ( c1 - && ( c2 - || c3)) - foo; - func( c1 - && ( c2 - || c3)) - foo; - } - ]=]) - - feed_command('set cino=k2s,(s') - feed('2kdd3j=][<cr>') - - expect([=[ - - void func(void) - { - if (condition1 - && condition2) - action(); - function(argument1 - && argument2); - - if (c1 && (c2 || - c3)) - foo; - if (c1 && - (c2 || c3)) - { - } - - if ( c1 - && ( c2 - || c3)) - foo; - func( c1 - && ( c2 - || c3)) - foo; - } - ]=]) - end) - - it('42 is working', function() - insert_([=[ - - - void func(void) - { - if (condition1 - && condition2) - action(); - function(argument1 - && argument2); - - if (c1 && (c2 || - c3)) - foo; - if (c1 && - (c2 || c3)) - { - } - if (c123456789 - && (c22345 - || c3)) - printf("foo\n"); - - c = c1 && - ( - c2 || - c3 - ) && c4; - } - ]=]) - - feed_command('set cino=k2s,(s,U1') - feed('2kdd3j=][<cr>') - - expect([=[ - - void func(void) - { - if (condition1 - && condition2) - action(); - function(argument1 - && argument2); - - if (c1 && (c2 || - c3)) - foo; - if (c1 && - (c2 || c3)) - { - } - if (c123456789 - && (c22345 - || c3)) - printf("foo\n"); - - c = c1 && - ( - c2 || - c3 - ) && c4; - } - ]=]) - end) - - it('43 is working', function() - insert_([=[ - - - void func(void) - { - if (condition1 - && condition2) - action(); - function(argument1 - && argument2); - - if (c1 && (c2 || - c3)) - foo; - if (c1 && - (c2 || c3)) - { - } - if (c123456789 - && (c22345 - || c3)) - printf("foo\n"); - - if ( c1 - && ( c2 - || c3)) - foo; - - a_long_line( - argument, - argument); - a_short_line(argument, - argument); - } - ]=]) - - feed_command('set cino=k2s,(0,W4') - feed('2kdd3j=][<cr>') - - expect([=[ - - void func(void) - { - if (condition1 - && condition2) - action(); - function(argument1 - && argument2); - - if (c1 && (c2 || - c3)) - foo; - if (c1 && - (c2 || c3)) - { - } - if (c123456789 - && (c22345 - || c3)) - printf("foo\n"); - - if ( c1 - && ( c2 - || c3)) - foo; - - a_long_line( - argument, - argument); - a_short_line(argument, - argument); - } - ]=]) - end) - - it('44 is working', function() - insert_([=[ - - - void func(void) - { - if (condition1 - && condition2) - action(); - function(argument1 - && argument2); - - if (c1 && (c2 || - c3)) - foo; - if (c1 && - (c2 || c3)) - { - } - if (c123456789 - && (c22345 - || c3)) - printf("foo\n"); - } - ]=]) - - feed_command('set cino=k2s,u2') - feed('2kdd3j=][<cr>') - - expect([=[ - - void func(void) - { - if (condition1 - && condition2) - action(); - function(argument1 - && argument2); - - if (c1 && (c2 || - c3)) - foo; - if (c1 && - (c2 || c3)) - { - } - if (c123456789 - && (c22345 - || c3)) - printf("foo\n"); - } - ]=]) - end) - - it('45 is working', function() - insert_([=[ - - - void func(void) - { - if (condition1 - && condition2) - action(); - function(argument1 - && argument2); - - if (c1 && (c2 || - c3)) - foo; - if (c1 && - (c2 || c3)) - { - } - if (c123456789 - && (c22345 - || c3)) - printf("foo\n"); - - if ( c1 - && ( c2 - || c3)) - foo; - func( c1 - && ( c2 - || c3)) - foo; - } - ]=]) - - feed_command('set cino=k2s,(0,w1') - feed('2kdd3j=][<cr>') - - expect([=[ - - void func(void) - { - if (condition1 - && condition2) - action(); - function(argument1 - && argument2); - - if (c1 && (c2 || - c3)) - foo; - if (c1 && - (c2 || c3)) - { - } - if (c123456789 - && (c22345 - || c3)) - printf("foo\n"); - - if ( c1 - && ( c2 - || c3)) - foo; - func( c1 - && ( c2 - || c3)) - foo; - } - ]=]) - end) - - it('46 is working', function() - insert_([=[ - - - void func(void) - { - if (condition1 - && condition2) - action(); - function(argument1 - && argument2); - - if (c1 && (c2 || - c3)) - foo; - if (c1 && - (c2 || c3)) - { - } - } - ]=]) - - feed_command('set cino=k2,(s') - feed('2kdd3j=][<cr>') - - expect([=[ - - void func(void) - { - if (condition1 - && condition2) - action(); - function(argument1 - && argument2); - - if (c1 && (c2 || - c3)) - foo; - if (c1 && - (c2 || c3)) - { - } - } - ]=]) - end) - - it('47 is working', function() - insert_([=[ - - NAMESPACESTART - /* valid namespaces with normal indent */ - namespace - { - { - 111111111111; - } - } - namespace /* test */ - { - 11111111111111111; - } - namespace // test - { - 111111111111111111; - } - namespace - { - 111111111111111111; - } - namespace test - { - 111111111111111111; - } - namespace test::cpp17 - { - 111111111111111111; - } - namespace ::incorrectcpp17 - { - 111111111111111111; - } - namespace test::incorrectcpp17:: - { - 111111111111111111; - } - namespace test:incorrectcpp17 - { - 111111111111111111; - } - namespace test:::incorrectcpp17 - { - 111111111111111111; - } - namespace{ - 111111111111111111; - } - namespace test{ - 111111111111111111; - } - namespace { - 111111111111111111; - } - namespace test { - 111111111111111111; - namespace test2 { - 22222222222222222; - } - } - - /* invalid namespaces use block indent */ - namespace test test2 { - 111111111111111111111; - } - namespace11111111111 { - 111111111111; - } - namespace() { - 1111111111111; - } - namespace() - { - 111111111111111111; - } - namespace test test2 - { - 1111111111111111111; - } - namespace111111111 - { - 111111111111111111; - } - NAMESPACEEND - ]=]) - - feed_command('set cino=N-s') - feed_command('/^NAMESPACESTART') - feed('=/^NAMESPACEEND<cr>') - - expect([=[ - - NAMESPACESTART - /* valid namespaces with normal indent */ - namespace - { - { - 111111111111; - } - } - namespace /* test */ - { - 11111111111111111; - } - namespace // test - { - 111111111111111111; - } - namespace - { - 111111111111111111; - } - namespace test - { - 111111111111111111; - } - namespace test::cpp17 - { - 111111111111111111; - } - namespace ::incorrectcpp17 - { - 111111111111111111; - } - namespace test::incorrectcpp17:: - { - 111111111111111111; - } - namespace test:incorrectcpp17 - { - 111111111111111111; - } - namespace test:::incorrectcpp17 - { - 111111111111111111; - } - namespace{ - 111111111111111111; - } - namespace test{ - 111111111111111111; - } - namespace { - 111111111111111111; - } - namespace test { - 111111111111111111; - namespace test2 { - 22222222222222222; - } - } - - /* invalid namespaces use block indent */ - namespace test test2 { - 111111111111111111111; - } - namespace11111111111 { - 111111111111; - } - namespace() { - 1111111111111; - } - namespace() - { - 111111111111111111; - } - namespace test test2 - { - 1111111111111111111; - } - namespace111111111 - { - 111111111111111111; - } - NAMESPACEEND - ]=]) - end) - - it('48 is working', function() - insert_([=[ - - JSSTART - var bar = { - foo: { - that: this, - some: ok, - }, - "bar":{ - a : 2, - b: "123abc", - x: 4, - "y": 5 - } - } - JSEND - ]=]) - - feed_command('set cino=j1,J1') - feed_command('/^JSSTART') - feed('=/^JSEND<cr>') - - expect([=[ - - JSSTART - var bar = { - foo: { - that: this, - some: ok, - }, - "bar":{ - a : 2, - b: "123abc", - x: 4, - "y": 5 - } - } - JSEND - ]=]) - end) - - it('49 is working', function() - insert_([=[ - - JSSTART - var foo = [ - 1, - 2, - 3 - ]; - JSEND - ]=]) - - feed_command('set cino=j1,J1') - feed_command('/^JSSTART') - feed('=/^JSEND<cr>') - - expect([=[ - - JSSTART - var foo = [ - 1, - 2, - 3 - ]; - JSEND - ]=]) - end) - - it('50 is working', function() - insert_([=[ - - JSSTART - function bar() { - var foo = [ - 1, - 2, - 3 - ]; - } - JSEND - ]=]) - - feed_command('set cino=j1,J1') - feed_command('/^JSSTART') - feed('=/^JSEND<cr>') - - expect([=[ - - JSSTART - function bar() { - var foo = [ - 1, - 2, - 3 - ]; - } - JSEND - ]=]) - end) - - it('51 is working', function() - insert_([=[ - - JSSTART - (function($){ - - if (cond && - cond) { - stmt; - } - window.something.left = - (width - 50 + offset) + "px"; - var class_name='myclass'; - - function private_method() { - } - - var public_method={ - method: function(options,args){ - private_method(); - } - } - - function init(options) { - - $(this).data(class_name+'_public',$.extend({},{ - foo: 'bar', - bar: 2, - foobar: [ - 1, - 2, - 3 - ], - callback: function(){ - return true; - } - }, options||{})); - } - - $.fn[class_name]=function() { - - var _arguments=arguments; - return this.each(function(){ - - var options=$(this).data(class_name+'_public'); - if (!options) { - init.apply(this,_arguments); - - } else { - var method=public_method[_arguments[0]]; - - if (typeof(method)!='function') { - console.log(class_name+' has no method "'+_arguments[0]+'"'); - return false; - } - _arguments[0]=options; - method.apply(this,_arguments); - } - }); - } - - })(jQuery); - JSEND - ]=]) - - feed_command('set cino=j1,J1') - feed_command('/^JSSTART') - feed('=/^JSEND<cr>') - - expect([=[ - - JSSTART - (function($){ - - if (cond && - cond) { - stmt; - } - window.something.left = - (width - 50 + offset) + "px"; - var class_name='myclass'; - - function private_method() { - } - - var public_method={ - method: function(options,args){ - private_method(); - } - } - - function init(options) { - - $(this).data(class_name+'_public',$.extend({},{ - foo: 'bar', - bar: 2, - foobar: [ - 1, - 2, - 3 - ], - callback: function(){ - return true; - } - }, options||{})); - } - - $.fn[class_name]=function() { - - var _arguments=arguments; - return this.each(function(){ - - var options=$(this).data(class_name+'_public'); - if (!options) { - init.apply(this,_arguments); - - } else { - var method=public_method[_arguments[0]]; - - if (typeof(method)!='function') { - console.log(class_name+' has no method "'+_arguments[0]+'"'); - return false; - } - _arguments[0]=options; - method.apply(this,_arguments); - } - }); - } - - })(jQuery); - JSEND - ]=]) - end) - - it('52 is working', function() - insert_([=[ - - JSSTART - function init(options) { - $(this).data(class_name+'_public',$.extend({},{ - foo: 'bar', - bar: 2, - foobar: [ - 1, - 2, - 3 - ], - callback: function(){ - return true; - } - }, options||{})); - } - JSEND - ]=]) - - feed_command('set cino=j1,J1') - feed_command('/^JSSTART') - feed('=/^JSEND<cr>') - - expect([=[ - - JSSTART - function init(options) { - $(this).data(class_name+'_public',$.extend({},{ - foo: 'bar', - bar: 2, - foobar: [ - 1, - 2, - 3 - ], - callback: function(){ - return true; - } - }, options||{})); - } - JSEND - ]=]) - end) - - it('53 is working', function() - insert_([=[ - - JSSTART - (function($){ - function init(options) { - $(this).data(class_name+'_public',$.extend({},{ - foo: 'bar', - bar: 2, - foobar: [ - 1, - 2, - 3 - ], - callback: function(){ - return true; - } - }, options||{})); - } - })(jQuery); - JSEND - ]=]) - - feed_command('set cino=j1,J1') - feed_command('/^JSSTART') - feed('=/^JSEND<cr>') - - expect([=[ - - JSSTART - (function($){ - function init(options) { - $(this).data(class_name+'_public',$.extend({},{ - foo: 'bar', - bar: 2, - foobar: [ - 1, - 2, - 3 - ], - callback: function(){ - return true; - } - }, options||{})); - } - })(jQuery); - JSEND - ]=]) - end) - - it('javascript indent / vim-patch 7.4.670', function() - insert_([=[ - - JSSTART - // Results of JavaScript indent - // 1 - (function(){ - var a = [ - 'a', - 'b', - 'c', - 'd', - 'e', - 'f', - 'g', - 'h', - 'i' - ]; - }()) - - // 2 - (function(){ - var a = [ - 0 + - 5 * - 9 * - 'a', - 'b', - 0 + - 5 * - 9 * - 'c', - 'd', - 'e', - 'f', - 'g', - 'h', - 'i' - ]; - }()) - - // 3 - (function(){ - var a = [ - 0 + - // comment 1 - 5 * - /* comment 2 */ - 9 * - 'a', - 'b', - 0 + - 5 * - 9 * - 'c', - 'd', - 'e', - 'f', - 'g', - 'h', - 'i' - ]; - }()) - - // 4 - { - var a = [ - 0, - 1 - ]; - var b; - var c; - } - - // 5 - { - var a = [ - [ - 0 - ], - 2, - 3 - ]; - } - - // 6 - { - var a = [ - [ - 0, - 1 - ], - 2, - 3 - ]; - } - - // 7 - { - var a = [ - // [ - 0, - // 1 - // ], - 2, - 3 - ]; - } - - // 8 - var x = [ - (function(){ - var a, - b, - c, - d, - e, - f, - g, - h, - i; - }) - ]; - - // 9 - var a = [ - 0 + - 5 * - 9 * - 'a', - 'b', - 0 + - 5 * - 9 * - 'c', - 'd', - 'e', - 'f', - 'g', - 'h', - 'i' - ]; - - // 10 - var a, - b, - c, - d, - e, - f, - g, - h, - i; - JSEND - ]=]) - - -- :set cino=j1,J1,+2 - feed_command('set cino=j1,J1,+2') - feed_command('/^JSSTART') - feed('=/^JSEND<cr>') - - expect([=[ - - JSSTART - // Results of JavaScript indent - // 1 - (function(){ - var a = [ - 'a', - 'b', - 'c', - 'd', - 'e', - 'f', - 'g', - 'h', - 'i' - ]; - }()) - - // 2 - (function(){ - var a = [ - 0 + - 5 * - 9 * - 'a', - 'b', - 0 + - 5 * - 9 * - 'c', - 'd', - 'e', - 'f', - 'g', - 'h', - 'i' - ]; - }()) - - // 3 - (function(){ - var a = [ - 0 + - // comment 1 - 5 * - /* comment 2 */ - 9 * - 'a', - 'b', - 0 + - 5 * - 9 * - 'c', - 'd', - 'e', - 'f', - 'g', - 'h', - 'i' - ]; - }()) - - // 4 - { - var a = [ - 0, - 1 - ]; - var b; - var c; - } - - // 5 - { - var a = [ - [ - 0 - ], - 2, - 3 - ]; - } - - // 6 - { - var a = [ - [ - 0, - 1 - ], - 2, - 3 - ]; - } - - // 7 - { - var a = [ - // [ - 0, - // 1 - // ], - 2, - 3 - ]; - } - - // 8 - var x = [ - (function(){ - var a, - b, - c, - d, - e, - f, - g, - h, - i; - }) - ]; - - // 9 - var a = [ - 0 + - 5 * - 9 * - 'a', - 'b', - 0 + - 5 * - 9 * - 'c', - 'd', - 'e', - 'f', - 'g', - 'h', - 'i' - ]; - - // 10 - var a, - b, - c, - d, - e, - f, - g, - h, - i; - JSEND - ]=]) - end) - - it('line continuations in macros / vim-patch 8.0.0148', function() - insert_([=[ - /* start of define */ - { - } - #define AAA \ - BBB\ - CCC - - #define CNT \ - 1 + \ - 2 + \ - 4 - /* end of define */]=]) - - feed_command('set cino&') - feed_command('/start of define') - feed('=/end of define<cr>') - - expect([=[ - /* start of define */ - { - } - #define AAA \ - BBB\ - CCC - - #define CNT \ - 1 + \ - 2 + \ - 4 - /* end of define */]=]) - end) - - it('* immediately follows comment / vim-patch 8.0.1291', function() - insert_([=[ - { - a = second/*bug*/*line; - }]=]) - - feed_command('set cin cino&') - feed_command('/a = second') - feed('ox') - - expect([=[ - { - a = second/*bug*/*line; - x - }]=]) - end) -end) diff --git a/test/functional/legacy/008_autocommands_spec.lua b/test/functional/legacy/008_autocommands_spec.lua index 002f037d09..4088cd1644 100644 --- a/test/functional/legacy/008_autocommands_spec.lua +++ b/test/functional/legacy/008_autocommands_spec.lua @@ -6,6 +6,7 @@ local source = helpers.source local clear, command, expect, eq, eval = helpers.clear, helpers.command, helpers.expect, helpers.eq, helpers.eval local write_file, dedent = helpers.write_file, helpers.dedent local read_file = helpers.read_file +local expect_exit = helpers.expect_exit describe('autocommands that delete and unload buffers:', function() local test_file = 'Xtest-008_autocommands.out' @@ -78,7 +79,7 @@ describe('autocommands that delete and unload buffers:', function() command('silent! edit Xxx1') command('silent! edit Makefile') -- an existing file command('silent! split new2') - command('silent! quit') + expect_exit(command, 'silent! quit') eq('VimLeave done', string.match(read_file(test_file), "^%s*(.-)%s*$")) end) diff --git a/test/functional/legacy/012_directory_spec.lua b/test/functional/legacy/012_directory_spec.lua index f666e51469..dd207ca0b4 100644 --- a/test/functional/legacy/012_directory_spec.lua +++ b/test/functional/legacy/012_directory_spec.lua @@ -16,6 +16,7 @@ local insert = helpers.insert local command = helpers.command local write_file = helpers.write_file local curbufmeths = helpers.curbufmeths +local expect_exit = helpers.expect_exit local function ls_dir_sorted(dirname) local files = {} @@ -43,7 +44,7 @@ describe("'directory' option", function() clear() end) teardown(function() - command('qall!') + expect_exit(command, 'qall!') helpers.rmdir('Xtest.je') helpers.rmdir('Xtest2') os.remove('Xtest1') diff --git a/test/functional/legacy/031_close_commands_spec.lua b/test/functional/legacy/031_close_commands_spec.lua index 64c67c9882..d02b1a2049 100644 --- a/test/functional/legacy/031_close_commands_spec.lua +++ b/test/functional/legacy/031_close_commands_spec.lua @@ -17,6 +17,7 @@ local source = helpers.source local insert = helpers.insert local expect = helpers.expect local feed_command = helpers.feed_command +local expect_exit = helpers.expect_exit describe('Commands that close windows and/or buffers', function() local function cleanup() @@ -118,7 +119,7 @@ describe('Commands that close windows and/or buffers', function() feed_command('q!') feed('<CR>') expect('testtext 1') - source([[ + expect_exit(source, [[ q! " Now nvim should have exited throw "Oh, Not finished yet."]]) diff --git a/test/functional/legacy/arglist_spec.lua b/test/functional/legacy/arglist_spec.lua index 6a2e86ccb4..8379e426e0 100644 --- a/test/functional/legacy/arglist_spec.lua +++ b/test/functional/legacy/arglist_spec.lua @@ -1,8 +1,11 @@ -- Test argument list commands local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') local clear, command, eq = helpers.clear, helpers.command, helpers.eq local eval, exc_exec, neq = helpers.eval, helpers.exc_exec, helpers.neq +local feed = helpers.feed +local pcall_err = helpers.pcall_err describe('argument list commands', function() before_each(clear) @@ -17,7 +20,7 @@ describe('argument list commands', function() end local function assert_fails(cmd, err) - neq(exc_exec(cmd):find(err), nil) + neq(nil, exc_exec(cmd):find(err)) end it('test that argidx() works', function() @@ -206,7 +209,6 @@ describe('argument list commands', function() command('%argd') end) - it('test for autocommand that redefines the argument list, when doing ":all"', function() command('autocmd BufReadPost Xxx2 next Xxx2 Xxx1') command("call writefile(['test file Xxx1'], 'Xxx1')") @@ -234,4 +236,45 @@ describe('argument list commands', function() command('argdelete Xxx*') command('bwipe! Xxx1 Xxx2 Xxx3') end) + + it('quitting Vim with unedited files in the argument list throws E173', function() + command('set nomore') + command('args a b c') + eq('Vim(quit):E173: 2 more files to edit', pcall_err(command, 'quit')) + end) + + it(':confirm quit with unedited files in arglist', function() + local screen = Screen.new(60, 6) + screen:attach() + command('set nomore') + command('args a b c') + feed(':confirm quit\n') + screen:expect([[ + | + ~ | + | + :confirm quit | + 2 more files to edit. Quit anyway? | + [Y]es, (N)o: ^ | + ]]) + feed('N') + screen:expect([[ + ^ | + ~ | + ~ | + ~ | + ~ | + | + ]]) + feed(':confirm quit\n') + screen:expect([[ + | + ~ | + | + :confirm quit | + 2 more files to edit. Quit anyway? | + [Y]es, (N)o: ^ | + ]]) + feed('Y') + end) end) diff --git a/test/functional/legacy/assert_spec.lua b/test/functional/legacy/assert_spec.lua index c2b22472bf..4829a0bbe1 100644 --- a/test/functional/legacy/assert_spec.lua +++ b/test/functional/legacy/assert_spec.lua @@ -19,36 +19,10 @@ describe('assert function:', function() clear() end) - describe('assert_beeps', function() - it('works', function() - call('assert_beeps', 'normal h') - expected_empty() - call('assert_beeps', 'normal 0') - expected_errors({'command did not beep: normal 0'}) - end) - - it('can be used as a method', function() - local tmpname = source [[ - call assert_equal(0, 'normal h'->assert_beeps()) - call assert_equal(1, 'normal 0'->assert_beeps()) - ]] - expected_errors({tmpname .. ' line 2: command did not beep: normal 0'}) - end) - end) - -- assert_equal({expected}, {actual}, [, {msg}]) describe('assert_equal', function() it('should not change v:errors when expected is equal to actual', function() source([[ - let s = 'foo' - call assert_equal('foo', s) - let n = 4 - call assert_equal(4, n) - let l = [1, 2, 3] - call assert_equal([1, 2, 3], l) - call assert_equal(v:_null_list, v:_null_list) - call assert_equal(v:_null_list, []) - call assert_equal([], v:_null_list) fu Func() endfu let F1 = function('Func') @@ -98,30 +72,6 @@ describe('assert function:', function() eq('Vim(call):E724: unable to correctly dump variable with self-referencing container', exc_exec('call CheckAssert()')) end) - - it('can specify a message and get a message about what failed', function() - call('assert_equal', 'foo', 'bar', 'testing') - expected_errors({"testing: Expected 'foo' but got 'bar'"}) - end) - - it('should shorten a long message', function() - call ('assert_equal', 'XxxxxxxxxxxxxxxxxxxxxxX', 'XyyyyyyyyyyyyyyyyyyyyyyyyyX') - expected_errors({"Expected 'X\\[x occurs 21 times]X' but got 'X\\[y occurs 25 times]X'"}) - end) - end) - - -- assert_notequal({expected}, {actual}[, {msg}]) - describe('assert_notequal', function() - it('should not change v:errors when expected differs from actual', function() - eq(0, call('assert_notequal', 'foo', 4)) - eq(0, call('assert_notequal', {1, 2, 3}, 'foo')) - expected_empty() - end) - - it('should change v:errors when expected is equal to actual', function() - eq(1, call('assert_notequal', 'foo', 'foo')) - expected_errors({"Expected not equal to 'foo'"}) - end) end) -- assert_false({actual}, [, {msg}]) @@ -141,14 +91,6 @@ describe('assert function:', function() call('assert_false', {}) expected_errors({'Expected False but got []'}) end) - - it('can be used as a method', function() - local tmpname = source [[ - call assert_equal(0, v:false->assert_false()) - call assert_equal(1, 123->assert_false()) - ]] - expected_errors({tmpname .. ' line 2: Expected False but got 123'}) - end) end) -- assert_true({actual}, [, {msg}]) @@ -164,14 +106,6 @@ describe('assert function:', function() eq(1, call('assert_true', 1.5)) expected_errors({'Expected True but got 1.5'}) end) - - it('can be used as a method', function() - local tmpname = source [[ - call assert_equal(0, v:true->assert_true()) - call assert_equal(1, 0->assert_true()) - ]] - expected_errors({tmpname .. ' line 2: Expected True but got 0'}) - end) end) describe('v:errors', function() @@ -202,91 +136,25 @@ describe('assert function:', function() end) it('should have file names and passed messages', function() - local tmpname_one = source([[ + source([[ call assert_equal(1, 100, 'equal assertion failed') call assert_false('true', 'true assertion failed') call assert_true('false', 'false assertion failed') ]]) - local tmpname_two = source([[ - call assert_true('', 'file two') - ]]) - expected_errors({ - tmpname_one .. " line 1: equal assertion failed: Expected 1 but got 100", - tmpname_one .. " line 2: true assertion failed: Expected False but got 'true'", - tmpname_one .. " line 3: false assertion failed: Expected True but got 'false'", - tmpname_two .. " line 1: file two: Expected True but got ''", - }) - end) - - it('is reset to a list by assert functions', function() source([[ - let save_verrors = v:errors - let v:['errors'] = {'foo': 3} - call assert_equal('yes', 'no') - let verrors = v:errors - let v:errors = save_verrors - call assert_equal(type([]), type(verrors)) + call assert_true('', 'file two') ]]) - expected_empty() - end) - end) - - -- assert_match({pat}, {text}[, {msg}]) - describe('assert_match', function() - it('should not change v:errors when pat matches text', function() - call('assert_match', '^f.*b.*r$', 'foobar') - expected_empty() - end) - - it('should change v:errors when pat does not match text', function() - call('assert_match', 'bar.*foo', 'foobar') - expected_errors({"Pattern 'bar.*foo' does not match 'foobar'"}) - end) - - it('should set v:errors to msg when given and match fails', function() - call('assert_match', 'bar.*foo', 'foobar', 'wrong') - expected_errors({"wrong: Pattern 'bar.*foo' does not match 'foobar'"}) - end) - - it('can be used as a method', function() - local tmpname = source [[ - call assert_equal(1, 'foobar'->assert_match('bar.*foo', 'wrong')) - ]] expected_errors({ - tmpname .. " line 1: wrong: Pattern 'bar.*foo' does not match 'foobar'" + "nvim_exec(): equal assertion failed: Expected 1 but got 100", + "nvim_exec(): true assertion failed: Expected False but got 'true'", + "nvim_exec(): false assertion failed: Expected True but got 'false'", + "nvim_exec(): file two: Expected True but got ''", }) end) end) - -- assert_notmatch({pat}, {text}[, {msg}]) - describe('assert_notmatch', function() - it('should not change v:errors when pat does not match text', function() - call('assert_notmatch', 'foo', 'bar') - call('assert_notmatch', '^foobar$', 'foobars') - expected_empty() - end) - - it('should change v:errors when pat matches text', function() - call('assert_notmatch', 'foo', 'foobar') - expected_errors({"Pattern 'foo' does match 'foobar'"}) - end) - - it('can be used as a method', function() - local tmpname = source [[ - call assert_equal(1, 'foobar'->assert_notmatch('foo')) - ]] - expected_errors({tmpname .. " line 1: Pattern 'foo' does match 'foobar'"}) - end) - end) - -- assert_fails({cmd}, [, {error}]) describe('assert_fails', function() - it('should change v:errors when error does not match v:errmsg', function() - eq(1, eval([[assert_fails('xxx', 'E12345')]])) - command([[call assert_match("Expected 'E12345' but got 'E492:", v:errors[0])]]) - expected_errors({"Expected 'E12345' but got 'E492: Not an editor command: xxx': xxx"}) - end) - it('should not change v:errors when cmd errors', function() eq(0, eval([[assert_fails('NonexistentCmd')]])) expected_empty() @@ -296,106 +164,5 @@ describe('assert function:', function() eq(1, eval([[assert_fails('call empty("")', '')]])) expected_errors({'command did not fail: call empty("")'}) end) - - it('can specify and get a message about what failed', function() - eq(1, eval([[assert_fails('xxx', 'E9876', 'stupid')]])) - command([[call assert_match("stupid: Expected 'E9876' but got 'E492:", v:errors[0])]]) - expected_errors({"stupid: Expected 'E9876' but got 'E492: Not an editor command: xxx': stupid"}) - end) - - it('can specify and get a message even when cmd succeeds', function() - eq(1, eval([[assert_fails('echo', '', 'echo command')]])) - expected_errors({'command did not fail: echo command'}) - end) - - it('can be used as a method', function() - local tmpname = source [[ - call assert_equal(1, 'echo'->assert_fails('', 'echo command')) - ]] - expected_errors({ - tmpname .. ' line 1: command did not fail: echo command' - }) - end) - end) - - -- assert_inrange({lower}, {upper}, {actual}[, {msg}]) - describe('assert_inrange()', function() - it('should not change v:errors when actual is in range', function() - call('assert_inrange', 7, 7, 7) - call('assert_inrange', 5, 7, 5) - call('assert_inrange', 5, 7, 6) - call('assert_inrange', 5, 7, 7) - expected_empty() - end) - - it('should change v:errors when actual is not in range', function() - call('assert_inrange', 5, 7, 4) - call('assert_inrange', 5, 7, 8) - expected_errors({ - "Expected range 5 - 7, but got 4", - "Expected range 5 - 7, but got 8", - }) - end) - - it('assert_inrange(1, 1) returns E119', function() - eq('Vim(call):E119: Not enough arguments for function: assert_inrange', - exc_exec("call assert_inrange(1, 1)")) - end) - - it('can be used as a method', function() - local tmpname = source [[ - call assert_equal(0, 5->assert_inrange(5, 7)) - call assert_equal(0, 7->assert_inrange(5, 7)) - call assert_equal(1, 8->assert_inrange(5, 7)) - ]] - expected_errors({tmpname .. ' line 3: Expected range 5 - 7, but got 8'}) - end) - end) - - -- assert_report({msg}) - describe('assert_report()', function() - it('should add a message to v:errors', function() - eq(1, call('assert_report', 'something is wrong')) - command("call assert_match('something is wrong', v:errors[0])") - command('call remove(v:errors, 0)') - expected_empty() - end) - - it('can be used as a method', function() - local tmpname = source [[ - call assert_equal(1, 'also wrong'->assert_report()) - ]] - expected_errors({tmpname .. ' line 1: also wrong'}) - end) - end) - - -- assert_exception({cmd}, [, {error}]) - describe('assert_exception()', function() - it('should assert thrown exceptions properly', function() - source([[ - try - nocommand - catch - call assert_equal(0, assert_exception('E492')) - endtry - ]]) - expected_empty() - end) - - it('should work properly when nested', function() - source([[ - try - nocommand - catch - try - " illegal argument, get NULL for error - call assert_equal(1, assert_exception([])) - catch - call assert_equal(0, assert_exception('E730')) - endtry - endtry - ]]) - expected_empty() - end) end) end) diff --git a/test/functional/legacy/autochdir_spec.lua b/test/functional/legacy/autochdir_spec.lua index 37a94476a0..13cb6cd287 100644 --- a/test/functional/legacy/autochdir_spec.lua +++ b/test/functional/legacy/autochdir_spec.lua @@ -1,8 +1,12 @@ local lfs = require('lfs') local helpers = require('test.functional.helpers')(after_each) local clear, eq, matches = helpers.clear, helpers.eq, helpers.matches -local eval, command, call = helpers.eval, helpers.command, helpers.call -local exec_capture = helpers.exec_capture +local eval, command, call, meths = helpers.eval, helpers.command, helpers.call, helpers.meths +local source, exec_capture = helpers.source, helpers.exec_capture + +local function expected_empty() + eq({}, meths.get_vvar('errors')) +end describe('autochdir behavior', function() local dir = 'Xtest_functional_legacy_autochdir' @@ -10,19 +14,66 @@ describe('autochdir behavior', function() before_each(function() lfs.mkdir(dir) clear() + command('set shellslash') end) after_each(function() helpers.rmdir(dir) end) - -- Tests vim/vim/777 without test_autochdir(). + -- Tests vim/vim#777 without test_autochdir(). it('sets filename', function() command('set acd') command('new') command('w '..dir..'/Xtest') eq('Xtest', eval("expand('%')")) - eq(dir, eval([[substitute(getcwd(), '.*[/\\]\(\k*\)', '\1', '')]])) + eq(dir, eval([[substitute(getcwd(), '.*/\(\k*\)', '\1', '')]])) + end) + + it(':file in win_execute() does not cause wrong directory', function() + command('cd '..dir) + source([[ + func Test_set_filename_other_window() + let cwd = getcwd() + call mkdir('Xa') + call mkdir('Xb') + call mkdir('Xc') + try + args Xa/aaa.txt Xb/bbb.txt + set acd + let winid = win_getid() + snext + call assert_equal('Xb', substitute(getcwd(), '.*/\([^/]*\)$', '\1', '')) + call win_execute(winid, 'file ' .. cwd .. '/Xc/ccc.txt') + call assert_equal('Xb', substitute(getcwd(), '.*/\([^/]*\)$', '\1', '')) + finally + set noacd + call chdir(cwd) + call delete('Xa', 'rf') + call delete('Xb', 'rf') + call delete('Xc', 'rf') + bwipe! aaa.txt + bwipe! bbb.txt + bwipe! ccc.txt + endtry + endfunc + ]]) + call('Test_set_filename_other_window') + expected_empty() + end) + + it('win_execute() does not change directory', function() + local subdir = 'Xfile' + command('cd '..dir) + command('set acd') + call('mkdir', subdir) + local winid = eval('win_getid()') + command('new '..subdir..'/file') + matches(dir..'/'..subdir..'$', eval('getcwd()')) + command('cd ..') + matches(dir..'$', eval('getcwd()')) + call('win_execute', winid, 'echo') + matches(dir..'$', eval('getcwd()')) end) it(':verbose pwd shows whether autochdir is used', function() @@ -30,29 +81,36 @@ describe('autochdir behavior', function() command('cd '..dir) local cwd = eval('getcwd()') command('edit global.txt') - matches('%[global%].*'..dir, exec_capture('verbose pwd')) + matches('%[global%].*'..dir..'$', exec_capture('verbose pwd')) call('mkdir', subdir) command('split '..subdir..'/local.txt') command('lcd '..subdir) - matches('%[window%].*'..dir..'[/\\]'..subdir, exec_capture('verbose pwd')) - command('set autochdir') + matches('%[window%].*'..dir..'/'..subdir..'$', exec_capture('verbose pwd')) + command('set acd') command('wincmd w') - matches('%[autochdir%].*'..dir, exec_capture('verbose pwd')) - command('lcd '..cwd) - matches('%[window%].*'..dir, exec_capture('verbose pwd')) + matches('%[autochdir%].*'..dir..'$', exec_capture('verbose pwd')) command('tcd '..cwd) - matches('%[tabpage%].*'..dir, exec_capture('verbose pwd')) + matches('%[tabpage%].*'..dir..'$', exec_capture('verbose pwd')) command('cd '..cwd) - matches('%[global%].*'..dir, exec_capture('verbose pwd')) + matches('%[global%].*'..dir..'$', exec_capture('verbose pwd')) + command('lcd '..cwd) + matches('%[window%].*'..dir..'$', exec_capture('verbose pwd')) command('edit') - matches('%[autochdir%].*'..dir, exec_capture('verbose pwd')) + matches('%[autochdir%].*'..dir..'$', exec_capture('verbose pwd')) + command('enew') command('wincmd w') - matches('%[autochdir%].*'..dir..'[/\\]'..subdir, exec_capture('verbose pwd')) - command('set noautochdir') - matches('%[autochdir%].*'..dir..'[/\\]'..subdir, exec_capture('verbose pwd')) + matches('%[autochdir%].*'..dir..'/'..subdir..'$', exec_capture('verbose pwd')) command('wincmd w') - matches('%[global%].*'..dir, exec_capture('verbose pwd')) + matches('%[window%].*'..dir..'$', exec_capture('verbose pwd')) + command('wincmd w') + matches('%[autochdir%].*'..dir..'/'..subdir..'$', exec_capture('verbose pwd')) + command('set noacd') + matches('%[autochdir%].*'..dir..'/'..subdir..'$', exec_capture('verbose pwd')) + command('wincmd w') + matches('%[window%].*'..dir..'$', exec_capture('verbose pwd')) + command('cd '..cwd) + matches('%[global%].*'..dir..'$', exec_capture('verbose pwd')) command('wincmd w') - matches('%[window%].*'..dir..'[/\\]'..subdir, exec_capture('verbose pwd')) + matches('%[window%].*'..dir..'/'..subdir..'$', exec_capture('verbose pwd')) end) end) diff --git a/test/functional/legacy/cdo_spec.lua b/test/functional/legacy/cdo_spec.lua deleted file mode 100644 index 8b3216cbfd..0000000000 --- a/test/functional/legacy/cdo_spec.lua +++ /dev/null @@ -1,228 +0,0 @@ --- Tests for the :cdo, :cfdo, :ldo and :lfdo commands - -local helpers = require('test.functional.helpers')(after_each) -local nvim, clear = helpers.meths, helpers.clear -local call, feed = helpers.call, helpers.feed -local source, eq = helpers.source, helpers.eq - -local function expected_empty() - eq({}, nvim.get_vvar('errors')) -end - -describe('cdo', function() - before_each(function() - clear() - - call('writefile', {'Line1', 'Line2', 'Line3'}, 'Xtestfile1') - call('writefile', {'Line1', 'Line2', 'Line3'}, 'Xtestfile2') - call('writefile', {'Line1', 'Line2', 'Line3'}, 'Xtestfile3') - - source([=[ - " Returns the current line in '<filename> <linenum>L <column>C' format - function GetRuler() - return expand('%') . ' ' . line('.') . 'L' . ' ' . col('.') . 'C' - endfunction - - " Tests for the :cdo and :ldo commands - function XdoTests(cchar) - enew - - " Shortcuts for calling the cdo and ldo commands - let Xdo = a:cchar . 'do' - let Xgetexpr = a:cchar . 'getexpr' - let Xprev = a:cchar. 'prev' - let XdoCmd = Xdo . ' call add(l, GetRuler())' - - " Try with an empty list - let l = [] - exe XdoCmd - call assert_equal([], l) - - " Populate the list and then try - exe Xgetexpr . " ['non-error 1', 'Xtestfile1:1:3:Line1', 'non-error 2', 'Xtestfile2:2:2:Line2', 'non-error 3', 'Xtestfile3:3:1:Line3']" - - let l = [] - exe XdoCmd - call assert_equal(['Xtestfile1 1L 3C', 'Xtestfile2 2L 2C', 'Xtestfile3 3L 1C'], l) - - " Run command only on selected error lines - let l = [] - enew - exe "2,3" . XdoCmd - call assert_equal(['Xtestfile2 2L 2C', 'Xtestfile3 3L 1C'], l) - - " Boundary condition tests - let l = [] - enew - exe "1,1" . XdoCmd - call assert_equal(['Xtestfile1 1L 3C'], l) - - let l = [] - enew - exe "3" . XdoCmd - call assert_equal(['Xtestfile3 3L 1C'], l) - - " Range test commands - let l = [] - enew - exe "%" . XdoCmd - call assert_equal(['Xtestfile1 1L 3C', 'Xtestfile2 2L 2C', 'Xtestfile3 3L 1C'], l) - - let l = [] - enew - exe "1,$" . XdoCmd - call assert_equal(['Xtestfile1 1L 3C', 'Xtestfile2 2L 2C', 'Xtestfile3 3L 1C'], l) - - let l = [] - enew - exe Xprev - exe "." . XdoCmd - call assert_equal(['Xtestfile2 2L 2C'], l) - - let l = [] - enew - exe "+" . XdoCmd - call assert_equal(['Xtestfile3 3L 1C'], l) - - " Invalid error lines test - let l = [] - enew - exe "silent! 27" . XdoCmd - exe "silent! 4,5" . XdoCmd - call assert_equal([], l) - - " Run commands from an unsaved buffer when 'hidden' is unset - set nohidden - let v:errmsg='' - let l = [] - enew - setlocal modified - exe "silent! 2,2" . XdoCmd - if v:errmsg !~# 'No write since last change' - call add(v:errors, 'Unsaved file change test failed') - endif - - " If the executed command fails, then the operation should be aborted - enew! - let subst_count = 0 - exe "silent!" . Xdo . " s/Line/xLine/ | let subst_count += 1" - if subst_count != 1 || getline('.') != 'xLine1' - call add(v:errors, 'Abort command on error test failed') - endif - set hidden - - let l = [] - exe "2,2" . Xdo . "! call add(l, GetRuler())" - call assert_equal(['Xtestfile2 2L 2C'], l) - - " List with no valid error entries - let l = [] - edit! +2 Xtestfile1 - exe Xgetexpr . " ['non-error 1', 'non-error 2', 'non-error 3']" - exe XdoCmd - call assert_equal([], l) - exe "silent! 2" . XdoCmd - call assert_equal([], l) - let v:errmsg='' - exe "%" . XdoCmd - exe "1,$" . XdoCmd - exe "." . XdoCmd - call assert_equal('', v:errmsg) - - " List with only one valid entry - let l = [] - exe Xgetexpr . " ['Xtestfile3:3:1:Line3']" - exe XdoCmd - call assert_equal(['Xtestfile3 3L 1C'], l) - - endfunction - - " Tests for the :cfdo and :lfdo commands - function XfdoTests(cchar) - enew - - " Shortcuts for calling the cfdo and lfdo commands - let Xfdo = a:cchar . 'fdo' - let Xgetexpr = a:cchar . 'getexpr' - let XfdoCmd = Xfdo . ' call add(l, GetRuler())' - let Xpfile = a:cchar. 'pfile' - - " Clear the quickfix/location list - exe Xgetexpr . " []" - - " Try with an empty list - let l = [] - exe XfdoCmd - call assert_equal([], l) - - " Populate the list and then try - exe Xgetexpr . " ['non-error 1', 'Xtestfile1:1:3:Line1', 'Xtestfile1:2:1:Line2', 'non-error 2', 'Xtestfile2:2:2:Line2', 'non-error 3', 'Xtestfile3:2:3:Line2', 'Xtestfile3:3:1:Line3']" - - let l = [] - exe XfdoCmd - call assert_equal(['Xtestfile1 1L 3C', 'Xtestfile2 2L 2C', 'Xtestfile3 2L 3C'], l) - - " Run command only on selected error lines - let l = [] - exe "2,3" . XfdoCmd - call assert_equal(['Xtestfile2 2L 2C', 'Xtestfile3 2L 3C'], l) - - " Boundary condition tests - let l = [] - exe "3" . XfdoCmd - call assert_equal(['Xtestfile3 2L 3C'], l) - - " Range test commands - let l = [] - exe "%" . XfdoCmd - call assert_equal(['Xtestfile1 1L 3C', 'Xtestfile2 2L 2C', 'Xtestfile3 2L 3C'], l) - - let l = [] - exe "1,$" . XfdoCmd - call assert_equal(['Xtestfile1 1L 3C', 'Xtestfile2 2L 2C', 'Xtestfile3 2L 3C'], l) - - let l = [] - exe Xpfile - exe "." . XfdoCmd - call assert_equal(['Xtestfile2 2L 2C'], l) - - " List with only one valid entry - let l = [] - exe Xgetexpr . " ['Xtestfile2:2:5:Line2']" - exe XfdoCmd - call assert_equal(['Xtestfile2 2L 5C'], l) - - endfunction - ]=]) - end) - - after_each(function() - os.remove('Xtestfile1') - os.remove('Xtestfile2') - os.remove('Xtestfile3') - end) - - it('works for :cdo', function() - -- call('XdoTests', 'c') - feed(":call XdoTests('c')<CR><C-l>") - expected_empty() - end) - - it('works for :cfdo', function() - -- call('XfdoTests', 'c') - feed(":call XfdoTests('c')<CR><C-l>") - expected_empty() - end) - - it('works for :ldo', function() - -- call('XdoTests', 'l') - feed(":call XdoTests('l')<CR><C-l>") - expected_empty() - end) - - it('works for :lfdo', function() - -- call('XfdoTests', 'l') - feed(":call XfdoTests('l')<CR><C-l>") - expected_empty() - end) -end) diff --git a/test/functional/legacy/cmdline_spec.lua b/test/functional/legacy/cmdline_spec.lua index 9ebe9aeb91..d8d849271b 100644 --- a/test/functional/legacy/cmdline_spec.lua +++ b/test/functional/legacy/cmdline_spec.lua @@ -11,6 +11,15 @@ describe('cmdline', function() it('is cleared when switching tabs', function() local screen = Screen.new(30, 10) screen:attach() + screen:set_default_attr_ids { + [1] = {underline = true, background = Screen.colors.LightGrey}; + [2] = {bold = true}; + [3] = {reverse = true}; + [4] = {bold = true, foreground = Screen.colors.Blue1}; + } + -- TODO(bfredl): redraw with tabs is severly broken. fix it + feed_command [[ set display-=msgsep ]] + feed_command([[call setline(1, range(30))]]) screen:expect([[ ^0 | @@ -24,18 +33,61 @@ describe('cmdline', function() 8 | :call setline(1, range(30)) | ]]) - feed([[:tabnew<cr><C-w>-<C-w>-gtgt]]) - screen:expect([[ - + [No Name] [No Name] X| + + feed [[:tabnew<cr>]] + screen:expect{grid=[[ + {1: + [No Name] }{2: [No Name] }{3: }{1:X}| + ^ | + {4:~ }| + {4:~ }| + {4:~ }| + {4:~ }| + {4:~ }| + {4:~ }| + {4:~ }| + :tabnew | + ]]} + + feed [[<C-w>-<C-w>-]] + screen:expect{grid=[[ + {1: + [No Name] }{2: [No Name] }{3: }{1:X}| ^ | - ~ | - ~ | - ~ | - ~ | - ~ | + {4:~ }| + {4:~ }| + {4:~ }| + {4:~ }| + {4:~ }| + | + | + :tabnew | + ]]} + + feed [[gt]] + screen:expect{grid=[[ + {2: + [No Name] }{1: [No Name] }{3: }{1:X}| + ^0 | + 1 | + 2 | + 3 | + 4 | + 5 | 6 | 7 | | + ]]} + + feed [[gt]] + screen:expect([[ + {1: + [No Name] }{2: [No Name] }{3: }{1:X}| + ^ | + {4:~ }| + {4:~ }| + {4:~ }| + {4:~ }| + {4:~ }| + | + | + | ]]) end) diff --git a/test/functional/legacy/cpoptions_spec.lua b/test/functional/legacy/cpoptions_spec.lua new file mode 100644 index 0000000000..d2f382ec12 --- /dev/null +++ b/test/functional/legacy/cpoptions_spec.lua @@ -0,0 +1,34 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') +local clear = helpers.clear +local command = helpers.command +local feed = helpers.feed + +before_each(clear) + +describe('cpoptions', function() + it('$', function() + local screen = Screen.new(30, 6) + screen:attach() + command('set cpo+=$') + command([[call setline(1, 'one two three')]]) + feed('c2w') + screen:expect([[ + ^one tw$ three | + ~ | + ~ | + ~ | + ~ | + -- INSERT -- | + ]]) + feed('vim<Esc>') + screen:expect([[ + vi^m three | + ~ | + ~ | + ~ | + ~ | + | + ]]) + end) +end) diff --git a/test/functional/legacy/delete_spec.lua b/test/functional/legacy/delete_spec.lua index 141d9583e6..4ba4c8d356 100644 --- a/test/functional/legacy/delete_spec.lua +++ b/test/functional/legacy/delete_spec.lua @@ -25,25 +25,6 @@ describe('Test for delete()', function() eq(0, eval("isdirectory('Xdir1')")) eq(-1, eval("delete('Xdir1', 'd')")) end) - it('recursive delete', function() - command("call mkdir('Xdir1')") - command("call mkdir('Xdir1/subdir')") - command("call mkdir('Xdir1/empty')") - command('split Xdir1/Xfile') - command("call setline(1, ['a', 'b'])") - command('w') - command('w Xdir1/subdir/Xfile') - command('close') - - eq(1, eval("isdirectory('Xdir1')")) - eq(eval("['a', 'b']"), eval("readfile('Xdir1/Xfile')")) - eq(1, eval("isdirectory('Xdir1/subdir')")) - eq(eval("['a', 'b']"), eval("readfile('Xdir1/subdir/Xfile')")) - eq(1, eval("'Xdir1/empty'->isdirectory()")) - eq(0, eval("delete('Xdir1', 'rf')")) - eq(0, eval("isdirectory('Xdir1')")) - eq(-1, eval("delete('Xdir1', 'd')")) - end) it('symlink delete', function() source([[ @@ -80,45 +61,8 @@ describe('Test for delete()', function() eq(0, eval("delete('Xdir1', 'd')")) end) - it('symlink recursive delete', function() - source([[ - call mkdir('Xdir3') - call mkdir('Xdir3/subdir') - call mkdir('Xdir4') - split Xdir3/Xfile - call setline(1, ['a', 'b']) - w - w Xdir3/subdir/Xfile - w Xdir4/Xfile - close - if has('win32') - silent !mklink /j Xdir3\Xlink Xdir4 - else - silent !ln -s ../Xdir4 Xdir3/Xlink - endif - ]]) - - eq(1, eval("isdirectory('Xdir3')")) - eq(eval("['a', 'b']"), eval("readfile('Xdir3/Xfile')")) - eq(1, eval("isdirectory('Xdir3/subdir')")) - eq(eval("['a', 'b']"), eval("readfile('Xdir3/subdir/Xfile')")) - eq(1, eval("isdirectory('Xdir4')")) - eq(1, eval("isdirectory('Xdir3/Xlink')")) - eq(eval("['a', 'b']"), eval("readfile('Xdir4/Xfile')")) - - eq(0, eval("delete('Xdir3', 'rf')")) - eq(0, eval("isdirectory('Xdir3')")) - eq(-1, eval("delete('Xdir3', 'd')")) - -- symlink is deleted, not the directory it points to - eq(1, eval("isdirectory('Xdir4')")) - eq(eval("['a', 'b']"), eval("readfile('Xdir4/Xfile')")) - eq(0, eval("delete('Xdir4/Xfile')")) - eq(0, eval("delete('Xdir4', 'd')")) - end) - it('gives correct emsgs', function() eq('Vim(call):E474: Invalid argument', exc_exec("call delete('')")) - eq('Vim(call):E15: Invalid expression: 0', - exc_exec("call delete('foo', 0)")) + eq('Vim(call):E15: Invalid expression: 0', exc_exec("call delete('foo', 0)")) end) end) diff --git a/test/functional/legacy/display_spec.lua b/test/functional/legacy/display_spec.lua index 3fbbe96947..f0ffaf2c48 100644 --- a/test/functional/legacy/display_spec.lua +++ b/test/functional/legacy/display_spec.lua @@ -2,23 +2,21 @@ local helpers = require('test.functional.helpers')(after_each) local Screen = require('test.functional.ui.screen') local clear = helpers.clear -local poke_eventloop = helpers.poke_eventloop +local exec = helpers.exec local feed = helpers.feed -local feed_command = helpers.feed_command +local command = helpers.command describe('display', function() - local screen + before_each(clear) - it('scroll when modified at topline', function() - clear() - screen = Screen.new(20, 4) + it('scroll when modified at topline vim-patch:8.2.1488', function() + local screen = Screen.new(20, 4) screen:attach() screen:set_default_attr_ids({ [1] = {bold = true}, }) - feed_command([[call setline(1, repeat('a', 21))]]) - poke_eventloop() + command([[call setline(1, repeat('a', 21))]]) feed('O') screen:expect([[ ^ | @@ -27,5 +25,79 @@ describe('display', function() {1:-- INSERT --} | ]]) end) -end) + it('scrolling when modified at topline in Visual mode vim-patch:8.2.4626', function() + local screen = Screen.new(60, 8) + screen:attach() + screen:set_default_attr_ids({ + [1] = {bold = true}, -- ModeMsg + [2] = {background = Screen.colors.LightGrey}, -- Visual + [3] = {background = Screen.colors.Grey, foreground = Screen.colors.DarkBlue}, -- SignColumn + }) + + exec([[ + set scrolloff=0 + call setline(1, repeat(['foo'], 10)) + call sign_define('foo', { 'text': '>' }) + call sign_place(1, 'bar', 'foo', bufnr(), { 'lnum': 2 }) + call sign_place(2, 'bar', 'foo', bufnr(), { 'lnum': 1 }) + autocmd CursorMoved * if getcurpos()[1] == 2 | call sign_unplace('bar', { 'id': 1 }) | endif + ]]) + feed('VG7kk') + screen:expect([[ + {3: }^f{2:oo} | + {3: }foo | + {3: }foo | + {3: }foo | + {3: }foo | + {3: }foo | + {3: }foo | + {1:-- VISUAL LINE --} | + ]]) + end) + + it('@@@ in the last line shows correctly in a narrow window vim-patch:8.2.4718', function() + local screen = Screen.new(60, 10) + screen:set_default_attr_ids({ + [1] = {bold = true, foreground = Screen.colors.Blue}, -- NonText + [2] = {bold = true, reverse = true}, -- StatusLine + [3] = {reverse = true}, -- StatusLineNC + }) + screen:attach() + exec([[ + call setline(1, ['aaa', 'b'->repeat(100)]) + set display=truncate + vsplit + 100wincmd < + ]]) + screen:expect([[ + ^a│aaa | + a│bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb| + a│bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb | + b│{1:~ }| + b│{1:~ }| + b│{1:~ }| + b│{1:~ }| + {1:@}│{1:~ }| + {2:< }{3:[No Name] [+] }| + | + ]]) + command('set display=lastline') + screen:expect_unchanged() + command('100wincmd >') + screen:expect([[ + ^aaa │a| + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb│a| + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb │a| + {1:~ }│b| + {1:~ }│b| + {1:~ }│b| + {1:~ }│b| + {1:~ }│{1:@}| + {2:[No Name] [+] }{3:<}| + | + ]]) + command('set display=truncate') + screen:expect_unchanged() + end) +end) diff --git a/test/functional/legacy/edit_spec.lua b/test/functional/legacy/edit_spec.lua index 91d602924c..7fc5f11a79 100644 --- a/test/functional/legacy/edit_spec.lua +++ b/test/functional/legacy/edit_spec.lua @@ -1,25 +1,26 @@ --- Test for edit functions --- See also: src/nvim/testdir/test_edit.vim - local helpers = require('test.functional.helpers')(after_each) -local source = helpers.source -local eq, eval = helpers.eq, helpers.eval -local funcs = helpers.funcs local clear = helpers.clear +local command = helpers.command +local expect = helpers.expect +local feed = helpers.feed +local sleep = helpers.sleep -describe('edit', function() - before_each(clear) - - it('reset insertmode from i_ctrl-r_=', function() - source([=[ - call setline(1, ['abc']) - call cursor(1, 4) - call feedkeys(":set im\<cr>ZZZ\<c-r>=setbufvar(1,'&im', 0)\<cr>",'tnix') - ]=]) - eq({'abZZZc'}, funcs.getline(1,'$')) - eq({0, 1, 1, 0}, funcs.getpos('.')) - eq(0, eval('&im')) - end) +before_each(clear) +-- oldtest: Test_autoindent_remove_indent() +it('autoindent removes indent when Insert mode is stopped', function() + command('set autoindent') + -- leaving insert mode in a new line with indent added by autoindent, should + -- remove the indent. + feed('i<Tab>foo<CR><Esc>') + -- Need to delay for sometime, otherwise the code in getchar.c will not be + -- exercised. + sleep(50) + -- when a line is wrapped and the cursor is at the start of the second line, + -- leaving insert mode, should move the cursor back to the first line. + feed('o' .. ('x'):rep(20) .. '<Esc>') + -- Need to delay for sometime, otherwise the code in getchar.c will not be + -- exercised. + sleep(50) + expect('\tfoo\n\n' .. ('x'):rep(20)) end) - diff --git a/test/functional/legacy/eval_spec.lua b/test/functional/legacy/eval_spec.lua index b5de5cd232..05d853622e 100644 --- a/test/functional/legacy/eval_spec.lua +++ b/test/functional/legacy/eval_spec.lua @@ -666,7 +666,7 @@ describe('eval', function() source([[ " Vim script used in test_eval.in. Needed for script-local function. - func! s:Testje() + func s:Testje() return "foo" endfunc diff --git a/test/functional/legacy/ex_mode_spec.lua b/test/functional/legacy/ex_mode_spec.lua new file mode 100644 index 0000000000..98f113bbd0 --- /dev/null +++ b/test/functional/legacy/ex_mode_spec.lua @@ -0,0 +1,125 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') +local clear = helpers.clear +local command = helpers.command +local eq = helpers.eq +local eval = helpers.eval +local feed = helpers.feed +local meths = helpers.meths + +before_each(clear) + +describe('Ex mode', function() + it('supports command line editing', function() + local function test_ex_edit(expected, cmd) + feed('gQ' .. cmd .. '<C-b>"<CR>') + local ret = eval('@:[1:]') -- Remove leading quote. + feed('visual<CR>') + eq(meths.replace_termcodes(expected, true, true, true), ret) + end + command('set sw=2') + test_ex_edit('bar', 'foo bar<C-u>bar') + test_ex_edit('1<C-u>2', '1<C-v><C-u>2') + test_ex_edit('213', '1<C-b>2<C-e>3') + test_ex_edit('2013', '01<Home>2<End>3') + test_ex_edit('0213', '01<Left>2<Right>3') + test_ex_edit('0342', '012<Left><Left><Insert>3<Insert>4') + test_ex_edit('foo ', 'foo bar<C-w>') + test_ex_edit('foo', 'fooba<Del><Del>') + test_ex_edit('foobar', 'foo<Tab>bar') + test_ex_edit('abbreviate', 'abbrev<Tab>') + test_ex_edit('1<C-t><C-t>', '1<C-t><C-t>') + test_ex_edit('1<C-t><C-t>', '1<C-t><C-t><C-d>') + test_ex_edit(' foo', ' foo<C-d>') + test_ex_edit(' foo0', ' foo0<C-d>') + test_ex_edit(' foo^', ' foo^<C-d>') + test_ex_edit('foo', '<BS><C-H><Del><kDel>foo') + -- default wildchar <Tab> interferes with this test + command('set wildchar=<c-e>') + test_ex_edit('a\tb', 'a\t\t<C-H>b') + test_ex_edit('\tm<C-T>n', '\tm<C-T>n') + command('set wildchar&') + end) + + it('substitute confirmation prompt', function() + command('set noincsearch nohlsearch inccommand=') + local screen = Screen.new(60, 6) + screen:set_default_attr_ids({ + [0] = {bold = true, reverse = true}, -- MsgSeparator + [1] = {foreground = Screen.colors.Brown}, -- LineNr + [2] = {bold = true, foreground = Screen.colors.Blue}, -- NonText + }) + screen:attach() + command([[call setline(1, ['foo foo', 'foo foo', 'foo foo'])]]) + command([[set number]]) + feed('gQ') + screen:expect([[ + {1: 1 }foo foo | + {1: 2 }foo foo | + {1: 3 }foo foo | + {0: }| + Entering Ex mode. Type "visual" to go to Normal mode. | + :^ | + ]]) + + feed('%s/foo/bar/gc<CR>') + screen:expect([[ + {1: 1 }foo foo | + {0: }| + Entering Ex mode. Type "visual" to go to Normal mode. | + :%s/foo/bar/gc | + {1: 1 }foo foo | + ^^^^ | + ]]) + feed('N<CR>') + screen:expect([[ + Entering Ex mode. Type "visual" to go to Normal mode. | + :%s/foo/bar/gc | + {1: 1 }foo foo | + ^^^N | + {1: 1 }foo foo | + ^^^^ | + ]]) + feed('n<CR>') + screen:expect([[ + {1: 1 }foo foo | + ^^^N | + {1: 1 }foo foo | + ^^^n | + {1: 1 }foo foo | + ^^^^ | + ]]) + feed('y<CR>') + + feed('q<CR>') + screen:expect([[ + {1: 1 }foo foo | + ^^^y | + {1: 2 }foo foo | + ^^^q | + {1: 2 }foo foo | + :^ | + ]]) + + -- Pressing enter in ex mode should print the current line + feed('<CR>') + screen:expect([[ + ^^^y | + {1: 2 }foo foo | + ^^^q | + {1: 2 }foo foo | + {1: 3 }foo foo | + :^ | + ]]) + + feed(':vi<CR>') + screen:expect([[ + {1: 1 }foo bar | + {1: 2 }foo foo | + {1: 3 }^foo foo | + {2:~ }| + {2:~ }| + | + ]]) + end) +end) diff --git a/test/functional/legacy/excmd_spec.lua b/test/functional/legacy/excmd_spec.lua new file mode 100644 index 0000000000..6b3b265579 --- /dev/null +++ b/test/functional/legacy/excmd_spec.lua @@ -0,0 +1,188 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') +local clear = helpers.clear +local command = helpers.command +local exec_lua = helpers.exec_lua +local feed = helpers.feed +local meths = helpers.meths +local poke_eventloop = helpers.poke_eventloop +local read_file = helpers.read_file +local source = helpers.source +local eq = helpers.eq +local write_file = helpers.write_file + +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({}, meths.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) + +it(':confirm command dialog', function() + local screen + + local function start_new() + clear() + screen = Screen.new(60, 20) + screen:attach() + end + + write_file('foo', 'foo1\n') + write_file('bar', 'bar1\n') + + -- Test for saving all the modified buffers + start_new() + command("set nomore") + command("new foo") + command("call setline(1, 'foo2')") + command("new bar") + command("call setline(1, 'bar2')") + command("wincmd b") + feed(':confirm qall\n') + screen:expect([[ + bar2 | + ~ | + ~ | + ~ | + ~ | + ~ | + bar [+] | + foo2 | + ~ | + ~ | + ~ | + ~ | + foo [+] | + | + ~ | + ~ | + | + :confirm qall | + Save changes to "bar"? | + [Y]es, (N)o, Save (A)ll, (D)iscard All, (C)ancel: ^ | + ]]) + feed('A') + poke_eventloop() + + eq('foo2\n', read_file('foo')) + eq('bar2\n', read_file('bar')) + + -- Test for discarding all the changes to modified buffers + start_new() + command("set nomore") + command("new foo") + command("call setline(1, 'foo3')") + command("new bar") + command("call setline(1, 'bar3')") + command("wincmd b") + feed(':confirm qall\n') + screen:expect([[ + bar3 | + ~ | + ~ | + ~ | + ~ | + ~ | + bar [+] | + foo3 | + ~ | + ~ | + ~ | + ~ | + foo [+] | + | + ~ | + ~ | + | + :confirm qall | + Save changes to "bar"? | + [Y]es, (N)o, Save (A)ll, (D)iscard All, (C)ancel: ^ | + ]]) + feed('D') + poke_eventloop() + + eq('foo2\n', read_file('foo')) + eq('bar2\n', read_file('bar')) + + -- Test for saving and discarding changes to some buffers + start_new() + command("set nomore") + command("new foo") + command("call setline(1, 'foo4')") + command("new bar") + command("call setline(1, 'bar4')") + command("wincmd b") + feed(':confirm qall\n') + screen:expect([[ + bar4 | + ~ | + ~ | + ~ | + ~ | + ~ | + bar [+] | + foo4 | + ~ | + ~ | + ~ | + ~ | + foo [+] | + | + ~ | + ~ | + | + :confirm qall | + Save changes to "bar"? | + [Y]es, (N)o, Save (A)ll, (D)iscard All, (C)ancel: ^ | + ]]) + feed('N') + screen:expect([[ + bar4 | + ~ | + ~ | + ~ | + ~ | + ~ | + bar [+] | + foo4 | + ~ | + ~ | + ~ | + ~ | + foo [+] | + | + | + :confirm qall | + Save changes to "bar"? | + | + Save changes to "foo"? | + [Y]es, (N)o, (C)ancel: ^ | + ]]) + feed('Y') + poke_eventloop() + + eq('foo4\n', read_file('foo')) + eq('bar2\n', read_file('bar')) + + os.remove('foo') + os.remove('bar') +end) diff --git a/test/functional/legacy/expand_spec.lua b/test/functional/legacy/expand_spec.lua deleted file mode 100644 index cd3713eabe..0000000000 --- a/test/functional/legacy/expand_spec.lua +++ /dev/null @@ -1,129 +0,0 @@ --- Test for expanding file names - -local helpers = require('test.functional.helpers')(after_each) -local eq = helpers.eq -local call = helpers.call -local nvim = helpers.meths -local clear = helpers.clear -local source = helpers.source - -local function expected_empty() - eq({}, nvim.get_vvar('errors')) -end - -describe('expand file name', function() - after_each(function() - helpers.rmdir('Xdir1') - helpers.rmdir('Xdir2') - helpers.rmdir('Xdir3') - helpers.rmdir('Xdir4') - end) - - before_each(function() - clear() - - source([[ - func Test_with_directories() - call mkdir('Xdir1') - call mkdir('Xdir2') - call mkdir('Xdir3') - cd Xdir3 - call mkdir('Xdir4') - cd .. - - split Xdir1/file - call setline(1, ['a', 'b']) - w - w Xdir3/Xdir4/file - close - - next Xdir?/*/file - call assert_equal('Xdir3/Xdir4/file', expand('%')) - if has('unix') - next! Xdir?/*/nofile - call assert_equal('Xdir?/*/nofile', expand('%')) - endif - " Edit another file, on MS-Windows the swap file would be in use and can't - " be deleted - edit foo - - call assert_equal(0, delete('Xdir1', 'rf')) - call assert_equal(0, delete('Xdir2', 'rf')) - call assert_equal(0, delete('Xdir3', 'rf')) - endfunc - - func Test_with_tilde() - let dir = getcwd() - call mkdir('Xdir ~ dir') - call assert_true(isdirectory('Xdir ~ dir')) - cd Xdir\ ~\ dir - call assert_true(getcwd() =~ 'Xdir \~ dir') - exe 'cd ' . fnameescape(dir) - call delete('Xdir ~ dir', 'd') - call assert_false(isdirectory('Xdir ~ dir')) - endfunc - - func Test_expand_tilde_filename() - split ~ - call assert_equal('~', expand('%')) - call assert_notequal(expand('%:p'), expand('~/')) - call assert_match('\~', expand('%:p')) - bwipe! - endfunc - - func Test_expandcmd() - let $FOO = 'Test' - call assert_equal('e x/Test/y', expandcmd('e x/$FOO/y')) - unlet $FOO - - new - edit Xfile1 - call assert_equal('e Xfile1', expandcmd('e %')) - edit Xfile2 - edit Xfile1 - call assert_equal('e Xfile2', 'e #'->expandcmd()) - edit Xfile2 - edit Xfile3 - edit Xfile4 - let bnum = bufnr('Xfile2') - call assert_equal('e Xfile2', expandcmd('e #' . bnum)) - call setline('.', 'Vim!@#') - call assert_equal('e Vim', expandcmd('e <cword>')) - call assert_equal('e Vim!@#', expandcmd('e <cWORD>')) - enew! - edit Xfile.java - call assert_equal('e Xfile.py', expandcmd('e %:r.py')) - call assert_equal('make abc.java', expandcmd('make abc.%:e')) - call assert_equal('make Xabc.java', expandcmd('make %:s?file?abc?')) - edit a1a2a3.rb - call assert_equal('make b1b2b3.rb a1a2a3 Xfile.o', expandcmd('make %:gs?a?b? %< #<.o')) - - call assert_fails('call expandcmd("make <afile>")', 'E495:') - call assert_fails('call expandcmd("make <afile>")', 'E495:') - enew - call assert_fails('call expandcmd("make %")', 'E499:') - close - endfunc - ]]) - end) - - it('works with directories', function() - call('Test_with_directories') - expected_empty() - end) - - it('works with tilde', function() - call('Test_with_tilde') - expected_empty() - end) - - it('does not expand tilde if it is a filename', function() - call('Test_expand_tilde_filename') - expected_empty() - end) - - it('works with expandcmd()', function() - call('Test_expandcmd') - expected_empty() - end) -end) diff --git a/test/functional/legacy/file_perm_spec.lua b/test/functional/legacy/file_perm_spec.lua deleted file mode 100644 index ccdbfe0534..0000000000 --- a/test/functional/legacy/file_perm_spec.lua +++ /dev/null @@ -1,43 +0,0 @@ --- Test getting and setting file permissions. -require('os') - -local helpers = require('test.functional.helpers')(after_each) -local clear, call, eq = helpers.clear, helpers.call, helpers.eq -local neq, exc_exec, eval = helpers.neq, helpers.exc_exec, helpers.eval - -describe('Test getting and setting file permissions', function() - local tempfile = helpers.tmpname() - - before_each(function() - os.remove(tempfile) - clear() - end) - - it('file permissions', function() - -- eval() is used to test VimL method syntax for setfperm() and getfperm() - eq('', call('getfperm', tempfile)) - eq(0, eval("'" .. tempfile .. "'->setfperm('r--------')")) - - call('writefile', {'one'}, tempfile) - eq(9, eval("len('" .. tempfile .. "'->getfperm())")) - - eq(1, call('setfperm', tempfile, 'rwx------')) - if helpers.is_os('win') then - eq('rw-rw-rw-', call('getfperm', tempfile)) - else - eq('rwx------', call('getfperm', tempfile)) - end - - eq(1, call('setfperm', tempfile, 'r--r--r--')) - eq('r--r--r--', call('getfperm', tempfile)) - - local err = exc_exec(('call setfperm("%s", "---")'):format(tempfile)) - neq(err:find('E475:'), nil) - - eq(1, call('setfperm', tempfile, 'rwx------')) - end) - - after_each(function() - os.remove(tempfile) - end) -end) diff --git a/test/functional/legacy/filechanged_spec.lua b/test/functional/legacy/filechanged_spec.lua new file mode 100644 index 0000000000..ecb861098c --- /dev/null +++ b/test/functional/legacy/filechanged_spec.lua @@ -0,0 +1,131 @@ +local helpers = require('test.functional.helpers')(after_each) +local clear, source = helpers.clear, helpers.source +local call, eq, meths = helpers.call, helpers.eq, helpers.meths + +local function expected_empty() + eq({}, meths.get_vvar('errors')) +end + +describe('file changed dialog', function() + before_each(function() + clear() + meths.ui_attach(80, 24, {}) + meths.set_option('autoread', false) + meths.set_option('fsync', true) + end) + + it('works', function() + if helpers.pending_win32(pending) then return end + source([[ + func Test_file_changed_dialog() + au! FileChangedShell + + new Xchanged_d + call setline(1, 'reload this') + write + " Need to wait until the timestamp would change by at least a second. + sleep 2 + silent !echo 'extra line' >>Xchanged_d + call nvim_input('L') + checktime + call assert_match('W11:', v:warningmsg) + call assert_equal(2, line('$')) + call assert_equal('reload this', getline(1)) + call assert_equal('extra line', getline(2)) + + " delete buffer, only shows an error, no prompt + silent !rm Xchanged_d + checktime + call assert_match('E211:', v:warningmsg) + call assert_equal(2, line('$')) + call assert_equal('extra line', getline(2)) + let v:warningmsg = 'empty' + + " change buffer, recreate the file and reload + call setline(1, 'buffer is changed') + silent !echo 'new line' >Xchanged_d + call nvim_input('L') + checktime + call assert_match('W12:', v:warningmsg) + call assert_equal(1, line('$')) + call assert_equal('new line', getline(1)) + + " Only mode changed, reload + silent !chmod +x Xchanged_d + call nvim_input('L') + checktime + call assert_match('W16:', v:warningmsg) + call assert_equal(1, line('$')) + call assert_equal('new line', getline(1)) + + " Only time changed, no prompt + sleep 2 + silent !touch Xchanged_d + let v:warningmsg = '' + checktime Xchanged_d + call assert_equal('', v:warningmsg) + call assert_equal(1, line('$')) + call assert_equal('new line', getline(1)) + + bwipe! + call delete('Xchanged_d') + endfunc + ]]) + call('Test_file_changed_dialog') + expected_empty() + end) + + it('works with FileChangedShell', function() + source([[ + func Test_FileChangedShell_edit_dialog() + new Xchanged_r + call setline(1, 'reload this') + set fileformat=unix + silent write " Use :silent to prevent a hit-enter prompt + + " File format changed, reload (content only) via prompt + augroup testreload + au! + au FileChangedShell Xchanged_r let g:reason = v:fcs_reason | let v:fcs_choice = 'ask' + augroup END + call assert_equal(&fileformat, 'unix') + sleep 10m " make the test less flaky in Nvim + call writefile(["line1\r", "line2\r"], 'Xchanged_r') + let g:reason = '' + call nvim_input('L') " load file content only + checktime + call assert_equal('changed', g:reason) + call assert_equal(&fileformat, 'unix') + call assert_equal("line1\r", getline(1)) + call assert_equal("line2\r", getline(2)) + %s/\r + silent write " Use :silent to prevent a hit-enter prompt + + " File format changed, reload (file and options) via prompt + augroup testreload + au! + au FileChangedShell Xchanged_r let g:reason = v:fcs_reason | let v:fcs_choice = 'ask' + augroup END + call assert_equal(&fileformat, 'unix') + sleep 10m " make the test less flaky in Nvim + call writefile(["line1\r", "line2\r"], 'Xchanged_r') + let g:reason = '' + call nvim_input('a') " load file content and options + checktime + call assert_equal('changed', g:reason) + call assert_equal(&fileformat, 'dos') + call assert_equal("line1", getline(1)) + call assert_equal("line2", getline(2)) + set fileformat=unix + silent write " Use :silent to prevent a hit-enter prompt + + au! testreload + bwipe! + call delete(undofile('Xchanged_r')) + call delete('Xchanged_r') + endfunc + ]]) + call('Test_FileChangedShell_edit_dialog') + expected_empty() + end) +end) diff --git a/test/functional/legacy/fixeol_spec.lua b/test/functional/legacy/fixeol_spec.lua index d3ff86d349..3cc9d54e2b 100644 --- a/test/functional/legacy/fixeol_spec.lua +++ b/test/functional/legacy/fixeol_spec.lua @@ -6,12 +6,11 @@ local clear, feed_command, expect = helpers.clear, helpers.feed_command, helpers describe('fixeol', function() local function rmtestfiles() - feed_command('%bwipeout!') - feed_command('call delete("test.out")') - feed_command('call delete("XXEol")') - feed_command('call delete("XXNoEol")') - feed_command('call delete("XXTestEol")') - feed_command('call delete("XXTestNoEol")') + os.remove("test.out") + os.remove("XXEol") + os.remove("XXNoEol") + os.remove("XXTestEol") + os.remove("XXTestNoEol") end setup(function() clear() diff --git a/test/functional/legacy/function_sort_spec.lua b/test/functional/legacy/function_sort_spec.lua index 12875460e0..414953aacc 100644 --- a/test/functional/legacy/function_sort_spec.lua +++ b/test/functional/legacy/function_sort_spec.lua @@ -52,6 +52,6 @@ describe('sort', function() eq({'2', 'A', 'AA', 'a', 1, 3.3}, eval([[sort([3.3, 1, "2", "A", "a", "AA"], '')]])) eq({'2', 'A', 'AA', 'a', 1, 3.3}, eval('sort([3.3, 1, "2", "A", "a", "AA"], 0)')) eq({'2', 'A', 'a', 'AA', 1, 3.3}, eval('sort([3.3, 1, "2", "A", "a", "AA"], 1)')) - neq(exc_exec('call sort([3.3, 1, "2"], 3)'):find('E474:'), nil) + neq(nil, exc_exec('call sort([3.3, 1, "2"], 3)'):find('E474:')) end) end) diff --git a/test/functional/legacy/listchars_spec.lua b/test/functional/legacy/listchars_spec.lua index dc6ccd3628..a9aa238d4e 100644 --- a/test/functional/legacy/listchars_spec.lua +++ b/test/functional/legacy/listchars_spec.lua @@ -1,7 +1,8 @@ -- Tests for 'listchars' display with 'list' and :list. local helpers = require('test.functional.helpers')(after_each) -local feed, insert, source = helpers.feed, helpers.insert, helpers.source +local Screen = require('test.functional.ui.screen') +local feed, insert, exec = helpers.feed, helpers.insert, helpers.exec local clear, feed_command, expect = helpers.clear, helpers.feed_command, helpers.expect -- luacheck: ignore 621 (Indentation) @@ -13,7 +14,7 @@ describe("'listchars'", function() -- luacheck: ignore 613 (Trailing whitespace in a string) it("works with 'list'", function() - source([[ + exec([[ function GetScreenCharsForLine(lnum) return join(map(range(1, virtcol('$')), 'nr2char(screenchar(a:lnum, v:val))'), '') endfunction @@ -98,4 +99,126 @@ describe("'listchars'", function() .....h>-$ iii<<<<><<$]]) end) + + it('"exceeds" character does not appear in foldcolumn vim-patch:8.2.3121', function() + local screen = Screen.new(60, 10) + screen:set_default_attr_ids({ + [1] = {bold = true, foreground = Screen.colors.Blue}, -- NonText + [2] = {bold = true, reverse = true}, -- StatusLine + [3] = {reverse = true}, -- StatusLineNC + [4] = {background = Screen.colors.Grey, foreground = Screen.colors.DarkBlue}, -- FoldColumn, SignColumn + }) + screen:attach() + exec([[ + call setline(1, ['aaa', '', 'a', 'aaaaaa']) + vsplit + vsplit + windo set signcolumn=yes foldcolumn=1 winminwidth=0 nowrap list listchars=extends:>,precedes:< + ]]) + feed('13<C-W>>') + screen:expect([[ + {4: }aaa │{4: }a{1:>}│{4: }^aaa | + {4: } │{4: } │{4: } | + {4: }a │{4: }a │{4: }a | + {4: }aaaaaa │{4: }a{1:>}│{4: }aaaaaa | + {1:~ }│{1:~ }│{1:~ }| + {1:~ }│{1:~ }│{1:~ }| + {1:~ }│{1:~ }│{1:~ }| + {1:~ }│{1:~ }│{1:~ }| + {3:[No Name] [+] <[+] }{2:[No Name] [+] }| + | + ]]) + feed('<C-W>>') + screen:expect([[ + {4: }aaa │{4: }{1:>}│{4: }^aaa | + {4: } │{4: } │{4: } | + {4: }a │{4: }a│{4: }a | + {4: }aaaaaa │{4: }{1:>}│{4: }aaaaaa | + {1:~ }│{1:~ }│{1:~ }| + {1:~ }│{1:~ }│{1:~ }| + {1:~ }│{1:~ }│{1:~ }| + {1:~ }│{1:~ }│{1:~ }| + {3:[No Name] [+] <+] }{2:[No Name] [+] }| + | + ]]) + feed('<C-W>>') + screen:expect([[ + {4: }aaa │{4: }│{4: }^aaa | + {4: } │{4: }│{4: } | + {4: }a │{4: }│{4: }a | + {4: }aaaaaa │{4: }│{4: }aaaaaa | + {1:~ }│{1:~ }│{1:~ }| + {1:~ }│{1:~ }│{1:~ }| + {1:~ }│{1:~ }│{1:~ }| + {1:~ }│{1:~ }│{1:~ }| + {3:[No Name] [+] <] }{2:[No Name] [+] }| + | + ]]) + feed('<C-W>>') + screen:expect([[ + {4: }aaa │{4: }│{4: }^aaa | + {4: } │{4: }│{4: } | + {4: }a │{4: }│{4: }a | + {4: }aaaaaa │{4: }│{4: }aaaaaa | + {1:~ }│{1:~ }│{1:~ }| + {1:~ }│{1:~ }│{1:~ }| + {1:~ }│{1:~ }│{1:~ }| + {1:~ }│{1:~ }│{1:~ }| + {3:[No Name] [+] < }{2:[No Name] [+] }| + | + ]]) + feed('<C-W>>') + screen:expect([[ + {4: }aaa │{4: }│{4: }^aaa | + {4: } │{4: }│{4: } | + {4: }a │{4: }│{4: }a | + {4: }aaaaaa │{4: }│{4: }aaaaaa | + {1:~ }│{1:~}│{1:~ }| + {1:~ }│{1:~}│{1:~ }| + {1:~ }│{1:~}│{1:~ }| + {1:~ }│{1:~}│{1:~ }| + {3:[No Name] [+] < }{2:[No Name] [+] }| + | + ]]) + feed('<C-W>h') + feed_command('set nowrap foldcolumn=4') + screen:expect([[ + {4: }aaa │{4: }^aaa │{4: }aaa | + {4: } │{4: } │{4: } | + {4: }a │{4: }a │{4: }a | + {4: }aaaaaa │{4: }aaaaaa │{4: }aaaaaa | + {1:~ }│{1:~ }│{1:~ }| + {1:~ }│{1:~ }│{1:~ }| + {1:~ }│{1:~ }│{1:~ }| + {1:~ }│{1:~ }│{1:~ }| + {3:[No Name] [+] }{2:[No Name] [+] }{3:[No Name] [+] }| + :set nowrap foldcolumn=4 | + ]]) + feed('15<C-W><lt>') + screen:expect([[ + {4: }aaa │{4: }│{4: }aaa | + {4: } │{4: }│{4: } | + {4: }a │{4: }│{4: }a | + {4: }aaaaaa │{4: ^ }│{4: }aaaaaa | + {1:~ }│{1:~ }│{1:~ }| + {1:~ }│{1:~ }│{1:~ }| + {1:~ }│{1:~ }│{1:~ }| + {1:~ }│{1:~ }│{1:~ }| + {3:[No Name] [+] }{2:<[+] }{3:[No Name] [+] }| + :set nowrap foldcolumn=4 | + ]]) + feed('4<C-W><lt>') + screen:expect([[ + {4: }aaa │{4: }│{4: }aaa | + {4: } │{4: }│{4: } | + {4: }a │{4: }│{4: }a | + {4: }aaaaaa │{4:^ }│{4: }aaaaaa | + {1:~ }│{1:~}│{1:~ }| + {1:~ }│{1:~}│{1:~ }| + {1:~ }│{1:~}│{1:~ }| + {1:~ }│{1:~}│{1:~ }| + {3:[No Name] [+] }{2:< }{3:[No Name] [+] }| + :set nowrap foldcolumn=4 | + ]]) + end) end) diff --git a/test/functional/legacy/mapping_spec.lua b/test/functional/legacy/mapping_spec.lua index 92a757ca85..456acc12b5 100644 --- a/test/functional/legacy/mapping_spec.lua +++ b/test/functional/legacy/mapping_spec.lua @@ -2,7 +2,9 @@ local helpers = require('test.functional.helpers')(after_each) local clear, feed, insert = helpers.clear, helpers.feed, helpers.insert -local feed_command, expect, poke_eventloop = helpers.feed_command, helpers.expect, helpers.poke_eventloop +local expect, poke_eventloop = helpers.expect, helpers.poke_eventloop +local command, eq, eval, meths = helpers.command, helpers.eq, helpers.eval, helpers.meths +local sleep = helpers.sleep describe('mapping', function() before_each(clear) @@ -13,7 +15,7 @@ describe('mapping', function() ]]) -- Abbreviations with р (0x80) should work. - feed_command('inoreab чкпр vim') + command('inoreab чкпр vim') feed('GAчкпр <esc>') expect([[ @@ -23,17 +25,15 @@ describe('mapping', function() it('Ctrl-c works in Insert mode', function() -- Mapping of ctrl-c in insert mode - feed_command('set cpo-=< cpo-=k') - feed_command('inoremap <c-c> <ctrl-c>') - feed_command('cnoremap <c-c> dummy') - feed_command('cunmap <c-c>') + command('set cpo-=< cpo-=k') + command('inoremap <c-c> <ctrl-c>') + command('cnoremap <c-c> dummy') + command('cunmap <c-c>') feed('GA<cr>') - feed('TEST2: CTRL-C |') + -- XXX: editor must be in Insert mode before <C-C> is put into input buffer poke_eventloop() - feed('<c-c>A|<cr><esc>') - poke_eventloop() - feed_command('unmap <c-c>') - feed_command('unmap! <c-c>') + feed('TEST2: CTRL-C |<c-c>A|<cr><esc>') + command('unmap! <c-c>') expect([[ @@ -42,13 +42,12 @@ describe('mapping', function() end) it('Ctrl-c works in Visual mode', function() - feed_command([[vnoremap <c-c> :<C-u>$put ='vmap works'<cr>]]) + command([[vnoremap <c-c> :<C-u>$put ='vmap works'<cr>]]) feed('GV') - -- XXX: For some reason the mapping is only triggered - -- when <C-c> is in a separate feed command. + -- XXX: editor must be in Visual mode before <C-C> is put into input buffer poke_eventloop() - feed('<c-c>') - feed_command('vunmap <c-c>') + feed('vV<c-c>') + command('vunmap <c-c>') expect([[ @@ -57,23 +56,23 @@ describe('mapping', function() it('langmap', function() -- langmap should not get remapped in insert mode. - feed_command('inoremap { FAIL_ilangmap') - feed_command('set langmap=+{ langnoremap') + command('inoremap { FAIL_ilangmap') + command('set langmap=+{ langnoremap') feed('o+<esc>') -- Insert mode expr mapping with langmap. - feed_command('inoremap <expr> { "FAIL_iexplangmap"') + command('inoremap <expr> { "FAIL_iexplangmap"') feed('o+<esc>') -- langmap should not get remapped in cmdline mode. - feed_command('cnoremap { FAIL_clangmap') + command('cnoremap { FAIL_clangmap') feed('o+<esc>') - feed_command('cunmap {') + command('cunmap {') -- cmdline mode expr mapping with langmap. - feed_command('cnoremap <expr> { "FAIL_cexplangmap"') + command('cnoremap <expr> { "FAIL_cexplangmap"') feed('o+<esc>') - feed_command('cunmap {') + command('cunmap {') -- Assert buffer contents. expect([[ @@ -91,10 +90,10 @@ describe('mapping', function() ]]) -- Vim's issue #212 (feedkeys insert mapping at current position) - feed_command('nnoremap . :call feedkeys(".", "in")<cr>') + command('nnoremap . :call feedkeys(".", "in")<cr>') feed('/^a b<cr>') feed('0qqdw.ifoo<esc>qj0@q<esc>') - feed_command('unmap .') + command('unmap .') expect([[ fooc d fooc d @@ -103,15 +102,15 @@ describe('mapping', function() it('i_CTRL-G_U', function() -- <c-g>U<cursor> works only within a single line - feed_command('imapclear') - feed_command('imap ( ()<c-g>U<left>') + command('imapclear') + command('imap ( ()<c-g>U<left>') feed('G2o<esc>ki<cr>Test1: text with a (here some more text<esc>k.') -- test undo feed('G2o<esc>ki<cr>Test2: text wit a (here some more text [und undo]<c-g>u<esc>k.u') - feed_command('imapclear') - feed_command('set whichwrap=<,>,[,]') + command('imapclear') + command('set whichwrap=<,>,[,]') feed('G3o<esc>2k') - feed_command([[:exe ":norm! iTest3: text with a (parenthesis here\<C-G>U\<Right>new line here\<esc>\<up>\<up>."]]) + command([[:exe ":norm! iTest3: text with a (parenthesis here\<C-G>U\<Right>new line here\<esc>\<up>\<up>."]]) expect([[ @@ -126,4 +125,58 @@ describe('mapping', function() new line here ]]) end) + + it('dragging starts Select mode even if coming from mapping vim-patch:8.2.4806', function() + command('set mouse=a') + command('set selectmode=mouse') + + command('nnoremap <LeftDrag> <LeftDrag><Cmd><CR>') + sleep(10) + meths.input_mouse('left', 'press', '', 0, 0, 0) + sleep(10) + meths.input_mouse('left', 'drag', '', 0, 0, 1) + sleep(10) + eq('s', eval('mode()')) + end) + + it('<LeftDrag> mapping in Insert mode works correctly vim-patch:8.2.4692', function() + command('set mouse=a') + + command('inoremap <LeftDrag> <LeftDrag><Cmd>let g:dragged = 1<CR>') + feed('i') + sleep(10) + meths.input_mouse('left', 'press', '', 0, 0, 0) + sleep(10) + meths.input_mouse('left', 'drag', '', 0, 0, 1) + sleep(10) + eq(1, eval('g:dragged')) + eq('v', eval('mode()')) + feed([[<C-\><C-N>]]) + + command([[inoremap <LeftDrag> <LeftDrag><C-\><C-N>]]) + feed('i') + sleep(10) + meths.input_mouse('left', 'press', '', 0, 0, 0) + sleep(10) + meths.input_mouse('left', 'drag', '', 0, 0, 1) + sleep(10) + eq('n', eval('mode()')) + end) + + it('timeout works after an <Nop> mapping is triggered on timeout vim-patch:8.1.0052', function() + command('set timeout timeoutlen=400') + command('inoremap ab TEST') + command('inoremap a <Nop>') + -- Enter Insert mode + feed('i') + -- Wait for the "a" mapping to time out + feed('a') + sleep(500) + -- Send "a" and wait for a period shorter than 'timeoutlen' + feed('a') + sleep(100) + -- Send "b", should trigger the "ab" mapping + feed('b') + expect('TEST') + end) end) diff --git a/test/functional/legacy/memory_usage_spec.lua b/test/functional/legacy/memory_usage_spec.lua index 8d25b9d927..eec89aa919 100644 --- a/test/functional/legacy/memory_usage_spec.lua +++ b/test/functional/legacy/memory_usage_spec.lua @@ -10,6 +10,7 @@ local source = helpers.source local poke_eventloop = helpers.poke_eventloop local uname = helpers.uname local load_adjust = helpers.load_adjust +local write_file = helpers.write_file local isCI = helpers.isCI local function isasan() @@ -84,6 +85,12 @@ setmetatable(monitor_memory_usage, end}) describe('memory usage', function() + local tmpfile = 'X_memory_usage' + + after_each(function() + os.remove(tmpfile) + end) + local function check_result(tbl, status, result) if not status then print('') @@ -103,7 +110,7 @@ describe('memory usage', function() it('function capture vargs', function() local pid = eval('getpid()') local before = monitor_memory_usage(pid) - source([[ + write_file(tmpfile, [[ func s:f(...) let x = a:000 endfunc @@ -111,6 +118,8 @@ describe('memory usage', function() call s:f(0) endfor ]]) + -- TODO: check_result fails if command() is used here. Why? #16064 + feed_command('source '..tmpfile) poke_eventloop() local after = monitor_memory_usage(pid) -- Estimate the limit of max usage as 2x initial usage. @@ -136,7 +145,7 @@ describe('memory usage', function() it('function capture lvars', function() local pid = eval('getpid()') local before = monitor_memory_usage(pid) - local fname = source([[ + write_file(tmpfile, [[ if !exists('s:defined_func') func s:f() let x = l: @@ -147,10 +156,12 @@ describe('memory usage', function() call s:f() endfor ]]) + feed_command('source '..tmpfile) poke_eventloop() local after = monitor_memory_usage(pid) for _ = 1, 3 do - feed_command('so '..fname) + -- TODO: check_result fails if command() is used here. Why? #16064 + feed_command('source '..tmpfile) poke_eventloop() end local last = monitor_memory_usage(pid) diff --git a/test/functional/legacy/messages_spec.lua b/test/functional/legacy/messages_spec.lua new file mode 100644 index 0000000000..b296ac909d --- /dev/null +++ b/test/functional/legacy/messages_spec.lua @@ -0,0 +1,421 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') +local clear = helpers.clear +local command = helpers.command +local exec = helpers.exec +local feed = helpers.feed + +before_each(clear) + +describe('messages', function() + local screen + + describe('more prompt', function() + before_each(function() + screen = Screen.new(75, 6) + screen:set_default_attr_ids({ + [0] = {bold = true, foreground = Screen.colors.Blue}, -- NonText + [1] = {bold = true, foreground = Screen.colors.SeaGreen}, -- MoreMsg + [2] = {foreground = Screen.colors.Brown}, -- LineNr + [3] = {foreground = Screen.colors.Blue}, -- SpecialKey + }) + screen:attach() + command('set more') + end) + + -- oldtest: Test_message_more() + it('works', function() + command('call setline(1, range(1, 100))') + + feed(':%p#\n') + screen:expect([[ + {2: 1 }1 | + {2: 2 }2 | + {2: 3 }3 | + {2: 4 }4 | + {2: 5 }5 | + {1:-- More --}^ | + ]]) + + feed('?') + screen:expect([[ + {2: 1 }1 | + {2: 2 }2 | + {2: 3 }3 | + {2: 4 }4 | + {2: 5 }5 | + {1:-- More -- SPACE/d/j: screen/page/line down, b/u/k: up, q: quit }^ | + ]]) + + -- Down a line with j, <CR>, <NL> or <Down>. + feed('j') + screen:expect([[ + {2: 2 }2 | + {2: 3 }3 | + {2: 4 }4 | + {2: 5 }5 | + {2: 6 }6 | + {1:-- More --}^ | + ]]) + feed('<NL>') + screen:expect([[ + {2: 3 }3 | + {2: 4 }4 | + {2: 5 }5 | + {2: 6 }6 | + {2: 7 }7 | + {1:-- More --}^ | + ]]) + feed('<CR>') + screen:expect([[ + {2: 4 }4 | + {2: 5 }5 | + {2: 6 }6 | + {2: 7 }7 | + {2: 8 }8 | + {1:-- More --}^ | + ]]) + feed('<Down>') + screen:expect([[ + {2: 5 }5 | + {2: 6 }6 | + {2: 7 }7 | + {2: 8 }8 | + {2: 9 }9 | + {1:-- More --}^ | + ]]) + + -- Down a screen with <Space>, f, or <PageDown>. + feed('f') + screen:expect([[ + {2: 10 }10 | + {2: 11 }11 | + {2: 12 }12 | + {2: 13 }13 | + {2: 14 }14 | + {1:-- More --}^ | + ]]) + feed('<Space>') + screen:expect([[ + {2: 15 }15 | + {2: 16 }16 | + {2: 17 }17 | + {2: 18 }18 | + {2: 19 }19 | + {1:-- More --}^ | + ]]) + feed('<PageDown>') + screen:expect([[ + {2: 20 }20 | + {2: 21 }21 | + {2: 22 }22 | + {2: 23 }23 | + {2: 24 }24 | + {1:-- More --}^ | + ]]) + + -- Down a page (half a screen) with d. + feed('d') + screen:expect([[ + {2: 23 }23 | + {2: 24 }24 | + {2: 25 }25 | + {2: 26 }26 | + {2: 27 }27 | + {1:-- More --}^ | + ]]) + + -- Down all the way with 'G'. + feed('G') + screen:expect([[ + {2: 96 }96 | + {2: 97 }97 | + {2: 98 }98 | + {2: 99 }99 | + {2:100 }100 | + {1:Press ENTER or type command to continue}^ | + ]]) + + -- Up a line k, <BS> or <Up>. + feed('k') + screen:expect([[ + {2: 95 }95 | + {2: 96 }96 | + {2: 97 }97 | + {2: 98 }98 | + {2: 99 }99 | + {1:-- More --}^ | + ]]) + feed('<BS>') + screen:expect([[ + {2: 94 }94 | + {2: 95 }95 | + {2: 96 }96 | + {2: 97 }97 | + {2: 98 }98 | + {1:-- More --}^ | + ]]) + feed('<Up>') + screen:expect([[ + {2: 93 }93 | + {2: 94 }94 | + {2: 95 }95 | + {2: 96 }96 | + {2: 97 }97 | + {1:-- More --}^ | + ]]) + + -- Up a screen with b or <PageUp>. + feed('b') + screen:expect([[ + {2: 88 }88 | + {2: 89 }89 | + {2: 90 }90 | + {2: 91 }91 | + {2: 92 }92 | + {1:-- More --}^ | + ]]) + feed('<PageUp>') + screen:expect([[ + {2: 83 }83 | + {2: 84 }84 | + {2: 85 }85 | + {2: 86 }86 | + {2: 87 }87 | + {1:-- More --}^ | + ]]) + + -- Up a page (half a screen) with u. + feed('u') + screen:expect([[ + {2: 80 }80 | + {2: 81 }81 | + {2: 82 }82 | + {2: 83 }83 | + {2: 84 }84 | + {1:-- More --}^ | + ]]) + + -- Up all the way with 'g'. + feed('g') + screen:expect([[ + {2: 1 }1 | + {2: 2 }2 | + {2: 3 }3 | + {2: 4 }4 | + {2: 5 }5 | + {1:-- More --}^ | + ]]) + + -- All the way down. Pressing f should do nothing but pressing + -- space should end the more prompt. + feed('G') + screen:expect([[ + {2: 96 }96 | + {2: 97 }97 | + {2: 98 }98 | + {2: 99 }99 | + {2:100 }100 | + {1:Press ENTER or type command to continue}^ | + ]]) + feed('f') + screen:expect_unchanged() + feed('<Space>') + screen:expect([[ + 96 | + 97 | + 98 | + 99 | + ^100 | + | + ]]) + + -- Pressing g< shows the previous command output. + feed('g<lt>') + screen:expect([[ + {2: 96 }96 | + {2: 97 }97 | + {2: 98 }98 | + {2: 99 }99 | + {2:100 }100 | + {1:Press ENTER or type command to continue}^ | + ]]) + + feed(':%p#\n') + screen:expect([[ + {2: 1 }1 | + {2: 2 }2 | + {2: 3 }3 | + {2: 4 }4 | + {2: 5 }5 | + {1:-- More --}^ | + ]]) + + -- Stop command output with q, <Esc> or CTRL-C. + feed('q') + screen:expect([[ + 96 | + 97 | + 98 | + 99 | + ^100 | + | + ]]) + + -- Execute a : command from the more prompt + feed(':%p#\n') + screen:expect([[ + {2: 1 }1 | + {2: 2 }2 | + {2: 3 }3 | + {2: 4 }4 | + {2: 5 }5 | + {1:-- More --}^ | + ]]) + feed(':') + screen:expect([[ + {2: 1 }1 | + {2: 2 }2 | + {2: 3 }3 | + {2: 4 }4 | + {2: 5 }5 | + :^ | + ]]) + feed("echo 'Hello'\n") + screen:expect([[ + {2: 2 }2 | + {2: 3 }3 | + {2: 4 }4 | + {2: 5 }5 | + Hello | + {1:Press ENTER or type command to continue}^ | + ]]) + end) + + -- oldtest: Test_quit_long_message() + it('with control characters can be quit vim-patch:8.2.1844', function() + screen:try_resize(40, 6) + feed([[:echom range(9999)->join("\x01")<CR>]]) + screen:expect([[ + 0{3:^A}1{3:^A}2{3:^A}3{3:^A}4{3:^A}5{3:^A}6{3:^A}7{3:^A}8{3:^A}9{3:^A}10{3:^A}11{3:^A}12| + {3:^A}13{3:^A}14{3:^A}15{3:^A}16{3:^A}17{3:^A}18{3:^A}19{3:^A}20{3:^A}21{3:^A}22| + {3:^A}23{3:^A}24{3:^A}25{3:^A}26{3:^A}27{3:^A}28{3:^A}29{3:^A}30{3:^A}31{3:^A}32| + {3:^A}33{3:^A}34{3:^A}35{3:^A}36{3:^A}37{3:^A}38{3:^A}39{3:^A}40{3:^A}41{3:^A}42| + {3:^A}43{3:^A}44{3:^A}45{3:^A}46{3:^A}47{3:^A}48{3:^A}49{3:^A}50{3:^A}51{3:^A}52| + {1:-- More --}^ | + ]]) + feed('q') + screen:expect([[ + ^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]) + end) + end) + + -- oldtest: Test_ask_yesno() + it('y/n prompt works', function() + screen = Screen.new(75, 6) + screen:set_default_attr_ids({ + [0] = {bold = true, foreground = Screen.colors.Blue}, -- NonText + [1] = {bold = true, foreground = Screen.colors.SeaGreen}, -- MoreMsg + [2] = {bold = true, reverse = true}, -- MsgSeparator + }) + screen:attach() + command('set noincsearch nohlsearch inccommand=') + command('call setline(1, range(1, 2))') + + feed(':2,1s/^/n/\n') + screen:expect([[ + 1 | + 2 | + {0:~ }| + {0:~ }| + {0:~ }| + {1:Backwards range given, OK to swap (y/n)?}^ | + ]]) + feed('n') + screen:expect([[ + ^1 | + 2 | + {0:~ }| + {0:~ }| + {0:~ }| + {1:Backwards range given, OK to swap (y/n)?}n | + ]]) + + feed(':2,1s/^/Esc/\n') + screen:expect([[ + 1 | + 2 | + {0:~ }| + {0:~ }| + {0:~ }| + {1:Backwards range given, OK to swap (y/n)?}^ | + ]]) + feed('<Esc>') + screen:expect([[ + ^1 | + 2 | + {0:~ }| + {0:~ }| + {0:~ }| + {1:Backwards range given, OK to swap (y/n)?}n | + ]]) + + feed(':2,1s/^/y/\n') + screen:expect([[ + 1 | + 2 | + {0:~ }| + {0:~ }| + {0:~ }| + {1:Backwards range given, OK to swap (y/n)?}^ | + ]]) + feed('y') + screen:expect([[ + y1 | + ^y2 | + {0:~ }| + {0:~ }| + {0:~ }| + {1:Backwards range given, OK to swap (y/n)?}y | + ]]) + end) + + -- oldtest: Test_fileinfo_after_echo() + it('fileinfo does not overwrite echo message vim-patch:8.2.4156', function() + screen = Screen.new(40, 6) + screen:set_default_attr_ids({ + [0] = {bold = true, foreground = Screen.colors.Blue}, -- NonText + }) + screen:attach() + exec([[ + set shortmess-=F + + file a.txt + + hide edit b.txt + call setline(1, "hi") + setlocal modified + + hide buffer a.txt + + autocmd CursorHold * buf b.txt | w | echo "'b' written" + ]]) + command('set updatetime=50') + feed('0$') + screen:expect([[ + ^hi | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + 'b' written | + ]]) + os.remove('b.txt') + end) +end) diff --git a/test/functional/legacy/packadd_spec.lua b/test/functional/legacy/packadd_spec.lua deleted file mode 100644 index 4f9f5a0237..0000000000 --- a/test/functional/legacy/packadd_spec.lua +++ /dev/null @@ -1,507 +0,0 @@ --- Tests for 'packpath' and :packadd - -local helpers = require('test.functional.helpers')(after_each) -local clear, source, command = helpers.clear, helpers.source, helpers.command -local call, eq, nvim = helpers.call, helpers.eq, helpers.meths -local feed = helpers.feed - -local function expected_empty() - eq({}, nvim.get_vvar('errors')) -end - -describe('packadd', function() - before_each(function() - clear() - - source([=[ - func Escape(s) - return escape(a:s, '\~') - endfunc - - func SetUp() - let s:topdir = expand(getcwd() . '/Xdir') - if isdirectory(s:topdir) - call delete(s:topdir, 'rf') - endif - exe 'set packpath=' . s:topdir - let s:plugdir = expand(s:topdir . '/pack/mine/opt/mytest') - endfunc - - func TearDown() - call delete(s:topdir, 'rf') - endfunc - - func Test_packadd() - if !exists('s:plugdir') - echomsg 'when running this test manually, call SetUp() first' - return - endif - - call mkdir(s:plugdir . '/plugin/also', 'p') - call mkdir(s:plugdir . '/ftdetect', 'p') - call mkdir(s:plugdir . '/after', 'p') - set rtp& - let rtp = &rtp - filetype on - - let rtp_entries = split(rtp, ',') - for entry in rtp_entries - if entry =~? '\<after\>' - let first_after_entry = entry - break - endif - endfor - - exe 'split ' . s:plugdir . '/plugin/test.vim' - call setline(1, 'let g:plugin_works = 42') - wq - - exe 'split ' . s:plugdir . '/plugin/also/loaded.vim' - call setline(1, 'let g:plugin_also_works = 77') - wq - - exe 'split ' . s:plugdir . '/ftdetect/test.vim' - call setline(1, 'let g:ftdetect_works = 17') - wq - - packadd mytest - - call assert_true(42, g:plugin_works) - call assert_equal(77, g:plugin_also_works) - call assert_true(17, g:ftdetect_works) - call assert_true(len(&rtp) > len(rtp)) - call assert_match(Escape(s:plugdir) . '\($\|,\)', &rtp) - - let new_after = match(&rtp, Escape(expand(s:plugdir . '/after') . ',')) - let forwarded = substitute(first_after_entry, '\\', '[/\\\\]', 'g') - let old_after = match(&rtp, ',' . escape(forwarded, '~') . '\>') - call assert_true(new_after > 0, 'rtp is ' . &rtp) - call assert_true(old_after > 0, 'match ' . forwarded . ' in ' . &rtp) - call assert_true(new_after < old_after, 'rtp is ' . &rtp) - - " NOTE: '/.../opt/myte' forwardly matches with '/.../opt/mytest' - call mkdir(fnamemodify(s:plugdir, ':h') . '/myte', 'p') - let rtp = &rtp - packadd myte - - " Check the path of 'myte' is added - call assert_true(len(&rtp) > len(rtp)) - call assert_match(Escape(s:plugdir) . '\($\|,\)', &rtp) - - " Check exception - call assert_fails("packadd directorynotfound", 'E919:') - call assert_fails("packadd", 'E471:') - endfunc - - func Test_packadd_start() - let plugdir = expand(s:topdir . '/pack/mine/start/other') - call mkdir(plugdir . '/plugin', 'p') - set rtp& - let rtp = &rtp - filetype on - - exe 'split ' . plugdir . '/plugin/test.vim' - call setline(1, 'let g:plugin_works = 24') - wq - - exe 'split ' . plugdir . '/plugin/test.lua' - call setline(1, 'vim.g.plugin_lua_works = 24') - wq - - packadd other - - call assert_equal(24, g:plugin_works) - call assert_equal(24, g:plugin_lua_works) - call assert_true(len(&rtp) > len(rtp)) - call assert_match(Escape(plugdir) . '\($\|,\)', &rtp) - endfunc - - func Test_packadd_noload() - call mkdir(s:plugdir . '/plugin', 'p') - call mkdir(s:plugdir . '/syntax', 'p') - set rtp& - let rtp = &rtp - - exe 'split ' . s:plugdir . '/plugin/test.vim' - call setline(1, 'let g:plugin_works = 42') - wq - exe 'split ' . s:plugdir . '/plugin/test.lua' - call setline(1, 'let g:plugin_lua_works = 42') - wq - let g:plugin_works = 0 - let g:plugin_lua_works = 0 - - packadd! mytest - - call assert_true(len(&rtp) > len(rtp)) - call assert_match(Escape(s:plugdir) . '\($\|,\)', &rtp) - call assert_equal(0, g:plugin_works) - call assert_equal(0, g:plugin_lua_works) - - " check the path is not added twice - let new_rtp = &rtp - packadd! mytest - call assert_equal(new_rtp, &rtp) - endfunc - - func Test_packadd_symlink_dir() - let top2_dir = expand(s:topdir . '/Xdir2') - let real_dir = expand(s:topdir . '/Xsym') - call mkdir(real_dir, 'p') - if has('win32') - exec "silent! !mklink /d" top2_dir "Xsym" - else - exec "silent! !ln -s Xsym" top2_dir - endif - let &rtp = top2_dir . ',' . expand(top2_dir . '/after') - let &packpath = &rtp - - let s:plugdir = expand(top2_dir . '/pack/mine/opt/mytest') - call mkdir(s:plugdir . '/plugin', 'p') - - exe 'split ' . s:plugdir . '/plugin/test.vim' - call setline(1, 'let g:plugin_works = 44') - wq - let g:plugin_works = 0 - - packadd mytest - - " Must have been inserted in the middle, not at the end - call assert_match(Escape(expand('/pack/mine/opt/mytest').','), &rtp) - call assert_equal(44, g:plugin_works) - - " No change when doing it again. - let rtp_before = &rtp - packadd mytest - call assert_equal(rtp_before, &rtp) - - set rtp& - let rtp = &rtp - exec "silent !" (has('win32') ? "rd /q/s" : "rm") top2_dir - endfunc - - func Test_packadd_symlink_dir2() - let top2_dir = expand(s:topdir . '/Xdir2') - let real_dir = expand(s:topdir . '/Xsym/pack') - call mkdir(top2_dir, 'p') - call mkdir(real_dir, 'p') - let &rtp = top2_dir . ',' . top2_dir . '/after' - let &packpath = &rtp - - if has('win32') - exec "silent! !mklink /d" top2_dir "Xsym" - else - exec "silent !ln -s ../Xsym/pack" top2_dir . '/pack' - endif - let s:plugdir = expand(top2_dir . '/pack/mine/opt/mytest') - call mkdir(s:plugdir . '/plugin', 'p') - - exe 'split ' . s:plugdir . '/plugin/test.vim' - call setline(1, 'let g:plugin_works = 48') - wq - let g:plugin_works = 0 - - packadd mytest - - " Must have been inserted in the middle, not at the end - call assert_match(Escape(expand('/Xdir2/pack/mine/opt/mytest').','), &rtp) - call assert_equal(48, g:plugin_works) - - " No change when doing it again. - let rtp_before = &rtp - packadd mytest - call assert_equal(rtp_before, &rtp) - - set rtp& - let rtp = &rtp - if has('win32') - exec "silent !rd /q/s" top2_dir - else - exec "silent !rm" top2_dir . '/pack' - exec "silent !rmdir" top2_dir - endif - endfunc - - func Test_packloadall() - " plugin foo with an autoload directory - let fooplugindir = &packpath . '/pack/mine/start/foo/plugin' - call mkdir(fooplugindir, 'p') - call writefile(['let g:plugin_foo_number = 1234', - \ 'let g:plugin_foo_auto = bbb#value', - \ 'let g:plugin_extra_auto = extra#value'], fooplugindir . '/bar.vim') - let fooautodir = &packpath . '/pack/mine/start/foo/autoload' - call mkdir(fooautodir, 'p') - call writefile(['let bar#value = 77'], fooautodir . '/bar.vim') - - " plugin aaa with an autoload directory - let aaaplugindir = &packpath . '/pack/mine/start/aaa/plugin' - call mkdir(aaaplugindir, 'p') - call writefile(['let g:plugin_aaa_number = 333', - \ 'let g:plugin_aaa_auto = bar#value'], aaaplugindir . '/bbb.vim') - let aaaautodir = &packpath . '/pack/mine/start/aaa/autoload' - call mkdir(aaaautodir, 'p') - call writefile(['let bbb#value = 55'], aaaautodir . '/bbb.vim') - - " plugin extra with only an autoload directory - let extraautodir = &packpath . '/pack/mine/start/extra/autoload' - call mkdir(extraautodir, 'p') - call writefile(['let extra#value = 99'], extraautodir . '/extra.vim') - - packloadall - call assert_equal(1234, g:plugin_foo_number) - call assert_equal(55, g:plugin_foo_auto) - call assert_equal(99, g:plugin_extra_auto) - call assert_equal(333, g:plugin_aaa_number) - call assert_equal(77, g:plugin_aaa_auto) - - " only works once - call writefile(['let g:plugin_bar_number = 4321'], - \ fooplugindir . '/bar2.vim') - packloadall - call assert_false(exists('g:plugin_bar_number')) - - " works when ! used - packloadall! - call assert_equal(4321, g:plugin_bar_number) - endfunc - - func Test_helptags() - let docdir1 = &packpath . '/pack/mine/start/foo/doc' - let docdir2 = &packpath . '/pack/mine/start/bar/doc' - call mkdir(docdir1, 'p') - call mkdir(docdir2, 'p') - call writefile(['look here: *look-here*'], docdir1 . '/bar.txt') - call writefile(['look away: *look-away*'], docdir2 . '/foo.txt') - exe 'set rtp=' . &packpath . '/pack/mine/start/foo,' . &packpath . '/pack/mine/start/bar' - - helptags ALL - - let tags1 = readfile(docdir1 . '/tags') - call assert_match('look-here', tags1[0]) - let tags2 = readfile(docdir2 . '/tags') - call assert_match('look-away', tags2[0]) - - call assert_fails('helptags abcxyz', 'E150:') - endfunc - - func Test_colorscheme() - let colordirrun = &packpath . '/runtime/colors' - let colordirstart = &packpath . '/pack/mine/start/foo/colors' - let colordiropt = &packpath . '/pack/mine/opt/bar/colors' - call mkdir(colordirrun, 'p') - call mkdir(colordirstart, 'p') - call mkdir(colordiropt, 'p') - call writefile(['let g:found_one = 1'], colordirrun . '/one.vim') - call writefile(['let g:found_two = 1'], colordirstart . '/two.vim') - call writefile(['let g:found_three = 1'], colordiropt . '/three.vim') - exe 'set rtp=' . &packpath . '/runtime' - - colorscheme one - call assert_equal(1, g:found_one) - colorscheme two - call assert_equal(1, g:found_two) - colorscheme three - call assert_equal(1, g:found_three) - endfunc - - func Test_runtime() - let rundir = &packpath . '/runtime/extra' - let startdir = &packpath . '/pack/mine/start/foo/extra' - let optdir = &packpath . '/pack/mine/opt/bar/extra' - call mkdir(rundir, 'p') - call mkdir(startdir, 'p') - call mkdir(optdir, 'p') - call writefile(['let g:sequence .= "run"'], rundir . '/bar.vim') - call writefile(['let g:sequence .= "start"'], startdir . '/bar.vim') - call writefile(['let g:sequence .= "foostart"'], startdir . '/foo.vim') - call writefile(['let g:sequence .= "opt"'], optdir . '/bar.vim') - call writefile(['let g:sequence .= "xxxopt"'], optdir . '/xxx.vim') - exe 'set rtp=' . &packpath . '/runtime' - - let g:sequence = '' - runtime extra/bar.vim - call assert_equal('run', g:sequence) - let g:sequence = '' - runtime START extra/bar.vim - call assert_equal('start', g:sequence) - let g:sequence = '' - runtime OPT extra/bar.vim - call assert_equal('opt', g:sequence) - let g:sequence = '' - runtime PACK extra/bar.vim - call assert_equal('start', g:sequence) - let g:sequence = '' - runtime! PACK extra/bar.vim - call assert_equal('startopt', g:sequence) - let g:sequence = '' - runtime PACK extra/xxx.vim - call assert_equal('xxxopt', g:sequence) - - let g:sequence = '' - runtime ALL extra/bar.vim - call assert_equal('run', g:sequence) - let g:sequence = '' - runtime ALL extra/foo.vim - call assert_equal('foostart', g:sequence) - let g:sequence = '' - runtime! ALL extra/xxx.vim - call assert_equal('xxxopt', g:sequence) - let g:sequence = '' - runtime! ALL extra/bar.vim - call assert_equal('runstartopt', g:sequence) - endfunc - ]=]) - call('SetUp') - end) - - after_each(function() - call('TearDown') - end) - - it('is working', function() - call('Test_packadd') - expected_empty() - end) - - it('works with packadd!', function() - call('Test_packadd_noload') - expected_empty() - end) - - it('works with symlinks', function() - call('Test_packadd_symlink_dir') - expected_empty() - end) - - it('works with :packloadall', function() - call('Test_packloadall') - expected_empty() - end) - - it('works with helptags', function() - call('Test_helptags') - expected_empty() - end) - - it('works with colorschemes', function() - call('Test_colorscheme') - expected_empty() - end) - - it('works with :runtime [what]', function() - call('Test_runtime') - expected_empty() - end) - - it('loads packages from "start" directory', function() - call('Test_packadd_start') - expected_empty() - end) - - describe('command line completion', function() - local Screen = require('test.functional.ui.screen') - local screen - - before_each(function() - screen = Screen.new(30, 5) - screen:attach() - screen:set_default_attr_ids({ - [0] = {bold=true, foreground=Screen.colors.Blue}, - [1] = { - foreground = Screen.colors.Black, - background = Screen.colors.Yellow, - }, - [2] = {bold = true, reverse = true} - }) - - command([[let optdir1 = &packpath . '/pack/mine/opt']]) - command([[let optdir2 = &packpath . '/pack/candidate/opt']]) - command([[call mkdir(optdir1 . '/pluginA', 'p')]]) - command([[call mkdir(optdir1 . '/pluginC', 'p')]]) - command([[call mkdir(optdir2 . '/pluginB', 'p')]]) - command([[call mkdir(optdir2 . '/pluginC', 'p')]]) - end) - - it('works', function() - feed(':packadd <Tab>') - screen:expect([=[ - | - {0:~ }| - {0:~ }| - {1:pluginA}{2: pluginB pluginC }| - :packadd pluginA^ | - ]=]) - feed('<Tab>') - screen:expect([=[ - | - {0:~ }| - {0:~ }| - {2:pluginA }{1:pluginB}{2: pluginC }| - :packadd pluginB^ | - ]=]) - feed('<Tab>') - screen:expect([=[ - | - {0:~ }| - {0:~ }| - {2:pluginA pluginB }{1:pluginC}{2: }| - :packadd pluginC^ | - ]=]) - feed('<Tab>') - screen:expect([=[ - | - {0:~ }| - {0:~ }| - {2:pluginA pluginB pluginC }| - :packadd ^ | - ]=]) - end) - - it('works for colorschemes', function() - source([[ - let colordirrun = &packpath . '/runtime/colors' - let colordirstart = &packpath . '/pack/mine/start/foo/colors' - let colordiropt = &packpath . '/pack/mine/opt/bar/colors' - call mkdir(colordirrun, 'p') - call mkdir(colordirstart, 'p') - call mkdir(colordiropt, 'p') - call writefile(['let g:found_one = 1'], colordirrun . '/one.vim') - call writefile(['let g:found_two = 1'], colordirstart . '/two.vim') - call writefile(['let g:found_three = 1'], colordiropt . '/three.vim') - exe 'set rtp=' . &packpath . '/runtime']]) - - feed(':colorscheme <Tab>') - screen:expect([=[ - | - {0:~ }| - {0:~ }| - {1:one}{2: three two }| - :colorscheme one^ | - ]=]) - feed('<Tab>') - screen:expect([=[ - | - {0:~ }| - {0:~ }| - {2:one }{1:three}{2: two }| - :colorscheme three^ | - ]=]) - feed('<Tab>') - screen:expect([=[ - | - {0:~ }| - {0:~ }| - {2:one three }{1:two}{2: }| - :colorscheme two^ | - ]=]) - feed('<Tab>') - screen:expect([=[ - | - {0:~ }| - {0:~ }| - {2:one three two }| - :colorscheme ^ | - ]=]) - end) - end) -end) diff --git a/test/functional/legacy/prompt_buffer_spec.lua b/test/functional/legacy/prompt_buffer_spec.lua index 47eca19de3..63338b8dba 100644 --- a/test/functional/legacy/prompt_buffer_spec.lua +++ b/test/functional/legacy/prompt_buffer_spec.lua @@ -23,7 +23,7 @@ describe('prompt buffer', function() close else call append(line("$") - 1, 'Command: "' . a:text . '"') - set nomodfied + set nomodified call timer_start(20, {id -> TimerFunc(a:text)}) endif endfunc @@ -33,7 +33,7 @@ describe('prompt buffer', function() endfunc func SwitchWindows() - call timer_start(0, {-> execute("wincmd p|wincmd p", "")}) + call timer_start(0, {-> execute("wincmd p", "")}) endfunc ]]) feed_command("set noshowmode | set laststatus=0") @@ -187,7 +187,19 @@ describe('prompt buffer', function() -- INSERT -- | ]]) feed("<C-O>:call SwitchWindows()<CR>") - poke_eventloop() + screen:expect{grid=[[ + cmd: | + ~ | + ~ | + ~ | + [Prompt] [+] | + ^other buffer | + ~ | + ~ | + ~ | + | + ]]} + feed("<C-O>:call SwitchWindows()<CR>") screen:expect([[ cmd: ^ | ~ | @@ -201,7 +213,6 @@ describe('prompt buffer', function() -- INSERT -- | ]]) feed("<Esc>") - poke_eventloop() screen:expect([[ cmd:^ | ~ | diff --git a/test/functional/legacy/put_spec.lua b/test/functional/legacy/put_spec.lua new file mode 100644 index 0000000000..3ddf65490e --- /dev/null +++ b/test/functional/legacy/put_spec.lua @@ -0,0 +1,45 @@ +local helpers = require('test.functional.helpers')(after_each) +local clear = helpers.clear +local exec_lua = helpers.exec_lua +local meths = helpers.meths +local source = helpers.source +local eq = helpers.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({}, meths.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) +end) diff --git a/test/functional/legacy/search_spec.lua b/test/functional/legacy/search_spec.lua index 4ed08881de..67991f5d48 100644 --- a/test/functional/legacy/search_spec.lua +++ b/test/functional/legacy/search_spec.lua @@ -7,6 +7,7 @@ local eval = helpers.eval local feed = helpers.feed local funcs = helpers.funcs local poke_eventloop = helpers.poke_eventloop +local exec = helpers.exec describe('search cmdline', function() local screen @@ -640,3 +641,34 @@ describe('search cmdline', function() feed('<esc>') end) end) + +describe('Search highlight', function() + before_each(clear) + it('Search highlight is combined with Visual highlight vim-patch:8.2.2797', function() + local screen = Screen.new(40, 6) + screen:set_default_attr_ids({ + [1] = {bold = true, foreground = Screen.colors.Blue}, -- NonText + [2] = {bold = true}, -- ModeMsg, Search + [3] = {background = Screen.colors.LightGrey}, -- Visual + [4] = {background = Screen.colors.Yellow, bold = true}, -- Search + [5] = {background = Screen.colors.LightGrey, bold = true}, -- Visual + Search + }) + screen:attach() + exec([[ + set hlsearch noincsearch + call setline(1, repeat(["xxx yyy zzz"], 3)) + hi Search gui=bold + /yyy + call cursor(1, 6) + ]]) + feed('vjj') + screen:expect([[ + xxx {4:y}{5:yy}{3: zzz} | + {3:xxx }{5:yyy}{3: zzz} | + {3:xxx }{5:y}{4:^yy} zzz | + {1:~ }| + {1:~ }| + {2:-- VISUAL --} | + ]]) + end) +end) diff --git a/test/functional/legacy/search_stat_spec.lua b/test/functional/legacy/search_stat_spec.lua new file mode 100644 index 0000000000..c2ca393a56 --- /dev/null +++ b/test/functional/legacy/search_stat_spec.lua @@ -0,0 +1,184 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') +local clear, feed, exec, command = helpers.clear, helpers.feed, helpers.exec, helpers.command +local poke_eventloop = helpers.poke_eventloop + +describe('search stat', function() + local screen + before_each(function() + clear() + screen = Screen.new(30, 10) + screen:set_default_attr_ids({ + [1] = {bold = true, foreground = Screen.colors.Blue}, -- NonText + [2] = {background = Screen.colors.Yellow}, -- Search + [3] = {foreground = Screen.colors.Blue4, background = Screen.colors.LightGrey}, -- Folded + [4] = {reverse = true}, -- IncSearch, TabLineFill + }) + screen:attach() + end) + + it('right spacing with silent mapping vim-patch:8.1.1970', function() + exec([[ + set shortmess-=S + " Append 50 lines with text to search for, "foobar" appears 20 times + call append(0, repeat(['foobar', 'foo', 'fooooobar', 'foba', 'foobar'], 20)) + call setline(2, 'find this') + call setline(70, 'find this') + nnoremap n n + let @/ = 'find this' + call cursor(1,1) + norm n + ]]) + screen:expect([[ + foobar | + {2:^find this} | + fooooobar | + foba | + foobar | + foobar | + foo | + fooooobar | + foba | + /find this [1/2] | + ]]) + command('nnoremap <silent> n n') + feed('gg0n') + screen:expect([[ + foobar | + {2:^find this} | + fooooobar | + foba | + foobar | + foobar | + foo | + fooooobar | + foba | + [1/2] | + ]]) + end) + + it('when only match is in fold vim-patch:8.2.0840', function() + exec([[ + set shortmess-=S + setl foldenable foldmethod=indent foldopen-=search + call append(0, ['if', "\tfoo", "\tfoo", 'endif']) + let @/ = 'foo' + call cursor(1,1) + norm n + ]]) + screen:expect([[ + if | + {3:^+-- 2 lines: foo·············}| + endif | + | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + /foo [1/2] | + ]]) + feed('n') + poke_eventloop() + screen:expect_unchanged() + feed('n') + poke_eventloop() + screen:expect_unchanged() + end) + + it('is cleared by gd and gD vim-patch:8.2.3583', function() + exec([[ + call setline(1, ['int cat;', 'int dog;', 'cat = dog;']) + set shortmess-=S + set hlsearch + ]]) + feed('/dog<CR>') + screen:expect([[ + int cat; | + int {2:^dog}; | + cat = {2:dog}; | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + /dog [1/2] | + ]]) + feed('G0gD') + screen:expect([[ + int {2:^cat}; | + int dog; | + {2:cat} = dog; | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]) + end) + + it('is not broken by calling searchcount() in tabline vim-patch:8.2.4378', function() + exec([[ + call setline(1, ['abc--c', '--------abc', '--abc']) + set hlsearch + set incsearch + set showtabline=2 + + function MyTabLine() + try + let a=searchcount(#{recompute: 1, maxcount: -1}) + return a.current .. '/' .. a.total + catch + return '' + endtry + endfunction + + set tabline=%!MyTabLine() + ]]) + + feed('/abc') + screen:expect([[ + {4: }| + {2:abc}--c | + --------{4:abc} | + --{2:abc} | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + /abc^ | + ]]) + + feed('<C-G>') + screen:expect([[ + {4:3/3 }| + {2:abc}--c | + --------{2:abc} | + --{4:abc} | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + /abc^ | + ]]) + + feed('<C-G>') + screen:expect([[ + {4:1/3 }| + {4:abc}--c | + --------{2:abc} | + --{2:abc} | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + /abc^ | + ]]) + end) +end) diff --git a/test/functional/legacy/searchpos_spec.lua b/test/functional/legacy/searchpos_spec.lua deleted file mode 100644 index fc18341c38..0000000000 --- a/test/functional/legacy/searchpos_spec.lua +++ /dev/null @@ -1,35 +0,0 @@ -local helpers = require('test.functional.helpers')(after_each) -local call = helpers.call -local clear = helpers.clear -local command = helpers.command -local eq = helpers.eq -local eval = helpers.eval -local insert = helpers.insert - -describe('searchpos', function() - before_each(clear) - - it('is working', function() - insert([[ - 1a3 - 123xyz]]) - - call('cursor', 1, 1) - eq({1, 1, 2}, eval([[searchpos('\%(\([a-z]\)\|\_.\)\{-}xyz', 'pcW')]])) - call('cursor', 1, 2) - eq({2, 1, 1}, eval([['\%(\([a-z]\)\|\_.\)\{-}xyz'->searchpos('pcW')]])) - - command('set cpo-=c') - call('cursor', 1, 2) - eq({1, 2, 2}, eval([[searchpos('\%(\([a-z]\)\|\_.\)\{-}xyz', 'pcW')]])) - call('cursor', 1, 3) - eq({1, 3, 1}, eval([[searchpos('\%(\([a-z]\)\|\_.\)\{-}xyz', 'pcW')]])) - - -- Now with \zs, first match is in column 0, "a" is matched. - call('cursor', 1, 3) - eq({2, 4, 2}, eval([[searchpos('\%(\([a-z]\)\|\_.\)\{-}\zsxyz', 'pcW')]])) - -- With z flag start at cursor column, don't see the "a". - call('cursor', 1, 3) - eq({2, 4, 1}, eval([[searchpos('\%(\([a-z]\)\|\_.\)\{-}\zsxyz', 'pcWz')]])) - end) -end) diff --git a/test/functional/legacy/set_spec.lua b/test/functional/legacy/set_spec.lua deleted file mode 100644 index deb268b1e8..0000000000 --- a/test/functional/legacy/set_spec.lua +++ /dev/null @@ -1,30 +0,0 @@ --- Tests for :set - -local helpers = require('test.functional.helpers')(after_each) -local clear, command, eval, eq = - helpers.clear, helpers.command, helpers.eval, helpers.eq - -describe(':set', function() - before_each(clear) - - it('handles backslash properly', function() - command('set iskeyword=a,b,c') - command('set iskeyword+=d') - eq('a,b,c,d', eval('&iskeyword')) - - command([[set iskeyword+=\\,e]]) - eq([[a,b,c,d,\,e]], eval('&iskeyword')) - - command('set iskeyword-=e') - eq([[a,b,c,d,\]], eval('&iskeyword')) - - command([[set iskeyword-=\]]) - eq('a,b,c,d', eval('&iskeyword')) - end) - - it('recognizes a trailing comma with +=', function() - command('set wildignore=*.png,') - command('set wildignore+=*.jpg') - eq('*.png,*.jpg', eval('&wildignore')) - end) -end) diff --git a/test/functional/legacy/statusline_spec.lua b/test/functional/legacy/statusline_spec.lua new file mode 100644 index 0000000000..e2b30a7c82 --- /dev/null +++ b/test/functional/legacy/statusline_spec.lua @@ -0,0 +1,71 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') +local clear = helpers.clear +local exec = helpers.exec +local feed = helpers.feed + +before_each(clear) + +describe('statusline', function() + local screen + + before_each(function() + screen = Screen.new(50, 7) + screen:attach() + end) + + it('is updated in cmdline mode when using window-local statusline vim-patch:8.2.2737', function() + screen:set_default_attr_ids({ + [1] = {bold = true, foreground = Screen.colors.Blue}, -- NonText + [2] = {bold = true, reverse = true}, -- StatusLine + [3] = {reverse = true}, -- StatusLineNC + }) + exec([[ + setlocal statusline=-%{mode()}- + split + setlocal statusline=+%{mode()}+ + ]]) + screen:expect([[ + ^ | + {1:~ }| + {2:+n+ }| + | + {1:~ }| + {3:-n- }| + | + ]]) + feed(':') + screen:expect([[ + | + {1:~ }| + {2:+c+ }| + | + {1:~ }| + {3:-c- }| + :^ | + ]]) + end) + + it('truncated item does not cause off-by-one highlight vim-patch:8.2.4929', function() + screen:set_default_attr_ids({ + [1] = {bold = true, foreground = Screen.colors.Blue}, -- NonText + [2] = {foreground = Screen.colors.Blue}, -- User1 + [3] = {background = Screen.colors.Red, foreground = Screen.colors.White}, -- User2 + }) + exec([[ + set laststatus=2 + hi! link User1 Directory + hi! link User2 ErrorMsg + set statusline=%.5(%1*ABC%2*DEF%1*GHI%) + ]]) + screen:expect([[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {3:<F}{2:GHI }| + | + ]]) + end) +end) diff --git a/test/functional/legacy/utf8_spec.lua b/test/functional/legacy/utf8_spec.lua index 8b5fc02d11..67a4bec4c5 100644 --- a/test/functional/legacy/utf8_spec.lua +++ b/test/functional/legacy/utf8_spec.lua @@ -28,7 +28,7 @@ describe('utf8', function() expect([[ start: axaa - xあああ + xあああ bxbb]]) end) diff --git a/test/functional/legacy/visual_mode_spec.lua b/test/functional/legacy/visual_mode_spec.lua index c8e83ed649..8b5dd0c2dc 100644 --- a/test/functional/legacy/visual_mode_spec.lua +++ b/test/functional/legacy/visual_mode_spec.lua @@ -1,5 +1,3 @@ --- Test visual line mode selection redraw after scrolling - local helpers = require('test.functional.helpers')(after_each) local Screen = require('test.functional.ui.screen') @@ -10,6 +8,7 @@ local feed_command = helpers.feed_command local funcs = helpers.funcs local meths = helpers.meths local eq = helpers.eq +local exec = helpers.exec describe('visual line mode', function() local screen @@ -40,3 +39,44 @@ describe('visual line mode', function() ]]) end) end) + +describe('visual block mode', function() + it('shows selection correctly with virtualedit=block', function() + clear() + local screen = Screen.new(30, 7) + screen:set_default_attr_ids({ + [1] = {bold = true}, -- ModeMsg + [2] = {background = Screen.colors.LightGrey}, -- Visual + [3] = {foreground = Screen.colors.Blue, bold = true} -- NonText + }) + screen:attach() + + exec([[ + call setline(1, ['aaaaaa', 'bbbb', 'cc']) + set virtualedit=block + normal G + ]]) + + feed('<C-V>gg$') + screen:expect([[ + {2:aaaaaa}^ | + {2:bbbb } | + {2:cc } | + {3:~ }| + {3:~ }| + {3:~ }| + {1:-- VISUAL BLOCK --} | + ]]) + + feed('<Esc>gg<C-V>G$') + screen:expect([[ + {2:aaaaaa } | + {2:bbbb } | + {2:cc}^ {2: } | + {3:~ }| + {3:~ }| + {3:~ }| + {1:-- VISUAL BLOCK --} | + ]]) + end) +end) diff --git a/test/functional/legacy/wordcount_spec.lua b/test/functional/legacy/wordcount_spec.lua index 826743b0ca..21f96628c0 100644 --- a/test/functional/legacy/wordcount_spec.lua +++ b/test/functional/legacy/wordcount_spec.lua @@ -50,44 +50,44 @@ describe('wordcount', function() ]=]) -- Test 1: empty window - eq(eval('DoRecordWin()'), - eval([=[ + eq(eval([=[ [[''], {'chars': 0, 'cursor_chars': 0, 'words': 0, 'cursor_words': 0, 'bytes': 0, 'cursor_bytes': 0}] - ]=]) + ]=]), + eval('DoRecordWin()') ) -- Test 2: some words, cursor at start command([[call PutInWindow('one two three')]]) - eq(eval('DoRecordWin([1, 1, 0])'), - eval([=[ + eq(eval([=[ [['', 'one two three'], {'chars': 15, 'cursor_chars': 1, 'words': 3, 'cursor_words': 0, 'bytes': 15, 'cursor_bytes': 1}] - ]=]) + ]=]), + eval('DoRecordWin([1, 1, 0])') ) -- Test 3: some words, cursor at end command([[call PutInWindow('one two three')]]) - eq(eval('DoRecordWin([2, 99, 0])'), - eval([=[ + eq(eval([=[ [['', 'one two three'], {'chars': 15, 'cursor_chars': 14, 'words': 3, 'cursor_words': 3, 'bytes': 15, 'cursor_bytes': 14}] - ]=]) + ]=]), + eval('DoRecordWin([2, 99, 0])') ) -- Test 4: some words, cursor at end, ve=all command('set ve=all') command([[call PutInWindow('one two three')]]) - eq(eval('DoRecordWin([2,99,0])'), - eval([=[ + eq(eval([=[ [['', 'one two three'], {'chars': 15, 'cursor_chars': 15, 'words': 3, 'cursor_words': 3, 'bytes': 15, 'cursor_bytes': 15}] - ]=]) + ]=]), + eval('DoRecordWin([2,99,0])') ) command('set ve=') -- Test 5: several lines with words command([=[call PutInWindow(['one two three', 'one two three', 'one two three'])]=]) - eq(eval('DoRecordWin([4,99,0])'), - eval([=[ + eq(eval([=[ [['', 'one two three', 'one two three', 'one two three'], {'chars': 43, 'cursor_chars': 42, 'words': 9, 'cursor_words': 9, 'bytes': 43, 'cursor_bytes': 42}] - ]=]) + ]=]), + eval('DoRecordWin([4,99,0])') ) -- Test 6: one line with BOM set @@ -95,10 +95,10 @@ describe('wordcount', function() command('wincmd k') command('set bomb') command('wincmd j') - eq(eval('DoRecordWin([2,99,0])'), - eval([=[ + eq(eval([=[ [['', 'one two three'], {'chars': 15, 'cursor_chars': 14, 'words': 3, 'cursor_words': 3, 'bytes': 18, 'cursor_bytes': 14}] - ]=]) + ]=]), + eval('DoRecordWin([2,99,0])') ) command('wincmd k') command('set nobomb') @@ -106,18 +106,18 @@ describe('wordcount', function() -- Test 7: one line with multibyte words command([=[call PutInWindow(['Äne M¤ne Müh'])]=]) - eq(eval('DoRecordWin([2,99,0])'), - eval([=[ + eq(eval([=[ [['', 'Äne M¤ne Müh'], {'chars': 14, 'cursor_chars': 13, 'words': 3, 'cursor_words': 3, 'bytes': 17, 'cursor_bytes': 16}] - ]=]) + ]=]), + eval('DoRecordWin([2,99,0])') ) -- Test 8: several lines with multibyte words command([=[call PutInWindow(['Äne M¤ne Müh', 'und raus bist dü!'])]=]) - eq(eval('DoRecordWin([3,99,0])'), - eval([=[ + eq(eval([=[ [['', 'Äne M¤ne Müh', 'und raus bist dü!'], {'chars': 32, 'cursor_chars': 31, 'words': 7, 'cursor_words': 7, 'bytes': 36, 'cursor_bytes': 35}] - ]=]) + ]=]), + eval('DoRecordWin([3,99,0])') ) -- Test 9: visual mode, complete buffer @@ -131,10 +131,10 @@ describe('wordcount', function() command('set stl= ls=1') command('let log=DoRecordWin([3,99,0])') command('let log[1]=g:visual_stat') - eq(eval('log'), - eval([=[ + eq(eval([=[ [['', 'Äne M¤ne Müh', 'und raus bist dü!'], {'chars': 32, 'words': 7, 'bytes': 36, 'visual_chars': 32, 'visual_words': 7, 'visual_bytes': 36}] - ]=]) + ]=]), + eval('log') ) -- Test 10: visual mode (empty) @@ -148,10 +148,10 @@ describe('wordcount', function() command('set stl= ls=1') command('let log=DoRecordWin([3,99,0])') command('let log[1]=g:visual_stat') - eq(eval('log'), - eval([=[ + eq(eval([=[ [['', 'Äne M¤ne Müh', 'und raus bist dü!'], {'chars': 32, 'words': 7, 'bytes': 36, 'visual_chars': 1, 'visual_words': 0, 'visual_bytes': 1}] - ]=]) + ]=]), + eval('log') ) -- Test 11: visual mode, single line @@ -165,10 +165,10 @@ describe('wordcount', function() command('set stl= ls=1') command('let log=DoRecordWin([3,99,0])') command('let log[1]=g:visual_stat') - eq(eval('log'), - eval([=[ + eq(eval([=[ [['', 'Äne M¤ne Müh', 'und raus bist dü!'], {'chars': 32, 'words': 7, 'bytes': 36, 'visual_chars': 13, 'visual_words': 3, 'visual_bytes': 16}] - ]=]) + ]=]), + eval('log') ) end) end) diff --git a/test/functional/lua/api_spec.lua b/test/functional/lua/api_spec.lua index cb37fb9a1c..f173a15d32 100644 --- a/test/functional/lua/api_spec.lua +++ b/test/functional/lua/api_spec.lua @@ -102,6 +102,13 @@ describe('luaeval(vim.api.…)', function() eq(false, funcs.luaeval('vim.api.nvim__id(false)')) eq(NIL, funcs.luaeval('vim.api.nvim__id(nil)')) + -- API strings from Blobs can work as NUL-terminated C strings + eq('Vim(call):E5555: API call: Vim:E15: Invalid expression: ', + exc_exec('call nvim_eval(v:_null_blob)')) + eq('Vim(call):E5555: API call: Vim:E15: Invalid expression: ', + exc_exec('call nvim_eval(0z)')) + eq(1, eval('nvim_eval(0z31)')) + eq(0, eval([[type(luaeval('vim.api.nvim__id(1)'))]])) eq(1, eval([[type(luaeval('vim.api.nvim__id("1")'))]])) eq(3, eval([[type(luaeval('vim.api.nvim__id({1})'))]])) diff --git a/test/functional/lua/buffer_updates_spec.lua b/test/functional/lua/buffer_updates_spec.lua index c83a50b78b..10de45274c 100644 --- a/test/functional/lua/buffer_updates_spec.lua +++ b/test/functional/lua/buffer_updates_spec.lua @@ -252,9 +252,8 @@ describe('lua buffer event callbacks: on_lines', function() eq(2, meths.win_get_cursor(0)[1]) end) - it('does not SEGFAULT when calling win_findbuf in on_detach', function() - - exec_lua[[ + it('does not SEGFAULT when accessing window buffer info in on_detach #14998', function() + local code = [[ local buf = vim.api.nvim_create_buf(false, false) vim.cmd"split" @@ -262,13 +261,19 @@ describe('lua buffer event callbacks: on_lines', function() vim.api.nvim_buf_attach(buf, false, { on_detach = function(_, buf) + vim.fn.tabpagebuflist() vim.fn.win_findbuf(buf) end }) ]] + exec_lua(code) command("q!") helpers.assert_alive() + + exec_lua(code) + command("bd!") + helpers.assert_alive() end) it('#12718 lnume', function() @@ -612,7 +617,15 @@ describe('lua: nvim_buf_attach on_bytes', function() } feed("<esc>") - -- replacing with escaped characters + -- replacing with expression register + feed([[:%s/b/\=5+5]]) + check_events { + { "test1", "bytes", 1, 3, 0, 1, 1, 0, 1, 1, 0, 2, 2 }; + { "test1", "bytes", 1, 5, 0, 1, 1, 0, 2, 2, 0, 1, 1 }; + } + + feed("<esc>") + -- replacing with backslash feed([[:%s/b/\\]]) check_events { { "test1", "bytes", 1, 3, 0, 1, 1, 0, 1, 1, 0, 1, 1 }; @@ -620,8 +633,24 @@ describe('lua: nvim_buf_attach on_bytes', function() } feed("<esc>") - -- replacing with expression register - feed([[:%s/b/\=5+5]]) + -- replacing with backslash from expression register + feed([[:%s/b/\='\']]) + check_events { + { "test1", "bytes", 1, 3, 0, 1, 1, 0, 1, 1, 0, 1, 1 }; + { "test1", "bytes", 1, 5, 0, 1, 1, 0, 1, 1, 0, 1, 1 }; + } + + feed("<esc>") + -- replacing with backslash followed by another character + feed([[:%s/b/\\!]]) + check_events { + { "test1", "bytes", 1, 3, 0, 1, 1, 0, 1, 1, 0, 2, 2 }; + { "test1", "bytes", 1, 5, 0, 1, 1, 0, 2, 2, 0, 1, 1 }; + } + + feed("<esc>") + -- replacing with backslash followed by another character from expression register + feed([[:%s/b/\='\!']]) check_events { { "test1", "bytes", 1, 3, 0, 1, 1, 0, 1, 1, 0, 2, 2 }; { "test1", "bytes", 1, 5, 0, 1, 1, 0, 2, 2, 0, 1, 1 }; @@ -1104,6 +1133,15 @@ describe('lua: nvim_buf_attach on_bytes', function() check_events { } end) + it("works with accepting spell suggestions", function() + local check_events = setup_eventcheck(verify, {"hallo"}) + + feed("gg0z=4<cr><cr>") -- accepts 'Hello' + check_events { + { "test1", "bytes", 1, 3, 0, 0, 0, 0, 2, 2, 0, 2, 2 }; + } + end) + local function test_lockmarks(mode) local description = (mode ~= "") and mode or "(baseline)" it("test_lockmarks " .. description .. " %delete _", function() diff --git a/test/functional/lua/command_line_completion_spec.lua b/test/functional/lua/command_line_completion_spec.lua index 3ba7e1589f..3a5966755e 100644 --- a/test/functional/lua/command_line_completion_spec.lua +++ b/test/functional/lua/command_line_completion_spec.lua @@ -106,6 +106,14 @@ describe('nlua_expand_pat', function() ) end) + it('should work with lazy submodules of "vim" global', function() + eq({{ 'inspect' }, 4 }, + get_completions('vim.inspec')) + + eq({{ 'set' }, 11 }, + get_completions('vim.keymap.se')) + end) + it('should be able to interpolate globals', function() eq( {{ diff --git a/test/functional/lua/diagnostic_spec.lua b/test/functional/lua/diagnostic_spec.lua index 7d260f2e29..f9647f5b6a 100644 --- a/test/functional/lua/diagnostic_spec.lua +++ b/test/functional/lua/diagnostic_spec.lua @@ -208,10 +208,10 @@ describe('vim.diagnostic', function() 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, 5), + make_warning("Warning on Server 1", 2, 1, 2, 3), } local ns_2_diags = { - make_warning("Warning 1", 2, 1, 2, 5), + make_warning("Warning 1", 2, 1, 2, 3), } vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, ns_1_diags) @@ -255,10 +255,10 @@ describe('vim.diagnostic', 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, 5), + make_warning("Warning on Server 1", 2, 1, 2, 3), } local ns_2_diags = { - make_warning("Warning 1", 2, 1, 2, 5), + make_warning("Warning 1", 2, 1, 2, 3), } vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, ns_1_diags) @@ -599,10 +599,10 @@ describe('vim.diagnostic', function() 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, 5), + make_warning("Warning on Server 1", 2, 1, 2, 3), } local ns_2_diags = { - make_warning("Warning 1", 2, 1, 2, 5), + make_warning("Warning 1", 2, 1, 2, 3), } vim.diagnostic.set(diagnostic_ns, diagnostic_bufnr, ns_1_diags) @@ -729,6 +729,19 @@ describe('vim.diagnostic', function() return vim.diagnostic.get_next_pos { namespace = diagnostic_ns } ]]) 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), + }) + 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 } + ]]) +end) end) describe('get_prev_pos()', function() @@ -787,7 +800,7 @@ describe('vim.diagnostic', 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, 5), + make_warning("Warning on Server 1", 1, 1, 2, 3), }) return #vim.diagnostic.get(diagnostic_bufnr) @@ -798,9 +811,9 @@ describe('vim.diagnostic', 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, 5), - make_info("Ignored information", 1, 1, 2, 5), - make_hint("Here's a hint", 1, 1, 2, 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), }) return { @@ -820,8 +833,8 @@ describe('vim.diagnostic', function() eq(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, 5), - make_info("Ignored information", 1, 1, 2, 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", 2, 1, 1, 5), }) @@ -1939,24 +1952,31 @@ describe('vim.diagnostic', function() end) it('triggers the autocommand when diagnostics are set', function() - eq(1, exec_lua [[ + eq(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) + vim.g.diagnostic_autocmd_triggered = 0 - vim.cmd('autocmd DiagnosticChanged * let g:diagnostic_autocmd_triggered = 1') + vim.cmd('autocmd DiagnosticChanged * let g:diagnostic_autocmd_triggered = +expand("<abuf>")') 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 vim.g.diagnostic_autocmd_triggered + return vim.g.diagnostic_autocmd_triggered == diagnostic_bufnr ]]) end) it('triggers the autocommand when diagnostics are cleared', function() - eq(1, exec_lua [[ + 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 = 1') + 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 + return vim.g.diagnostic_autocmd_triggered == diagnostic_bufnr ]]) end) end) diff --git a/test/functional/lua/filetype_spec.lua b/test/functional/lua/filetype_spec.lua index 1b2cb61cc2..be57b2db31 100644 --- a/test/functional/lua/filetype_spec.lua +++ b/test/functional/lua/filetype_spec.lua @@ -3,6 +3,7 @@ local exec_lua = helpers.exec_lua local eq = helpers.eq local clear = helpers.clear local pathroot = helpers.pathroot +local command = helpers.command local root = pathroot() @@ -23,8 +24,7 @@ describe('vim.filetype', function() rs = 'radicalscript', }, }) - vim.filetype.match('main.rs') - return vim.bo.filetype + return vim.filetype.match({ filename = 'main.rs' }) ]]) end) @@ -38,8 +38,7 @@ describe('vim.filetype', function() ['main.rs'] = 'somethingelse', }, }) - vim.filetype.match('main.rs') - return vim.bo.filetype + return vim.filetype.match({ filename = 'main.rs' }) ]]) end) @@ -50,8 +49,7 @@ describe('vim.filetype', function() ['s_O_m_e_F_i_l_e'] = 'nim', }, }) - vim.filetype.match('s_O_m_e_F_i_l_e') - return vim.bo.filetype + return vim.filetype.match({ filename = 's_O_m_e_F_i_l_e' }) ]]) eq('dosini', exec_lua([[ @@ -62,25 +60,26 @@ describe('vim.filetype', function() [root .. '/.config/fun/config'] = 'dosini', }, }) - vim.filetype.match(root .. '/.config/fun/config') - return vim.bo.filetype + return vim.filetype.match({ filename = root .. '/.config/fun/config' }) ]], root)) end) it('works with patterns', function() eq('markdown', exec_lua([[ local root = ... + vim.env.HOME = '/a-funky+home%dir' vim.filetype.add({ pattern = { - [root .. '/blog/.*%.txt'] = 'markdown', + ['~/blog/.*%.txt'] = 'markdown', } }) - vim.filetype.match(root .. '/blog/why_neovim_is_awesome.txt') - return vim.bo.filetype + return vim.filetype.match({ filename = '~/blog/why_neovim_is_awesome.txt' }) ]], root)) end) it('works with functions', function() + command('new') + command('file relevant_to_me') eq('foss', exec_lua [[ vim.filetype.add({ pattern = { @@ -91,8 +90,7 @@ describe('vim.filetype', function() end, } }) - vim.filetype.match('relevant_to_me') - return vim.bo.filetype + return vim.filetype.match({ buf = 0 }) ]]) end) end) diff --git a/test/functional/lua/fs_spec.lua b/test/functional/lua/fs_spec.lua new file mode 100644 index 0000000000..2bcc84db0f --- /dev/null +++ b/test/functional/lua/fs_spec.lua @@ -0,0 +1,101 @@ +local helpers = require('test.functional.helpers')(after_each) + +local clear = helpers.clear +local exec_lua = helpers.exec_lua +local eq = helpers.eq +local mkdir_p = helpers.mkdir_p +local rmdir = helpers.rmdir +local nvim_dir = helpers.nvim_dir +local test_build_dir = helpers.test_build_dir +local iswin = helpers.iswin +local nvim_prog = helpers.nvim_prog + +local nvim_prog_basename = iswin() and 'nvim.exe' or 'nvim' + +before_each(clear) + +describe('vim.fs', function() + describe('parents()', function() + it('works', function() + local test_dir = nvim_dir .. '/test' + mkdir_p(test_dir) + local dirs = exec_lua([[ + local test_dir, test_build_dir = ... + local dirs = {} + for dir in vim.fs.parents(test_dir .. "/foo.txt") do + dirs[#dirs + 1] = dir + if dir == test_build_dir then + break + end + end + return dirs + ]], test_dir, test_build_dir) + eq({test_dir, nvim_dir, test_build_dir}, dirs) + rmdir(test_dir) + end) + end) + + describe('dirname()', function() + it('works', function() + eq(test_build_dir, exec_lua([[ + local nvim_dir = ... + return vim.fs.dirname(nvim_dir) + ]], nvim_dir)) + end) + end) + + describe('basename()', function() + it('works', function() + eq(nvim_prog_basename, exec_lua([[ + local nvim_prog = ... + return vim.fs.basename(nvim_prog) + ]], nvim_prog)) + end) + end) + + describe('dir()', 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 + end + end + return false + ]], nvim_dir, nvim_prog_basename)) + end) + end) + + describe('find()', function() + it('works', function() + eq({test_build_dir}, exec_lua([[ + local dir = ... + return vim.fs.find('build', { path = dir, upward = true, type = 'directory' }) + ]], nvim_dir)) + eq({nvim_prog}, exec_lua([[ + local dir, nvim = ... + return vim.fs.find(nvim, { path = dir, type = 'file' }) + ]], test_build_dir, nvim_prog_basename)) + end) + end) + + describe('normalize()', function() + it('works with backward slashes', function() + eq('C:/Users/jdoe', exec_lua [[ return vim.fs.normalize('C:\\Users\\jdoe') ]]) + end) + it('works with ~', function() + if iswin() then + pending([[$HOME does not exist on Windows ¯\_(ツ)_/¯]]) + end + eq(os.getenv('HOME') .. '/src/foo', exec_lua [[ return vim.fs.normalize('~/src/foo') ]]) + end) + it('works with environment variables', 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)) + end) + end) +end) diff --git a/test/functional/lua/highlight_spec.lua b/test/functional/lua/highlight_spec.lua index 50eecb5d09..60d0ed5017 100644 --- a/test/functional/lua/highlight_spec.lua +++ b/test/functional/lua/highlight_spec.lua @@ -6,20 +6,29 @@ local command = helpers.command local clear = helpers.clear describe('vim.highlight.on_yank', function() - before_each(function() clear() end) it('does not show errors even if buffer is wiped before timeout', function() command('new') - exec_lua[[ + exec_lua([[ vim.highlight.on_yank({timeout = 10, on_macro = true, event = {operator = "y", regtype = "v"}}) vim.cmd('bwipeout!') - ]] + ]]) helpers.sleep(10) helpers.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"}}) + vim.loop.sleep(10) + vim.schedule(function() + vim.highlight.on_yank({timeout = 0, on_macro = true, event = {operator = "y"}}) + end) + ]]) + eq('', eval('v:errmsg')) + end) end) diff --git a/test/functional/lua/luaeval_spec.lua b/test/functional/lua/luaeval_spec.lua index c543dd1995..1322155595 100644 --- a/test/functional/lua/luaeval_spec.lua +++ b/test/functional/lua/luaeval_spec.lua @@ -532,6 +532,7 @@ describe('v:lua', function() command('set pp+=test/functional/fixtures') eq('\tbadval', eval("v:lua.require'leftpad'('badval')")) eq(9003, eval("v:lua.require'bar'.doit()")) + eq(9004, eval("v:lua.require'baz-quux'.doit()")) end) it('throw errors for invalid use', function() diff --git a/test/functional/lua/overrides_spec.lua b/test/functional/lua/overrides_spec.lua index b0712ff366..9b51af1eec 100644 --- a/test/functional/lua/overrides_spec.lua +++ b/test/functional/lua/overrides_spec.lua @@ -61,6 +61,44 @@ describe('print', function() eq('Vim(lua):E5108: Error executing lua E5114: Error while converting print argument #2: <Unknown error: lua_tolstring returned NULL for tostring result>', pcall_err(command, 'lua print("foo", v_tblout, "bar")')) end) + it('coerces error values into strings', function() + write_file(fname, [[ + function string_error() error("my mistake") end + function number_error() error(1234) end + function nil_error() error(nil) end + function table_error() error({message = "my mistake"}) end + function custom_error() + local err = {message = "my mistake", code = 11234} + setmetatable(err, { + __tostring = function(t) + return "Internal Error [" .. t.code .. "] " .. t.message + end + }) + error(err) + end + function bad_custom_error() + local err = {message = "my mistake", code = 11234} + setmetatable(err, { + -- intentionally not a function, downstream programmer has made an mistake + __tostring = "Internal Error [" .. err.code .. "] " .. err.message + }) + error(err) + end + ]]) + eq('', exec_capture('luafile ' .. fname)) + eq('Vim(lua):E5108: Error executing lua Xtest-functional-lua-overrides-luafile:0: my mistake', + pcall_err(command, 'lua string_error()')) + eq('Vim(lua):E5108: Error executing lua Xtest-functional-lua-overrides-luafile:0: 1234', + pcall_err(command, 'lua number_error()')) + eq('Vim(lua):E5108: Error executing lua [NULL]', + pcall_err(command, 'lua nil_error()')) + eq('Vim(lua):E5108: Error executing lua [NULL]', + pcall_err(command, 'lua table_error()')) + eq('Vim(lua):E5108: Error executing lua Internal Error [11234] my mistake', + pcall_err(command, 'lua custom_error()')) + eq('Vim(lua):E5108: Error executing lua [NULL]', + pcall_err(command, 'lua bad_custom_error()')) + end) it('prints strings with NULs and NLs correctly', function() meths.set_option('more', true) eq('abc ^@ def\nghi^@^@^@jkl\nTEST\n\n\nT\n', diff --git a/test/functional/lua/thread_spec.lua b/test/functional/lua/thread_spec.lua new file mode 100644 index 0000000000..e183ce3a57 --- /dev/null +++ b/test/functional/lua/thread_spec.lua @@ -0,0 +1,408 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') +local assert_alive = helpers.assert_alive +local clear = helpers.clear +local feed = helpers.feed +local eq = helpers.eq +local exec_lua = helpers.exec_lua +local next_msg = helpers.next_msg +local NIL = helpers.NIL +local pcall_err = helpers.pcall_err + +describe('thread', function() + local screen + + before_each(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() + exec_lua [[ + local thread = vim.loop.new_thread(function() + error('Error in thread entry func') + end) + vim.loop.thread_join(thread) + ]] + + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {2: }| + {3:Error in luv thread:} | + {3:[string "<nvim>"]:2: Error in thread entry func} | + {4:Press ENTER or type command to continue}^ | + ]]) + feed('<cr>') + assert_alive() + end) + + it('callback is executed in protected mode', function() + exec_lua [[ + local thread = vim.loop.new_thread(function() + local timer = vim.loop.new_timer() + local function ontimeout() + timer:stop() + timer:close() + error('Error in thread callback') + end + timer:start(10, 0, ontimeout) + vim.loop.run() + end) + vim.loop.thread_join(thread) + ]] + + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {2: }| + {3:Error in luv callback, thread:} | + {3:[string "<nvim>"]:6: Error in thread callback} | + {4:Press ENTER or type command to continue}^ | + ]]) + feed('<cr>') + assert_alive() + end) + + describe('print', function() + it('works', function() + exec_lua [[ + local thread = vim.loop.new_thread(function() + print('print in thread') + end) + vim.loop.thread_join(thread) + ]] + + screen:expect([[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + print in thread | + ]]) + end) + + it('vim.inspect', function() + exec_lua [[ + local thread = vim.loop.new_thread(function() + print(vim.inspect({1,2})) + end) + vim.loop.thread_join(thread) + ]] + + screen:expect([[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + { 1, 2 } | + ]]) + end) + end) + + describe('vim.*', function() + before_each(function() + clear() + exec_lua [[ + Thread_Test = {} + + Thread_Test.entry_func = function(async, entry_str, args) + local decoded_args = vim.mpack.decode(args) + assert(loadstring(entry_str))(async, decoded_args) + end + + function Thread_Test:do_test() + local async + local on_async = self.on_async + async = vim.loop.new_async(function(ret) + on_async(ret) + async:close() + end) + local thread = + vim.loop.new_thread(self.entry_func, async, self.entry_str, self.args) + vim.loop.thread_join(thread) + end + + Thread_Test.new = function(entry, on_async, ...) + self = {} + setmetatable(self, {__index = Thread_Test}) + self.args = vim.mpack.encode({...}) + self.entry_str = string.dump(entry) + self.on_async = on_async + return self + end + ]] + end) + + it('is_thread', function() + exec_lua [[ + local entry = function(async) + async:send(vim.is_thread()) + end + local on_async = function(ret) + vim.rpcnotify(1, 'result', ret) + end + local thread_test = Thread_Test.new(entry, on_async) + thread_test:do_test() + ]] + + eq({'notification', 'result', {true}}, next_msg()) + end) + + it('loop', function() + exec_lua [[ + local entry = function(async) + async:send(vim.loop.version()) + end + local on_async = function(ret) + vim.rpcnotify(1, ret) + end + local thread_test = Thread_Test.new(entry, on_async) + thread_test:do_test() + ]] + + local msg = next_msg() + eq(msg[1], 'notification') + assert(tonumber(msg[2]) >= 72961) + end) + + it('mpack', function() + exec_lua [[ + local entry = function(async) + async:send(vim.mpack.encode({33, vim.NIL, 'text'})) + end + local on_async = function(ret) + vim.rpcnotify(1, 'result', vim.mpack.decode(ret)) + end + local thread_test = Thread_Test.new(entry, on_async) + thread_test:do_test() + ]] + + eq({'notification', 'result', {{33, NIL, 'text'}}}, next_msg()) + end) + + it('json', function() + exec_lua [[ + local entry = function(async) + async:send(vim.json.encode({33, vim.NIL, 'text'})) + end + local on_async = function(ret) + vim.rpcnotify(1, 'result', vim.json.decode(ret)) + end + local thread_test = Thread_Test.new(entry, on_async) + thread_test:do_test() + ]] + + eq({'notification', 'result', {{33, NIL, 'text'}}}, next_msg()) + end) + + it('diff', function() + exec_lua [[ + local entry = function(async) + async:send(vim.diff('Hello\n', 'Helli\n')) + end + local on_async = function(ret) + vim.rpcnotify(1, 'result', ret) + end + local thread_test = Thread_Test.new(entry, on_async) + thread_test:do_test() + ]] + + eq({'notification', 'result', + {table.concat({ + '@@ -1 +1 @@', + '-Hello', + '+Helli', + '' + }, '\n')}}, + next_msg()) + end) + end) +end) + +describe('threadpool', function() + before_each(clear) + + it('is_thread', function() + eq(false, exec_lua [[return vim.is_thread()]]) + + exec_lua [[ + local work_fn = function() + return vim.is_thread() + end + local after_work_fn = function(ret) + vim.rpcnotify(1, 'result', ret) + end + local work = vim.loop.new_work(work_fn, after_work_fn) + work:queue() + ]] + + eq({'notification', 'result', {true}}, next_msg()) + end) + + it('with invalid argument', function() + local status = pcall_err(exec_lua, [[ + local work = vim.loop.new_thread(function() end, function() end) + work:queue({}) + ]]) + + eq([[Error executing lua: [string "<nvim>"]:0: Error: thread arg not support type 'function' at 1]], + status) + end) + + 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.loop.new_work(function() return {} end, function() end) + work:queue() + ]] + + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {2: }| + {3:Error in luv thread:} | + {3:Error: thread arg not support type 'table' at 1} | + {4:Press ENTER or type command to continue}^ | + ]]) + end) + + describe('vim.*', function() + before_each(function() + clear() + exec_lua [[ + Threadpool_Test = {} + + Threadpool_Test.work_fn = function(work_fn_str, args) + local decoded_args = vim.mpack.decode(args) + return assert(loadstring(work_fn_str))(decoded_args) + end + + function Threadpool_Test:do_test() + local work = + vim.loop.new_work(self.work_fn, self.after_work) + work:queue(self.work_fn_str, self.args) + end + + Threadpool_Test.new = function(work_fn, after_work, ...) + self = {} + setmetatable(self, {__index = Threadpool_Test}) + self.args = vim.mpack.encode({...}) + self.work_fn_str = string.dump(work_fn) + self.after_work = after_work + return self + end + ]] + end) + + it('loop', function() + exec_lua [[ + local work_fn = function() + return vim.loop.version() + end + local after_work_fn = function(ret) + vim.rpcnotify(1, ret) + end + local threadpool_test = Threadpool_Test.new(work_fn, after_work_fn) + threadpool_test:do_test() + ]] + + local msg = next_msg() + eq(msg[1], 'notification') + assert(tonumber(msg[2]) >= 72961) + end) + + it('mpack', function() + exec_lua [[ + local work_fn = function() + local var = vim.mpack.encode({33, vim.NIL, 'text'}) + return var + end + local after_work_fn = function(ret) + vim.rpcnotify(1, 'result', vim.mpack.decode(ret)) + end + local threadpool_test = Threadpool_Test.new(work_fn, after_work_fn) + threadpool_test:do_test() + ]] + + eq({'notification', 'result', {{33, NIL, 'text'}}}, next_msg()) + end) + + it('json', function() + exec_lua [[ + local work_fn = function() + local var = vim.json.encode({33, vim.NIL, 'text'}) + return var + end + local after_work_fn = function(ret) + vim.rpcnotify(1, 'result', vim.json.decode(ret)) + end + local threadpool_test = Threadpool_Test.new(work_fn, after_work_fn) + threadpool_test:do_test() + ]] + + eq({'notification', 'result', {{33, NIL, 'text'}}}, next_msg()) + end) + + it('work', function() + exec_lua [[ + local work_fn = function() + return vim.diff('Hello\n', 'Helli\n') + end + local after_work_fn = function(ret) + vim.rpcnotify(1, 'result', ret) + end + local threadpool_test = Threadpool_Test.new(work_fn, after_work_fn) + threadpool_test:do_test() + ]] + + eq({'notification', 'result', + {table.concat({ + '@@ -1 +1 @@', + '-Hello', + '+Helli', + '' + }, '\n')}}, + next_msg()) + end) + end) +end) diff --git a/test/functional/lua/ui_spec.lua b/test/functional/lua/ui_spec.lua index 2371939204..3fcb2dec8d 100644 --- a/test/functional/lua/ui_spec.lua +++ b/test/functional/lua/ui_spec.lua @@ -2,6 +2,8 @@ local helpers = require('test.functional.helpers')(after_each) local eq = helpers.eq local exec_lua = helpers.exec_lua local clear = helpers.clear +local feed = helpers.feed +local eval = helpers.eval describe('vim.ui', function() before_each(function() @@ -67,5 +69,19 @@ describe('vim.ui', function() eq('Inputted text', result[1]) eq('Input: ', result[2]) end) + + it('can input text on nil opt', function() + feed(':lua vim.ui.input(nil, function(input) result = input end)<cr>') + eq('', eval('v:errmsg')) + feed('Inputted text<cr>') + eq('Inputted text', exec_lua('return result')) + end) + + it('can input text on {} opt', function() + feed(':lua vim.ui.input({}, function(input) result = input end)<cr>') + eq('', eval('v:errmsg')) + feed('abcdefg<cr>') + eq('abcdefg', exec_lua('return result')) + end) end) end) diff --git a/test/functional/lua/uri_spec.lua b/test/functional/lua/uri_spec.lua index fa11fdf794..4635f17557 100644 --- a/test/functional/lua/uri_spec.lua +++ b/test/functional/lua/uri_spec.lua @@ -101,7 +101,7 @@ describe('URI methods', function() eq('C:\\Foo\\Bar\\Baz.txt', exec_lua(test_case)) end) - it('file path includes only ascii charactors with encoded colon character', function() + it('file path includes only ascii characters with encoded colon character', function() local test_case = [[ local uri = 'file:///C%3A/Foo/Bar/Baz.txt' return vim.uri_to_fname(uri) diff --git a/test/functional/lua/vim_spec.lua b/test/functional/lua/vim_spec.lua index 642dc59bbc..883e0e373b 100644 --- a/test/functional/lua/vim_spec.lua +++ b/test/functional/lua/vim_spec.lua @@ -14,15 +14,19 @@ local feed = helpers.feed local pcall_err = helpers.pcall_err local exec_lua = helpers.exec_lua local matches = helpers.matches -local source = helpers.source +local exec = helpers.exec local NIL = helpers.NIL local retry = helpers.retry local next_msg = helpers.next_msg local remove_trace = helpers.remove_trace - -before_each(clear) +local mkdir_p = helpers.mkdir_p +local rmdir = helpers.rmdir +local write_file = helpers.write_file +local expect_exit = helpers.expect_exit +local poke_eventloop = helpers.poke_eventloop describe('lua stdlib', function() + before_each(clear) -- İ: `tolower("İ")` is `i` which has length 1 while `İ` itself has -- length 2 (in bytes). -- Ⱥ: `tolower("Ⱥ")` is `ⱥ` which has length 2 while `Ⱥ` itself has @@ -487,6 +491,16 @@ describe('lua stdlib', function() eq(false, exec_lua("return vim.tbl_isempty({a=1, b=2, c=3})")) end) + it('vim.tbl_get', function() + eq(true, exec_lua("return vim.tbl_get({ test = { nested_test = true }}, 'test', 'nested_test')")) + eq(NIL, exec_lua("return vim.tbl_get({ unindexable = true }, 'unindexable', 'missing_key')")) + eq(NIL, exec_lua("return vim.tbl_get({ unindexable = 1 }, 'unindexable', 'missing_key')")) + eq(NIL, exec_lua("return vim.tbl_get({ unindexable = coroutine.create(function () end) }, 'unindexable', 'missing_key')")) + eq(NIL, exec_lua("return vim.tbl_get({ unindexable = function () end }, 'unindexable', 'missing_key')")) + eq(NIL, exec_lua("return vim.tbl_get({}, 'missing_key')")) + eq(NIL, exec_lua("return vim.tbl_get({})")) + end) + it('vim.tbl_extend', function() ok(exec_lua([[ local a = {x = 1} @@ -636,17 +650,17 @@ describe('lua stdlib', function() return vim.tbl_islist(c) and count == 0 ]])) - eq(exec_lua([[ + eq({a = {b = 1}}, exec_lua([[ local a = { a = { b = 1 } } local b = { a = {} } return vim.tbl_deep_extend("force", a, b) - ]]), {a = {b = 1}}) + ]])) - eq(exec_lua([[ + eq({a = {b = 1}}, exec_lua([[ local a = { a = 123 } local b = { a = { b = 1} } return vim.tbl_deep_extend("force", a, b) - ]]), {a = {b = 1}}) + ]])) ok(exec_lua([[ local a = { a = {[2] = 3} } @@ -655,11 +669,11 @@ describe('lua stdlib', function() return vim.deep_equal(c, {a = {[3] = 3}}) ]])) - eq(exec_lua([[ + eq({a = 123}, exec_lua([[ local a = { a = { b = 1} } local b = { a = 123 } return vim.tbl_deep_extend("force", a, b) - ]]), {a = 123 }) + ]])) matches('invalid "behavior": nil', pcall_err(exec_lua, [[ @@ -740,7 +754,7 @@ describe('lua stdlib', function() -- compat: nvim_call_function uses "special" value for vimL float eq(false, exec_lua([[return vim.api.nvim_call_function('sin', {0.0}) == 0.0 ]])) - source([[ + exec([[ func! FooFunc(test) let g:test = a:test return {} @@ -768,6 +782,12 @@ describe('lua stdlib', function() -- error handling eq({false, 'Vim:E897: List or Blob required'}, exec_lua([[return {pcall(vim.fn.add, "aa", "bb")}]])) + + -- conversion between LuaRef and Vim Funcref + eq(true, exec_lua([[ + local x = vim.fn.VarArg(function() return 'foo' end, function() return 'bar' end) + return #x == 2 and x[1]() == 'foo' and x[2]() == 'bar' + ]])) end) it('vim.fn should error when calling API function', function() @@ -775,6 +795,20 @@ describe('lua stdlib', function() pcall_err(exec_lua, "vim.fn.nvim_get_current_line()")) end) + it('vim.fn is allowed in "fast" context by some functions #18306', function() + exec_lua([[ + local timer = vim.loop.new_timer() + timer:start(0, 0, function() + timer:close() + assert(vim.in_fast_event()) + vim.g.fnres = vim.fn.iconv('hello', 'utf-8', 'utf-8') + end) + ]]) + + helpers.poke_eventloop() + eq('hello', exec_lua[[return vim.g.fnres]]) + end) + it('vim.rpcrequest and vim.rpcnotify', function() exec_lua([[ chan = vim.fn.jobstart({'cat'}, {rpc=true}) @@ -987,6 +1021,77 @@ describe('lua stdlib', function() matches([[attempt to index .* nil value]], pcall_err(exec_lua, 'return vim.g[0].testing')) + + exec_lua [[ + local counter = 0 + local function add_counter() counter = counter + 1 end + local function get_counter() return counter end + vim.g.AddCounter = add_counter + vim.g.GetCounter = get_counter + vim.g.funcs = {add = add_counter, get = get_counter} + ]] + + eq(0, eval('g:GetCounter()')) + eval('g:AddCounter()') + eq(1, eval('g:GetCounter()')) + eval('g:AddCounter()') + eq(2, eval('g:GetCounter()')) + exec_lua([[vim.g.AddCounter()]]) + eq(3, exec_lua([[return vim.g.GetCounter()]])) + exec_lua([[vim.api.nvim_get_var('AddCounter')()]]) + eq(4, exec_lua([[return vim.api.nvim_get_var('GetCounter')()]])) + exec_lua([[vim.g.funcs.add()]]) + eq(5, exec_lua([[return vim.g.funcs.get()]])) + exec_lua([[vim.api.nvim_get_var('funcs').add()]]) + eq(6, exec_lua([[return vim.api.nvim_get_var('funcs').get()]])) + + exec_lua [[ + local counter = 0 + local function add_counter() counter = counter + 1 end + local function get_counter() return counter end + vim.api.nvim_set_var('AddCounter', add_counter) + vim.api.nvim_set_var('GetCounter', get_counter) + vim.api.nvim_set_var('funcs', {add = add_counter, get = get_counter}) + ]] + + eq(0, eval('g:GetCounter()')) + eval('g:AddCounter()') + eq(1, eval('g:GetCounter()')) + eval('g:AddCounter()') + eq(2, eval('g:GetCounter()')) + exec_lua([[vim.g.AddCounter()]]) + eq(3, exec_lua([[return vim.g.GetCounter()]])) + exec_lua([[vim.api.nvim_get_var('AddCounter')()]]) + eq(4, exec_lua([[return vim.api.nvim_get_var('GetCounter')()]])) + exec_lua([[vim.g.funcs.add()]]) + eq(5, exec_lua([[return vim.g.funcs.get()]])) + exec_lua([[vim.api.nvim_get_var('funcs').add()]]) + eq(6, exec_lua([[return vim.api.nvim_get_var('funcs').get()]])) + + exec([[ + function Test() + endfunction + function s:Test() + endfunction + let g:Unknown_func = function('Test') + let g:Unknown_script_func = function('s:Test') + ]]) + eq(NIL, exec_lua([[return vim.g.Unknown_func]])) + eq(NIL, exec_lua([[return vim.g.Unknown_script_func]])) + + -- Check if autoload works properly + local pathsep = helpers.get_pathsep() + local xconfig = 'Xhome' .. pathsep .. 'Xconfig' + local xdata = 'Xhome' .. pathsep .. 'Xdata' + local autoload_folder = table.concat({xconfig, 'nvim', 'autoload'}, pathsep) + local autoload_file = table.concat({autoload_folder , 'testload.vim'}, pathsep) + mkdir_p(autoload_folder) + write_file(autoload_file , [[let testload#value = 2]]) + + clear{ args_rm={'-u'}, env={ XDG_CONFIG_HOME=xconfig, XDG_DATA_HOME=xdata } } + + eq(2, exec_lua("return vim.g['testload#value']")) + rmdir('Xhome') end) it('vim.b', function() @@ -1022,6 +1127,63 @@ describe('lua stdlib', function() eq(NIL, funcs.luaeval "vim.b.to_delete") exec_lua [[ + local counter = 0 + local function add_counter() counter = counter + 1 end + local function get_counter() return counter end + vim.b.AddCounter = add_counter + vim.b.GetCounter = get_counter + vim.b.funcs = {add = add_counter, get = get_counter} + ]] + + eq(0, eval('b:GetCounter()')) + eval('b:AddCounter()') + eq(1, eval('b:GetCounter()')) + eval('b:AddCounter()') + eq(2, eval('b:GetCounter()')) + exec_lua([[vim.b.AddCounter()]]) + eq(3, exec_lua([[return vim.b.GetCounter()]])) + exec_lua([[vim.api.nvim_buf_get_var(0, 'AddCounter')()]]) + eq(4, exec_lua([[return vim.api.nvim_buf_get_var(0, 'GetCounter')()]])) + exec_lua([[vim.b.funcs.add()]]) + eq(5, exec_lua([[return vim.b.funcs.get()]])) + exec_lua([[vim.api.nvim_buf_get_var(0, 'funcs').add()]]) + eq(6, exec_lua([[return vim.api.nvim_buf_get_var(0, 'funcs').get()]])) + + exec_lua [[ + local counter = 0 + local function add_counter() counter = counter + 1 end + local function get_counter() return counter end + vim.api.nvim_buf_set_var(0, 'AddCounter', add_counter) + vim.api.nvim_buf_set_var(0, 'GetCounter', get_counter) + vim.api.nvim_buf_set_var(0, 'funcs', {add = add_counter, get = get_counter}) + ]] + + eq(0, eval('b:GetCounter()')) + eval('b:AddCounter()') + eq(1, eval('b:GetCounter()')) + eval('b:AddCounter()') + eq(2, eval('b:GetCounter()')) + exec_lua([[vim.b.AddCounter()]]) + eq(3, exec_lua([[return vim.b.GetCounter()]])) + exec_lua([[vim.api.nvim_buf_get_var(0, 'AddCounter')()]]) + eq(4, exec_lua([[return vim.api.nvim_buf_get_var(0, 'GetCounter')()]])) + exec_lua([[vim.b.funcs.add()]]) + eq(5, exec_lua([[return vim.b.funcs.get()]])) + exec_lua([[vim.api.nvim_buf_get_var(0, 'funcs').add()]]) + eq(6, exec_lua([[return vim.api.nvim_buf_get_var(0, 'funcs').get()]])) + + exec([[ + function Test() + endfunction + function s:Test() + endfunction + let b:Unknown_func = function('Test') + let b:Unknown_script_func = function('s:Test') + ]]) + eq(NIL, exec_lua([[return vim.b.Unknown_func]])) + eq(NIL, exec_lua([[return vim.b.Unknown_script_func]])) + + exec_lua [[ vim.cmd "vnew" ]] @@ -1059,6 +1221,63 @@ describe('lua stdlib', function() eq(NIL, funcs.luaeval "vim.w.to_delete") exec_lua [[ + local counter = 0 + local function add_counter() counter = counter + 1 end + local function get_counter() return counter end + vim.w.AddCounter = add_counter + vim.w.GetCounter = get_counter + vim.w.funcs = {add = add_counter, get = get_counter} + ]] + + eq(0, eval('w:GetCounter()')) + eval('w:AddCounter()') + eq(1, eval('w:GetCounter()')) + eval('w:AddCounter()') + eq(2, eval('w:GetCounter()')) + exec_lua([[vim.w.AddCounter()]]) + eq(3, exec_lua([[return vim.w.GetCounter()]])) + exec_lua([[vim.api.nvim_win_get_var(0, 'AddCounter')()]]) + eq(4, exec_lua([[return vim.api.nvim_win_get_var(0, 'GetCounter')()]])) + exec_lua([[vim.w.funcs.add()]]) + eq(5, exec_lua([[return vim.w.funcs.get()]])) + exec_lua([[vim.api.nvim_win_get_var(0, 'funcs').add()]]) + eq(6, exec_lua([[return vim.api.nvim_win_get_var(0, 'funcs').get()]])) + + exec_lua [[ + local counter = 0 + local function add_counter() counter = counter + 1 end + local function get_counter() return counter end + vim.api.nvim_win_set_var(0, 'AddCounter', add_counter) + vim.api.nvim_win_set_var(0, 'GetCounter', get_counter) + vim.api.nvim_win_set_var(0, 'funcs', {add = add_counter, get = get_counter}) + ]] + + eq(0, eval('w:GetCounter()')) + eval('w:AddCounter()') + eq(1, eval('w:GetCounter()')) + eval('w:AddCounter()') + eq(2, eval('w:GetCounter()')) + exec_lua([[vim.w.AddCounter()]]) + eq(3, exec_lua([[return vim.w.GetCounter()]])) + exec_lua([[vim.api.nvim_win_get_var(0, 'AddCounter')()]]) + eq(4, exec_lua([[return vim.api.nvim_win_get_var(0, 'GetCounter')()]])) + exec_lua([[vim.w.funcs.add()]]) + eq(5, exec_lua([[return vim.w.funcs.get()]])) + exec_lua([[vim.api.nvim_win_get_var(0, 'funcs').add()]]) + eq(6, exec_lua([[return vim.api.nvim_win_get_var(0, 'funcs').get()]])) + + exec([[ + function Test() + endfunction + function s:Test() + endfunction + let w:Unknown_func = function('Test') + let w:Unknown_script_func = function('s:Test') + ]]) + eq(NIL, exec_lua([[return vim.w.Unknown_func]])) + eq(NIL, exec_lua([[return vim.w.Unknown_script_func]])) + + exec_lua [[ vim.cmd "vnew" ]] @@ -1091,6 +1310,52 @@ describe('lua stdlib', function() eq(NIL, funcs.luaeval "vim.t.to_delete") exec_lua [[ + local counter = 0 + local function add_counter() counter = counter + 1 end + local function get_counter() return counter end + vim.t.AddCounter = add_counter + vim.t.GetCounter = get_counter + vim.t.funcs = {add = add_counter, get = get_counter} + ]] + + eq(0, eval('t:GetCounter()')) + eval('t:AddCounter()') + eq(1, eval('t:GetCounter()')) + eval('t:AddCounter()') + eq(2, eval('t:GetCounter()')) + exec_lua([[vim.t.AddCounter()]]) + eq(3, exec_lua([[return vim.t.GetCounter()]])) + exec_lua([[vim.api.nvim_tabpage_get_var(0, 'AddCounter')()]]) + eq(4, exec_lua([[return vim.api.nvim_tabpage_get_var(0, 'GetCounter')()]])) + exec_lua([[vim.t.funcs.add()]]) + eq(5, exec_lua([[return vim.t.funcs.get()]])) + exec_lua([[vim.api.nvim_tabpage_get_var(0, 'funcs').add()]]) + eq(6, exec_lua([[return vim.api.nvim_tabpage_get_var(0, 'funcs').get()]])) + + exec_lua [[ + local counter = 0 + local function add_counter() counter = counter + 1 end + local function get_counter() return counter end + vim.api.nvim_tabpage_set_var(0, 'AddCounter', add_counter) + vim.api.nvim_tabpage_set_var(0, 'GetCounter', get_counter) + vim.api.nvim_tabpage_set_var(0, 'funcs', {add = add_counter, get = get_counter}) + ]] + + eq(0, eval('t:GetCounter()')) + eval('t:AddCounter()') + eq(1, eval('t:GetCounter()')) + eval('t:AddCounter()') + eq(2, eval('t:GetCounter()')) + exec_lua([[vim.t.AddCounter()]]) + eq(3, exec_lua([[return vim.t.GetCounter()]])) + exec_lua([[vim.api.nvim_tabpage_get_var(0, 'AddCounter')()]]) + eq(4, exec_lua([[return vim.api.nvim_tabpage_get_var(0, 'GetCounter')()]])) + exec_lua([[vim.t.funcs.add()]]) + eq(5, exec_lua([[return vim.t.funcs.get()]])) + exec_lua([[vim.api.nvim_tabpage_get_var(0, 'funcs').add()]]) + eq(6, exec_lua([[return vim.api.nvim_tabpage_get_var(0, 'funcs').get()]])) + + exec_lua [[ vim.cmd "tabnew" ]] @@ -1131,10 +1396,12 @@ describe('lua stdlib', function() ]] eq('', funcs.luaeval "vim.bo.filetype") eq(true, funcs.luaeval "vim.bo[BUF].modifiable") - matches("Invalid option name: 'nosuchopt'$", + matches("unknown option 'nosuchopt'$", pcall_err(exec_lua, 'return vim.bo.nosuchopt')) matches("Expected lua string$", pcall_err(exec_lua, 'return vim.bo[0][0].autoread')) + matches("Invalid buffer id: %-1$", + pcall_err(exec_lua, 'return vim.bo[-1].filetype')) end) it('vim.wo', function() @@ -1150,15 +1417,24 @@ describe('lua stdlib', function() eq(0, funcs.luaeval "vim.wo.cole") eq(0, funcs.luaeval "vim.wo[0].cole") eq(0, funcs.luaeval "vim.wo[1001].cole") - matches("Invalid option name: 'notanopt'$", + matches("unknown option 'notanopt'$", pcall_err(exec_lua, 'return vim.wo.notanopt')) matches("Expected lua string$", pcall_err(exec_lua, 'return vim.wo[0][0].list')) + matches("Invalid window id: %-1$", + pcall_err(exec_lua, 'return vim.wo[-1].list')) eq(2, funcs.luaeval "vim.wo[1000].cole") exec_lua [[ vim.wo[1000].cole = 0 ]] eq(0, funcs.luaeval "vim.wo[1000].cole") + + -- Can handle global-local values + exec_lua [[vim.o.scrolloff = 100]] + exec_lua [[vim.wo.scrolloff = 200]] + eq(200, funcs.luaeval "vim.wo.scrolloff") + exec_lua [[vim.wo.scrolloff = -1]] + eq(100, funcs.luaeval "vim.wo.scrolloff") end) describe('vim.opt', function() @@ -2004,6 +2280,22 @@ describe('lua stdlib', function() eq('iworld<ESC>', exec_lua[[return table.concat(keys, '')]]) end) + + it('can call vim.fn functions on Ctrl-C #17273', function() + exec_lua([[ + _G.ctrl_c_cmdtype = '' + + vim.on_key(function(c) + if c == '\3' then + _G.ctrl_c_cmdtype = vim.fn.getcmdtype() + end + end) + ]]) + feed('/') + poke_eventloop() -- This is needed because Ctrl-C flushes input + feed('<C-C>') + eq('/', exec_lua([[return _G.ctrl_c_cmdtype]])) + end) end) describe('vim.wait', function() @@ -2278,6 +2570,17 @@ describe('lua stdlib', function() eq(buf1, meths.get_current_buf()) eq(buf2, val) end) + + it('does not cause ml_get errors with invalid visual selection', function() + -- Should be fixed by vim-patch:8.2.4028. + exec_lua [[ + local a = vim.api + local t = function(s) return a.nvim_replace_termcodes(s, true, true, true) end + a.nvim_buf_set_lines(0, 0, -1, true, {"a", "b", "c"}) + a.nvim_feedkeys(t "G<C-V>", "txn", false) + a.nvim_buf_call(a.nvim_create_buf(false, true), function() vim.cmd "redraw" end) + ]] + end) end) describe('vim.api.nvim_win_call', function() @@ -2306,12 +2609,109 @@ describe('lua stdlib', function() eq(win1, meths.get_current_win()) eq(win2, val) end) + + it('does not cause ml_get errors with invalid visual selection', function() + -- Add lines to the current buffer and make another window looking into an empty buffer. + exec_lua [[ + _G.a = vim.api + _G.t = function(s) return a.nvim_replace_termcodes(s, true, true, true) end + _G.win_lines = a.nvim_get_current_win() + vim.cmd "new" + _G.win_empty = a.nvim_get_current_win() + a.nvim_set_current_win(win_lines) + a.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. + exec_lua [[ + a.nvim_feedkeys(t "G<C-V>", "txn", false) + a.nvim_win_call(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. + exec_lua [[ + a.nvim_feedkeys(t "<Esc>gg", "txn", false) + a.nvim_set_current_win(win_empty) + a.nvim_feedkeys(t "gg<C-V>", "txn", false) + a.nvim_win_call(win_lines, function() a.nvim_feedkeys(t "G<C-V>", "txn", false) end) + vim.cmd "redraw" + ]] + end) + + 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.a = vim.api + vim.opt.ruler = true + local lines = {} + for i = 0, 499 do lines[#lines + 1] = tostring(i) end + a.nvim_buf_set_lines(0, 0, -1, true, lines) + a.nvim_win_set_cursor(0, {20, 0}) + vim.cmd "split" + _G.win = a.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 [[ + a.nvim_win_call(win, function() a.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) + end) +end) + +describe('lua: builtin modules', function() + local function do_tests() + eq(2, exec_lua[[return vim.tbl_count {x=1,y=2}]]) + eq('{ 10, "spam" }', exec_lua[[return vim.inspect {10, 'spam'}]]) + end + + it('works', function() + clear() + do_tests() + end) + + it('works when disabled', function() + clear('--luamod-dev') + do_tests() + end) + + it('works without runtime', function() + clear{env={VIMRUNTIME='fixtures/a'}} + do_tests() + end) + + + it('does not work when disabled without runtime', function() + clear{args={'--luamod-dev'}, env={VIMRUNTIME='fixtures/a'}} + expect_exit(exec_lua, [[return vim.tbl_count {x=1,y=2}]]) end) end) describe('lua: require("mod") from packages', function() before_each(function() - command('set rtp+=test/functional/fixtures pp+=test/functional/fixtures') + clear('--cmd', 'set rtp+=test/functional/fixtures pp+=test/functional/fixtures') end) it('propagates syntax error', function() @@ -2334,6 +2734,8 @@ describe('lua: require("mod") from packages', function() end) describe('vim.keymap', function() + before_each(clear) + it('can make a mapping', function() eq(0, exec_lua [[ GlobalCount = 0 @@ -2397,6 +2799,39 @@ describe('vim.keymap', function() eq('\nNo mapping found', helpers.exec_capture('nmap asdf')) end) + it('works with buffer-local mappings', function() + eq(0, exec_lua [[ + GlobalCount = 0 + vim.keymap.set('n', 'asdf', function() GlobalCount = GlobalCount + 1 end, {buffer=true}) + return GlobalCount + ]]) + + feed('asdf\n') + + eq(1, exec_lua[[return GlobalCount]]) + + exec_lua [[ + vim.keymap.del('n', 'asdf', {buffer=true}) + ]] + + feed('asdf\n') + + eq(1, exec_lua[[return GlobalCount]]) + eq('\nNo mapping found', helpers.exec_capture('nmap asdf')) + end) + + it('does not mutate the opts parameter', function() + eq(true, exec_lua [[ + opts = {buffer=true} + vim.keymap.set('n', 'asdf', function() end, opts) + return opts.buffer + ]]) + eq(true, exec_lua [[ + vim.keymap.del('n', 'asdf', opts) + return opts.buffer + ]]) + end) + it('can do <Plug> mappings', function() eq(0, exec_lua [[ GlobalCount = 0 diff --git a/test/functional/lua/xdiff_spec.lua b/test/functional/lua/xdiff_spec.lua index 4f28f84c01..d55268fc78 100644 --- a/test/functional/lua/xdiff_spec.lua +++ b/test/functional/lua/xdiff_spec.lua @@ -90,6 +90,48 @@ describe('xdiff bindings', function() exec_lua([[return vim.diff(a2, b2, {result_type = 'indices'})]])) end) + it('can run different algorithms', function() + local a = table.concat({ + '.foo1 {', + ' margin: 0;', + '}', + '', + '.bar {', + ' margin: 0;', + '}', + ''}, '\n') + + local b = table.concat({ + '.bar {', + ' margin: 0;', + '}', + '', + '.foo1 {', + ' margin: 0;', + ' color: green;', + '}', + ''}, '\n') + + eq( + table.concat({'@@ -1,4 +0,0 @@', + '-.foo1 {', + '- margin: 0;', + '-}', + '-', + '@@ -7,0 +4,5 @@', + '+', + '+.foo1 {', + '+ margin: 0;', + '+ color: green;', + '+}', + ''}, '\n'), + exec_lua([[ + local args = {...} + return vim.diff(args[1], args[2], { + algorithm = 'patience' + }) + ]], a, b)) + end) end) it('can handle bad args', function() diff --git a/test/functional/options/autochdir_spec.lua b/test/functional/options/autochdir_spec.lua index 2fce0a5ed9..74959a8e76 100644 --- a/test/functional/options/autochdir_spec.lua +++ b/test/functional/options/autochdir_spec.lua @@ -1,7 +1,9 @@ +local lfs = require('lfs') local helpers = require('test.functional.helpers')(after_each) local clear = helpers.clear local eq = helpers.eq -local getcwd = helpers.funcs.getcwd +local funcs = helpers.funcs +local command = helpers.command describe("'autochdir'", function() it('given on the shell gets processed properly', function() @@ -9,11 +11,34 @@ describe("'autochdir'", function() -- By default 'autochdir' is off, thus getcwd() returns the repo root. clear(targetdir..'/tty-test.c') - local rootdir = getcwd() + local rootdir = funcs.getcwd() local expected = rootdir .. '/' .. targetdir -- With 'autochdir' on, we should get the directory of tty-test.c. clear('--cmd', 'set autochdir', targetdir..'/tty-test.c') - eq(helpers.iswin() and expected:gsub('/', '\\') or expected, getcwd()) + eq(helpers.iswin() and expected:gsub('/', '\\') or expected, funcs.getcwd()) + end) + + it('is not overwritten by getwinvar() call #17609',function() + local curdir = string.gsub(lfs.currentdir(), '\\', '/') + local dir_a = curdir..'/Xtest-functional-options-autochdir.dir_a' + local dir_b = curdir..'/Xtest-functional-options-autochdir.dir_b' + lfs.mkdir(dir_a) + lfs.mkdir(dir_b) + clear() + command('set shellslash') + command('set autochdir') + command('edit '..dir_a..'/file1') + eq(dir_a, funcs.getcwd()) + command('lcd '..dir_b) + eq(dir_b, funcs.getcwd()) + command('botright vnew ../file2') + eq(curdir, funcs.getcwd()) + command('wincmd w') + eq(dir_a, funcs.getcwd()) + funcs.getwinvar(2, 'foo') + eq(dir_a, funcs.getcwd()) + helpers.rmdir(dir_a) + helpers.rmdir(dir_b) end) end) diff --git a/test/functional/options/cursorbind_spec.lua b/test/functional/options/cursorbind_spec.lua new file mode 100644 index 0000000000..1a03ed099a --- /dev/null +++ b/test/functional/options/cursorbind_spec.lua @@ -0,0 +1,91 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') +local clear = helpers.clear +local command = helpers.command +local exec = helpers.exec +local feed = helpers.feed + +before_each(clear) + +describe("'cursorbind'", function() + it("behaves consistently whether 'cursorline' is set or not vim-patch:8.2.4795", function() + local screen = Screen.new(60, 8) + screen:set_default_attr_ids({ + [1] = {bold = true, foreground = Screen.colors.Blue}, -- NonText + [2] = {bold = true, reverse = true}, -- StatusLine + [3] = {reverse = true}, -- StatusLineNC + [4] = {background = Screen.colors.Grey90}, -- CursorLine, CursorColumn + }) + screen:attach() + exec([[ + call setline(1, 'aa bb cc dd ee ff gg hh ii jj kk ll mm' .. + \ ' nn oo pp qq rr ss tt uu vv ww xx yy zz') + set nowrap + " The following makes the cursor apparent on the screen dump + set sidescroll=1 cursorcolumn + " add empty lines, required for cursorcolumn + call append(1, ['','','','']) + 20vsp + windo :set cursorbind + ]]) + feed('20l') + screen:expect([[ + a bb cc dd ee ff gg │aa bb cc dd ee ff gg^ hh ii jj kk ll mm | + {4: }│ {4: } | + {4: }│ {4: } | + {4: }│ {4: } | + {4: }│ {4: } | + {1:~ }│{1:~ }| + {3:[No Name] [+] }{2:[No Name] [+] }| + | + ]]) + feed('10l') + screen:expect([[ + hh ii jj kk ll mm n│aa bb cc dd ee ff gg hh ii jj ^kk ll mm | + {4: } │ {4: } | + {4: } │ {4: } | + {4: } │ {4: } | + {4: } │ {4: } | + {1:~ }│{1:~ }| + {3:[No Name] [+] }{2:[No Name] [+] }| + | + ]]) + command('windo :set cursorline') + feed('0') + feed('20l') + screen:expect([[ + {4:a bb cc dd ee ff gg }│{4:aa bb cc dd ee ff gg^ hh ii jj kk ll mm }| + {4: }│ {4: } | + {4: }│ {4: } | + {4: }│ {4: } | + {4: }│ {4: } | + {1:~ }│{1:~ }| + {3:[No Name] [+] }{2:[No Name] [+] }| + | + ]]) + feed('10l') + screen:expect([[ + {4: hh ii jj kk ll mm n}│{4:aa bb cc dd ee ff gg hh ii jj ^kk ll mm }| + {4: } │ {4: } | + {4: } │ {4: } | + {4: } │ {4: } | + {4: } │ {4: } | + {1:~ }│{1:~ }| + {3:[No Name] [+] }{2:[No Name] [+] }| + | + ]]) + command('windo :set nocursorline nocursorcolumn') + feed('0') + feed('40l') + screen:expect([[ + kk ll mm nn oo pp qq│ bb cc dd ee ff gg hh ii jj kk ll mm n^n| + │ | + │ | + │ | + │ | + {1:~ }│{1:~ }| + {3:[No Name] [+] }{2:[No Name] [+] }| + | + ]]) + end) +end) diff --git a/test/functional/options/defaults_spec.lua b/test/functional/options/defaults_spec.lua index 6620c9acef..9244ca0974 100644 --- a/test/functional/options/defaults_spec.lua +++ b/test/functional/options/defaults_spec.lua @@ -9,6 +9,7 @@ local clear = helpers.clear local exc_exec = helpers.exc_exec local eval = helpers.eval local eq = helpers.eq +local ok = helpers.ok local funcs = helpers.funcs local insert = helpers.insert local iswin = helpers.iswin @@ -17,6 +18,7 @@ local mkdir = helpers.mkdir local rmdir = helpers.rmdir local alter_slashes = helpers.alter_slashes local tbl_contains = helpers.tbl_contains +local expect_exit = helpers.expect_exit describe('startup defaults', function() describe(':filetype', function() @@ -163,7 +165,7 @@ describe('startup defaults', function() end) it("'shadafile' ('viminfofile')", function() - local env = {XDG_DATA_HOME='Xtest-userdata', XDG_CONFIG_HOME='Xtest-userconfig'} + local env = {XDG_DATA_HOME='Xtest-userdata', XDG_STATE_HOME='Xtest-userstate', XDG_CONFIG_HOME='Xtest-userconfig'} clear{args={}, args_rm={'-i'}, env=env} -- Default 'shadafile' is empty. -- This means use the default location. :help shada-file-name @@ -174,11 +176,11 @@ describe('startup defaults', function() command('write') local f = eval('fnamemodify(@%,":p")') assert(string.len(f) > 3) - command('qall') + expect_exit(command, 'qall') clear{args={}, args_rm={'-i'}, env=env} eq({ f }, eval('v:oldfiles')) os.remove('Xtest-foo') - rmdir('Xtest-userdata') + rmdir('Xtest-userstate') -- Handles viminfo/viminfofile as alias for shada/shadafile. eq('\n shadafile=', eval('execute("set shadafile?")')) @@ -206,7 +208,7 @@ describe('startup defaults', function() describe('$NVIM_LOG_FILE', function() local xdgdir = 'Xtest-startup-xdg-logpath' - local xdgcachedir = xdgdir..'/nvim' + local xdgstatedir = iswin() and xdgdir..'/nvim-data' or xdgdir..'/nvim' after_each(function() os.remove('Xtest-logpath') rmdir(xdgdir) @@ -218,26 +220,26 @@ describe('startup defaults', function() }}) eq('Xtest-logpath', eval('$NVIM_LOG_FILE')) end) - it('defaults to stdpath("cache")/log if empty', function() - eq(true, mkdir(xdgdir) and mkdir(xdgcachedir)) + it('defaults to stdpath("log")/log if empty', function() + eq(true, mkdir(xdgdir) and mkdir(xdgstatedir)) clear({env={ - XDG_CACHE_HOME=xdgdir, + XDG_STATE_HOME=xdgdir, NVIM_LOG_FILE='', -- Empty is invalid. }}) - eq(xdgcachedir..'/log', string.gsub(eval('$NVIM_LOG_FILE'), '\\', '/')) + eq(xdgstatedir..'/log', string.gsub(eval('$NVIM_LOG_FILE'), '\\', '/')) end) - it('defaults to stdpath("cache")/log if invalid', function() - eq(true, mkdir(xdgdir) and mkdir(xdgcachedir)) + it('defaults to stdpath("log")/log if invalid', function() + eq(true, mkdir(xdgdir) and mkdir(xdgstatedir)) clear({env={ - XDG_CACHE_HOME=xdgdir, + XDG_STATE_HOME=xdgdir, NVIM_LOG_FILE='.', -- Any directory is invalid. }}) - eq(xdgcachedir..'/log', string.gsub(eval('$NVIM_LOG_FILE'), '\\', '/')) + eq(xdgstatedir..'/log', string.gsub(eval('$NVIM_LOG_FILE'), '\\', '/')) end) end) end) -describe('XDG-based defaults', function() +describe('XDG defaults', function() -- Need separate describe() blocks to not run clear() twice. -- Do not put before_each() here for the same reasons. @@ -264,6 +266,7 @@ describe('XDG-based defaults', function() XDG_CONFIG_HOME=nil, XDG_DATA_HOME=nil, XDG_CACHE_HOME=nil, + XDG_STATE_HOME=nil, XDG_RUNTIME_DIR=nil, XDG_CONFIG_DIRS=nil, XDG_DATA_DIRS=nil, @@ -280,6 +283,7 @@ describe('XDG-based defaults', function() eq('.', meths.get_option('viewdir')) eq('.', meths.get_option('directory')) eq('.', meths.get_option('undodir')) + ok((funcs.tempname()):len() > 4) end) end) @@ -293,6 +297,7 @@ describe('XDG-based defaults', function() local env_sep = iswin() and ';' or ':' local data_dir = iswin() and 'nvim-data' or 'nvim' + local state_dir = iswin() and 'nvim-data' or 'nvim' local root_path = iswin() and 'C:' or '' describe('with too long XDG variables', function() @@ -303,6 +308,8 @@ describe('XDG-based defaults', function() .. env_sep.. root_path .. ('/b'):rep(2048) .. (env_sep .. root_path .. '/c'):rep(512)), XDG_DATA_HOME=(root_path .. ('/X'):rep(4096)), + XDG_RUNTIME_DIR=(root_path .. ('/X'):rep(4096)), + XDG_STATE_HOME=(root_path .. ('/X'):rep(4096)), XDG_DATA_DIRS=(root_path .. ('/A'):rep(2048) .. env_sep .. root_path .. ('/B'):rep(2048) .. (env_sep .. root_path .. '/C'):rep(512)), @@ -355,13 +362,13 @@ describe('XDG-based defaults', function() .. ',' .. root_path .. ('/a'):rep(2048) .. '/nvim/after' .. ',' .. root_path .. ('/x'):rep(4096) .. '/nvim/after' ):gsub('\\', '/')), (meths.get_option('runtimepath')):gsub('\\', '/')) - eq('.,' .. root_path .. ('/X'):rep(4096).. '/' .. data_dir .. '/backup//', + eq('.,' .. root_path .. ('/X'):rep(4096).. '/' .. state_dir .. '/backup//', (meths.get_option('backupdir'):gsub('\\', '/'))) - eq(root_path .. ('/X'):rep(4096) .. '/' .. data_dir .. '/swap//', + eq(root_path .. ('/X'):rep(4096) .. '/' .. state_dir .. '/swap//', (meths.get_option('directory')):gsub('\\', '/')) - eq(root_path .. ('/X'):rep(4096) .. '/' .. data_dir .. '/undo//', + eq(root_path .. ('/X'):rep(4096) .. '/' .. state_dir .. '/undo//', (meths.get_option('undodir')):gsub('\\', '/')) - eq(root_path .. ('/X'):rep(4096) .. '/' .. data_dir .. '/view//', + eq(root_path .. ('/X'):rep(4096) .. '/' .. state_dir .. '/view//', (meths.get_option('viewdir')):gsub('\\', '/')) end) end) @@ -372,6 +379,8 @@ describe('XDG-based defaults', function() XDG_CONFIG_HOME='$XDG_DATA_HOME', XDG_CONFIG_DIRS='$XDG_DATA_DIRS', XDG_DATA_HOME='$XDG_CONFIG_HOME', + XDG_RUNTIME_DIR='$XDG_RUNTIME_DIR', + XDG_STATE_HOME='$XDG_CONFIG_HOME', XDG_DATA_DIRS='$XDG_CONFIG_DIRS', }}) end) @@ -405,13 +414,13 @@ describe('XDG-based defaults', function() .. ',$XDG_DATA_DIRS/nvim/after' .. ',$XDG_DATA_HOME/nvim/after' ):gsub('\\', '/')), (meths.get_option('runtimepath')):gsub('\\', '/')) - eq(('.,$XDG_CONFIG_HOME/' .. data_dir .. '/backup//'), + eq(('.,$XDG_CONFIG_HOME/' .. state_dir .. '/backup//'), meths.get_option('backupdir'):gsub('\\', '/')) - eq(('$XDG_CONFIG_HOME/' .. data_dir .. '/swap//'), + eq(('$XDG_CONFIG_HOME/' .. state_dir .. '/swap//'), meths.get_option('directory'):gsub('\\', '/')) - eq(('$XDG_CONFIG_HOME/' .. data_dir .. '/undo//'), + eq(('$XDG_CONFIG_HOME/' .. state_dir .. '/undo//'), meths.get_option('undodir'):gsub('\\', '/')) - eq(('$XDG_CONFIG_HOME/' .. data_dir .. '/view//'), + eq(('$XDG_CONFIG_HOME/' .. state_dir .. '/view//'), meths.get_option('viewdir'):gsub('\\', '/')) meths.command('set all&') eq(('$XDG_DATA_HOME/nvim' @@ -425,14 +434,15 @@ describe('XDG-based defaults', function() .. ',$XDG_DATA_DIRS/nvim/after' .. ',$XDG_DATA_HOME/nvim/after' ):gsub('\\', '/'), (meths.get_option('runtimepath')):gsub('\\', '/')) - eq(('.,$XDG_CONFIG_HOME/' .. data_dir .. '/backup//'), + eq(('.,$XDG_CONFIG_HOME/' .. state_dir .. '/backup//'), meths.get_option('backupdir'):gsub('\\', '/')) - eq(('$XDG_CONFIG_HOME/' .. data_dir .. '/swap//'), + eq(('$XDG_CONFIG_HOME/' .. state_dir .. '/swap//'), meths.get_option('directory'):gsub('\\', '/')) - eq(('$XDG_CONFIG_HOME/' .. data_dir .. '/undo//'), + eq(('$XDG_CONFIG_HOME/' .. state_dir .. '/undo//'), meths.get_option('undodir'):gsub('\\', '/')) - eq(('$XDG_CONFIG_HOME/' .. data_dir .. '/view//'), + eq(('$XDG_CONFIG_HOME/' .. state_dir .. '/view//'), meths.get_option('viewdir'):gsub('\\', '/')) + eq(nil, (funcs.tempname()):match('XDG_RUNTIME_DIR')) end) end) @@ -442,6 +452,7 @@ describe('XDG-based defaults', function() XDG_CONFIG_HOME=', , ,', XDG_CONFIG_DIRS=',-,-,' .. env_sep .. '-,-,-', XDG_DATA_HOME=',=,=,', + XDG_STATE_HOME=',=,=,', XDG_DATA_DIRS=',≡,≡,' .. env_sep .. '≡,≡,≡', }}) end) @@ -484,13 +495,13 @@ describe('XDG-based defaults', function() .. ',\\,-\\,-\\,' .. path_sep ..'nvim' .. path_sep ..'after' .. ',\\, \\, \\,' .. path_sep ..'nvim' .. path_sep ..'after' ), meths.get_option('runtimepath')) - eq('.,\\,=\\,=\\,' .. path_sep .. data_dir .. '' .. path_sep ..'backup' .. (path_sep):rep(2), + eq('.,\\,=\\,=\\,' .. path_sep .. state_dir .. '' .. path_sep ..'backup' .. (path_sep):rep(2), meths.get_option('backupdir')) - eq('\\,=\\,=\\,' .. path_sep ..'' .. data_dir .. '' .. path_sep ..'swap' .. (path_sep):rep(2), + eq('\\,=\\,=\\,' .. path_sep ..'' .. state_dir .. '' .. path_sep ..'swap' .. (path_sep):rep(2), meths.get_option('directory')) - eq('\\,=\\,=\\,' .. path_sep ..'' .. data_dir .. '' .. path_sep ..'undo' .. (path_sep):rep(2), + eq('\\,=\\,=\\,' .. path_sep ..'' .. state_dir .. '' .. path_sep ..'undo' .. (path_sep):rep(2), meths.get_option('undodir')) - eq('\\,=\\,=\\,' .. path_sep ..'' .. data_dir .. '' .. path_sep ..'view' .. (path_sep):rep(2), + eq('\\,=\\,=\\,' .. path_sep ..'' .. state_dir .. '' .. path_sep ..'view' .. (path_sep):rep(2), meths.get_option('viewdir')) end) end) @@ -499,8 +510,9 @@ end) describe('stdpath()', function() -- Windows appends 'nvim-data' instead of just 'nvim' to prevent collisions - -- due to XDG_CONFIG_HOME and XDG_DATA_HOME being the same. + -- due to XDG_CONFIG_HOME, XDG_DATA_HOME and XDG_STATE_HOME being the same. local datadir = iswin() and 'nvim-data' or 'nvim' + local statedir = iswin() and 'nvim-data' or 'nvim' local env_sep = iswin() and ';' or ':' it('acceptance', function() @@ -509,8 +521,10 @@ describe('stdpath()', function() eq('nvim', funcs.fnamemodify(funcs.stdpath('cache'), ':t')) eq('nvim', funcs.fnamemodify(funcs.stdpath('config'), ':t')) eq(datadir, funcs.fnamemodify(funcs.stdpath('data'), ':t')) + eq(statedir, funcs.fnamemodify(funcs.stdpath('state'), ':t')) eq('table', type(funcs.stdpath('config_dirs'))) eq('table', type(funcs.stdpath('data_dirs'))) + eq('string', type(funcs.stdpath('run'))) assert_alive() -- Check for crash. #8393 end) @@ -582,6 +596,39 @@ describe('stdpath()', function() end) end) + describe('with "state"' , function () + it('knows XDG_STATE_HOME', function() + clear({env={ + XDG_STATE_HOME=alter_slashes('/home/docwhat/.local'), + }}) + eq(alter_slashes('/home/docwhat/.local/'..statedir), funcs.stdpath('state')) + end) + + it('handles changes during runtime', function() + clear({env={ + XDG_STATE_HOME=alter_slashes('/home/original'), + }}) + eq(alter_slashes('/home/original/'..statedir), funcs.stdpath('state')) + command("let $XDG_STATE_HOME='"..alter_slashes('/home/new').."'") + eq(alter_slashes('/home/new/'..statedir), funcs.stdpath('state')) + end) + + it("doesn't expand $VARIABLES", function() + clear({env={ + XDG_STATE_HOME='$VARIABLES', + VARIABLES='this-should-not-happen', + }}) + eq(alter_slashes('$VARIABLES/'..statedir), funcs.stdpath('state')) + end) + + it("doesn't expand ~/", function() + clear({env={ + XDG_STATE_HOME=alter_slashes('~/frobnitz'), + }}) + eq(alter_slashes('~/frobnitz/'..statedir), funcs.stdpath('state')) + end) + end) + describe('with "cache"' , function () it('knows XDG_CACHE_HOME', function() clear({env={ diff --git a/test/functional/options/keymap_spec.lua b/test/functional/options/keymap_spec.lua index 52a714f7a8..a814c35a39 100644 --- a/test/functional/options/keymap_spec.lua +++ b/test/functional/options/keymap_spec.lua @@ -120,9 +120,9 @@ describe("'keymap' / :lmap", function() it("Can be toggled with <C-^> in insert mode", function() feed('i<C-^>l<C-^>l<esc>') expect('lalllaaa') - eq(eval('&iminsert'), 1) + eq(1, eval('&iminsert')) feed('i<C-^><esc>') - eq(eval('&iminsert'), 0) + eq(0, eval('&iminsert')) end) end) describe("'imsearch' option", function() @@ -136,36 +136,36 @@ describe("'keymap' / :lmap", function() expect('aaa') end) it("Can be toggled with C-^", function() - eq(eval('&imsearch'), 1) + eq(1, eval('&imsearch')) feed('/<C-^>lll<cr>3x') expect('aaa') - eq(eval('&imsearch'), 0) + eq(0, eval('&imsearch')) feed('u0/<C-^>lll<cr>3x') expect('lll') - eq(eval('&imsearch'), 1) + eq(1, eval('&imsearch')) end) it("can follow 'iminsert'", function() command('set imsearch=-1') feed('/lll<cr>3x') expect('lll') - eq(eval('&imsearch'), -1) - eq(eval('&iminsert'), 1) + eq(-1, eval('&imsearch')) + eq(1, eval('&iminsert')) feed('u/<C-^>lll<cr>3x') expect('aaa') - eq(eval('&imsearch'), -1) - eq(eval('&iminsert'), 0) + eq(-1, eval('&imsearch')) + eq(0, eval('&iminsert')) end) end) it(":lmap not applied to macros", function() command("call setreg('a', 'il')") feed('@a') expect('llllaaa') - eq(call('getreg', 'a'), 'il') + eq('il', call('getreg', 'a')) end) it(":lmap applied to macro recording", function() feed('qail<esc>q@a') expect('aalllaaa') - eq(call('getreg', 'a'), 'ia') + eq('ia', call('getreg', 'a')) end) it(":lmap not applied to mappings", function() command('imap t l') diff --git a/test/functional/options/mousescroll_spec.lua b/test/functional/options/mousescroll_spec.lua new file mode 100644 index 0000000000..2c9b2d175e --- /dev/null +++ b/test/functional/options/mousescroll_spec.lua @@ -0,0 +1,151 @@ +local helpers = require('test.functional.helpers')(after_each) +local command = helpers.command +local clear = helpers.clear +local eval = helpers.eval +local eq = helpers.eq +local exc_exec = helpers.exc_exec +local feed = helpers.feed + +local scroll = function(direction) + return helpers.request('nvim_input_mouse', 'wheel', direction, '', 0, 2, 2) +end + +local screenrow = function() + return helpers.call('screenrow') +end + +local screencol = function() + return helpers.call('screencol') +end + +describe("'mousescroll'", function() + local invalid_arg = 'Vim(set):E474: Invalid argument: mousescroll=' + local digit_expected = 'Vim(set):E548: digit expected: mousescroll=' + + local function should_fail(val, errorstr) + eq(errorstr..val, exc_exec('set mousescroll='..val)) + end + + local function should_succeed(val) + eq(0, exc_exec('set mousescroll='..val)) + end + + before_each(function() + clear() + command('set nowrap lines=20 columns=20 virtualedit=all') + feed('100o<Esc>50G10|') + end) + + it('handles invalid values', function() + should_fail('', invalid_arg) -- empty string + should_fail('foo:123', invalid_arg) -- unknown direction + should_fail('hor:1,hor:2', invalid_arg) -- duplicate direction + should_fail('ver:99999999999999999999', invalid_arg) -- integer overflow + should_fail('ver:bar', digit_expected) -- expected digit + should_fail('ver:-1', digit_expected) -- negative count + end) + + it('handles valid values', function() + should_succeed('hor:1,ver:1') -- both directions set + should_succeed('hor:1') -- only horizontal + should_succeed('ver:1') -- only vertical + should_succeed('hor:0,ver:0') -- zero + should_succeed('hor:2147483647') -- large count + end) + + it('default set correctly', function() + eq('ver:3,hor:6', eval('&mousescroll')) + + eq(10, screenrow()) + scroll('up') + eq(13, screenrow()) + scroll('down') + eq(10, screenrow()) + + eq(10, screencol()) + scroll('right') + eq(4, screencol()) + scroll('left') + eq(10, screencol()) + end) + + it('vertical scrolling falls back to default value', function() + command('set mousescroll=hor:1') + eq(10, screenrow()) + scroll('up') + eq(13, screenrow()) + end) + + it('horizontal scrolling falls back to default value', function() + command('set mousescroll=ver:1') + eq(10, screencol()) + scroll('right') + eq(4, screencol()) + end) + + it('count of zero disables mouse scrolling', function() + command('set mousescroll=hor:0,ver:0') + + eq(10, screenrow()) + scroll('up') + eq(10, screenrow()) + scroll('down') + eq(10, screenrow()) + + eq(10, screencol()) + scroll('right') + eq(10, screencol()) + scroll('left') + eq(10, screencol()) + end) + + local test_vertical_scrolling = function() + eq(10, screenrow()) + + command('set mousescroll=ver:1') + scroll('up') + eq(11, screenrow()) + + command('set mousescroll=ver:2') + scroll('down') + eq(9, screenrow()) + + command('set mousescroll=ver:5') + scroll('up') + eq(14, screenrow()) + end + + it('controls vertical scrolling in normal mode', function() + test_vertical_scrolling() + end) + + it('controls vertical scrolling in insert mode', function() + feed('i') + test_vertical_scrolling() + end) + + local test_horizontal_scrolling = function() + eq(10, screencol()) + + command('set mousescroll=hor:1') + scroll('right') + eq(9, screencol()) + + command('set mousescroll=hor:3') + scroll('right') + eq(6, screencol()) + + command('set mousescroll=hor:2') + scroll('left') + eq(8, screencol()) + end + + it('controls horizontal scrolling in normal mode', function() + test_horizontal_scrolling() + end) + + it('controls horizontal scrolling in insert mode', function() + feed('i') + test_horizontal_scrolling() + end) +end) diff --git a/test/functional/options/num_options_spec.lua b/test/functional/options/num_options_spec.lua index 4754c14f5b..f343e2da75 100644 --- a/test/functional/options/num_options_spec.lua +++ b/test/functional/options/num_options_spec.lua @@ -54,7 +54,7 @@ describe(':set validation', function() should_fail('iminsert', 3, 'E474') should_fail('imsearch', 3, 'E474') should_fail('titlelen', -1, 'E487') - should_fail('cmdheight', 0, 'E487') + should_fail('cmdheight', -1, 'E487') should_fail('updatecount', -1, 'E487') should_fail('textwidth', -1, 'E487') should_fail('tabstop', 0, 'E487') diff --git a/test/functional/options/pastetoggle_spec.lua b/test/functional/options/pastetoggle_spec.lua index a1f86f73ff..40c14fa187 100644 --- a/test/functional/options/pastetoggle_spec.lua +++ b/test/functional/options/pastetoggle_spec.lua @@ -4,16 +4,14 @@ local clear = helpers.clear local feed = helpers.feed local command = helpers.command local eq = helpers.eq +local expect = helpers.expect local eval = helpers.eval +local insert = helpers.insert +local meths = helpers.meths local sleep = helpers.sleep -local expect = helpers.expect describe("'pastetoggle' option", function() - before_each(function() - clear() - command('set nopaste') - end) - + before_each(clear) it("toggles 'paste'", function() command('set pastetoggle=a') eq(0, eval('&paste')) @@ -22,19 +20,71 @@ describe("'pastetoggle' option", function() feed('j') eq(1, eval('&paste')) end) + describe("multiple key 'pastetoggle'", function() + before_each(function() + eq(0, eval('&paste')) + command('set timeoutlen=1 ttimeoutlen=10000') + end) + it('is waited for when chars are typed', function() + local pastetoggle = 'lllll' + command('set pastetoggle=' .. pastetoggle) + feed(pastetoggle:sub(0, 2)) + -- sleep() for long enough that vgetorpeek() is gotten into, but short + -- enough that ttimeoutlen is not reached. + sleep(200) + feed(pastetoggle:sub(3, -1)) + -- Need another key so that the vgetorpeek() function returns. + feed('j') + eq(1, eval('&paste')) + end) + it('is not waited for when there are no typed chars after mapped chars', function() + command('set pastetoggle=abc') + command('imap d a') + meths.feedkeys('id', 't', true) + -- sleep() for long enough that vgetorpeek() is gotten into, but short + -- enough that ttimeoutlen is not reached. + sleep(200) + feed('bc') + -- Need another key so that the vgetorpeek() function returns. + feed('j') + -- 'ttimeoutlen' should NOT apply + eq(0, eval('&paste')) + end) - it('does not wait for timeout', function() - command('set pastetoggle=abc') - command('set ttimeoutlen=9999999') - eq(0, eval('&paste')) - -- n.b. need <esc> to return from vgetorpeek() - feed('abc<esc>') - eq(1, eval('&paste')) - feed('ab') - sleep(10) - feed('c<esc>') - expect('bc') - eq(1, eval('&paste')) + it('is waited for when there are typed chars after mapped chars', function() + command('set pastetoggle=abc') + command('imap d a') + meths.feedkeys('idb', 't', true) + -- sleep() for long enough that vgetorpeek() is gotten into, but short + -- enough that ttimeoutlen is not reached. + sleep(200) + feed('c') + -- Need another key so that the vgetorpeek() function returns. + feed('j') + -- 'ttimeoutlen' should apply + eq(1, eval('&paste')) + end) + + it('is waited for when there are typed chars after noremapped chars', function() + command('set pastetoggle=abc') + command('inoremap d a') + meths.feedkeys('idb', 't', true) + -- sleep() for long enough that vgetorpeek() is gotten into, but short + -- enough that ttimeoutlen is not reached. + sleep(200) + feed('c') + -- Need another key so that the vgetorpeek() function returns. + feed('j') + -- 'ttimeoutlen' should apply + eq(1, eval('&paste')) + end) + end) + it('does not interfere with character-find', function() + insert('foo,bar') + feed('0') + command('set pastetoggle=,sp') + feed('dt,') + expect(',bar') end) end) diff --git a/test/functional/plugin/health_spec.lua b/test/functional/plugin/health_spec.lua index 37de5d0ce6..ba66117fb1 100644 --- a/test/functional/plugin/health_spec.lua +++ b/test/functional/plugin/health_spec.lua @@ -5,7 +5,7 @@ local Screen = require('test.functional.ui.screen') local clear = helpers.clear local curbuf_contents = helpers.curbuf_contents local command = helpers.command -local eq = helpers.eq +local eq, neq = helpers.eq, helpers.neq local getcompletion = helpers.funcs.getcompletion describe(':checkhealth', function() @@ -37,6 +37,7 @@ describe(':checkhealth', function() eq('nvim', getcompletion('nvim', 'checkhealth')[1]) eq('provider', getcompletion('prov', 'checkhealth')[1]) eq('vim.lsp', getcompletion('vim.ls', 'checkhealth')[1]) + neq('vim', getcompletion('^vim', 'checkhealth')[1]) -- should not complete vim.health end) end) @@ -153,6 +154,10 @@ describe('health.vim', function() ## report 2 - OK: nothing to see here + test_plug.submodule_empty: require("test_plug.submodule_empty.health").check() + ======================================================================== + - ERROR: The healthcheck report for "test_plug.submodule_empty" plugin is empty. + test_plug.submodule_failed: require("test_plug.submodule_failed.health").check() ======================================================================== - ERROR: Failed to run healthcheck for "test_plug.submodule_failed" plugin. Exception: @@ -172,6 +177,16 @@ describe('health.vim', function() ]]) end) + it("... including empty reports", function() + command("checkhealth test_plug.submodule_empty") + helpers.expect([[ + + test_plug.submodule_empty: require("test_plug.submodule_empty.health").check() + ======================================================================== + - ERROR: The healthcheck report for "test_plug.submodule_empty" plugin is empty. + ]]) + end) + it("gracefully handles broken lua healthcheck", function() command("checkhealth test_plug.submodule_failed") local buf_lines = helpers.curbuf('get_lines', 0, -1, true) @@ -228,5 +243,23 @@ describe('health.vim', function() - ERROR: No healthcheck found for "non_existent_healthcheck" plugin. ]]) end) + + it("does not use vim.health as a healtcheck", function() + -- vim.health is not a healthcheck + command("checkhealth vim") + helpers.expect([[ + ERROR: No healthchecks found.]]) + end) + end) +end) + +describe(':checkhealth provider', function() + it("works correctly with a wrongly configured 'shell'", function() + clear() + command([[set shell=echo\ WRONG!!!]]) + command('let g:loaded_perl_provider = 0') + command('let g:loaded_python3_provider = 0') + command('checkhealth provider') + eq(nil, string.match(curbuf_contents(), 'WRONG!!!')) end) end) diff --git a/test/functional/plugin/lsp/diagnostic_spec.lua b/test/functional/plugin/lsp/diagnostic_spec.lua index 1269a2350c..f73ffc29b0 100644 --- a/test/functional/plugin/lsp/diagnostic_spec.lua +++ b/test/functional/plugin/lsp/diagnostic_spec.lua @@ -3,6 +3,7 @@ local helpers = require('test.functional.helpers')(after_each) local clear = helpers.clear local exec_lua = helpers.exec_lua local eq = helpers.eq +local neq = require('test.helpers').neq describe('vim.lsp.diagnostic', function() local fake_uri @@ -110,6 +111,7 @@ describe('vim.lsp.diagnostic', function() } ]] eq({code = 42, tags = {"foo", "bar"}, data = "Hello world"}, result[1].user_data.lsp) + eq(42, result[1].code) eq(42, result[2].code) eq({"foo", "bar"}, result[2].tags) eq("Hello world", result[2].data) @@ -219,12 +221,50 @@ describe('vim.lsp.diagnostic', function() local diags = vim.diagnostic.get(diagnostic_bufnr) vim.lsp.stop_client(client_id) - vim.lsp._vim_exit_handler() + vim.api.nvim_exec_autocmds('VimLeavePre', { modeline = false }) return diags ]], line) 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) 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(bufnr, -1) + + -- 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(bufnr, -1) + eq(exec_lua([[return #vim.diagnostic.get(...)]], bufnr), 1) + + -- 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(bufnr, -1) + eq(exec_lua([[return #vim.diagnostic.get(...)]], bufnr), 0) + end) end) end) diff --git a/test/functional/plugin/lsp/incremental_sync_spec.lua b/test/functional/plugin/lsp/incremental_sync_spec.lua index 4e3eddb960..4985da9cd7 100644 --- a/test/functional/plugin/lsp/incremental_sync_spec.lua +++ b/test/functional/plugin/lsp/incremental_sync_spec.lua @@ -207,16 +207,16 @@ describe('incremental synchronization', function() { range = { ['start'] = { - character = 0, - line = 1 + character = 11, + line = 0, }, ['end'] = { character = 0, line = 1 } }, - rangeLength = 0, - text = 'hello world\n' + rangeLength = 1, + text = '\nhello world\n' } } test_edit({"hello world"}, {"yyp"}, expected_text_changes, 'utf-16', '\n') @@ -226,19 +226,57 @@ describe('incremental synchronization', function() { range = { ['start'] = { + character = 11, + line = 0 + }, + ['end'] = { character = 0, line = 1 + } + }, + rangeLength = 1, + text = '\n\n' + } + } + test_edit({"hello world"}, {"o"}, expected_text_changes, 'utf-16', '\n') + end) + it('adding a line to an empty buffer', function() + local expected_text_changes = { + { + range = { + ['start'] = { + character = 0, + line = 0 }, ['end'] = { character = 0, line = 1 } }, + rangeLength = 1, + text = '\n\n' + } + } + test_edit({""}, {"o"}, expected_text_changes, 'utf-16', '\n') + end) + it('insert a line above the current line', function() + local expected_text_changes = { + { + range = { + ['start'] = { + character = 0, + line = 0 + }, + ['end'] = { + character = 0, + line = 0 + } + }, rangeLength = 0, text = '\n' } } - test_edit({"hello world"}, {"o"}, expected_text_changes, 'utf-16', '\n') + test_edit({""}, {"O"}, expected_text_changes, 'utf-16', '\n') end) end) describe('multi line edit', function() diff --git a/test/functional/plugin/lsp/snippet_spec.lua b/test/functional/plugin/lsp/snippet_spec.lua index 4e127743eb..7903885420 100644 --- a/test/functional/plugin/lsp/snippet_spec.lua +++ b/test/functional/plugin/lsp/snippet_spec.lua @@ -19,9 +19,9 @@ describe('vim.lsp._snippet', function() { type = snippet.NodeType.TEXT, raw = 'TE\\$\\}XT', - esc = 'TE$}XT' - } - } + esc = 'TE$}XT', + }, + }, }, parse('TE\\$\\}XT')) end) @@ -36,8 +36,8 @@ describe('vim.lsp._snippet', function() { type = snippet.NodeType.TABSTOP, tabstop = 2, - } - } + }, + }, }, parse('$1${2}')) end) @@ -56,7 +56,7 @@ describe('vim.lsp._snippet', function() { type = snippet.NodeType.TEXT, raw = 'TE\\$\\}XT', - esc = 'TE$}XT' + esc = 'TE$}XT', }, { type = snippet.NodeType.TABSTOP, @@ -73,21 +73,21 @@ describe('vim.lsp._snippet', function() { type = snippet.NodeType.FORMAT, capture_index = 1, - modifier = 'upcase' - } - } + modifier = 'upcase', + }, + }, }, }, { type = snippet.NodeType.TEXT, raw = 'TE\\$\\}XT', - esc = 'TE$}XT' + esc = 'TE$}XT', }, - } - } - } + }, + }, + }, }, - } + }, }, parse('${1:${2:TE\\$\\}XT$3${1/regex/${1:/upcase}/i}TE\\$\\}XT}}')) end) @@ -110,8 +110,8 @@ describe('vim.lsp._snippet', function() { type = snippet.NodeType.TABSTOP, tabstop = 1, - } - } + }, + }, }, { type = snippet.NodeType.VARIABLE, @@ -124,11 +124,11 @@ describe('vim.lsp._snippet', function() type = snippet.NodeType.FORMAT, capture_index = 1, modifier = 'upcase', - } - } - } + }, + }, + }, }, - } + }, }, parse('$VAR${VAR}${VAR:$1}${VAR/regex/${1:/upcase}/}')) end) @@ -141,12 +141,96 @@ describe('vim.lsp._snippet', function() tabstop = 1, items = { ',', - '|' - } - } - } + '|', + }, + }, + }, }, parse('${1|\\,,\\||}')) end) -end) + it('should parse format', function() + eq({ + type = snippet.NodeType.SNIPPET, + children = { + { + type = snippet.NodeType.VARIABLE, + name = 'VAR', + transform = { + type = snippet.NodeType.TRANSFORM, + pattern = 'regex', + format = { + { + type = snippet.NodeType.FORMAT, + capture_index = 1, + modifier = 'upcase', + }, + { + type = snippet.NodeType.FORMAT, + capture_index = 1, + if_text = 'if_text', + else_text = '', + }, + { + type = snippet.NodeType.FORMAT, + capture_index = 1, + if_text = '', + else_text = 'else_text', + }, + { + type = snippet.NodeType.FORMAT, + capture_index = 1, + else_text = 'else_text', + if_text = 'if_text', + }, + { + type = snippet.NodeType.FORMAT, + capture_index = 1, + if_text = '', + else_text = 'else_text', + }, + }, + }, + }, + }, + }, parse('${VAR/regex/${1:/upcase}${1:+if_text}${1:-else_text}${1:?if_text:else_text}${1:else_text}/}')) + end) + it('should parse empty strings', function() + eq({ + children = { + { + children = { { + esc = '', + raw = '', + type = 7, + } }, + tabstop = 1, + type = 2, + }, + { + esc = ' ', + raw = ' ', + type = 7, + }, + { + name = 'VAR', + transform = { + format = { + { + capture_index = 1, + else_text = '', + if_text = '', + type = 6, + }, + }, + option = 'g', + pattern = 'erg', + type = 5, + }, + type = 3, + }, + }, + type = 0, + }, parse('${1:} ${VAR/erg/${1:?:}/g}')) + end) +end) diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 7ad1a52fe3..c166982052 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -18,6 +18,7 @@ local NIL = helpers.NIL local read_file = require('test.helpers').read_file local write_file = require('test.helpers').write_file local isCI = helpers.isCI +local meths = helpers.meths -- Use these to get access to a coroutine so that I can run async tests and use -- yield. @@ -44,10 +45,10 @@ local function clear_notrace() end -local function fake_lsp_server_setup(test_name, timeout_ms, options) +local function fake_lsp_server_setup(test_name, timeout_ms, options, settings) exec_lua([=[ lsp = require('vim.lsp') - local test_name, fixture_filename, logfile, timeout, options = ... + local test_name, fixture_filename, logfile, timeout, options, settings = ... TEST_RPC_CLIENT_ID = lsp.start_client { cmd_env = { NVIM_LOG_FILE = logfile; @@ -78,17 +79,18 @@ local function fake_lsp_server_setup(test_name, timeout_ms, options) 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; } - ]=], test_name, fake_lsp_code, fake_lsp_logfile, timeout_ms or 1e3, options or {}) + ]=], test_name, fake_lsp_code, fake_lsp_logfile, timeout_ms or 1e3, options or {}, settings or {}) end local function test_rpc_server(config) if config.test_name then clear_notrace() - fake_lsp_server_setup(config.test_name, config.timeout_ms or 1e3, config.options) + fake_lsp_server_setup(config.test_name, config.timeout_ms or 1e3, config.options, config.settings) end local client = setmetatable({}, { __index = function(_, name) @@ -135,7 +137,7 @@ local function test_rpc_server(config) end stop() if config.test_name then - exec_lua("lsp._vim_exit_handler()") + exec_lua("vim.api.nvim_exec_autocmds('VimLeavePre', { modeline = false })") end end @@ -170,7 +172,7 @@ describe('LSP', function() end) after_each(function() - exec_lua("lsp._vim_exit_handler()") + exec_lua("vim.api.nvim_exec_autocmds('VimLeavePre', { modeline = false })") -- exec_lua("lsp.stop_all_clients(true)") end) @@ -221,11 +223,33 @@ describe('LSP', function() end) end) + describe('lsp._cmd_parts test', function() + local function _cmd_parts(input) + return exec_lua([[ + lsp = require('vim.lsp') + return lsp._cmd_parts(...) + ]], input) + end + it('should valid cmd argument', function() + eq(true, pcall(_cmd_parts, {"nvim"})) + eq(true, pcall(_cmd_parts, {"nvim", "--head"})) + end) + + it('should invalid cmd argument', function() + eq('Error executing lua: .../lsp.lua:0: cmd: expected list, got nvim', + pcall_err(_cmd_parts, 'nvim')) + eq('Error executing lua: .../lsp.lua:0: cmd argument: expected string, got number', + pcall_err(_cmd_parts, {'nvim', 1})) + end) + end) +end) + +describe('LSP', function() describe('basic_init test', function() after_each(function() stop() - exec_lua("lsp.stop_client(lsp.get_active_clients())") - exec_lua("lsp._vim_exit_handler()") + exec_lua("lsp.stop_client(lsp.get_active_clients(), true)") + exec_lua("vim.api.nvim_exec_autocmds('VimLeavePre', { modeline = false })") end) it('should run correctly', function() @@ -242,8 +266,8 @@ describe('LSP', function() end; -- If the program timed out, then code will be nil. on_exit = function(code, signal) - eq(0, code, "exit code", fake_lsp_logfile) - eq(0, signal, "exit signal", fake_lsp_logfile) + eq(0, code, "exit code") + eq(0, signal, "exit signal") end; -- Note that NIL must be used here. -- on_handler(err, method, result, client_id) @@ -264,8 +288,8 @@ describe('LSP', function() client.stop() end; on_exit = function(code, signal) - eq(101, code, "exit code", fake_lsp_logfile) -- See fake-lsp-server.lua - eq(0, signal, "exit signal", fake_lsp_logfile) + eq(101, code, "exit code") -- See fake-lsp-server.lua + eq(0, signal, "exit signal") assert_log(pesc([[assert_eq failed: left == "\"shutdown\"", right == "\"test\""]]), fake_lsp_logfile) end; @@ -275,6 +299,22 @@ describe('LSP', function() } end) + it('should send didChangeConfiguration after initialize if there are settings', function() + test_rpc_server({ + test_name = 'basic_init_did_change_configuration', + on_init = function(client, _) + client.stop() + end, + on_exit = function(code, signal) + eq(0, code, 'exit code', fake_lsp_logfile) + eq(0, signal, 'exit signal', fake_lsp_logfile) + end, + settings = { + dummy = 1, + }, + }) + end) + it('should succeed with manual shutdown', function() if isCI() then pending('hangs the build on CI #14028, re-enable with freeze timeout #14204') @@ -289,14 +329,14 @@ describe('LSP', function() test_rpc_server { test_name = "basic_init"; on_init = function(client) - eq(0, client.resolved_capabilities().text_document_did_change) + eq(0, client.server_capabilities().textDocumentSync.change) client.request('shutdown') client.notify('exit') client.stop() end; on_exit = function(code, signal) - eq(0, code, "exit code", fake_lsp_logfile) - eq(0, signal, "exit signal", fake_lsp_logfile) + eq(0, code, "exit code") + eq(0, signal, "exit signal") end; on_handler = function(...) eq(table.remove(expected_handlers), {...}, "expected handler") @@ -327,8 +367,8 @@ describe('LSP', function() client.notify('finish') end; on_exit = function(code, signal) - eq(0, code, "exit code", fake_lsp_logfile) - eq(0, signal, "exit signal", fake_lsp_logfile) + eq(0, code, "exit code") + eq(0, signal, "exit signal") end; on_handler = function(err, result, ctx) eq(table.remove(expected_handlers), {err, result, ctx}, "expected handler") @@ -341,12 +381,51 @@ describe('LSP', function() } end) + it('should fire autocommands on attach and detach', function() + local client + test_rpc_server { + test_name = "basic_init"; + on_setup = function() + exec_lua [[ + 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 + 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 + end, + }) + ]] + end; + on_init = function(_client) + client = _client + eq(true, exec_lua("return lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID)")) + client.notify('finish') + end; + on_handler = function(_, _, ctx) + if ctx.method == 'finish' then + eq('basic_init', meths.get_var('lsp_attached')) + exec_lua("return lsp.buf_detach_client(BUFFER, TEST_RPC_CLIENT_ID)") + eq('basic_init', meths.get_var('lsp_detached')) + client.stop() + end + end; + } + end) + it('client should return settings via workspace/configuration handler', function() local expected_handlers = { {NIL, {}, {method="shutdown", client_id=1}}; {NIL, { items = { { section = "testSetting1" }; { section = "testSetting2" }; + { section = "test.Setting3" }; + { section = "test.Setting4" }; }}, { method="workspace/configuration", client_id=1}}; {NIL, {}, {method="start", client_id=1}}; } @@ -357,8 +436,8 @@ describe('LSP', function() client = _client end; on_exit = function(code, signal) - eq(0, code, "exit code", fake_lsp_logfile) - eq(0, signal, "exit signal", fake_lsp_logfile) + eq(0, code, "exit code") + eq(0, signal, "exit signal") end; on_handler = function(err, result, ctx) eq(table.remove(expected_handlers), {err, result, ctx}, "expected handler") @@ -368,6 +447,7 @@ describe('LSP', function() client.config.settings = { testSetting1 = true; testSetting2 = false; + test = {Setting3 = 'nested' }; }]=]) end if ctx.method == 'workspace/configuration' then @@ -383,16 +463,23 @@ describe('LSP', function() } end) it('workspace/configuration returns NIL per section if client was started without config.settings', function() - fake_lsp_server_setup('workspace/configuration no settings') - eq({ NIL, NIL, }, exec_lua [[ - local result = { - items = { - {section = 'foo'}, - {section = 'bar'}, - } - } - return vim.lsp.handlers['workspace/configuration'](nil, result, {client_id=TEST_RPC_CLIENT_ID}) - ]]) + local result = nil + test_rpc_server { + test_name = 'basic_init'; + on_init = function(c) c.stop() end, + on_setup = function() + result = exec_lua [[ + local result = { + items = { + {section = 'foo'}, + {section = 'bar'}, + } + } + return vim.lsp.handlers['workspace/configuration'](nil, result, {client_id=TEST_RPC_CLIENT_ID}) + ]] + end + } + eq({ NIL, NIL }, result) end) it('should verify capabilities sent', function() @@ -404,14 +491,13 @@ describe('LSP', function() on_init = function(client) client.stop() local full_kind = exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Full") - eq(full_kind, client.resolved_capabilities().text_document_did_change) - eq(true, client.resolved_capabilities().text_document_save) - eq(false, client.resolved_capabilities().code_lens) - eq(false, client.resolved_capabilities().code_lens_resolve) + eq(full_kind, client.server_capabilities().textDocumentSync.change) + eq({includeText = false}, client.server_capabilities().textDocumentSync.save) + eq(false, client.server_capabilities().codeLensProvider) end; on_exit = function(code, signal) - eq(0, code, "exit code", fake_lsp_logfile) - eq(0, signal, "exit signal", fake_lsp_logfile) + eq(0, code, "exit code") + eq(0, signal, "exit signal") end; on_handler = function(...) eq(table.remove(expected_handlers), {...}, "expected handler") @@ -419,6 +505,67 @@ describe('LSP', function() } end) + it('BufWritePost sends didSave with bool textDocumentSync.save', function() + local expected_handlers = { + {NIL, {}, {method="shutdown", client_id=1}}; + {NIL, {}, {method="start", client_id=1}}; + } + local client + test_rpc_server { + test_name = "text_document_sync_save_bool"; + on_init = function(c) + client = c + end; + on_exit = function(code, signal) + eq(0, code, "exit code") + eq(0, signal, "exit signal") + end; + 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 }) + ]=]) + else + client.stop() + end + end; + } + end) + + it('BufWritePost sends didSave including text if server capability is set', function() + local expected_handlers = { + {NIL, {}, {method="shutdown", client_id=1}}; + {NIL, {}, {method="start", client_id=1}}; + } + local client + test_rpc_server { + test_name = "text_document_sync_save_includeText"; + on_init = function(c) + client = c + end; + on_exit = function(code, signal) + eq(0, code, "exit code") + eq(0, signal, "exit signal") + end; + 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 }) + ]=]) + else + client.stop() + end + end; + } + end) + it('client.supports_methods() should validate capabilities', function() local expected_handlers = { {NIL, {}, {method="shutdown", client_id=1}}; @@ -427,14 +574,19 @@ describe('LSP', function() test_name = "capabilities_for_client_supports_method"; on_init = function(client) client.stop() - local full_kind = exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Full") - eq(full_kind, client.resolved_capabilities().text_document_did_change) - eq(true, client.resolved_capabilities().completion) - eq(true, client.resolved_capabilities().hover) - eq(false, client.resolved_capabilities().goto_definition) - eq(false, client.resolved_capabilities().rename) - eq(true, client.resolved_capabilities().code_lens) - eq(true, client.resolved_capabilities().code_lens_resolve) + local expected_sync_capabilities = { + change = 1, + openClose = true, + save = { includeText = false }, + willSave = false, + willSaveWaitUntil = false, + } + eq(expected_sync_capabilities, client.server_capabilities().textDocumentSync) + eq(true, client.server_capabilities().completionProvider) + eq(true, client.server_capabilities().hoverProvider) + eq(false, client.server_capabilities().definitionProvider) + eq(false, client.server_capabilities().renameProvider) + eq(true, client.server_capabilities().codeLensProvider.resolveProvider) -- known methods for resolved capabilities eq(true, client.supports_method("textDocument/hover")) @@ -444,8 +596,8 @@ describe('LSP', function() eq(true, client.supports_method("unknown-method")) end; on_exit = function(code, signal) - eq(0, code, "exit code", fake_lsp_logfile) - eq(0, signal, "exit signal", fake_lsp_logfile) + eq(0, code, "exit code") + eq(0, signal, "exit signal") end; on_handler = function(...) eq(table.remove(expected_handlers), {...}, "expected handler") @@ -474,8 +626,8 @@ describe('LSP', function() ]] end; on_exit = function(code, signal) - eq(0, code, "exit code", fake_lsp_logfile) - eq(0, signal, "exit signal", fake_lsp_logfile) + eq(0, code, "exit code") + eq(0, signal, "exit signal") end; on_handler = function(...) eq(table.remove(expected_handlers), {...}, "expected handler") @@ -499,8 +651,8 @@ describe('LSP', function() exec_lua("vim.lsp.buf.type_definition()") end; on_exit = function(code, signal) - eq(0, code, "exit code", fake_lsp_logfile) - eq(0, signal, "exit signal", fake_lsp_logfile) + eq(0, code, "exit code") + eq(0, signal, "exit signal") end; on_handler = function(...) eq(table.remove(expected_handlers), {...}, "expected handler") @@ -520,8 +672,8 @@ describe('LSP', function() client = _client end; on_exit = function(code, signal) - eq(0, code, "exit code", fake_lsp_logfile) - eq(0, signal, "exit signal", fake_lsp_logfile) + eq(0, code, "exit code") + eq(0, signal, "exit signal") eq(0, #expected_handlers, "did not call expected handler") end; on_handler = function(err, _, ctx) @@ -544,8 +696,8 @@ describe('LSP', function() client = _client end; on_exit = function(code, signal) - eq(0, code, "exit code", fake_lsp_logfile) - eq(0, signal, "exit signal", fake_lsp_logfile) + eq(0, code, "exit code") + eq(0, signal, "exit signal") eq(0, #expected_handlers, "did not call expected handler") end; on_handler = function(err, _, ctx) @@ -574,8 +726,8 @@ describe('LSP', function() client.notify("release") end; on_exit = function(code, signal) - eq(0, code, "exit code", fake_lsp_logfile) - eq(0, signal, "exit signal", fake_lsp_logfile) + eq(0, code, "exit code") + eq(0, signal, "exit signal") eq(0, #expected_handlers, "did not call expected handler") end; on_handler = function(err, _, ctx) @@ -607,8 +759,8 @@ describe('LSP', function() client.notify("release") end; on_exit = function(code, signal) - eq(0, code, "exit code", fake_lsp_logfile) - eq(0, signal, "exit signal", fake_lsp_logfile) + eq(0, code, "exit code") + eq(0, signal, "exit signal") eq(0, #expected_handlers, "did not call expected handler") end; on_handler = function(err, _, ctx) @@ -641,8 +793,8 @@ describe('LSP', function() client.notify("release") end; on_exit = function(code, signal) - eq(0, code, "exit code", fake_lsp_logfile) - eq(0, signal, "exit signal", fake_lsp_logfile) + eq(0, code, "exit code") + eq(0, signal, "exit signal") eq(0, #expected_handlers, "did not call expected handler") end; on_handler = function(err, _, ctx) @@ -676,8 +828,8 @@ describe('LSP', function() client.notify("release") end; on_exit = function(code, signal) - eq(0, code, "exit code", fake_lsp_logfile) - eq(0, signal, "exit signal", fake_lsp_logfile) + eq(0, code, "exit code") + eq(0, signal, "exit signal") eq(0, #expected_handlers, "did not call expected handler") eq(3, eval('g:requests')) end; @@ -717,13 +869,13 @@ describe('LSP', function() on_init = function(_client) client = _client local full_kind = exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Full") - eq(full_kind, client.resolved_capabilities().text_document_did_change) - eq(true, client.resolved_capabilities().text_document_open_close) + eq(full_kind, client.server_capabilities().textDocumentSync.change) + eq(true, client.server_capabilities().textDocumentSync.openClose) client.notify('finish') end; on_exit = function(code, signal) - eq(0, code, "exit code", fake_lsp_logfile) - eq(0, signal, "exit signal", fake_lsp_logfile) + eq(0, code, "exit code") + eq(0, signal, "exit signal") end; on_handler = function(err, result, ctx) eq(table.remove(expected_handlers), {err, result, ctx}, "expected handler") @@ -758,15 +910,15 @@ describe('LSP', function() on_init = function(_client) client = _client local full_kind = exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Full") - eq(full_kind, client.resolved_capabilities().text_document_did_change) - eq(true, client.resolved_capabilities().text_document_open_close) + eq(full_kind, client.server_capabilities().textDocumentSync.change) + eq(true, client.server_capabilities().textDocumentSync.openClose) exec_lua [[ assert(not lsp.buf_attach_client(BUFFER, TEST_RPC_CLIENT_ID), "Shouldn't attach twice") ]] end; on_exit = function(code, signal) - eq(0, code, "exit code", fake_lsp_logfile) - eq(0, signal, "exit signal", fake_lsp_logfile) + eq(0, code, "exit code") + eq(0, signal, "exit signal") end; on_handler = function(err, result, ctx) if ctx.method == 'start' then @@ -801,15 +953,15 @@ describe('LSP', function() on_init = function(_client) client = _client local full_kind = exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Full") - eq(full_kind, client.resolved_capabilities().text_document_did_change) - eq(true, client.resolved_capabilities().text_document_open_close) + 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)) ]] end; on_exit = function(code, signal) - eq(0, code, "exit code", fake_lsp_logfile) - eq(0, signal, "exit signal", fake_lsp_logfile) + eq(0, code, "exit code") + eq(0, signal, "exit signal") end; on_handler = function(err, result, ctx) if ctx.method == 'start' then @@ -844,15 +996,15 @@ describe('LSP', function() on_init = function(_client) client = _client local full_kind = exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Full") - eq(full_kind, client.resolved_capabilities().text_document_did_change) - eq(true, client.resolved_capabilities().text_document_open_close) + 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)) ]] end; on_exit = function(code, signal) - eq(0, code, "exit code", fake_lsp_logfile) - eq(0, signal, "exit signal", fake_lsp_logfile) + eq(0, code, "exit code") + eq(0, signal, "exit signal") end; on_handler = function(err, result, ctx) if ctx.method == 'start' then @@ -893,15 +1045,15 @@ describe('LSP', function() on_init = function(_client) client = _client local full_kind = exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Full") - eq(full_kind, client.resolved_capabilities().text_document_did_change) - eq(true, client.resolved_capabilities().text_document_open_close) + 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)) ]] end; on_exit = function(code, signal) - eq(0, code, "exit code", fake_lsp_logfile) - eq(0, signal, "exit signal", fake_lsp_logfile) + eq(0, code, "exit code") + eq(0, signal, "exit signal") end; on_handler = function(err, result, ctx) if ctx.method == 'start' then @@ -944,15 +1096,15 @@ describe('LSP', function() on_init = function(_client) client = _client local sync_kind = exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Incremental") - eq(sync_kind, client.resolved_capabilities().text_document_did_change) - eq(true, client.resolved_capabilities().text_document_open_close) + 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)) ]] end; on_exit = function(code, signal) - eq(0, code, "exit code", fake_lsp_logfile) - eq(0, signal, "exit signal", fake_lsp_logfile) + eq(0, code, "exit code") + eq(0, signal, "exit signal") end; on_handler = function(err, result, ctx) if ctx.method == 'start' then @@ -995,15 +1147,15 @@ describe('LSP', function() on_init = function(_client) client = _client local sync_kind = exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Incremental") - eq(sync_kind, client.resolved_capabilities().text_document_did_change) - eq(true, client.resolved_capabilities().text_document_open_close) + 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)) ]] end; on_exit = function(code, signal) - eq(0, code, "exit code", fake_lsp_logfile) - eq(0, signal, "exit signal", fake_lsp_logfile) + eq(0, code, "exit code") + eq(0, signal, "exit signal") end; on_handler = function(err, result, ctx) if ctx.method == 'start' then @@ -1044,15 +1196,15 @@ describe('LSP', function() on_init = function(_client) client = _client local sync_kind = exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Incremental") - eq(sync_kind, client.resolved_capabilities().text_document_did_change) - eq(true, client.resolved_capabilities().text_document_open_close) + 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)) ]] end; on_exit = function(code, signal) - eq(0, code, "exit code", fake_lsp_logfile) - eq(0, signal, "exit signal", fake_lsp_logfile) + eq(0, code, "exit code") + eq(0, signal, "exit signal") end; on_handler = function(err, result, ctx) if ctx.method == 'start' then @@ -1088,15 +1240,15 @@ describe('LSP', function() on_init = function(_client) client = _client local sync_kind = exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Full") - eq(sync_kind, client.resolved_capabilities().text_document_did_change) - eq(true, client.resolved_capabilities().text_document_open_close) + 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)) ]] end; on_exit = function(code, signal) - eq(0, code, "exit code", fake_lsp_logfile) - eq(0, signal, "exit signal", fake_lsp_logfile) + eq(0, code, "exit code") + eq(0, signal, "exit signal") end; on_handler = function(err, result, ctx) if ctx.method == 'start' then @@ -1139,15 +1291,15 @@ describe('LSP', function() on_init = function(_client) client = _client local sync_kind = exec_lua("return require'vim.lsp.protocol'.TextDocumentSyncKind.Full") - eq(sync_kind, client.resolved_capabilities().text_document_did_change) - eq(true, client.resolved_capabilities().text_document_open_close) + 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)) ]] end; on_exit = function(code, signal) - eq(0, code, "exit code", fake_lsp_logfile) - eq(0, signal, "exit signal", fake_lsp_logfile) + eq(0, code, "exit code") + eq(0, signal, "exit signal") end; on_handler = function(err, result,ctx) if ctx.method == 'start' then @@ -1188,8 +1340,8 @@ describe('LSP', function() client.stop(true) end; on_exit = function(code, signal) - eq(0, code, "exit code", fake_lsp_logfile) - eq(0, signal, "exit signal", fake_lsp_logfile) + eq(0, code, "exit code") + eq(0, signal, "exit signal") end; on_handler = function(err, result, ctx) eq(table.remove(expected_handlers), {err, result, ctx}, "expected handler") @@ -1227,8 +1379,8 @@ describe('LSP', function() ]] end; on_exit = function(code, signal) - eq(0, code, "exit code", fake_lsp_logfile) - eq(0, signal, "exit signal", fake_lsp_logfile) + eq(0, code, "exit code") + eq(0, signal, "exit signal") end; on_handler = function(err, result, ctx) eq(table.remove(expected_handlers), {err, result, ctx}, "expected handler") @@ -1239,25 +1391,6 @@ describe('LSP', function() } end) end) - describe('lsp._cmd_parts test', function() - local function _cmd_parts(input) - return exec_lua([[ - lsp = require('vim.lsp') - return lsp._cmd_parts(...) - ]], input) - end - it('should valid cmd argument', function() - eq(true, pcall(_cmd_parts, {"nvim"})) - eq(true, pcall(_cmd_parts, {"nvim", "--head"})) - end) - - it('should invalid cmd argument', function() - eq('Error executing lua: .../lsp.lua:0: cmd: expected list, got nvim', - pcall_err(_cmd_parts, 'nvim')) - eq('Error executing lua: .../lsp.lua:0: cmd argument: expected string, got number', - pcall_err(_cmd_parts, {'nvim', 1})) - end) - end) end) describe('LSP', function() @@ -1291,7 +1424,7 @@ describe('LSP', function() make_edit(2, 0, 2, 2, {"3"}); make_edit(3, 2, 3, 4, {""}); } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1) + exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, "utf-16") eq({ '123First line of text'; '2econd line of text'; @@ -1311,7 +1444,7 @@ describe('LSP', function() 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) + exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, "utf-16") eq({ ''; '123'; @@ -1335,7 +1468,7 @@ describe('LSP', function() 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) + exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, "utf-16") eq({ ''; '123'; @@ -1352,7 +1485,7 @@ describe('LSP', function() local edits = { make_edit(4, 3, 4, 4, {"ä"}); } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1) + exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, "utf-16") eq({ 'First line of text'; 'Second line of text'; @@ -1365,7 +1498,7 @@ describe('LSP', function() local edits = { make_edit(5, 0, 5, 0, "foobar"); } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1) + exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, "utf-16") eq({ 'First line of text'; 'Second line of text'; @@ -1375,6 +1508,20 @@ 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") + eq({ + 'First line of text'; + 'Second line of text'; + 'Third line of text'; + 'Fourth line of text'; + 'foobar'; + }, buf_lines(1)) + end) describe('cursor position', function() it('don\'t fix the cursor if the range contains the cursor', function() @@ -1382,7 +1529,7 @@ describe('LSP', function() local edits = { make_edit(1, 0, 1, 19, 'Second line of text') } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1) + exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, "utf-16") eq({ 'First line of text'; 'Second line of text'; @@ -1399,7 +1546,7 @@ describe('LSP', function() make_edit(1, 0, 1, 6, ''), make_edit(1, 6, 1, 19, '') } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1) + exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, "utf-16") eq({ 'First line of text'; ''; @@ -1416,7 +1563,7 @@ describe('LSP', function() make_edit(1, 0, 1, 6, ''), make_edit(0, 18, 5, 0, '') } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1) + exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, "utf-16") eq({ 'First line of text'; }, buf_lines(1)) @@ -1428,7 +1575,7 @@ describe('LSP', function() local edits = { make_edit(1, 0, 2, 0, '') } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1) + exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, "utf-16") eq({ 'First line of text'; 'Third line of text'; @@ -1443,7 +1590,7 @@ describe('LSP', function() local edits = { make_edit(1, 7, 1, 11, '') } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1) + exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, "utf-16") eq({ 'First line of text'; 'Second of text'; @@ -1459,7 +1606,7 @@ describe('LSP', function() local edits = { make_edit(0, 11, 1, 12, '') } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1) + exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, "utf-16") eq({ 'First line of text'; 'Third line of text'; @@ -1475,21 +1622,21 @@ describe('LSP', function() local edits = { make_edit(0, 0, 5, 0, {"All replaced"}); } - exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1) + exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, "utf-16") 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) + exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, "utf-16") 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) + exec_lua('vim.lsp.util.apply_text_edits(...)', edits, 1, "utf-16") eq({'All replaced'}, buf_lines(1)) end) end) @@ -1517,7 +1664,7 @@ describe('LSP', function() ]] end) it('correctly goes ahead with the edit if all is normal', function() - exec_lua('vim.lsp.util.apply_text_document_edit(...)', text_document_edit(5)) + exec_lua("vim.lsp.util.apply_text_document_edit(..., nil, 'utf-16')", text_document_edit(5)) eq({ 'First ↥ 🤦 🦄 line of text'; '2nd line of 语text'; @@ -1529,7 +1676,7 @@ describe('LSP', function() local bufnr = select(1, ...) local text_edit = select(2, ...) vim.lsp.util.buf_versions[bufnr] = 10 - vim.lsp.util.apply_text_document_edit(text_edit) + vim.lsp.util.apply_text_document_edit(text_edit, nil, 'utf-16') ]], target_bufnr, text_document_edit(0)) eq({ 'First ↥ 🤦 🦄 line of text'; @@ -1542,7 +1689,7 @@ describe('LSP', function() local args = {...} local versionedBuf = args[2] vim.lsp.util.buf_versions[versionedBuf.bufnr] = versionedBuf.currentVersion - vim.lsp.util.apply_text_document_edit(args[1]) + vim.lsp.util.apply_text_document_edit(args[1], nil, 'utf-16') ]], edit, versionedBuf) end @@ -1568,17 +1715,36 @@ describe('LSP', function() describe('workspace_apply_edit', function() it('workspace/applyEdit returns ApplyWorkspaceEditResponse', function() - local expected = { - applied = true; - failureReason = nil; + local expected_handlers = { + {NIL, {}, {method="test", client_id=1}}; + } + test_rpc_server { + test_name = "basic_init"; + on_init = function(client, _) + client.stop() + end; + -- If the program timed out, then code will be nil. + on_exit = function(code, signal) + eq(0, code, "exit code") + eq(0, signal, "exit signal") + end; + -- Note that NIL must be used here. + -- on_handler(err, method, result, client_id) + on_handler = function(...) + local expected = { + applied = true; + failureReason = nil; + } + 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}) + ]]) + eq(table.remove(expected_handlers), {...}) + end; } - eq(expected, exec_lua [[ - local apply_edit = { - label = nil; - edit = {}; - } - return vim.lsp.handlers['workspace/applyEdit'](nil, apply_edit) - ]]) end) end) @@ -1652,7 +1818,7 @@ describe('LSP', function() local workspace_edits = args[1] local target_bufnr = args[2] - vim.lsp.util.apply_workspace_edit(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)) @@ -1674,7 +1840,7 @@ describe('LSP', function() local workspace_edits = args[1] local target_bufnr = args[2] - vim.lsp.util.apply_workspace_edit(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)) @@ -1691,7 +1857,7 @@ describe('LSP', function() }, } } - exec_lua('vim.lsp.util.apply_workspace_edit(...)', edit) + exec_lua('vim.lsp.util.apply_workspace_edit(...)', edit, 'utf-16') eq(true, exec_lua('return vim.loop.fs_stat(...) ~= nil', tmpfile)) end) it('createFile does not touch file if it exists and ignoreIfExists is set', function() @@ -1709,7 +1875,7 @@ describe('LSP', function() }, } } - exec_lua('vim.lsp.util.apply_workspace_edit(...)', edit) + exec_lua('vim.lsp.util.apply_workspace_edit(...)', edit, 'utf-16') eq(true, exec_lua('return vim.loop.fs_stat(...) ~= nil', tmpfile)) eq('Dummy content', read_file(tmpfile)) end) @@ -1729,7 +1895,7 @@ describe('LSP', function() }, } } - exec_lua('vim.lsp.util.apply_workspace_edit(...)', edit) + exec_lua('vim.lsp.util.apply_workspace_edit(...)', edit, 'utf-16') eq(true, exec_lua('return vim.loop.fs_stat(...) ~= nil', tmpfile)) eq('', read_file(tmpfile)) end) @@ -1750,7 +1916,7 @@ describe('LSP', function() } } } - eq(true, pcall(exec_lua, 'vim.lsp.util.apply_workspace_edit(...)', edit)) + eq(true, pcall(exec_lua, 'vim.lsp.util.apply_workspace_edit(...)', edit, 'utf-16')) eq(false, exec_lua('return vim.loop.fs_stat(...) ~= nil', tmpfile)) eq(false, exec_lua('return vim.api.nvim_buf_is_loaded(vim.fn.bufadd(...))', tmpfile)) end) @@ -1911,7 +2077,7 @@ describe('LSP', function() } }, } - return vim.lsp.util.locations_to_items(locations) + return vim.lsp.util.locations_to_items(locations, 'utf-16') ]] eq(expected, actual) end) @@ -1941,7 +2107,7 @@ describe('LSP', function() } }, } - return vim.lsp.util.locations_to_items(locations) + return vim.lsp.util.locations_to_items(locations, 'utf-16') ]] eq(expected, actual) end) @@ -2245,7 +2411,7 @@ describe('LSP', function() end local jump = function(msg) - eq(true, exec_lua('return vim.lsp.util.jump_to_location(...)', msg)) + eq(true, exec_lua('return vim.lsp.util.jump_to_location(...)', msg, "utf-16")) eq(target_bufnr, exec_lua[[return vim.fn.bufnr('%')]]) return { line = exec_lua[[return vim.fn.line('.')]], @@ -2319,19 +2485,38 @@ describe('LSP', function() end) end) + describe('lsp.util.convert_signature_help_to_markdown_lines', function() + it('can handle negative activeSignature', function() + local result = exec_lua[[ + local signature_help = { + activeParameter = 0, + activeSignature = -1, + signatures = { + { + documentation = "", + label = "TestEntity.TestEntity()", + parameters = {} + }, + } + } + return vim.lsp.util.convert_signature_help_to_markdown_lines(signature_help, 'cs', {','}) + ]] + local expected = {'```cs', 'TestEntity.TestEntity()', '```', ''} + eq(expected, result) + end) + end) + describe('lsp.util.get_effective_tabstop', function() - local function test_tabstop(tabsize, softtabstop) + local function test_tabstop(tabsize, shiftwidth) exec_lua(string.format([[ - vim.api.nvim_buf_set_option(0, 'softtabstop', %d) + vim.api.nvim_buf_set_option(0, 'shiftwidth', %d) vim.api.nvim_buf_set_option(0, 'tabstop', 2) - vim.api.nvim_buf_set_option(0, 'shiftwidth', 3) - ]], softtabstop)) + ]], shiftwidth)) eq(tabsize, exec_lua('return vim.lsp.util.get_effective_tabstop()')) end - it('with softtabstop = 1', function() test_tabstop(1, 1) end) - it('with softtabstop = 0', function() test_tabstop(2, 0) end) - it('with softtabstop = -1', function() test_tabstop(3, -1) end) + it('with shiftwidth = 1', function() test_tabstop(1, 1) end) + it('with shiftwidth = 0', function() test_tabstop(2, 0) end) end) describe('vim.lsp.buf.outgoing_calls', function() @@ -2516,10 +2701,8 @@ describe('LSP', function() name = "prepare_rename_error", expected_handlers = { {NIL, {}, {method="shutdown", client_id=1}}; - {NIL, NIL, {method="textDocument/rename", client_id=1, bufnr=1}}; {NIL, {}, {method="start", client_id=1}}; }, - expected_text = "two", -- see test case }, }) do it(test.it, function() @@ -2528,7 +2711,7 @@ describe('LSP', function() test_name = test.name; on_init = function(_client) client = _client - eq(true, client.resolved_capabilities().rename) + eq(true, client.server_capabilities().renameProvider.prepareProvider) end; on_setup = function() exec_lua([=[ @@ -2545,8 +2728,8 @@ describe('LSP', function() ]=]) end; on_exit = function(code, signal) - eq(0, code, "exit code", fake_lsp_logfile) - eq(0, signal, "exit signal", fake_lsp_logfile) + eq(0, code, "exit code") + eq(0, signal, "exit signal") end; on_handler = function(err, result, ctx) -- Don't compare & assert params, they're not relevant for the testcase @@ -2585,8 +2768,8 @@ describe('LSP', function() on_setup = function() end, on_exit = function(code, signal) - eq(0, code, "exit code", fake_lsp_logfile) - eq(0, signal, "exit signal", fake_lsp_logfile) + eq(0, code, "exit code") + eq(0, signal, "exit signal") end, on_handler = function(err, result, ctx) eq(table.remove(expected_handlers), {err, result, ctx}) @@ -2610,6 +2793,102 @@ describe('LSP', function() end } end) + it('Calls workspace/executeCommand if no client side command', function() + local client + local expected_handlers = { + { NIL, {}, { method = 'shutdown', client_id = 1 } }, + { + NIL, + { command = 'dummy1', title = 'Command 1' }, + { bufnr = 1, method = 'workspace/executeCommand', client_id = 1 }, + }, + { NIL, {}, { method = 'start', client_id = 1 } }, + } + test_rpc_server({ + test_name = 'code_action_server_side_command', + on_init = function(client_) + client = client_ + end, + on_setup = function() end, + on_exit = function(code, signal) + eq(0, code, 'exit code', fake_lsp_logfile) + eq(0, signal, 'exit signal', fake_lsp_logfile) + end, + on_handler = function(err, result, ctx) + ctx.params = nil -- don't compare in assert + eq(table.remove(expected_handlers), { err, result, ctx }) + if ctx.method == 'start' then + exec_lua([[ + local bufnr = vim.api.nvim_get_current_buf() + vim.lsp.buf_attach_client(bufnr, TEST_RPC_CLIENT_ID) + vim.fn.inputlist = function() + return 1 + end + vim.lsp.buf.code_action() + ]]) + elseif ctx.method == 'shutdown' then + client.stop() + end + end, + }) + end) + it('Filters and automatically applies action if requested', function() + local client + local expected_handlers = { + {NIL, {}, {method="shutdown", client_id=1}}; + {NIL, {}, {method="start", client_id=1}}; + } + test_rpc_server { + test_name = 'code_action_filter', + on_init = function(client_) + client = client_ + end, + on_setup = function() + end, + on_exit = function(code, signal) + eq(0, code, "exit code") + eq(0, signal, "exit signal") + end, + 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 + end + vim.lsp.commands['quickfix_command'] = function(cmd) + vim.lsp.commands['executed_quickfix'] = 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.code_action({ + -- expect to be returned actions 'quickfix' and 'quickfix.foo' + context = { only = {'quickfix'}, }, + apply = true, + filter = function(a) + if a.kind == 'quickfix.foo' then + vim.lsp.commands['filtered_quickfix_foo'] = function() end + return false + elseif a.kind == 'quickfix' then + return true + else + assert(nil, 'unreachable') + 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_quickfix_foo'])]]) + eq('function', exec_lua[[return type(vim.lsp.commands['executed_quickfix'])]]) + client.stop() + end + end + } + end) end) describe('vim.lsp.commands', function() it('Accepts only string keys', function() @@ -2640,8 +2919,8 @@ describe('LSP', function() on_setup = function() end, on_exit = function(code, signal) - eq(0, code, "exit code", fake_lsp_logfile) - eq(0, signal, "exit signal", fake_lsp_logfile) + eq(0, code, "exit code") + eq(0, signal, "exit signal") end, on_handler = function(err, result, ctx) eq(table.remove(expected_handlers), {err, result, ctx}) @@ -2677,5 +2956,189 @@ describe('LSP', function() end } end) + + it('releases buffer refresh lock', function() + local client + local expected_handlers = { + {NIL, {}, {method="shutdown", client_id=1}}; + {NIL, {}, {method="start", client_id=1}}; + } + test_rpc_server { + test_name = 'codelens_refresh_lock', + on_init = function(client_) + 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 + ]=]) + end, + on_exit = function(code, signal) + eq(0, code, "exit code") + eq(0, signal, "exit signal") + end, + on_handler = function(err, result, ctx) + eq(table.remove(expected_handlers), {err, result, ctx}) + if ctx.method == 'start' then + -- 1. first codelens request errors + local response = exec_lua([=[ + CALLED = false + vim.lsp.codelens.refresh() + vim.wait(100, function () return CALLED end) + return RESPONSE + ]=]) + 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 + end + vim.lsp.codelens.refresh() + vim.wait(100, function () return CALLED end) + vim.lsp.codelens.run() + vim.wait(100, function () return cmd_called end) + return cmd_called + ]=]) + 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 + end + vim.lsp.codelens.refresh() + vim.wait(100, function () return CALLED end) + vim.lsp.codelens.run() + vim.wait(100, function () return cmd_called end) + return cmd_called + ]=]) + eq( { command = "Dummy", title = "Lens2" }, response) + elseif ctx.method == 'shutdown' then + client.stop() + end + end + } + end) + end) + + describe("vim.lsp.buf.format", function() + it("Aborts with notify if no client matches filter", function() + local client + test_rpc_server { + test_name = "basic_init", + on_init = function(c) + client = c + end, + on_handler = function() + local notify_msg = exec_lua([[ + local bufnr = vim.api.nvim_get_current_buf() + vim.lsp.buf_attach_client(bufnr, TEST_RPC_CLIENT_ID) + local notify_msg + local notify = vim.notify + vim.notify = function(msg, log_level) + notify_msg = msg + end + vim.lsp.buf.format({ name = 'does-not-exist' }) + vim.notify = notify + return notify_msg + ]]) + 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}}; + {NIL, {}, {method="start", client_id=1}}; + } + local client + test_rpc_server { + test_name = "basic_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([[ + local bufnr = vim.api.nvim_get_current_buf() + vim.lsp.buf_attach_client(bufnr, TEST_RPC_CLIENT_ID) + local notify_msg + local notify = vim.notify + vim.notify = function(msg, log_level) + notify_msg = msg + end + vim.lsp.buf.format({ bufnr = bufnr }) + vim.notify = notify + return notify_msg + ]]) + 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}}; + {NIL, {}, {method="start", client_id=1}}; + } + local client + test_rpc_server { + test_name = "basic_formatting", + on_init = function(c) + client = c + end, + on_handler = function(_, _, ctx) + table.remove(expected_handlers) + if ctx.method == "start" then + local result = exec_lua([[ + local bufnr = vim.api.nvim_get_current_buf() + vim.lsp.buf_attach_client(bufnr, TEST_RPC_CLIENT_ID) + + local notify_msg + local notify = vim.notify + vim.notify = function(msg, log_level) + notify_msg = msg + end + + local handler = vim.lsp.handlers['textDocument/formatting'] + local handler_called = false + 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.notify = notify + vim.lsp.handlers['textDocument/formatting'] = handler + return {notify = notify_msg, handler_called = handler_called} + ]]) + eq({handler_called=true}, result) + elseif ctx.method == "shutdown" then + client.stop() + end + end, + } + end) end) end) diff --git a/test/functional/plugin/man_spec.lua b/test/functional/plugin/man_spec.lua index e5b2e7dc1f..c8da5a711f 100644 --- a/test/functional/plugin/man_spec.lua +++ b/test/functional/plugin/man_spec.lua @@ -2,13 +2,19 @@ local helpers = require('test.functional.helpers')(after_each) local Screen = require('test.functional.ui.screen') local command, eval, rawfeed = helpers.command, helpers.eval, helpers.rawfeed local clear = helpers.clear +local funcs = helpers.funcs +local nvim_prog = helpers.nvim_prog +local matches = helpers.matches describe(':Man', function() + before_each(function() + clear() + end) + describe('man.lua: highlight_line()', function() local screen before_each(function() - clear() command('syntax on') command('set filetype=man') command('syntax off') -- Ignore syntax groups @@ -137,4 +143,10 @@ describe(':Man', function() ]]) end) end) + + it('q quits in "$MANPAGER mode" (:Man!) #18281', function() + -- This will hang if #18281 regresses. + local args = {nvim_prog, '--headless', '+autocmd VimLeave * echo "quit works!!"', '+Man!', '+call nvim_input("q")'} + matches('quit works!!', funcs.system(args, {'manpage contents'})) + end) end) diff --git a/test/functional/plugin/matchparen_spec.lua b/test/functional/plugin/matchparen_spec.lua index 13e1283e2c..2670734c1a 100644 --- a/test/functional/plugin/matchparen_spec.lua +++ b/test/functional/plugin/matchparen_spec.lua @@ -27,7 +27,7 @@ describe('matchparen', function() feed('{<cr>') feed('}') - -- critical part: up + cr should result in an empty line inbetween the + -- critical part: up + cr should result in an empty line in between the -- brackets... if the bug is there, the empty line will be before the '{' feed('<up>') feed('<cr>') diff --git a/test/functional/plugin/shada_spec.lua b/test/functional/plugin/shada_spec.lua index a4d78682ad..6f22f865e6 100644 --- a/test/functional/plugin/shada_spec.lua +++ b/test/functional/plugin/shada_spec.lua @@ -1,7 +1,7 @@ local helpers = require('test.functional.helpers')(after_each) local clear = helpers.clear -local eq, nvim_eval, nvim_command, nvim, exc_exec, funcs, nvim_feed, curbuf = - helpers.eq, helpers.eval, helpers.command, helpers.nvim, helpers.exc_exec, +local eq, meths, nvim_eval, nvim_command, nvim, exc_exec, funcs, nvim_feed, curbuf = + helpers.eq, helpers.meths, helpers.eval, helpers.command, helpers.nvim, helpers.exc_exec, helpers.funcs, helpers.feed, helpers.curbuf local neq = helpers.neq local read_file = helpers.read_file @@ -2162,6 +2162,10 @@ describe('plugin/shada.vim', function() wshada('\004\000\009\147\000\196\002ab\196\001a') wshada_tmp('\004\000\009\147\000\196\002ab\196\001b') + + local bufread_commands = meths.get_autocmds({ group = "ShaDaCommands", event = "BufReadCmd" }) + eq(2, #bufread_commands--[[, vim.inspect(bufread_commands) ]]) + -- Need to set nohidden so that the buffer containing 'fname' is not unloaded -- after loading 'fname_tmp', otherwise the '++opt not supported' test below -- won't work since the BufReadCmd autocmd won't be triggered. diff --git a/test/functional/preload.lua b/test/functional/preload.lua index 24a3977e6b..74f03eaecf 100644 --- a/test/functional/preload.lua +++ b/test/functional/preload.lua @@ -1,8 +1,9 @@ --- Modules loaded here will not be cleared and reloaded by Busted. +-- Modules loaded here will NOT be cleared and reloaded by Busted. -- Busted started doing this to help provide more isolation. See issue #62 -- for more information about this. local helpers = require('test.functional.helpers')(nil) local iswin = helpers.iswin +local busted = require("busted") if iswin() then local ffi = require('ffi') @@ -12,3 +13,28 @@ if iswin() then ]] ffi.C._set_fmode(0x8000) end + +local testid = (function() + local id = 0 + return (function() + id = id + 1 + return id + end) +end)() + +-- Global before_each. https://github.com/Olivine-Labs/busted/issues/613 +local function before_each(_element, _parent) + local id = ('T%d'):format(testid()) + _G._nvim_test_id = id + return nil, true +end +busted.subscribe({ 'test', 'start' }, + before_each, + { + -- Ensure our --helper is handled before --output (see busted/runner.lua). + priority = 1, + -- Don't generate a test-id for skipped tests. /shrug + predicate = function (element, _, status) + return not ((element.descriptor == 'pending' or status == 'pending')) + end + }) diff --git a/test/functional/provider/clipboard_spec.lua b/test/functional/provider/clipboard_spec.lua index 986db96a18..5bdfec574e 100644 --- a/test/functional/provider/clipboard_spec.lua +++ b/test/functional/provider/clipboard_spec.lua @@ -50,29 +50,33 @@ local function basic_register_test(noblock) text, stuff and some more some some text, stuff and some more]]) - -- deleting a word to named ("a) updates "1 (and not "-) + -- deleting a word to named ("a) doesn't update "1 or "- feed('gg"adwj"1P^"-P') expect([[ , stuff and some more - some textsome some text, stuff and some more]]) + some some random text + some some text, stuff and some more]]) -- deleting a line does update "" feed('ggdd""P') expect([[ , stuff and some more - some textsome some text, stuff and some more]]) + some some random text + some some text, stuff and some more]]) feed('ggw<c-v>jwyggP') if noblock then expect([[ stuf - me t + me s , stuff and some more - some textsome some text, stuff and some more]]) + some some random text + some some text, stuff and some more]]) else expect([[ stuf, stuff and some more - me tsome textsome some text, stuff and some more]]) + me ssome some random text + some some text, stuff and some more]]) end -- pasting in visual does unnamed delete of visual selection diff --git a/test/functional/provider/nodejs_spec.lua b/test/functional/provider/nodejs_spec.lua index 661a6f4f94..187f1c0412 100644 --- a/test/functional/provider/nodejs_spec.lua +++ b/test/functional/provider/nodejs_spec.lua @@ -29,7 +29,7 @@ describe('nodejs host', function() local fname = 'Xtest-nodejs-hello.js' write_file(fname, [[ const neovim = require('neovim'); - const nvim = neovim.attach({socket: process.env.NVIM_LISTEN_ADDRESS}); + const nvim = neovim.attach({socket: process.env.NVIM}); nvim.command('let g:job_out = "hello"'); ]]) command('let g:job_id = jobstart(["node", "'..fname..'"])') @@ -39,7 +39,7 @@ describe('nodejs host', function() local fname = 'Xtest-nodejs-hello-plugin.js' write_file(fname, [[ const neovim = require('neovim'); - const nvim = neovim.attach({socket: process.env.NVIM_LISTEN_ADDRESS}); + const nvim = neovim.attach({socket: process.env.NVIM}); class TestPlugin { hello() { diff --git a/test/functional/provider/perl_spec.lua b/test/functional/provider/perl_spec.lua index 125674660b..aff5e36e24 100644 --- a/test/functional/provider/perl_spec.lua +++ b/test/functional/provider/perl_spec.lua @@ -83,7 +83,7 @@ describe('perl provider', function() use Neovim::Ext; use Neovim::Ext::MsgPack::RPC; - my $session = Neovim::Ext::MsgPack::RPC::socket_session($ENV{NVIM_LISTEN_ADDRESS}); + my $session = Neovim::Ext::MsgPack::RPC::socket_session($ENV{NVIM}); my $nvim = Neovim::Ext::from_session($session); $nvim->command('let g:job_out = "hello"'); 1; @@ -116,7 +116,7 @@ describe('perl provider', function() use Neovim::Ext; use Neovim::Ext::MsgPack::RPC; - my $session = Neovim::Ext::MsgPack::RPC::socket_session($ENV{NVIM_LISTEN_ADDRESS}); + my $session = Neovim::Ext::MsgPack::RPC::socket_session($ENV{NVIM}); my $nvim = Neovim::Ext::from_session($session); my $plugin = TestPlugin->new($nvim); $plugin->test_command(); diff --git a/test/functional/provider/provider_spec.lua b/test/functional/provider/provider_spec.lua index 78bc4a4edb..3895b8613f 100644 --- a/test/functional/provider/provider_spec.lua +++ b/test/functional/provider/provider_spec.lua @@ -14,8 +14,8 @@ describe('providers', function() command('set loadplugins') -- Using test-fixture with broken impl: -- test/functional/fixtures/autoload/provider/python.vim - eq('Vim:provider: python: missing required variable g:loaded_python_provider', - pcall_err(eval, "has('python')")) + eq('Vim:provider: python3: missing required variable g:loaded_python3_provider', + pcall_err(eval, "has('python3')")) end) it('with g:loaded_xx_provider, missing #Call()', function() diff --git a/test/functional/provider/python3_spec.lua b/test/functional/provider/python3_spec.lua index d100db8de2..d9c44c3315 100644 --- a/test/functional/provider/python3_spec.lua +++ b/test/functional/provider/python3_spec.lua @@ -8,6 +8,8 @@ local source = helpers.source local missing_provider = helpers.missing_provider local matches = helpers.matches local pcall_err = helpers.pcall_err +local funcs = helpers.funcs +local dedent = helpers.dedent do clear() @@ -48,7 +50,12 @@ describe('python3 provider', function() local very_long_symbol = string.rep('a', 1200) feed_command(':silent! py3 print('..very_long_symbol..' b)') -- Error message will contain this (last) line. - eq('Error invoking \'python_execute\' on channel 3 (python3-script-host):\n File "<string>", line 1\n print('..very_long_symbol..' b)\n '..string.rep(' ',1200)..' ^\nSyntaxError: invalid syntax', eval('v:errmsg')) + matches(string.format(dedent([[ + ^Error invoking 'python_execute' on channel 3 %%(python3%%-script%%-host%%): + File "<string>", line 1 + print%%(%s b%%) + %%C* + SyntaxError: invalid syntax%%C*$]]), very_long_symbol), eval('v:errmsg')) end) it('python3_execute with nested commands', function() @@ -93,16 +100,40 @@ describe('python3 provider', function() ghi]]) end) - it('py3eval', function() - eq({1, 2, {['key'] = 'val'}}, eval([[py3eval('[1, 2, {"key": "val"}]')]])) + describe('py3eval()', function() + it('works', function() + eq({1, 2, {['key'] = 'val'}}, funcs.py3eval('[1, 2, {"key": "val"}]')) + end) + + it('errors out when given non-string', function() + eq('Vim:E474: Invalid argument', pcall_err(eval, 'py3eval(10)')) + eq('Vim:E474: Invalid argument', pcall_err(eval, 'py3eval(v:_null_dict)')) + eq('Vim:E474: Invalid argument', pcall_err(eval, 'py3eval(v:_null_list)')) + eq('Vim:E474: Invalid argument', pcall_err(eval, 'py3eval(0.0)')) + eq('Vim:E474: Invalid argument', pcall_err(eval, 'py3eval(function("tr"))')) + eq('Vim:E474: Invalid argument', pcall_err(eval, 'py3eval(v:true)')) + eq('Vim:E474: Invalid argument', pcall_err(eval, 'py3eval(v:false)')) + eq('Vim:E474: Invalid argument', pcall_err(eval, 'py3eval(v:null)')) + end) + + it('accepts NULL string', function() + matches('.*SyntaxError.*', pcall_err(eval, 'py3eval($XXX_NONEXISTENT_VAR_XXX)')) + end) end) it('pyxeval #10758', function() - eq(0, eval([[&pyxversion]])) + eq(3, eval([[&pyxversion]])) eq(3, eval([[pyxeval('sys.version_info[:3][0]')]])) eq(3, eval([[&pyxversion]])) end) + it("setting 'pyxversion'", function() + command 'set pyxversion=3' -- no error + eq('Vim(set):E474: Invalid argument: pyxversion=2', pcall_err(command, 'set pyxversion=2')) + command 'set pyxversion=0' -- allowed, but equivalent to pyxversion=3 + eq(3, eval'&pyxversion') + end) + it('RPC call to expand("<afile>") during BufDelete #5245 #5617', function() helpers.add_builddir_to_rtp() source([=[ @@ -120,3 +151,15 @@ describe('python3 provider', function() assert_alive() end) end) + +describe('python2 feature test', function() + -- python2 is not supported, so correct behaviour is to return 0 + it('works', function() + eq(0, funcs.has('python2')) + eq(0, funcs.has('python')) + eq(0, funcs.has('python_compiled')) + eq(0, funcs.has('python_dynamic')) + eq(0, funcs.has('python_dynamic_')) + eq(0, funcs.has('python_')) + end) +end) diff --git a/test/functional/provider/python_spec.lua b/test/functional/provider/python_spec.lua deleted file mode 100644 index d60d8d1001..0000000000 --- a/test/functional/provider/python_spec.lua +++ /dev/null @@ -1,123 +0,0 @@ -local helpers = require('test.functional.helpers')(after_each) - -local eq = helpers.eq -local neq = helpers.neq -local feed = helpers.feed -local clear = helpers.clear -local funcs = helpers.funcs -local meths = helpers.meths -local insert = helpers.insert -local expect = helpers.expect -local command = helpers.command -local exc_exec = helpers.exc_exec -local write_file = helpers.write_file -local curbufmeths = helpers.curbufmeths -local missing_provider = helpers.missing_provider -local matches = helpers.matches -local pcall_err = helpers.pcall_err - -do - clear() - local reason = missing_provider('python') - if reason then - it(':python reports E319 if provider is missing', function() - local expected = [[Vim%(py.*%):E319: No "python" provider found.*]] - matches(expected, pcall_err(command, 'py print("foo")')) - matches(expected, pcall_err(command, 'pyfile foo')) - end) - pending(string.format('Python 2 (or the pynvim module) is broken/missing (%s)', reason), function() end) - return - end -end - -before_each(function() - clear() - command('python import vim') -end) - -describe('python feature test', function() - it('works', function() - eq(1, funcs.has('python')) - eq(1, funcs.has('python_compiled')) - eq(1, funcs.has('python_dynamic')) - eq(0, funcs.has('python_dynamic_')) - eq(0, funcs.has('python_')) - end) -end) - -describe(':python command', function() - it('works with a line', function() - command('python vim.vars["set_by_python"] = [100, 0]') - eq({100, 0}, meths.get_var('set_by_python')) - end) - - -- TODO(ZyX-I): works with << EOF - -- TODO(ZyX-I): works with execute 'python' line1."\n".line2."\n"… - - it('supports nesting', function() - command([[python vim.command('python vim.command("python vim.command(\'let set_by_nested_python = 555\')")')]]) - eq(555, meths.get_var('set_by_nested_python')) - end) - - it('supports range', function() - insert([[ - line1 - line2 - line3 - line4]]) - feed('ggjvj:python vim.vars["range"] = vim.current.range[:]<CR>') - eq({'line2', 'line3'}, meths.get_var('range')) - end) -end) - -describe(':pyfile command', function() - it('works', function() - local fname = 'pyfile.py' - write_file(fname, 'vim.command("let set_by_pyfile = 123")') - command('pyfile pyfile.py') - eq(123, meths.get_var('set_by_pyfile')) - os.remove(fname) - end) -end) - -describe(':pydo command', function() - it('works', function() - -- :pydo 42 returns None for all lines, - -- the buffer should not be changed - command('normal :pydo 42') - eq(false, curbufmeths.get_option('modified')) - -- insert some text - insert('abc\ndef\nghi') - expect([[ - abc - def - ghi]]) - -- go to top and select and replace the first two lines - feed('ggvj:pydo return str(linenr)<CR>') - expect([[ - 1 - 2 - ghi]]) - end) -end) - -describe('pyeval()', function() - it('works', function() - eq({1, 2, {['key'] = 'val'}}, funcs.pyeval('[1, 2, {"key": "val"}]')) - end) - - it('errors out when given non-string', function() - eq('Vim(call):E474: Invalid argument', exc_exec('call pyeval(10)')) - eq('Vim(call):E474: Invalid argument', exc_exec('call pyeval(v:_null_dict)')) - eq('Vim(call):E474: Invalid argument', exc_exec('call pyeval(v:_null_list)')) - eq('Vim(call):E474: Invalid argument', exc_exec('call pyeval(0.0)')) - eq('Vim(call):E474: Invalid argument', exc_exec('call pyeval(function("tr"))')) - eq('Vim(call):E474: Invalid argument', exc_exec('call pyeval(v:true)')) - eq('Vim(call):E474: Invalid argument', exc_exec('call pyeval(v:false)')) - eq('Vim(call):E474: Invalid argument', exc_exec('call pyeval(v:null)')) - end) - - it('accepts NULL string', function() - neq(0, exc_exec('call pyeval($XXX_NONEXISTENT_VAR_XXX)')) - end) -end) diff --git a/test/functional/shada/buffers_spec.lua b/test/functional/shada/buffers_spec.lua index 04c9c01d7c..26a4569fce 100644 --- a/test/functional/shada/buffers_spec.lua +++ b/test/functional/shada/buffers_spec.lua @@ -2,6 +2,7 @@ local helpers = require('test.functional.helpers')(after_each) local nvim_command, funcs, eq, curbufmeths = helpers.command, helpers.funcs, helpers.eq, helpers.curbufmeths +local expect_exit = helpers.expect_exit local shada_helpers = require('test.functional.shada.helpers') local reset, clear = shada_helpers.reset, shada_helpers.clear @@ -15,7 +16,7 @@ describe('shada support code', function() reset('set shada+=%') nvim_command('edit ' .. testfilename) nvim_command('edit ' .. testfilename_2) - nvim_command('qall') + expect_exit(nvim_command, 'qall') reset('set shada+=%') eq(3, funcs.bufnr('$')) eq('', funcs.bufname(1)) @@ -27,7 +28,7 @@ describe('shada support code', function() reset('set shada+=%') nvim_command('edit ' .. testfilename) nvim_command('edit ' .. testfilename_2) - nvim_command('qall') + expect_exit(nvim_command, 'qall') reset() eq(1, funcs.bufnr('$')) eq('', funcs.bufname(1)) @@ -37,7 +38,7 @@ describe('shada support code', function() reset() nvim_command('edit ' .. testfilename) nvim_command('edit ' .. testfilename_2) - nvim_command('qall') + expect_exit(nvim_command, 'qall') reset('set shada+=%') eq(1, funcs.bufnr('$')) eq('', funcs.bufname(1)) @@ -48,7 +49,7 @@ describe('shada support code', function() nvim_command('edit ' .. testfilename) nvim_command('edit ' .. testfilename_2) curbufmeths.set_option('buflisted', false) - nvim_command('qall') + expect_exit(nvim_command, 'qall') reset('set shada+=%') eq(2, funcs.bufnr('$')) eq('', funcs.bufname(1)) @@ -60,7 +61,7 @@ describe('shada support code', function() nvim_command('edit ' .. testfilename) nvim_command('edit ' .. testfilename_2) curbufmeths.set_option('buftype', 'quickfix') - nvim_command('qall') + expect_exit(nvim_command, 'qall') reset('set shada+=%') eq(2, funcs.bufnr('$')) eq('', funcs.bufname(1)) @@ -73,7 +74,7 @@ describe('shada support code', function() nvim_command('enew') curbufmeths.set_lines(0, 1, true, {'bar'}) eq(2, funcs.bufnr('$')) - nvim_command('qall!') + expect_exit(nvim_command, 'qall!') reset('set shada+=% hidden') eq(1, funcs.bufnr('$')) eq('', funcs.bufname(1)) @@ -83,7 +84,7 @@ describe('shada support code', function() reset('set shada+=%1') nvim_command('edit ' .. testfilename) nvim_command('edit ' .. testfilename_2) - nvim_command('qall') + expect_exit(nvim_command, 'qall') reset('set shada+=%1') eq(2, funcs.bufnr('$')) eq('', funcs.bufname(1)) diff --git a/test/functional/shada/history_spec.lua b/test/functional/shada/history_spec.lua index 84cc34c7cc..d1daf6c7cc 100644 --- a/test/functional/shada/history_spec.lua +++ b/test/functional/shada/history_spec.lua @@ -3,6 +3,7 @@ local helpers = require('test.functional.helpers')(after_each) local nvim_command, funcs, meths, nvim_feed, eq = helpers.command, helpers.funcs, helpers.meths, helpers.feed, helpers.eq local assert_alive = helpers.assert_alive +local expect_exit = helpers.expect_exit local shada_helpers = require('test.functional.shada.helpers') local reset, clear = shada_helpers.reset, shada_helpers.clear @@ -25,13 +26,13 @@ describe('ShaDa support code', function() nvim_command('set shada=\'0 history=2') nvim_feed(':" Test\n') nvim_feed(':" Test 2\n') - nvim_command('qall') + expect_exit(nvim_command, 'qall') reset() nvim_command('set shada=\'0 history=2') nvim_command('rshada') eq('" Test 2', funcs.histget(':', -1)) eq('" Test', funcs.histget(':', -2)) - nvim_command('qall') + expect_exit(nvim_command, 'qall') end) it('respects &history when dumping', @@ -138,7 +139,7 @@ describe('ShaDa support code', function() nvim_command('set hlsearch shada-=h') nvim_feed('/test\n') eq(1, meths.get_vvar('hlsearch')) - nvim_command('qall') + expect_exit(nvim_command, 'qall') reset() eq(1, meths.get_vvar('hlsearch')) end) @@ -147,7 +148,7 @@ describe('ShaDa support code', function() nvim_command('set hlsearch shada-=h') nvim_feed('/test\n') nvim_command('nohlsearch') - nvim_command('qall') + expect_exit(nvim_command, 'qall') reset() eq(0, meths.get_vvar('hlsearch')) end) @@ -156,7 +157,7 @@ describe('ShaDa support code', function() nvim_command('set hlsearch') nvim_feed('/test\n') eq(1, meths.get_vvar('hlsearch')) - nvim_command('qall') + expect_exit(nvim_command, 'qall') reset() eq(0, meths.get_vvar('hlsearch')) end) @@ -175,7 +176,7 @@ describe('ShaDa support code', function() it('dumps and loads history with UTF-8 characters', function() reset() nvim_feed(':echo "«"\n') - nvim_command('qall') + expect_exit(nvim_command, 'qall') reset() eq('echo "«"', funcs.histget(':', -1)) end) @@ -183,7 +184,7 @@ describe('ShaDa support code', function() it('dumps and loads replacement with UTF-8 characters', function() nvim_command('substitute/./«/ge') - nvim_command('qall!') + expect_exit(nvim_command, 'qall!') reset() funcs.setline('.', {'.'}) nvim_command('&') @@ -193,7 +194,7 @@ describe('ShaDa support code', function() it('dumps and loads substitute pattern with UTF-8 characters', function() nvim_command('substitute/«/./ge') - nvim_command('qall!') + expect_exit(nvim_command, 'qall!') reset() funcs.setline('.', {'«\171'}) nvim_command('&') @@ -204,7 +205,7 @@ describe('ShaDa support code', function() function() nvim_command('silent! /«/') nvim_command('set shada+=/0') - nvim_command('qall!') + expect_exit(nvim_command, 'qall!') reset() funcs.setline('.', {'\171«'}) nvim_command('~&') @@ -217,7 +218,7 @@ describe('ShaDa support code', function() -- \171 is U+00AB LEFT-POINTING DOUBLE ANGLE QUOTATION MARK in latin1 nvim_command('silent! /\171/') nvim_command('set shada+=/0') - nvim_command('qall!') + expect_exit(nvim_command, 'qall!') reset() funcs.setline('.', {'\171«'}) nvim_command('~&') diff --git a/test/functional/shada/marks_spec.lua b/test/functional/shada/marks_spec.lua index 153a1c346f..a91be18841 100644 --- a/test/functional/shada/marks_spec.lua +++ b/test/functional/shada/marks_spec.lua @@ -4,6 +4,7 @@ local meths, curwinmeths, curbufmeths, nvim_command, funcs, eq = helpers.meths, helpers.curwinmeths, helpers.curbufmeths, helpers.command, helpers.funcs, helpers.eq local exc_exec, exec_capture = helpers.exc_exec, helpers.exec_capture +local expect_exit = helpers.expect_exit local shada_helpers = require('test.functional.shada.helpers') local reset, clear = shada_helpers.reset, shada_helpers.clear @@ -79,7 +80,7 @@ describe('ShaDa support code', function() nvim_command('mark a') nvim_command('2') nvim_command('kb') - nvim_command('qall') + expect_exit(nvim_command, 'qall') reset() nvim_command('edit ' .. testfilename) nvim_command('normal! `a') @@ -92,7 +93,7 @@ describe('ShaDa support code', function() it('is able to dump and read back mark "', function() nvim_command('edit ' .. testfilename) nvim_command('2') - nvim_command('qall') + expect_exit(nvim_command, 'qall') reset() nvim_command('edit ' .. testfilename) nvim_command('normal! `"') @@ -104,7 +105,7 @@ describe('ShaDa support code', function() nvim_command('tabedit ' .. testfilename_2) nvim_command('2') nvim_command('q!') - nvim_command('qall') + expect_exit(nvim_command, 'qall') reset() nvim_command('edit ' .. testfilename_2) nvim_command('normal! `"') @@ -116,7 +117,7 @@ describe('ShaDa support code', function() local tf_full = curbufmeths.get_name() nvim_command('edit ' .. testfilename_2) local tf_full_2 = curbufmeths.get_name() - nvim_command('qall') + expect_exit(nvim_command, 'qall') reset() local oldfiles = meths.get_vvar('oldfiles') table.sort(oldfiles) @@ -145,7 +146,7 @@ describe('ShaDa support code', function() nvim_command('enew') nvim_command('normal! gg') local saved = exec_capture('jumps') - nvim_command('qall') + expect_exit(nvim_command, 'qall') reset() eq(saved, exec_capture('jumps')) end) @@ -176,7 +177,7 @@ describe('ShaDa support code', function() nvim_command('normal! G') nvim_command('sleep 2') nvim_command('normal! gg') - nvim_command('qall') + expect_exit(nvim_command, 'qall') reset() nvim_command('redraw') nvim_command('edit ' .. testfilename) @@ -200,7 +201,7 @@ describe('ShaDa support code', function() nvim_command('edit ' .. testfilename) nvim_command('normal! Gra') nvim_command('normal! ggrb') - nvim_command('qall!') + expect_exit(nvim_command, 'qall!') reset() nvim_command('edit ' .. testfilename) nvim_command('normal! Gg;') diff --git a/test/functional/shada/registers_spec.lua b/test/functional/shada/registers_spec.lua index 1f06cbe350..6aaa54cce8 100644 --- a/test/functional/shada/registers_spec.lua +++ b/test/functional/shada/registers_spec.lua @@ -4,6 +4,7 @@ local nvim_command, funcs, eq = helpers.command, helpers.funcs, helpers.eq local shada_helpers = require('test.functional.shada.helpers') local reset, clear = shada_helpers.reset, shada_helpers.clear +local expect_exit = helpers.expect_exit local setreg = function(name, contents, typ) if type(contents) == 'string' then @@ -27,7 +28,7 @@ describe('ShaDa support code', function() setreg('c', {'d', 'e', ''}, 'c') setreg('l', {'a', 'b', 'cde'}, 'l') setreg('b', {'bca', 'abc', 'cba'}, 'b3') - nvim_command('qall') + expect_exit(nvim_command, 'qall') reset() eq({{'d', 'e', ''}, 'v'}, getreg('c')) eq({{'a', 'b', 'cde'}, 'V'}, getreg('l')) @@ -39,7 +40,7 @@ describe('ShaDa support code', function() setreg('c', {'d', 'e', ''}, 'c') setreg('l', {'a', 'b', 'cde'}, 'l') setreg('b', {'bca', 'abc', 'cba'}, 'b3') - nvim_command('qall') + expect_exit(nvim_command, 'qall') reset() eq({{}, ''}, getreg('c')) eq({{}, ''}, getreg('l')) @@ -50,7 +51,7 @@ describe('ShaDa support code', function() setreg('c', {'d', 'e', ''}, 'c') setreg('l', {'a', 'b', 'cde'}, 'l') setreg('b', {'bca', 'abc', 'cba'}, 'b3') - nvim_command('qall') + expect_exit(nvim_command, 'qall') reset('set shada=\'0,<0') eq({{'d', 'e', ''}, 'v'}, getreg('c')) eq({{'a', 'b', 'cde'}, 'V'}, getreg('l')) @@ -62,7 +63,7 @@ describe('ShaDa support code', function() setreg('c', {'d', 'e', ''}, 'c') setreg('l', {'a', 'b', 'cde'}, 'l') setreg('b', {'bca', 'abc', 'cba'}, 'b3') - nvim_command('qall') + expect_exit(nvim_command, 'qall') reset() eq({{}, ''}, getreg('c')) eq({{}, ''}, getreg('l')) @@ -73,7 +74,7 @@ describe('ShaDa support code', function() setreg('c', {'d', 'e', ''}, 'c') setreg('l', {'a', 'b', 'cde'}, 'l') setreg('b', {'bca', 'abc', 'cba'}, 'b3') - nvim_command('qall') + expect_exit(nvim_command, 'qall') reset('set shada=\'0,\\"0') eq({{'d', 'e', ''}, 'v'}, getreg('c')) eq({{'a', 'b', 'cde'}, 'V'}, getreg('l')) @@ -85,7 +86,7 @@ describe('ShaDa support code', function() setreg('c', {'d', 'e', ''}, 'c') setreg('l', {'a', 'b', 'cde'}, 'l') setreg('b', {'bca', 'abc', 'cba'}, 'b3') - nvim_command('qall') + expect_exit(nvim_command, 'qall') reset() eq({{'d', 'e', ''}, 'v'}, getreg('c')) eq({{'a', 'b', 'cde'}, 'V'}, getreg('l')) @@ -96,7 +97,7 @@ describe('ShaDa support code', function() nvim_command('set shada=\'0,<2') setreg('o', {'d'}, 'c') setreg('t', {'a', 'b', 'cde'}, 'l') - nvim_command('qall') + expect_exit(nvim_command, 'qall') reset() eq({{'d'}, 'v'}, getreg('o')) eq({{}, ''}, getreg('t')) @@ -106,7 +107,7 @@ describe('ShaDa support code', function() nvim_command('set shada=\'0,\\"2') setreg('o', {'d'}, 'c') setreg('t', {'a', 'b', 'cde'}, 'l') - nvim_command('qall') + expect_exit(nvim_command, 'qall') reset() eq({{'d'}, 'v'}, getreg('o')) eq({{}, ''}, getreg('t')) @@ -117,7 +118,7 @@ describe('ShaDa support code', function() setreg('o', {'d'}, 'c') setreg('t', {'a', 'b', 'cde'}, 'l') setreg('h', {'abc', 'acb', 'bac', 'bca', 'cab', 'cba'}, 'b3') - nvim_command('qall') + expect_exit(nvim_command, 'qall') reset() eq({{'d'}, 'v'}, getreg('o')) eq({{'a', 'b', 'cde'}, 'V'}, getreg('t')) @@ -128,7 +129,7 @@ describe('ShaDa support code', function() function() reset() setreg('e', {'«'}, 'c') - nvim_command('qall') + expect_exit(nvim_command, 'qall') reset() eq({{'«'}, 'v'}, getreg('e')) end) @@ -138,7 +139,7 @@ describe('ShaDa support code', function() reset() -- \171 is U+00AB LEFT-POINTING DOUBLE ANGLE QUOTATION MARK in latin1 setreg('e', {'\171«'}, 'c') - nvim_command('qall') + expect_exit(nvim_command, 'qall') reset() eq({{'\171«'}, 'v'}, getreg('e')) end) @@ -148,7 +149,7 @@ describe('ShaDa support code', function() setreg('1', {'one'}, 'c') setreg('2', {'two'}, 'c') setreg('a', {'a'}, 'c') - nvim_command('qall') + expect_exit(nvim_command, 'qall') reset() eq({{}, ''}, getreg('0')) eq({{'one'}, 'v'}, getreg('1')) @@ -161,7 +162,7 @@ describe('ShaDa support code', function() setreg('0', {'zero'}, 'c') setreg('1', {'one'}, 'c') setreg('2', {'two'}, 'c') - nvim_command('qall') + expect_exit(nvim_command, 'qall') reset() eq({{'zero'}, 'v'}, getreg('0')) eq({{'one'}, 'v'}, getreg('1')) @@ -173,7 +174,7 @@ describe('ShaDa support code', function() setreg('0', {'zero'}, 'c') setreg('1', {'one'}, 'cu') setreg('2', {'two'}, 'c') - nvim_command('qall') + expect_exit(nvim_command, 'qall') reset() eq({{'zero'}, 'v'}, getreg('0')) eq({{'one'}, 'v'}, getreg('1')) diff --git a/test/functional/shada/shada_spec.lua b/test/functional/shada/shada_spec.lua index ff63aed235..d10a2facbb 100644 --- a/test/functional/shada/shada_spec.lua +++ b/test/functional/shada/shada_spec.lua @@ -7,7 +7,7 @@ local write_file, spawn, set_session, nvim_prog, exc_exec = helpers.exc_exec local lfs = require('lfs') -local paths = require('test.config.paths') +local paths = require('test.cmakeconfig.paths') local mpack = require('mpack') diff --git a/test/functional/shada/variables_spec.lua b/test/functional/shada/variables_spec.lua index 854add1363..a91b7eb193 100644 --- a/test/functional/shada/variables_spec.lua +++ b/test/functional/shada/variables_spec.lua @@ -2,6 +2,7 @@ local helpers = require('test.functional.helpers')(after_each) local meths, funcs, nvim_command, eq, eval = helpers.meths, helpers.funcs, helpers.command, helpers.eq, helpers.eval +local expect_exit = helpers.expect_exit local shada_helpers = require('test.functional.shada.helpers') local reset, clear = shada_helpers.reset, shada_helpers.clear @@ -33,7 +34,7 @@ describe('ShaDa support code', function() local vartype = eval('type(g:' .. varname .. ')') -- Exit during `reset` is not a regular exit: it does not write shada -- automatically - nvim_command('qall') + expect_exit(nvim_command, 'qall') reset('set shada+=!') eq(vartype, eval('type(g:' .. varname .. ')')) eq(varval, meths.get_var(varname)) @@ -98,7 +99,7 @@ describe('ShaDa support code', function() meths.set_var('LSTVAR', {'«'}) meths.set_var('DCTVAR', {['«']='«'}) meths.set_var('NESTEDVAR', {['«']={{'«'}, {['«']='«'}, {a='Test'}}}) - nvim_command('qall') + expect_exit(nvim_command, 'qall') reset() eq('«', meths.get_var('STRVAR')) eq({'«'}, meths.get_var('LSTVAR')) @@ -116,7 +117,7 @@ describe('ShaDa support code', function() meths.set_var('DCTVAR', {['«\171']='«\171'}) meths.set_var('NESTEDVAR', {['\171']={{'\171«'}, {['\171']='\171'}, {a='Test'}}}) - nvim_command('qall') + expect_exit(nvim_command, 'qall') reset() eq('\171', meths.get_var('STRVAR')) eq({'\171'}, meths.get_var('LSTVAR')) diff --git a/test/functional/terminal/api_spec.lua b/test/functional/terminal/api_spec.lua index e28cc03597..5305b8af9c 100644 --- a/test/functional/terminal/api_spec.lua +++ b/test/functional/terminal/api_spec.lua @@ -6,7 +6,7 @@ if helpers.pending_win32(pending) then return end describe('api', function() local screen - local socket_name = "Xtest_functional_api.sock" + local socket_name = "./Xtest_functional_api.sock" before_each(function() helpers.clear() @@ -29,7 +29,7 @@ describe('api', function() {4:~ }| {4:~ }| {4:~ }| - ]]..socket_name..[[ | + ]]..socket_name..[[ | {3:-- TERMINAL --} | ]]) @@ -48,8 +48,8 @@ describe('api', function() {3:-- TERMINAL --} | ]]) - ok(socket_session1:request("nvim_ui_attach", 42, 6, {rgb=true})) - ok(socket_session2:request("nvim_ui_attach", 25, 30, {rgb=true})) + ok((socket_session1:request("nvim_ui_attach", 42, 6, {rgb=true}))) + ok((socket_session2:request("nvim_ui_attach", 25, 30, {rgb=true}))) socket_session1:notify("nvim_input", "\n[socket 1] this is more than 25 columns") socket_session2:notify("nvim_input", "\n[socket 2] input") diff --git a/test/functional/terminal/buffer_spec.lua b/test/functional/terminal/buffer_spec.lua index beb43e0271..1cef771f0d 100644 --- a/test/functional/terminal/buffer_spec.lua +++ b/test/functional/terminal/buffer_spec.lua @@ -258,6 +258,7 @@ describe(':terminal buffer', function() end) it('it works with set rightleft #11438', function() + if helpers.pending_win32(pending) then return end local columns = eval('&columns') feed(string.rep('a', columns)) command('set rightleft') diff --git a/test/functional/terminal/channel_spec.lua b/test/functional/terminal/channel_spec.lua index 7223f5ba61..b5f3c2bd31 100644 --- a/test/functional/terminal/channel_spec.lua +++ b/test/functional/terminal/channel_spec.lua @@ -1,45 +1,94 @@ local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') local clear = helpers.clear local eq = helpers.eq +local eval = helpers.eval local command = helpers.command local pcall_err = helpers.pcall_err local feed = helpers.feed -local sleep = helpers.sleep local poke_eventloop = helpers.poke_eventloop -describe('associated channel is closed and later freed for terminal', function() - before_each(clear) +describe('terminal channel is closed and later released if', function() + local screen + + before_each(function() + clear() + screen = Screen.new() + screen:attach() + end) it('opened by nvim_open_term() and deleted by :bdelete!', function() command([[let id = nvim_open_term(0, {})]]) - -- channel hasn't been freed yet - eq("Vim(call):Can't send data to closed stream", pcall_err(command, [[bdelete! | call chansend(id, 'test')]])) - -- channel has been freed after one main loop iteration - eq("Vim(call):E900: Invalid channel id", pcall_err(command, [[call chansend(id, 'test')]])) + local chans = eval('len(nvim_list_chans())') + -- channel hasn't been released yet + eq("Vim(call):Can't send data to closed stream", + pcall_err(command, [[bdelete! | call chansend(id, 'test')]])) + -- channel has been released after one main loop iteration + eq(chans - 1, eval('len(nvim_list_chans())')) + end) + + it('opened by nvim_open_term(), closed by chanclose(), and deleted by pressing a key', function() + command('let id = nvim_open_term(0, {})') + local chans = eval('len(nvim_list_chans())') + -- channel has been closed but not released + eq("Vim(call):Can't send data to closed stream", + pcall_err(command, [[call chanclose(id) | call chansend(id, 'test')]])) + screen:expect({any='%[Terminal closed%]'}) + eq(chans, eval('len(nvim_list_chans())')) + -- delete terminal + feed('i<CR>') + -- need to first process input + poke_eventloop() + -- channel has been released after another main loop iteration + eq(chans - 1, eval('len(nvim_list_chans())')) + end) + + it('opened by nvim_open_term(), closed by chanclose(), and deleted by :bdelete', function() + command('let id = nvim_open_term(0, {})') + local chans = eval('len(nvim_list_chans())') + -- channel has been closed but not released + eq("Vim(call):Can't send data to closed stream", + pcall_err(command, [[call chanclose(id) | call chansend(id, 'test')]])) + screen:expect({any='%[Terminal closed%]'}) + eq(chans, eval('len(nvim_list_chans())')) + -- channel still hasn't been released yet + eq("Vim(call):Can't send data to closed stream", + pcall_err(command, [[bdelete | call chansend(id, 'test')]])) + -- channel has been released after one main loop iteration + eq(chans - 1, eval('len(nvim_list_chans())')) end) it('opened by termopen(), exited, and deleted by pressing a key', function() command([[let id = termopen('echo')]]) - sleep(500) - -- process has exited - eq("Vim(call):Can't send data to closed stream", pcall_err(command, [[call chansend(id, 'test')]])) + local chans = eval('len(nvim_list_chans())') + -- wait for process to exit + screen:expect({any='%[Process exited 0%]'}) + -- process has exited but channel has't been released + eq("Vim(call):Can't send data to closed stream", + pcall_err(command, [[call chansend(id, 'test')]])) + eq(chans, eval('len(nvim_list_chans())')) -- delete terminal feed('i<CR>') -- need to first process input poke_eventloop() - -- channel has been freed after another main loop iteration - eq("Vim(call):E900: Invalid channel id", pcall_err(command, [[call chansend(id, 'test')]])) + -- channel has been released after another main loop iteration + eq(chans - 1, eval('len(nvim_list_chans())')) end) -- This indirectly covers #16264 it('opened by termopen(), exited, and deleted by :bdelete', function() command([[let id = termopen('echo')]]) - sleep(500) - -- process has exited - eq("Vim(call):Can't send data to closed stream", pcall_err(command, [[call chansend(id, 'test')]])) - -- channel hasn't been freed yet - eq("Vim(call):Can't send data to closed stream", pcall_err(command, [[bdelete | call chansend(id, 'test')]])) - -- channel has been freed after one main loop iteration - eq("Vim(call):E900: Invalid channel id", pcall_err(command, [[call chansend(id, 'test')]])) + local chans = eval('len(nvim_list_chans())') + -- wait for process to exit + screen:expect({any='%[Process exited 0%]'}) + -- process has exited but channel hasn't been released + eq("Vim(call):Can't send data to closed stream", + pcall_err(command, [[call chansend(id, 'test')]])) + eq(chans, eval('len(nvim_list_chans())')) + -- channel still hasn't been released yet + eq("Vim(call):Can't send data to closed stream", + pcall_err(command, [[bdelete | call chansend(id, 'test')]])) + -- channel has been released after one main loop iteration + eq(chans - 1, eval('len(nvim_list_chans())')) end) end) diff --git a/test/functional/terminal/cursor_spec.lua b/test/functional/terminal/cursor_spec.lua index e9495f45a2..2d1c790d2f 100644 --- a/test/functional/terminal/cursor_spec.lua +++ b/test/functional/terminal/cursor_spec.lua @@ -2,9 +2,10 @@ local helpers = require('test.functional.helpers')(after_each) local Screen = require('test.functional.ui.screen') local thelpers = require('test.functional.terminal.helpers') local feed, clear, nvim = helpers.feed, helpers.clear, helpers.nvim -local nvim_dir, command = helpers.nvim_dir, helpers.command +local testprg, command = helpers.testprg, helpers.command local nvim_prog = helpers.nvim_prog local eq, eval = helpers.eq, helpers.eval +local matches = helpers.matches local feed_command = helpers.feed_command local hide_cursor = thelpers.hide_cursor local show_cursor = thelpers.show_cursor @@ -87,6 +88,7 @@ describe(':terminal cursor', function() describe('when invisible', function() it('is not highlighted and is detached from screen cursor', function() + if helpers.pending_win32(pending) then return end hide_cursor() screen:expect([[ tty ready | @@ -148,7 +150,7 @@ describe('cursor with customized highlighting', function() [3] = {bold = true}, }) screen:attach({rgb=false}) - command('call termopen(["'..nvim_dir..'/tty-test"])') + command('call termopen(["'..testprg('tty-test')..'"])') feed_command('startinsert') end) @@ -523,6 +525,33 @@ describe('buffer cursor position is correct in terminal without number column', eq({6, 1}, eval('nvim_win_get_cursor(0)')) end) end) + + it('at the end of a line with trailing spaces #16234', function() + setup_ex_register('aaaaaaaa ') + feed('<C-R>r') + screen:expect([[ + | + | + | + | + Entering Ex mode. Type "visual" to go to Normal mode. | + :aaaaaaaa {1: } | + {3:-- TERMINAL --} | + ]]) + matches('^:aaaaaaaa [ ]*$', eval('nvim_get_current_line()')) + eq({6, 13}, eval('nvim_win_get_cursor(0)')) + feed([[<C-\><C-N>]]) + screen:expect([[ + | + | + | + | + Entering Ex mode. Type "visual" to go to Normal mode. | + :aaaaaaaa ^ {2: } | + | + ]]) + eq({6, 12}, eval('nvim_win_get_cursor(0)')) + end) end) describe('buffer cursor position is correct in terminal with number column', function() @@ -876,4 +905,31 @@ describe('buffer cursor position is correct in terminal with number column', fun eq({6, 1}, eval('nvim_win_get_cursor(0)')) end) end) + + it('at the end of a line with trailing spaces #16234', function() + setup_ex_register('aaaaaaaa ') + feed('<C-R>r') + screen:expect([[ + {7: 1 } | + {7: 2 } | + {7: 3 } | + {7: 4 } | + {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | + {7: 6 }:aaaaaaaa {1: } | + {3:-- TERMINAL --} | + ]]) + matches('^:aaaaaaaa [ ]*$', eval('nvim_get_current_line()')) + eq({6, 13}, eval('nvim_win_get_cursor(0)')) + feed([[<C-\><C-N>]]) + screen:expect([[ + {7: 1 } | + {7: 2 } | + {7: 3 } | + {7: 4 } | + {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | + {7: 6 }:aaaaaaaa ^ {2: } | + | + ]]) + eq({6, 12}, eval('nvim_win_get_cursor(0)')) + end) end) diff --git a/test/functional/terminal/edit_spec.lua b/test/functional/terminal/edit_spec.lua index fabc5524ed..aeb4b7cc2e 100644 --- a/test/functional/terminal/edit_spec.lua +++ b/test/functional/terminal/edit_spec.lua @@ -3,7 +3,7 @@ local screen = require('test.functional.ui.screen') local curbufmeths = helpers.curbufmeths local curwinmeths = helpers.curwinmeths -local nvim_dir = helpers.nvim_dir +local testprg = helpers.testprg local command = helpers.command local funcs = helpers.funcs local meths = helpers.meths @@ -21,7 +21,7 @@ describe(':edit term://*', function() before_each(function() clear() - meths.set_option('shell', nvim_dir .. '/shell-test') + meths.set_option('shell', testprg('shell-test')) meths.set_option('shellcmdflag', 'EXE') end) @@ -36,6 +36,7 @@ describe(':edit term://*', function() end) it("runs TermOpen early enough to set buffer-local 'scrollback'", function() + if helpers.pending_win32(pending) then return end local columns, lines = 20, 4 local scr = get_screen(columns, lines) local rep = 97 diff --git a/test/functional/terminal/ex_terminal_spec.lua b/test/functional/terminal/ex_terminal_spec.lua index 065dd72485..23b69319f0 100644 --- a/test/functional/terminal/ex_terminal_spec.lua +++ b/test/functional/terminal/ex_terminal_spec.lua @@ -2,7 +2,7 @@ local helpers = require('test.functional.helpers')(after_each) local Screen = require('test.functional.ui.screen') local assert_alive = helpers.assert_alive local clear, poke_eventloop, nvim = helpers.clear, helpers.poke_eventloop, helpers.nvim -local nvim_dir, source, eq = helpers.nvim_dir, helpers.source, helpers.eq +local testprg, source, eq = helpers.testprg, helpers.source, helpers.eq local feed = helpers.feed local feed_command, eval = helpers.feed_command, helpers.eval local funcs = helpers.funcs @@ -28,7 +28,7 @@ describe(':terminal', function() echomsg "msg3" ]]) -- Invoke a command that emits frequent terminal activity. - feed([[:terminal "]]..nvim_dir..[[/shell-test" REP 9999 !terminal_output!<cr>]]) + feed([[:terminal "]]..testprg('shell-test')..[[" REP 9999 !terminal_output!<cr>]]) feed([[<C-\><C-N>]]) poke_eventloop() -- Wait for some terminal activity. @@ -131,7 +131,7 @@ describe(':terminal (with fake shell)', function() screen = Screen.new(50, 4) screen:attach({rgb=false}) -- shell-test.c is a fake shell that prints its arguments and exits. - nvim('set_option', 'shell', nvim_dir..'/shell-test') + nvim('set_option', 'shell', testprg('shell-test')) nvim('set_option', 'shellcmdflag', 'EXE') end) @@ -142,6 +142,7 @@ describe(':terminal (with fake shell)', function() end it('with no argument, acts like termopen()', function() + if helpers.pending_win32(pending) then return end terminal_with_fake_shell() retry(nil, 4 * screen.timeout, function() screen:expect([[ @@ -165,7 +166,8 @@ describe(':terminal (with fake shell)', function() end) it("with no argument, but 'shell' has arguments, acts like termopen()", function() - nvim('set_option', 'shell', nvim_dir..'/shell-test -t jeff') + if helpers.pending_win32(pending) then return end + nvim('set_option', 'shell', testprg('shell-test')..' -t jeff') terminal_with_fake_shell() screen:expect([[ ^jeff $ | @@ -176,6 +178,7 @@ describe(':terminal (with fake shell)', function() end) it('executes a given command through the shell', function() + if helpers.pending_win32(pending) then return end command('set shellxquote=') -- win: avoid extra quotes terminal_with_fake_shell('echo hi') screen:expect([[ @@ -187,7 +190,8 @@ describe(':terminal (with fake shell)', function() end) it("executes a given command through the shell, when 'shell' has arguments", function() - nvim('set_option', 'shell', nvim_dir..'/shell-test -t jeff') + if helpers.pending_win32(pending) then return end + nvim('set_option', 'shell', testprg('shell-test')..' -t jeff') command('set shellxquote=') -- win: avoid extra quotes terminal_with_fake_shell('echo hi') screen:expect([[ @@ -199,6 +203,7 @@ describe(':terminal (with fake shell)', function() end) it('allows quotes and slashes', function() + if helpers.pending_win32(pending) then return end command('set shellxquote=') -- win: avoid extra quotes terminal_with_fake_shell([[echo 'hello' \ "world"]]) screen:expect([[ @@ -235,6 +240,7 @@ describe(':terminal (with fake shell)', function() end) it('works with :find', function() + if helpers.pending_win32(pending) then return end terminal_with_fake_shell() screen:expect([[ ^ready $ | @@ -253,6 +259,7 @@ describe(':terminal (with fake shell)', function() end) it('works with gf', function() + if helpers.pending_win32(pending) then return end command('set shellxquote=') -- win: avoid extra quotes terminal_with_fake_shell([[echo "scripts/shadacat.py"]]) retry(nil, 4 * screen.timeout, function() diff --git a/test/functional/terminal/helpers.lua b/test/functional/terminal/helpers.lua index d909888613..bcfd3559e6 100644 --- a/test/functional/terminal/helpers.lua +++ b/test/functional/terminal/helpers.lua @@ -3,11 +3,13 @@ -- operate on the _host_ session, _not_ the child session. local helpers = require('test.functional.helpers')(nil) local Screen = require('test.functional.ui.screen') -local nvim_dir = helpers.nvim_dir +local testprg = helpers.testprg local feed_command, nvim = helpers.feed_command, helpers.nvim local function feed_data(data) - nvim('set_var', 'term_data', data) + -- A string containing NUL bytes is not converted to a Blob when + -- calling nvim_set_var() API, so convert it using Lua instead. + nvim('exec_lua', 'vim.g.term_data = ...', {data}) nvim('command', 'call jobsend(b:terminal_job_id, term_data)') end @@ -35,7 +37,7 @@ local function clear_attrs() feed_termcode('[0;10m') end local function enable_mouse() feed_termcode('[?1002h') end local function disable_mouse() feed_termcode('[?1002l') end -local default_command = '["'..nvim_dir..'/tty-test'..'"]' +local default_command = '["'..testprg('tty-test')..'"]' local function screen_setup(extra_rows, command, cols, opts) extra_rows = extra_rows and extra_rows or 0 @@ -94,7 +96,7 @@ local function screen_setup(extra_rows, command, cols, opts) table.insert(expected, '{3:-- TERMINAL --}' .. ((' '):rep(cols - 14))) screen:expect(table.concat(expected, '|\n')..'|') else - -- This eval also acts as a wait(). + -- This eval also acts as a poke_eventloop(). if 0 == nvim('eval', "exists('b:terminal_job_id')") then error("terminal job failed to start") end diff --git a/test/functional/terminal/highlight_spec.lua b/test/functional/terminal/highlight_spec.lua index 8d3f0218af..28ca07d815 100644 --- a/test/functional/terminal/highlight_spec.lua +++ b/test/functional/terminal/highlight_spec.lua @@ -2,7 +2,7 @@ local helpers = require('test.functional.helpers')(after_each) local Screen = require('test.functional.ui.screen') local thelpers = require('test.functional.terminal.helpers') local feed, clear, nvim = helpers.feed, helpers.clear, helpers.nvim -local nvim_dir, command = helpers.nvim_dir, helpers.command +local testprg, command = helpers.testprg, helpers.command local nvim_prog_abs = helpers.nvim_prog_abs local eq, eval = helpers.eq, helpers.eval local funcs = helpers.funcs @@ -28,7 +28,7 @@ describe(':terminal highlight', function() [11] = {background = 11}, }) screen:attach({rgb=false}) - command('enew | call termopen(["'..nvim_dir..'/tty-test"])') + command(("enew | call termopen(['%s'])"):format(testprg('tty-test'))) feed('i') screen:expect([[ tty ready | @@ -173,7 +173,7 @@ describe(':terminal highlight forwarding', function() [4] = {{foreground = tonumber('0xff8000')}, {}}, }) screen:attach() - command('enew | call termopen(["'..nvim_dir..'/tty-test"])') + command(("enew | call termopen(['%s'])"):format(testprg('tty-test'))) feed('i') screen:expect([[ tty ready | @@ -214,7 +214,7 @@ describe(':terminal highlight with custom palette', function() clear() screen = Screen.new(50, 7) screen:set_default_attr_ids({ - [1] = {foreground = tonumber('0x123456')}, -- no fg_indexed when overriden + [1] = {foreground = tonumber('0x123456')}, -- no fg_indexed when overridden [2] = {foreground = 12}, [3] = {bold = true, reverse = true}, [5] = {background = 11}, @@ -225,7 +225,7 @@ describe(':terminal highlight with custom palette', function() }) screen:attach({rgb=true}) nvim('set_var', 'terminal_color_3', '#123456') - command('enew | call termopen(["'..nvim_dir..'/tty-test"])') + command(("enew | call termopen(['%s'])"):format(testprg('tty-test'))) feed('i') screen:expect([[ tty ready | @@ -304,10 +304,17 @@ describe('synIDattr()', function() eq('79', eval('synIDattr(hlID("Keyword"), "fg")')) end) - it('returns "1" if group has "strikethrough" attribute', function() - eq('', eval('synIDattr(hlID("Normal"), "strikethrough")')) - eq('1', eval('synIDattr(hlID("Keyword"), "strikethrough")')) - eq('1', eval('synIDattr(hlID("Keyword"), "strikethrough", "gui")')) + it('returns "1" if group has given highlight attribute', function() + local hl_attrs = { + 'underline', 'undercurl', 'underdouble', 'underdotted', 'underdashed', 'strikethrough' + } + for _,hl_attr in ipairs(hl_attrs) do + local context = 'using ' .. hl_attr .. ' attr' + command('highlight Keyword cterm=' .. hl_attr .. ' gui=' .. hl_attr) + eq('', eval('synIDattr(hlID("Normal"), "'.. hl_attr .. '")'), context) + eq('1', eval('synIDattr(hlID("Keyword"), "' .. hl_attr .. '")'), context) + eq('1', eval('synIDattr(hlID("Keyword"), "' .. hl_attr .. '", "gui")'), context) + end end) end) diff --git a/test/functional/terminal/mouse_spec.lua b/test/functional/terminal/mouse_spec.lua index 780a0b9b5a..6e2c851df7 100644 --- a/test/functional/terminal/mouse_spec.lua +++ b/test/functional/terminal/mouse_spec.lua @@ -1,7 +1,7 @@ local helpers = require('test.functional.helpers')(after_each) local thelpers = require('test.functional.terminal.helpers') local clear, eq, eval = helpers.clear, helpers.eq, helpers.eval -local feed, nvim = helpers.feed, helpers.nvim +local feed, nvim, command = helpers.feed, helpers.nvim, helpers.command local feed_data = thelpers.feed_data describe(':terminal mouse', function() @@ -10,9 +10,9 @@ describe(':terminal mouse', function() before_each(function() clear() nvim('set_option', 'statusline', '==========') - nvim('command', 'highlight StatusLine cterm=NONE') - nvim('command', 'highlight StatusLineNC cterm=NONE') - nvim('command', 'highlight VertSplit cterm=NONE') + command('highlight StatusLine cterm=NONE') + command('highlight StatusLineNC cterm=NONE') + command('highlight VertSplit cterm=NONE') screen = thelpers.screen_setup() local lines = {} for i = 1, 30 do @@ -38,6 +38,26 @@ describe(':terminal mouse', function() eq('nt', eval('mode(1)')) end) + it('will exit focus and trigger Normal mode mapping on mouse click', function() + command('let g:got_leftmouse = 0') + command('nnoremap <LeftMouse> <Cmd>let g:got_leftmouse = 1<CR>') + eq('t', eval('mode(1)')) + eq(0, eval('g:got_leftmouse')) + feed('<LeftMouse>') + eq('nt', eval('mode(1)')) + eq(1, eval('g:got_leftmouse')) + end) + + it('will exit focus and trigger Normal mode mapping on mouse click with modifier', function() + command('let g:got_ctrl_leftmouse = 0') + command('nnoremap <C-LeftMouse> <Cmd>let g:got_ctrl_leftmouse = 1<CR>') + eq('t', eval('mode(1)')) + eq(0, eval('g:got_ctrl_leftmouse')) + feed('<C-LeftMouse>') + eq('nt', eval('mode(1)')) + eq(1, eval('g:got_ctrl_leftmouse')) + end) + it('will exit focus on <C-\\> + mouse-scroll', function() eq('t', eval('mode(1)')) feed('<C-\\>') @@ -180,7 +200,7 @@ describe(':terminal mouse', function() it('will forward mouse clicks to the program with the correct even if set nu', function() if helpers.pending_win32(pending) then return end - nvim('command', 'set number') + command('set number') -- When the display area such as a number is clicked, it returns to the -- normal mode. feed('<LeftMouse><3,0>') diff --git a/test/functional/terminal/scrollback_spec.lua b/test/functional/terminal/scrollback_spec.lua index 11bdc73a47..b491cb2735 100644 --- a/test/functional/terminal/scrollback_spec.lua +++ b/test/functional/terminal/scrollback_spec.lua @@ -2,10 +2,11 @@ local Screen = require('test.functional.ui.screen') local helpers = require('test.functional.helpers')(after_each) local thelpers = require('test.functional.terminal.helpers') local clear, eq, curbuf = helpers.clear, helpers.eq, helpers.curbuf -local feed, nvim_dir, feed_command = helpers.feed, helpers.nvim_dir, helpers.feed_command +local feed, testprg, feed_command = helpers.feed, helpers.testprg, helpers.feed_command local iswin = helpers.iswin local eval = helpers.eval local command = helpers.command +local matches = helpers.matches local poke_eventloop = helpers.poke_eventloop local retry = helpers.retry local curbufmeths = helpers.curbufmeths @@ -345,10 +346,11 @@ end) describe(':terminal prints more lines than the screen height and exits', function() it('will push extra lines to scrollback', function() + if helpers.pending_win32(pending) then return end clear() local screen = Screen.new(30, 7) screen:attach({rgb=false}) - feed_command('call termopen(["'..nvim_dir..'/tty-test", "10"]) | startinsert') + feed_command(("call termopen(['%s', '10']) | startinsert"):format(testprg('tty-test'))) poke_eventloop() screen:expect([[ line6 | @@ -380,7 +382,7 @@ describe("'scrollback' option", function() local function set_fake_shell() -- shell-test.c is a fake shell that prints its arguments and exits. - nvim('set_option', 'shell', nvim_dir..'/shell-test') + nvim('set_option', 'shell', testprg('shell-test')) nvim('set_option', 'shellcmdflag', 'EXE') end @@ -401,7 +403,7 @@ describe("'scrollback' option", function() end curbufmeths.set_option('scrollback', 0) - feed_data(nvim_dir..'/shell-test REP 31 line'..(iswin() and '\r' or '\n')) + feed_data(('%s REP 31 line%s'):format(testprg('shell-test'), iswin() and '\r' or '\n')) screen:expect{any='30: line '} retry(nil, nil, function() expect_lines(7) end) end) @@ -421,7 +423,7 @@ describe("'scrollback' option", function() -- Wait for prompt. screen:expect{any='%$'} - feed_data(nvim_dir.."/shell-test REP 31 line"..(iswin() and '\r' or '\n')) + feed_data(('%s REP 31 line%s'):format(testprg('shell-test'), iswin() and '\r' or '\n')) screen:expect{any='30: line '} retry(nil, nil, function() expect_lines(33, 2) end) @@ -434,7 +436,7 @@ describe("'scrollback' option", function() -- 'scrollback' option is synchronized with the internal sb_buffer. command('sleep 100m') - feed_data(nvim_dir.."/shell-test REP 41 line"..(iswin() and '\r' or '\n')) + feed_data(('%s REP 41 line%s'):format(testprg('shell-test'), iswin() and '\r' or '\n')) if iswin() then screen:expect{grid=[[ 37: line | @@ -459,8 +461,36 @@ describe("'scrollback' option", function() expect_lines(58) -- Verify off-screen state - eq((iswin() and '36: line' or '35: line'), eval("getline(line('w0') - 1)")) - eq((iswin() and '27: line' or '26: line'), eval("getline(line('w0') - 10)")) + matches((iswin() and '^36: line[ ]*$' or '^35: line[ ]*$'), eval("getline(line('w0') - 1)")) + matches((iswin() and '^27: line[ ]*$' or '^26: line[ ]*$'), eval("getline(line('w0') - 10)")) + end) + + it('deletes extra lines immediately', function() + -- Scrollback is 10 on screen_setup + local screen = thelpers.screen_setup(nil, nil, 30) + local lines = {} + for i = 1, 30 do + table.insert(lines, 'line'..tostring(i)) + end + table.insert(lines, '') + feed_data(lines) + screen:expect([[ + line26 | + line27 | + line28 | + line29 | + line30 | + {1: } | + {3:-- TERMINAL --} | + ]]) + local term_height = 6 -- Actual terminal screen height, not the scrollback + -- Initial + local scrollback = curbufmeths.get_option('scrollback') + eq(scrollback + term_height, eval('line("$")')) + -- Reduction + scrollback = scrollback - 2 + curbufmeths.set_option('scrollback', scrollback) + eq(scrollback + term_height, eval('line("$")')) end) it('defaults to 10000 in :terminal buffers', function() @@ -576,6 +606,7 @@ describe("pending scrollback line handling", function() end) it("does not crash after nvim_buf_call #14891", function() + if helpers.pending_win32(pending) then return end exec_lua [[ local a = vim.api local bufnr = a.nvim_create_buf(false, true) diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua index faf44fa01d..dd88379344 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -14,12 +14,13 @@ local feed_command = helpers.feed_command local feed_data = thelpers.feed_data local clear = helpers.clear local command = helpers.command -local nvim_dir = helpers.nvim_dir +local testprg = helpers.testprg local retry = helpers.retry local nvim_prog = helpers.nvim_prog local nvim_set = helpers.nvim_set local ok = helpers.ok local read_file = helpers.read_file +local funcs = helpers.funcs if helpers.pending_win32(pending) then return end @@ -85,6 +86,24 @@ describe('TUI', function() assert_alive() end) + it('resize at startup', function() + -- Issues: #17285 #15044 #11330 + screen:try_resize(50, 10) + feed_command([[call termopen([v:progpath, '--clean', '--cmd', 'let start = reltime() | while v:true | if reltimefloat(reltime(start)) > 2 | break | endif | endwhile']) | sleep 500m | vs new]]) + screen:expect([[ + {1: } │ | + {4:~ }│{4:~ }| + {4:~ }│{4:~ }| + {4:~ }│{4:~ }| + {4:~ }│{4:~ }| + {4:~ }│{5:[No Name] 0,0-1 All}| + {4:~ }│ | + {5:new }{MATCH:<.*[/\]nvim }| + | + {3:-- TERMINAL --} | + ]]) + end) + it('accepts resize while pager is active', function() child_session:request("nvim_command", [[ set more @@ -214,7 +233,7 @@ describe('TUI', function() ]]) end) - it('interprets ESC+key as ALT chord', function() + it('interprets ESC+key as ALT chord in i_CTRL-V', function() -- Vim represents ALT/META by setting the "high bit" of the modified key: -- ALT+j inserts "ê". Nvim does not (#3982). feed_data('i\022\027j') @@ -229,6 +248,38 @@ describe('TUI', function() ]]) end) + it('interprets <Esc>[27u as <Esc>', function() + feed_command('nnoremap <M-;> <Nop>') + feed_command('nnoremap <Esc> AESC<Esc>') + feed_command('nnoremap ; Asemicolon<Esc>') + feed_data('\027[27u;') + screen:expect([[ + ESCsemicolo{1:n} | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] [+] }| + | + {3:-- TERMINAL --} | + ]]) + -- <Esc>; should be recognized as <M-;> when <M-;> is mapped + feed_data('\027;') + screen:expect_unchanged() + end) + + it('interprets <Esc><Nul> as <M-C-Space> #17198', function() + feed_data('i\022\027\000') + screen:expect([[ + <M-C-Space>{1: } | + {4:~ }| + {4:~ }| + {4:~ }| + {5:[No Name] [+] }| + {3:-- INSERT --} | + {3:-- TERMINAL --} | + ]]) + end) + it('accepts ASCII control sequences', function() feed_data('i') feed_data('\022\007') -- ctrl+g @@ -247,6 +298,179 @@ describe('TUI', function() ]], attrs) end) + it('accepts keypad keys from kitty keyboard protocol #19180', function() + feed_data('i') + feed_data(funcs.nr2char(57399)) -- KP_0 + feed_data(funcs.nr2char(57400)) -- KP_1 + feed_data(funcs.nr2char(57401)) -- KP_2 + feed_data(funcs.nr2char(57402)) -- KP_3 + feed_data(funcs.nr2char(57403)) -- KP_4 + feed_data(funcs.nr2char(57404)) -- KP_5 + feed_data(funcs.nr2char(57405)) -- KP_6 + feed_data(funcs.nr2char(57406)) -- KP_7 + feed_data(funcs.nr2char(57407)) -- KP_8 + feed_data(funcs.nr2char(57408)) -- KP_9 + feed_data(funcs.nr2char(57409)) -- KP_DECIMAL + feed_data(funcs.nr2char(57410)) -- KP_DIVIDE + feed_data(funcs.nr2char(57411)) -- KP_MULTIPLY + feed_data(funcs.nr2char(57412)) -- KP_SUBTRACT + feed_data(funcs.nr2char(57413)) -- KP_ADD + feed_data(funcs.nr2char(57414)) -- KP_ENTER + feed_data(funcs.nr2char(57415)) -- KP_EQUAL + screen:expect([[ + 0123456789./*-+ | + ={1: } | + {4:~ }| + {4:~ }| + {5:[No Name] [+] }| + {3:-- INSERT --} | + {3:-- TERMINAL --} | + ]]) + feed_data(funcs.nr2char(57417)) -- KP_LEFT + screen:expect([[ + 0123456789./*-+ | + {1:=} | + {4:~ }| + {4:~ }| + {5:[No Name] [+] }| + {3:-- INSERT --} | + {3:-- TERMINAL --} | + ]]) + feed_data(funcs.nr2char(57418)) -- KP_RIGHT + screen:expect([[ + 0123456789./*-+ | + ={1: } | + {4:~ }| + {4:~ }| + {5:[No Name] [+] }| + {3:-- INSERT --} | + {3:-- TERMINAL --} | + ]]) + feed_data(funcs.nr2char(57419)) -- KP_UP + screen:expect([[ + 0{1:1}23456789./*-+ | + = | + {4:~ }| + {4:~ }| + {5:[No Name] [+] }| + {3:-- INSERT --} | + {3:-- TERMINAL --} | + ]]) + feed_data(funcs.nr2char(57420)) -- KP_DOWN + screen:expect([[ + 0123456789./*-+ | + ={1: } | + {4:~ }| + {4:~ }| + {5:[No Name] [+] }| + {3:-- INSERT --} | + {3:-- TERMINAL --} | + ]]) + feed_data(funcs.nr2char(57425)) -- KP_INSERT + screen:expect([[ + 0123456789./*-+ | + ={1: } | + {4:~ }| + {4:~ }| + {5:[No Name] [+] }| + {3:-- REPLACE --} | + {3:-- TERMINAL --} | + ]]) + feed_data('\027[27u') -- ESC + screen:expect([[ + 0123456789./*-+ | + {1:=} | + {4:~ }| + {4:~ }| + {5:[No Name] [+] }| + | + {3:-- TERMINAL --} | + ]]) + feed_data('\027[57417;5u') -- CTRL + KP_LEFT + screen:expect([[ + {1:0}123456789./*-+ | + = | + {4:~ }| + {4:~ }| + {5:[No Name] [+] }| + | + {3:-- TERMINAL --} | + ]]) + feed_data('\027[57418;2u') -- SHIFT + KP_RIGHT + screen:expect([[ + 0123456789{1:.}/*-+ | + = | + {4:~ }| + {4:~ }| + {5:[No Name] [+] }| + | + {3:-- TERMINAL --} | + ]]) + feed_data(funcs.nr2char(57426)) -- KP_DELETE + screen:expect([[ + 0123456789{1:/}*-+ | + = | + {4:~ }| + {4:~ }| + {5:[No Name] [+] }| + | + {3:-- TERMINAL --} | + ]]) + feed_data(funcs.nr2char(57423)) -- KP_HOME + screen:expect([[ + {1:0}123456789/*-+ | + = | + {4:~ }| + {4:~ }| + {5:[No Name] [+] }| + | + {3:-- TERMINAL --} | + ]]) + feed_data(funcs.nr2char(57424)) -- KP_END + screen:expect([[ + 0123456789/*-{1:+} | + = | + {4:~ }| + {4:~ }| + {5:[No Name] [+] }| + | + {3:-- TERMINAL --} | + ]]) + feed_data(':tab split\r:tabnew\r') + feed_data(':highlight Tabline ctermbg=NONE ctermfg=NONE cterm=underline\r') + local attrs = screen:get_default_attr_ids() + attrs[11] = {underline = true} + screen:expect([[ + {11: + [No Name] + [No Name] }{3: [No Name] }{1: }{11:X}| + {1: } | + {4:~ }| + {4:~ }| + {5:[No Name] }| + | + {3:-- TERMINAL --} | + ]], attrs) + feed_data('\027[57421;5u') -- CTRL + KP_PAGE_UP + screen:expect([[ + {11: + [No Name] }{3: + [No Name] }{11: [No Name] }{1: }{11:X}| + 0123456789/*-{1:+} | + = | + {4:~ }| + {5:[No Name] [+] }| + | + {3:-- TERMINAL --} | + ]], attrs) + feed_data('\027[57422;5u') -- CTRL + KP_PAGE_DOWN + screen:expect([[ + {11: + [No Name] + [No Name] }{3: [No Name] }{1: }{11:X}| + {1: } | + {4:~ }| + {4:~ }| + {5:[No Name] }| + | + {3:-- TERMINAL --} | + ]], attrs) + end) + it('paste: Insert mode', function() -- "bracketed paste" feed_data('i""\027i\027[200~') @@ -271,7 +495,7 @@ describe('TUI', function() {3:-- TERMINAL --} | ]]) feed_data('\027[201~') -- End paste. - feed_data('\027\000') -- ESC: go to Normal mode. + feed_data('\027[27u') -- ESC: go to Normal mode. wait_for_mode('n') screen:expect([[ "pasted from termina{1:l}" | @@ -323,7 +547,7 @@ describe('TUI', function() feed_data('just paste it™') feed_data('\027[201~') screen:expect{grid=[[ - thisjust paste it™{1:3} is here | + thisjust paste it{1:™}3 is here | | {4:~ }| {4:~ }| @@ -353,7 +577,7 @@ describe('TUI', function() return end feed_data(':set statusline=^^^^^^^\n') - feed_data(':terminal '..nvim_dir..'/tty-test\n') + feed_data(':terminal '..testprg('tty-test')..'\n') feed_data('i') screen:expect{grid=[[ tty ready | @@ -379,7 +603,7 @@ describe('TUI', function() end) it('paste: normal-mode (+CRLF #10872)', function() - feed_data(':set ruler') + feed_data(':set ruler | echo') wait_for_mode('c') feed_data('\n') wait_for_mode('n') @@ -423,13 +647,13 @@ describe('TUI', function() expect_child_buf_lines(expected_crlf) feed_data('u') expect_child_buf_lines({''}) + feed_data(':echo') + wait_for_mode('c') + feed_data('\n') + wait_for_mode('n') -- CRLF input feed_data('\027[200~'..table.concat(expected_lf,'\r\n')..'\027[201~') - screen:expect{ - grid=expected_grid1:gsub( - ':set ruler *', - '3 fewer lines; before #1 0 seconds ago '), - attr_ids=expected_attr} + screen:expect{grid=expected_grid1, attr_ids=expected_attr} expect_child_buf_lines(expected_crlf) end) @@ -453,7 +677,7 @@ describe('TUI', function() {3:-- TERMINAL --} | ]]} -- Dot-repeat/redo. - feed_data('\027\000') + feed_data('\027[27u') wait_for_mode('n') feed_data('.') screen:expect{grid=[[ @@ -499,7 +723,7 @@ describe('TUI', function() vim.paste = function(lines, phase) error("fake fail") end ]], {}) -- Prepare something for dot-repeat/redo. - feed_data('ifoo\n\027\000') + feed_data('ifoo\n\027[27u') wait_for_mode('n') screen:expect{grid=[[ foo | @@ -541,7 +765,7 @@ describe('TUI', function() {3:-- TERMINAL --} | ]]} -- Editor should still work after failed/drained paste. - feed_data('ityped input...\027\000') + feed_data('ityped input...\027[27u') screen:expect{grid=[[ foo | foo | @@ -575,7 +799,7 @@ describe('TUI', function() vim.paste = function(lines, phase) return false end ]], {}) feed_data('\027[200~line A\nline B\n\027[201~') - feed_data('ifoo\n\027\000') + feed_data('ifoo\n\027[27u') expect_child_buf_lines({'foo',''}) end) @@ -669,7 +893,7 @@ describe('TUI', function() {3:-- INSERT --} | {3:-- TERMINAL --} | ]]) - feed_data('\027\000') -- ESC: go to Normal mode. + feed_data('\027[27u') -- ESC: go to Normal mode. wait_for_mode('n') -- Dot-repeat/redo. feed_data('.') @@ -871,7 +1095,7 @@ describe('TUI', function() feed_data(':set statusline=^^^^^^^\n') feed_data(':set termguicolors\n') - feed_data(':terminal '..nvim_dir..'/tty-test\n') + feed_data(':terminal '..testprg('tty-test')..'\n') -- Depending on platform the above might or might not fit in the cmdline -- so clear it for consistent behavior. feed_data(':\027') @@ -1098,7 +1322,7 @@ describe('TUI FocusGained/FocusLost', function() end) it('in terminal-mode', function() - feed_data(':set shell='..nvim_dir..'/shell-test\n') + feed_data(':set shell='..testprg('shell-test')..'\n') feed_data(':set noshowmode laststatus=0\n') feed_data(':terminal\n') diff --git a/test/functional/terminal/window_spec.lua b/test/functional/terminal/window_spec.lua index 9f278fd157..0d3295cf32 100644 --- a/test/functional/terminal/window_spec.lua +++ b/test/functional/terminal/window_spec.lua @@ -18,6 +18,7 @@ describe(':terminal window', function() end) it('sets topline correctly #8556', function() + if helpers.pending_win32(pending) then return end -- Test has hardcoded assumptions of dimensions. eq(7, eval('&lines')) feed_data('\n\n\n') -- Add blank lines. diff --git a/test/functional/treesitter/highlight_spec.lua b/test/functional/treesitter/highlight_spec.lua index 175525b3f2..5ec0a8a060 100644 --- a/test/functional/treesitter/highlight_spec.lua +++ b/test/functional/treesitter/highlight_spec.lua @@ -23,7 +23,7 @@ local hl_query = [[ "enum" @type "extern" @type - (string_literal) @string + (string_literal) @string.nonexistent-specializer-for-string.should-fallback-to-string (number_literal) @number (char_literal) @string @@ -613,4 +613,131 @@ describe('treesitter highlighting', function() [12] = {background = Screen.colors.Red, bold = true, foreground = Screen.colors.Grey100}; }} end) + + it("allows to use captures with dots (don't use fallback when specialization of foo exists)", function() + if pending_c_parser(pending) then return end + + insert([[ + char* x = "Will somebody ever read this?"; + ]]) + + screen:expect{grid=[[ + char* x = "Will somebody ever read this?"; | + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]} + + exec_lua [[ + local parser = vim.treesitter.get_parser(0, "c", {}) + local highlighter = vim.treesitter.highlighter + highlighter.hl_map['foo.bar'] = 'Type' + highlighter.hl_map['foo'] = 'String' + test_hl = highlighter.new(parser, {queries = {c = "(primitive_type) @foo.bar (string_literal) @foo"}}) + ]] + + screen:expect{grid=[[ + {3:char}* x = {5:"Will somebody ever read this?"}; | + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]} + end) + + it("supports conceal attribute", function() + if pending_c_parser(pending) then return end + insert(hl_text) + + -- conceal can be empty or a single cchar. + exec_lua [=[ + vim.opt.cole = 2 + local parser = vim.treesitter.get_parser(0, "c") + test_hl = vim.treesitter.highlighter.new(parser, {queries = {c = [[ + ("static" @keyword + (set! conceal "R")) + + ((identifier) @Identifier + (set! conceal "") + (eq? @Identifier "lstate")) + ]]}}) + ]=] + + screen:expect{grid=[[ + /// Schedule Lua callback on main loop's event queue | + {4:R} int nlua_schedule(lua_State *const ) | + { | + if (lua_type(, 1) != LUA_TFUNCTION | + || != ) { | + lua_pushliteral(, "vim.schedule: expected function"); | + return lua_error(); | + } | + | + LuaRef cb = nlua_ref(, 1); | + | + multiqueue_put(main_loop.events, nlua_schedule_event, | + 1, (void *)(ptrdiff_t)cb); | + return 0; | + ^} | + {1:~ }| + {1:~ }| + | + ]]} + end) + + it("hl_map has the correct fallback behavior", function() + exec_lua [[ + local hl_map = vim.treesitter.highlighter.hl_map + hl_map["foo"] = 1 + hl_map["foo.bar"] = 2 + hl_map["foo.bar.baz"] = 3 + + assert(hl_map["foo"] == 1) + assert(hl_map["foo.a.b.c.d"] == 1) + assert(hl_map["foo.bar"] == 2) + assert(hl_map["foo.bar.a.b.c.d"] == 2) + assert(hl_map["foo.bar.baz"] == 3) + assert(hl_map["foo.bar.baz.d"] == 3) + + hl_map["FOO"] = 1 + hl_map["FOO.BAR"] = 2 + assert(hl_map["FOO.BAR.BAZ"] == 2) + + hl_map["foo.missing.exists"] = 3 + assert(hl_map["foo.missing"] == 1) + assert(hl_map["foo.missing.exists"] == 3) + assert(hl_map["foo.missing.exists.bar"] == 3) + assert(hl_map["total.nonsense.but.a.lot.of.dots"] == nil) + -- It will not perform a second look up of this variable but return a sentinel value + assert(hl_map["total.nonsense.but.a.lot.of.dots"] == "__notfound") + ]] + + end) end) diff --git a/test/functional/treesitter/language_spec.lua b/test/functional/treesitter/language_spec.lua index afb17dd2cf..30585be328 100644 --- a/test/functional/treesitter/language_spec.lua +++ b/test/functional/treesitter/language_spec.lua @@ -2,6 +2,7 @@ local helpers = require('test.functional.helpers')(after_each) local clear = helpers.clear local eq = helpers.eq +local command = helpers.command local exec_lua = helpers.exec_lua local pcall_err = helpers.pcall_err local matches = helpers.matches @@ -67,5 +68,16 @@ describe('treesitter API', function() end eq({true,true}, {has_named,has_anonymous}) end) + + it('checks if vim.treesitter.get_parser tries to create a new parser on filetype change', function () + if pending_c_parser(pending) then return end + command("set filetype=c") + -- Should not throw an error when filetype is c + eq('c', exec_lua("return vim.treesitter.get_parser(0):lang()")) + command("set filetype=borklang") + -- Should throw an error when filetype changes to borklang + eq("Error executing lua: .../language.lua:0: no parser for 'borklang' language, see :help treesitter-parsers", + pcall_err(exec_lua, "new_parser = vim.treesitter.get_parser(0)")) + end) end) diff --git a/test/functional/treesitter/parser_spec.lua b/test/functional/treesitter/parser_spec.lua index 911fa017ab..7f3b0e770a 100644 --- a/test/functional/treesitter/parser_spec.lua +++ b/test/functional/treesitter/parser_spec.lua @@ -167,6 +167,27 @@ void ui_refresh(void) eq('variable', ret) end) + it("supports caching queries", function() + local long_query = query:rep(100) + local function q(n) + return exec_lua ([[ + local query, n = ... + local before = vim.loop.hrtime() + for i=1,n,1 do + cquery = vim.treesitter.parse_query("c", ...) + end + local after = vim.loop.hrtime() + return after - before + ]], long_query, n) + end + + local firstrun = q(1) + local manyruns = q(100) + + -- First run should be at least 4x slower. + assert(400 * manyruns < firstrun, ('firstrun: %d ms, manyruns: %d ms'):format(firstrun / 1000, manyruns / 1000)) + end) + it('support query and iter by capture', function() insert(test_text) @@ -264,6 +285,25 @@ end]] eq(true, result) end) + it('support getting empty text if node range is zero width', function() + local text = [[ +```lua +{} +```]] + insert(text) + local result = exec_lua([[ + local fake_node = {} + function fake_node:start() + return 1, 0, 7 + end + function fake_node:end_() + return 1, 0, 7 + end + return vim.treesitter.get_node_text(fake_node, 0) == '' + ]]) + eq(true, result) + end) + it('can match special regex characters like \\ * + ( with `vim-match?`', function() insert('char* astring = "\\n"; (1 + 1) * 2 != 2;') @@ -645,6 +685,21 @@ int x = INT_MAX; -- READ_STRING_OK(x, y) (char_u *)read_string((x), (size_t)(y)) }, get_ranges()) end) + + it("should not inject bad languages", function() + if helpers.pending_win32(pending) then return end + exec_lua([=[ + vim.treesitter.add_directive("inject-bad!", function(match, _, _, pred, metadata) + metadata.language = "{" + metadata.combined = true + metadata.content = pred[2] + end) + + parser = vim.treesitter.get_parser(0, "c", { + injections = { + c = "(preproc_function_def value: ((preproc_arg) @_a (#inject-bad! @_a)))"}}) + ]=]) + end) end) describe("when using the offset directive", function() diff --git a/test/functional/ui/bufhl_spec.lua b/test/functional/ui/bufhl_spec.lua index 16ed3b9486..7c0831bd09 100644 --- a/test/functional/ui/bufhl_spec.lua +++ b/test/functional/ui/bufhl_spec.lua @@ -564,6 +564,16 @@ describe('Buffer highlighting', function() ]] clear_namespace(id, 0, -1) + screen:expect{grid=[[ + fooba^r | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]} set_extmark(id, 0, 0, { end_line = 0, @@ -755,12 +765,26 @@ describe('Buffer highlighting', function() -- TODO: only a virtual text from the same ns curretly overrides -- an existing virtual text. We might add a prioritation system. set_virtual_text(id1, 0, s1, {}) - eq({{1, 0, 0, { priority = 0, virt_text = s1}}}, get_extmarks(id1, {0,0}, {0, -1}, {details=true})) + eq({{1, 0, 0, { + priority = 0, + virt_text = s1, + -- other details + right_gravity = true, + virt_text_pos = 'eol', + virt_text_hide = false, + }}}, get_extmarks(id1, {0,0}, {0, -1}, {details=true})) -- TODO: is this really valid? shouldn't the max be line_count()-1? local lastline = line_count() set_virtual_text(id1, line_count(), s2, {}) - eq({{3, lastline, 0, { priority = 0, virt_text = s2}}}, get_extmarks(id1, {lastline,0}, {lastline, -1}, {details=true})) + eq({{3, lastline, 0, { + priority = 0, + virt_text = s2, + -- other details + right_gravity = true, + virt_text_pos = 'eol', + virt_text_hide = false, + }}}, get_extmarks(id1, {lastline,0}, {lastline, -1}, {details=true})) eq({}, get_extmarks(id1, {lastline+9000,0}, {lastline+9000, -1}, {})) end) diff --git a/test/functional/ui/cmdline_highlight_spec.lua b/test/functional/ui/cmdline_highlight_spec.lua index 9c746b99bd..384761ab17 100644 --- a/test/functional/ui/cmdline_highlight_spec.lua +++ b/test/functional/ui/cmdline_highlight_spec.lua @@ -33,7 +33,7 @@ before_each(function() let g:NUM_LVLS = 4 function Redraw() mode - return '' + return "\<Ignore>" endfunction let g:id = '' cnoremap <expr> {REDRAW} Redraw() @@ -42,7 +42,7 @@ before_each(function() let Cb = g:Nvim_color_input{g:id} let out = input({'prompt': ':', 'highlight': Cb}) let g:out{id} = out - return (a:do_return ? out : '') + return (a:do_return ? out : "\<Ignore>") endfunction nnoremap <expr> {PROMPT} DoPrompt(0) cnoremap <expr> {PROMPT} DoPrompt(1) @@ -410,7 +410,7 @@ describe('Command-line coloring', function() end) it('stops executing callback after a number of errors', function() set_color_cb('SplittedMultibyteStart') - start_prompt('let x = "«»«»«»«»«»"\n') + start_prompt('let x = "«»«»«»«»«»"') screen:expect([[ {EOB:~ }| {EOB:~ }| @@ -419,7 +419,7 @@ describe('Command-line coloring', function() :let x = " | {ERR:E5405: Chunk 0 start 10 splits multibyte}| {ERR: character} | - ^:let x = "«»«»«»«»«»" | + :let x = "«»«»«»«»«»"^ | ]]) feed('\n') screen:expect([[ @@ -432,6 +432,7 @@ describe('Command-line coloring', function() {EOB:~ }| | ]]) + feed('\n') eq('let x = "«»«»«»«»«»"', meths.get_var('out')) local msg = '\nE5405: Chunk 0 start 10 splits multibyte character' eq(msg:rep(1), funcs.execute('messages')) @@ -474,14 +475,14 @@ describe('Command-line coloring', function() ]]) feed('\n') screen:expect([[ - | + ^ | {EOB:~ }| {EOB:~ }| {EOB:~ }| {EOB:~ }| {EOB:~ }| {EOB:~ }| - ^:echo 42 | + :echo 42 | ]]) feed('\n') eq('echo 42', meths.get_var('out')) diff --git a/test/functional/ui/cmdline_spec.lua b/test/functional/ui/cmdline_spec.lua index ad23402ff9..eb0a14da31 100644 --- a/test/functional/ui/cmdline_spec.lua +++ b/test/functional/ui/cmdline_spec.lua @@ -4,6 +4,9 @@ local clear, feed = helpers.clear, helpers.feed local source = helpers.source local command = helpers.command local assert_alive = helpers.assert_alive +local uname = helpers.uname +local eval = helpers.eval +local eq = helpers.eq local function new_screen(opt) local screen = Screen.new(25, 5) @@ -16,6 +19,8 @@ local function new_screen(opt) [5] = {bold = true, foreground = Screen.colors.SeaGreen4}, [6] = {foreground = Screen.colors.Magenta}, [7] = {bold = true, foreground = Screen.colors.Brown}, + [8] = {background = Screen.colors.LightGrey}, + [9] = {bold = true}, }) return screen end @@ -824,7 +829,7 @@ describe('cmdline redraw', function() end) it('with <Cmd>', function() - if 'openbsd' == helpers.uname() then + if string.find(uname(), 'bsd') then pending('FIXME #10804') end command('cmap a <Cmd>call sin(0)<CR>') -- no-op @@ -845,6 +850,37 @@ describe('cmdline redraw', function() 456789^ | ]], unchanged=true} end) + + it('after pressing Ctrl-C in cmdwin in Visual mode #18967', function() + screen:try_resize(40, 10) + command('set cmdwinheight=3') + feed('q:iabc<Esc>vhh') + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }| + {2:[No Name] }| + {1::}^a{8:bc} | + {1:~ }| + {1:~ }| + {3:[Command Line] }| + {9:-- VISUAL --} | + ]]) + feed('<C-C>') + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }| + {2:[No Name] }| + {1::}a{8:bc} | + {1:~ }| + {1:~ }| + {3:[Command Line] }| + :^abc | + ]]) + end) end) describe("cmdline height", function() @@ -857,3 +893,228 @@ describe("cmdline height", function() assert_alive() end) end) + +describe('cmdheight=0', function() + local screen + before_each(function() + clear() + screen = Screen.new(25, 5) + screen:attach() + end) + + it("with cmdheight=1 noruler laststatus=2", function() + command("set cmdheight=1 noruler laststatus=2") + screen:expect{grid=[[ + ^ | + ~ | + ~ | + [No Name] | + | + ]]} + end) + + it("with cmdheight=0 noruler laststatus=2", function() + command("set cmdheight=0 noruler laststatus=2") + screen:expect{grid=[[ + ^ | + ~ | + ~ | + ~ | + [No Name] | + ]]} + end) + + it("with cmdheight=0 ruler laststatus=0", function() + command("set cmdheight=0 ruler laststatus=0") + screen:expect{grid=[[ + ^ | + ~ | + ~ | + ~ | + ~ | + ]]} + end) + + it("with cmdheight=0 ruler laststatus=0", function() + command("set cmdheight=0 noruler laststatus=0 showmode") + feed('i') + screen:expect{grid=[[ + ^ | + ~ | + ~ | + ~ | + ~ | + ]], showmode={}} + feed('<Esc>') + eq(0, eval('&cmdheight')) + end) + + it("with cmdheight=0 ruler rulerformat laststatus=0", function() + command("set cmdheight=0 noruler laststatus=0 rulerformat=%l,%c%= showmode") + feed('i') + screen:expect{grid=[[ + ^ | + ~ | + ~ | + ~ | + ~ | + ]], showmode={}} + feed('<Esc>') + eq(0, eval('&cmdheight')) + end) + + it("with showmode", function() + command("set cmdheight=1 noruler laststatus=0 showmode") + feed('i') + screen:expect{grid=[[ + ^ | + ~ | + ~ | + ~ | + -- INSERT -- | + ]]} + feed('<Esc>') + eq(1, eval('&cmdheight')) + end) + + it("when using command line", function() + command("set cmdheight=0 noruler laststatus=0") + feed(':') + screen:expect{grid=[[ + | + ~ | + ~ | + ~ | + :^ | + ]]} + eq(1, eval('&cmdheight')) + feed('<cr>') + screen:expect{grid=[[ + ^ | + ~ | + ~ | + ~ | + ~ | + ]], showmode={}} + eq(0, eval('&cmdheight')) + end) + + it("when using input()", function() + command("set cmdheight=0 noruler laststatus=0") + feed(':call input("foo >")<cr>') + screen:expect{grid=[[ + | + ~ | + ~ | + ~ | + foo >^ | + ]]} + eq(1, eval('&cmdheight')) + feed('<cr>') + screen:expect{grid=[[ + ^ | + ~ | + ~ | + ~ | + ~ | + ]], showmode={}} + eq(0, eval('&cmdheight')) + end) + + it("with winbar and splits", function() + command("set cmdheight=0 noruler laststatus=3 winbar=foo") + feed(':split<CR>') + screen:expect{grid=[[ + foo | + | + E36: Not enough room | + Press ENTER or type comma| + nd to continue^ | + ]]} + feed('<CR>') + screen:expect{grid=[[ + foo | + ^ | + ~ | + ~ | + [No Name] | + ]]} + feed(':') + screen:expect{grid=[[ + foo | + | + ~ | + [No Name] | + :^ | + ]]} + feed('<Esc>') + screen:expect{grid=[[ + foo | + ^ | + ~ | + ~ | + [No Name] | + ]], showmode={}} + eq(0, eval('&cmdheight')) + + assert_alive() + end) + + it("when macro with lastline", function() + command("set cmdheight=0 display=lastline") + feed('qq') + screen:expect{grid=[[ + ^ | + ~ | + ~ | + ~ | + ~ | + ]], showmode={}} + feed('q') + screen:expect{grid=[[ + ^ | + ~ | + ~ | + ~ | + ~ | + ]], showmode={}} + end) + + it("when substitute text", function() + command("set cmdheight=0 noruler laststatus=3") + feed('ifoo<ESC>') + screen:expect{grid=[[ + fo^o | + ~ | + ~ | + ~ | + [No Name] [+] | + ]]} + + feed(':%s/foo/bar/gc<CR>') + screen:expect{grid=[[ + foo | + ~ | + ~ | + [No Name] [+] | + replace wi...q/l/^E/^Y)?^ | + ]]} + + feed('y') + screen:expect{grid=[[ + ^bar | + ~ | + ~ | + ~ | + [No Name] [+] | + ]]} + + assert_alive() + end) + + it("when window resize", function() + command("set cmdheight=0") + feed('<C-w>+') + eq(0, eval('&cmdheight')) + end) +end) diff --git a/test/functional/ui/cursor_spec.lua b/test/functional/ui/cursor_spec.lua index 03cd4bfd06..a1423c98a8 100644 --- a/test/functional/ui/cursor_spec.lua +++ b/test/functional/ui/cursor_spec.lua @@ -215,7 +215,7 @@ describe('ui/cursor', function() m.hl_id = 60 m.attr = {background = Screen.colors.DarkGray} end - if m.id_lm then m.id_lm = 61 end + if m.id_lm then m.id_lm = 65 end end -- Assert the new expectation. diff --git a/test/functional/ui/decorations_spec.lua b/test/functional/ui/decorations_spec.lua index 1575cab591..9af5d386db 100644 --- a/test/functional/ui/decorations_spec.lua +++ b/test/functional/ui/decorations_spec.lua @@ -30,6 +30,7 @@ describe('decorations providers', function() [11] = {foreground = Screen.colors.Red, background = tonumber('0x005028')}; [12] = {foreground = tonumber('0x990000')}; [13] = {background = Screen.colors.LightBlue}; + [14] = {background = Screen.colors.WebGray, foreground = Screen.colors.DarkBlue}; } end) @@ -404,6 +405,50 @@ describe('decorations providers', function() | ]]} end) + + it('can create and remove signs when CursorMoved autocommand validates botline #18661', function() + exec_lua([[ + local lines = {} + for i = 1, 200 do + lines[i] = 'hello' .. tostring(i) + end + vim.api.nvim_buf_set_lines(0, 0, -1, false, lines) + ]]) + setup_provider([[ + local function on_do(kind, winid, bufnr, topline, botline_guess) + if kind == 'win' then + if topline < 100 and botline_guess > 100 then + vim.api.nvim_buf_set_extmark(bufnr, ns1, 99, -1, { sign_text = 'X' }) + else + vim.api.nvim_buf_clear_namespace(bufnr, ns1, 0, -1) + end + end + end + ]]) + command([[autocmd CursorMoved * call line('w$')]]) + meths.win_set_cursor(0, {100, 0}) + screen:expect([[ + {14: }hello97 | + {14: }hello98 | + {14: }hello99 | + X ^hello100 | + {14: }hello101 | + {14: }hello102 | + {14: }hello103 | + | + ]]) + meths.win_set_cursor(0, {1, 0}) + screen:expect([[ + ^hello1 | + hello2 | + hello3 | + hello4 | + hello5 | + hello6 | + hello7 | + | + ]]) + end) end) describe('extmark decorations', function() @@ -437,6 +482,8 @@ describe('extmark decorations', function() [22] = {foreground = tonumber('0xb20000'), background = tonumber('0xf13f3f')}; [23] = {foreground = Screen.colors.Magenta1, background = Screen.colors.LightGrey}; [24] = {bold = true}; + [25] = {background = Screen.colors.LightRed}; + [26] = {background=Screen.colors.DarkGrey, foreground=Screen.colors.LightGrey}; } ns = meths.create_namespace 'test' @@ -456,6 +503,31 @@ for _,item in ipairs(items) do end end]] + it('empty virtual text at eol should not break colorcolumn #17860', function() + insert(example_text) + feed('gg') + command('set colorcolumn=40') + screen:expect([[ + ^for _,item in ipairs(items) do {25: } | + local text, hl_id_cell, count = unp{25:a}ck(item) | + if hl_id_cell ~= nil then {25: } | + hl_id = hl_id_cell {25: } | + end {25: } | + for _ = 1, (count or 1) do {25: } | + local cell = line[colpos] {25: } | + cell.text = text {25: } | + cell.hl_id = hl_id {25: } | + colpos = colpos+1 {25: } | + end {25: } | + end {25: } | + {1:~ }| + {1:~ }| + | + ]]) + meths.buf_set_extmark(0, ns, 4, 0, { virt_text={{''}}, virt_text_pos='eol'}) + screen:expect_unchanged() + end) + it('can have virtual text of overlay position', function() insert(example_text) feed 'gg' @@ -471,7 +543,9 @@ end]] -- can "float" beyond end of line meths.buf_set_extmark(0, ns, 5, 28, { virt_text={{'loopy', 'ErrorMsg'}}, virt_text_pos='overlay'}) -- bound check: right edge of window - meths.buf_set_extmark(0, ns, 2, 26, { virt_text={{'bork bork bork ' }, {'bork bork bork', 'ErrorMsg'}}, virt_text_pos='overlay'}) + meths.buf_set_extmark(0, ns, 2, 26, { virt_text={{'bork bork bork '}, {'bork bork bork', 'ErrorMsg'}}, virt_text_pos='overlay'}) + -- empty virt_text should not change anything + meths.buf_set_extmark(0, ns, 6, 16, { virt_text={{''}}, virt_text_pos='overlay'}) screen:expect{grid=[[ ^for _,item in ipairs(items) do | @@ -621,6 +695,8 @@ end]] meths.buf_set_extmark(0, ns, 2, 10, { virt_text={{'Much', 'ErrorMsg'}}, virt_text_win_col=31, hl_mode='blend'}) meths.buf_set_extmark(0, ns, 3, 15, { virt_text={{'Error', 'ErrorMsg'}}, virt_text_win_col=31, hl_mode='blend'}) meths.buf_set_extmark(0, ns, 7, 21, { virt_text={{'-', 'NonText'}}, virt_text_win_col=4, hl_mode='blend'}) + -- empty virt_text should not change anything + meths.buf_set_extmark(0, ns, 8, 0, { virt_text={{''}}, virt_text_win_col=14, hl_mode='blend'}) screen:expect{grid=[[ ^for _,item in ipairs(items) do | @@ -698,7 +774,7 @@ end]] ]]} end) - it('can have virtual text which combines foreground and backround groups', function() + it('can have virtual text which combines foreground and background groups', function() screen:set_default_attr_ids { [1] = {bold=true, foreground=Screen.colors.Blue}; [2] = {background = tonumber('0x123456'), foreground = tonumber('0xbbbbbb')}; @@ -789,6 +865,20 @@ end]] ]]} helpers.assert_alive() end) + + it('conceal #19007', function() + screen:try_resize(50, 5) + insert('foo\n') + command('let &conceallevel=2') + meths.buf_set_extmark(0, ns, 0, 0, {end_col=0, end_row=2, conceal='X'}) + screen:expect([[ + {26:X} | + ^ | + {1:~ }| + {1:~ }| + | + ]]) + end) end) describe('decorations: virtual lines', function() @@ -1111,6 +1201,27 @@ if (h->n_buckets < new_n_buckets) { // expand ]]} end) + it('does not cause syntax ml_get error at the end of a buffer #17816', function() + command([[syntax region foo keepend start='^foo' end='^$']]) + command('syntax sync minlines=100') + insert('foo') + meths.buf_set_extmark(0, ns, 0, 0, {virt_lines = {{{'bar', 'Comment'}}}}) + screen:expect([[ + fo^o | + {6:bar} | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]) + end) + it('works with a block scrolling up', function() screen:try_resize(30, 7) insert("aa\nbb\ncc\ndd\nee\nff\ngg\nhh") @@ -1362,3 +1473,372 @@ if (h->n_buckets < new_n_buckets) { // expand end) end) + +describe('decorations: signs', function() + local screen, ns + before_each(function() + clear() + screen = Screen.new(50, 10) + screen:attach() + screen:set_default_attr_ids { + [1] = {foreground = Screen.colors.Blue4, background = Screen.colors.Grey}; + [2] = {foreground = Screen.colors.Blue1, bold = true}; + [3] = {background = Screen.colors.Yellow1, foreground = Screen.colors.Blue1}; + } + + ns = meths.create_namespace 'test' + meths.win_set_option(0, 'signcolumn', 'auto:9') + end) + + local example_text = [[ +l1 +l2 +l3 +l4 +l5 +]] + + it('can add a single sign (no end row)', function() + insert(example_text) + feed 'gg' + + meths.buf_set_extmark(0, ns, 1, -1, {sign_text='S'}) + + screen:expect{grid=[[ + {1: }^l1 | + S l2 | + {1: }l3 | + {1: }l4 | + {1: }l5 | + {1: } | + {2:~ }| + {2:~ }| + {2:~ }| + | + ]]} + + end) + + it('can add a single sign (with end row)', function() + insert(example_text) + feed 'gg' + + meths.buf_set_extmark(0, ns, 1, -1, {sign_text='S', end_row=1}) + + screen:expect{grid=[[ + {1: }^l1 | + S l2 | + {1: }l3 | + {1: }l4 | + {1: }l5 | + {1: } | + {2:~ }| + {2:~ }| + {2:~ }| + | + ]]} + + end) + + it('can add multiple signs (single extmark)', function() + pending('TODO(lewis6991): Support ranged signs') + insert(example_text) + feed 'gg' + + meths.buf_set_extmark(0, ns, 1, -1, {sign_text='S', end_row = 2}) + + screen:expect{grid=[[ + {1: }^l1 | + S l2 | + S l3 | + {1: }l4 | + {1: }l5 | + {1: } | + {2:~ }| + {2:~ }| + {2:~ }| + | + ]]} + + end) + + it('can add multiple signs (multiple extmarks)', function() + pending('TODO(lewis6991): Support ranged signs') + insert(example_text) + feed'gg' + + meths.buf_set_extmark(0, ns, 1, -1, {sign_text='S1'}) + meths.buf_set_extmark(0, ns, 3, -1, {sign_text='S2', end_row = 4}) + + screen:expect{grid=[[ + {1: }^l1 | + S1l2 | + {1: }l3 | + S2l4 | + S2l5 | + {1: } | + {2:~ }| + {2:~ }| + {2:~ }| + | + ]]} + + end) + + it('can add multiple signs (multiple extmarks) 2', function() + insert(example_text) + feed 'gg' + + meths.buf_set_extmark(0, ns, 1, -1, {sign_text='S1'}) + meths.buf_set_extmark(0, ns, 1, -1, {sign_text='S2'}) + + screen:expect{grid=[[ + {1: }^l1 | + S2S1l2 | + {1: }l3 | + {1: }l4 | + {1: }l5 | + {1: } | + {2:~ }| + {2:~ }| + {2:~ }| + | + ]]} + + -- TODO(lewis6991): Support ranged signs + -- meths.buf_set_extmark(1, ns, 1, -1, {sign_text='S3', end_row = 2}) + + -- screen:expect{grid=[[ + -- {1: }^l1 | + -- S3S2S1l2 | + -- S3{1: }l3 | + -- {1: }l4 | + -- {1: }l5 | + -- {1: } | + -- {2:~ }| + -- {2:~ }| + -- {2:~ }| + -- | + -- ]]} + + end) + + it('can add multiple signs (multiple extmarks) 3', function() + pending('TODO(lewis6991): Support ranged signs') + + insert(example_text) + feed 'gg' + + meths.buf_set_extmark(0, ns, 1, -1, {sign_text='S1', end_row=2}) + meths.buf_set_extmark(0, ns, 2, -1, {sign_text='S2', end_row=3}) + + screen:expect{grid=[[ + {1: }^l1 | + S1{1: }l2 | + S2S1l3 | + S2{1: }l4 | + {1: }l5 | + {1: } | + {2:~ }| + {2:~ }| + {2:~ }| + | + ]]} + end) + + it('can add multiple signs (multiple extmarks) 4', function() + insert(example_text) + feed 'gg' + + meths.buf_set_extmark(0, ns, 0, -1, {sign_text='S1', end_row=0}) + meths.buf_set_extmark(0, ns, 1, -1, {sign_text='S2', end_row=1}) + + screen:expect{grid=[[ + S1^l1 | + S2l2 | + {1: }l3 | + {1: }l4 | + {1: }l5 | + {1: } | + {2:~ }| + {2:~ }| + {2:~ }| + | + ]]} + end) + + it('works with old signs', function() + insert(example_text) + feed 'gg' + + helpers.command('sign define Oldsign text=x') + helpers.command([[exe 'sign place 42 line=2 name=Oldsign buffer=' . bufnr('')]]) + + meths.buf_set_extmark(0, ns, 0, -1, {sign_text='S1'}) + meths.buf_set_extmark(0, ns, 1, -1, {sign_text='S2'}) + meths.buf_set_extmark(0, ns, 0, -1, {sign_text='S4'}) + meths.buf_set_extmark(0, ns, 2, -1, {sign_text='S5'}) + + screen:expect{grid=[[ + S4S1^l1 | + S2x l2 | + S5{1: }l3 | + {1: }l4 | + {1: }l5 | + {1: } | + {2:~ }| + {2:~ }| + {2:~ }| + | + ]]} + end) + + it('works with old signs (with range)', function() + pending('TODO(lewis6991): Support ranged signs') + insert(example_text) + feed 'gg' + + helpers.command('sign define Oldsign text=x') + helpers.command([[exe 'sign place 42 line=2 name=Oldsign buffer=' . bufnr('')]]) + + meths.buf_set_extmark(0, ns, 0, -1, {sign_text='S1'}) + meths.buf_set_extmark(0, ns, 1, -1, {sign_text='S2'}) + meths.buf_set_extmark(0, ns, 0, -1, {sign_text='S3', end_row = 4}) + meths.buf_set_extmark(0, ns, 0, -1, {sign_text='S4'}) + meths.buf_set_extmark(0, ns, 2, -1, {sign_text='S5'}) + + screen:expect{grid=[[ + S3S4S1^l1 | + S2S3x l2 | + S5S3{1: }l3 | + S3{1: }l4 | + S3{1: }l5 | + {1: } | + {2:~ }| + {2:~ }| + {2:~ }| + | + ]]} + end) + + it('can add a ranged sign (with start out of view)', function() + pending('TODO(lewis6991): Support ranged signs') + + insert(example_text) + command 'set signcolumn=yes:2' + feed 'gg' + feed '2<C-e>' + + meths.buf_set_extmark(0, ns, 1, -1, {sign_text='X', end_row=3}) + + screen:expect{grid=[[ + X {1: }^l3 | + X {1: }l4 | + {1: }l5 | + {1: } | + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + | + ]]} + + end) + + it('can add lots of signs', function() + screen:try_resize(40, 10) + command 'normal 10oa b c d e f g h' + + for i = 1, 10 do + meths.buf_set_extmark(0, ns, i, 0, { end_col = 1, hl_group='Todo' }) + meths.buf_set_extmark(0, ns, i, 2, { end_col = 3, hl_group='Todo' }) + meths.buf_set_extmark(0, ns, i, 4, { end_col = 5, hl_group='Todo' }) + meths.buf_set_extmark(0, ns, i, 6, { end_col = 7, hl_group='Todo' }) + meths.buf_set_extmark(0, ns, i, 8, { end_col = 9, hl_group='Todo' }) + meths.buf_set_extmark(0, ns, i, 10, { end_col = 11, hl_group='Todo' }) + meths.buf_set_extmark(0, ns, i, 12, { end_col = 13, hl_group='Todo' }) + meths.buf_set_extmark(0, ns, i, 14, { end_col = 15, hl_group='Todo' }) + meths.buf_set_extmark(0, ns, i, -1, { sign_text='W' }) + meths.buf_set_extmark(0, ns, i, -1, { sign_text='X' }) + meths.buf_set_extmark(0, ns, i, -1, { sign_text='Y' }) + meths.buf_set_extmark(0, ns, i, -1, { sign_text='Z' }) + end + + screen:expect{grid=[[ + X Y Z W {3:a} {3:b} {3:c} {3:d} {3:e} {3:f} {3:g} {3:h} | + X Y Z W {3:a} {3:b} {3:c} {3:d} {3:e} {3:f} {3:g} {3:h} | + X Y Z W {3:a} {3:b} {3:c} {3:d} {3:e} {3:f} {3:g} {3:h} | + X Y Z W {3:a} {3:b} {3:c} {3:d} {3:e} {3:f} {3:g} {3:h} | + X Y Z W {3:a} {3:b} {3:c} {3:d} {3:e} {3:f} {3:g} {3:h} | + X Y Z W {3:a} {3:b} {3:c} {3:d} {3:e} {3:f} {3:g} {3:h} | + X Y Z W {3:a} {3:b} {3:c} {3:d} {3:e} {3:f} {3:g} {3:h} | + X Y Z W {3:a} {3:b} {3:c} {3:d} {3:e} {3:f} {3:g} {3:h} | + X Y Z W {3:a} {3:b} {3:c} {3:d} {3:e} {3:f} {3:g} {3:^h} | + | + ]]} + end) + +end) + +describe('decorations: virt_text', function() + local screen + + before_each(function() + clear() + screen = Screen.new(50, 10) + screen:attach() + screen:set_default_attr_ids { + [1] = {foreground = Screen.colors.Brown}; + [2] = {foreground = Screen.colors.Fuchsia}; + [3] = {bold = true, foreground = Screen.colors.Blue1}; + } + end) + + it('avoids regression in #17638', function() + exec_lua[[ + vim.wo.number = true + vim.wo.relativenumber = true + ]] + + command 'normal 4ohello' + command 'normal aVIRTUAL' + + local ns = meths.create_namespace('test') + + meths.buf_set_extmark(0, ns, 2, 0, { + virt_text = {{"hello", "String"}}, + virt_text_win_col = 20, + }) + + screen:expect{grid=[[ + {1: 4 } | + {1: 3 }hello | + {1: 2 }hello {2:hello} | + {1: 1 }hello | + {1:5 }helloVIRTUA^L | + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + | + ]]} + + -- Trigger a screen update + feed('k') + + screen:expect{grid=[[ + {1: 3 } | + {1: 2 }hello | + {1: 1 }hello {2:hello} | + {1:4 }hell^o | + {1: 1 }helloVIRTUAL | + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + | + ]]} + end) + +end) diff --git a/test/functional/ui/diff_spec.lua b/test/functional/ui/diff_spec.lua index bd2692d19a..5d056cd6c3 100644 --- a/test/functional/ui/diff_spec.lua +++ b/test/functional/ui/diff_spec.lua @@ -6,7 +6,7 @@ local clear = helpers.clear local command = helpers.command local insert = helpers.insert local write_file = helpers.write_file -local source = helpers.source +local exec = helpers.exec describe('Diff mode screen', function() local fname = 'Xtest-functional-diff-screen-1' @@ -57,40 +57,40 @@ describe('Diff mode screen', function() feed(':set diffopt=filler<cr>') screen:expect([[ - {1: }{2:------------------}{3:│}{1: }{4:0 }| - {1: }^1 {3:│}{1: }1 | - {1: }2 {3:│}{1: }2 | - {1: }3 {3:│}{1: }3 | - {1: }4 {3:│}{1: }4 | - {1: }5 {3:│}{1: }5 | - {1: }6 {3:│}{1: }6 | - {1:+ }{5:+-- 4 lines: 7···}{3:│}{1:+ }{5:+-- 4 lines: 7··}| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| + {1: }{2:------------------}│{1: }{4:0 }| + {1: }^1 │{1: }1 | + {1: }2 │{1: }2 | + {1: }3 │{1: }3 | + {1: }4 │{1: }4 | + {1: }5 │{1: }5 | + {1: }6 │{1: }6 | + {1:+ }{5:+-- 4 lines: 7···}│{1:+ }{5:+-- 4 lines: 7··}| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| {7:<onal-diff-screen-1 }{3:<l-diff-screen-1.2 }| :set diffopt=filler | ]]) feed(':set diffopt+=internal<cr>') screen:expect([[ - {1: }{2:------------------}{3:│}{1: }{4:0 }| - {1: }^1 {3:│}{1: }1 | - {1: }2 {3:│}{1: }2 | - {1: }3 {3:│}{1: }3 | - {1: }4 {3:│}{1: }4 | - {1: }5 {3:│}{1: }5 | - {1: }6 {3:│}{1: }6 | - {1:+ }{5:+-- 4 lines: 7···}{3:│}{1:+ }{5:+-- 4 lines: 7··}| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| + {1: }{2:------------------}│{1: }{4:0 }| + {1: }^1 │{1: }1 | + {1: }2 │{1: }2 | + {1: }3 │{1: }3 | + {1: }4 │{1: }4 | + {1: }5 │{1: }5 | + {1: }6 │{1: }6 | + {1:+ }{5:+-- 4 lines: 7···}│{1:+ }{5:+-- 4 lines: 7··}| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| {7:<onal-diff-screen-1 }{3:<l-diff-screen-1.2 }| :set diffopt+=internal | ]]) @@ -103,40 +103,40 @@ describe('Diff mode screen', function() feed(":set diffopt=filler<cr>") screen:expect([[ - {1: }{4:^0 }{3:│}{1: }{2:-----------------}| - {1: }1 {3:│}{1: }1 | - {1: }2 {3:│}{1: }2 | - {1: }3 {3:│}{1: }3 | - {1: }4 {3:│}{1: }4 | - {1: }5 {3:│}{1: }5 | - {1: }6 {3:│}{1: }6 | - {1:+ }{5:+-- 4 lines: 7···}{3:│}{1:+ }{5:+-- 4 lines: 7··}| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| + {1: }{4:^0 }│{1: }{2:-----------------}| + {1: }1 │{1: }1 | + {1: }2 │{1: }2 | + {1: }3 │{1: }3 | + {1: }4 │{1: }4 | + {1: }5 │{1: }5 | + {1: }6 │{1: }6 | + {1:+ }{5:+-- 4 lines: 7···}│{1:+ }{5:+-- 4 lines: 7··}| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| {7:<onal-diff-screen-1 }{3:<l-diff-screen-1.2 }| :set diffopt=filler | ]]) feed(":set diffopt+=internal<cr>") screen:expect([[ - {1: }{4:^0 }{3:│}{1: }{2:-----------------}| - {1: }1 {3:│}{1: }1 | - {1: }2 {3:│}{1: }2 | - {1: }3 {3:│}{1: }3 | - {1: }4 {3:│}{1: }4 | - {1: }5 {3:│}{1: }5 | - {1: }6 {3:│}{1: }6 | - {1:+ }{5:+-- 4 lines: 7···}{3:│}{1:+ }{5:+-- 4 lines: 7··}| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| + {1: }{4:^0 }│{1: }{2:-----------------}| + {1: }1 │{1: }1 | + {1: }2 │{1: }2 | + {1: }3 │{1: }3 | + {1: }4 │{1: }4 | + {1: }5 │{1: }5 | + {1: }6 │{1: }6 | + {1:+ }{5:+-- 4 lines: 7···}│{1:+ }{5:+-- 4 lines: 7··}| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| {7:<onal-diff-screen-1 }{3:<l-diff-screen-1.2 }| :set diffopt+=internal | ]]) @@ -149,53 +149,53 @@ describe('Diff mode screen', function() feed(":set diffopt=filler<cr>") screen:expect([[ - {1:+ }{5:^+-- 4 lines: 1···}{3:│}{1:+ }{5:+-- 4 lines: 1··}| - {1: }5 {3:│}{1: }5 | - {1: }6 {3:│}{1: }6 | - {1: }7 {3:│}{1: }7 | - {1: }8 {3:│}{1: }8 | - {1: }9 {3:│}{1: }9 | - {1: }10 {3:│}{1: }10 | - {1: }{2:------------------}{3:│}{1: }{4:11 }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| + {1:+ }{5:^+-- 4 lines: 1···}│{1:+ }{5:+-- 4 lines: 1··}| + {1: }5 │{1: }5 | + {1: }6 │{1: }6 | + {1: }7 │{1: }7 | + {1: }8 │{1: }8 | + {1: }9 │{1: }9 | + {1: }10 │{1: }10 | + {1: }{2:------------------}│{1: }{4:11 }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| {7:<onal-diff-screen-1 }{3:<l-diff-screen-1.2 }| :set diffopt=filler | ]]) feed(":set diffopt+=internal<cr>") screen:expect([[ - {1:+ }{5:^+-- 4 lines: 1···}{3:│}{1:+ }{5:+-- 4 lines: 1··}| - {1: }5 {3:│}{1: }5 | - {1: }6 {3:│}{1: }6 | - {1: }7 {3:│}{1: }7 | - {1: }8 {3:│}{1: }8 | - {1: }9 {3:│}{1: }9 | - {1: }10 {3:│}{1: }10 | - {1: }{2:------------------}{3:│}{1: }{4:11 }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| + {1:+ }{5:^+-- 4 lines: 1···}│{1:+ }{5:+-- 4 lines: 1··}| + {1: }5 │{1: }5 | + {1: }6 │{1: }6 | + {1: }7 │{1: }7 | + {1: }8 │{1: }8 | + {1: }9 │{1: }9 | + {1: }10 │{1: }10 | + {1: }{2:------------------}│{1: }{4:11 }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| {7:<onal-diff-screen-1 }{3:<l-diff-screen-1.2 }| :set diffopt+=internal | ]]) screen:try_resize(40, 9) screen:expect([[ - {1:+ }{5:^+-- 4 lines: 1···}{3:│}{1:+ }{5:+-- 4 lines: 1··}| - {1: }5 {3:│}{1: }5 | - {1: }6 {3:│}{1: }6 | - {1: }7 {3:│}{1: }7 | - {1: }8 {3:│}{1: }8 | - {1: }9 {3:│}{1: }9 | - {1: }10 {3:│}{1: }10 | + {1:+ }{5:^+-- 4 lines: 1···}│{1:+ }{5:+-- 4 lines: 1··}| + {1: }5 │{1: }5 | + {1: }6 │{1: }6 | + {1: }7 │{1: }7 | + {1: }8 │{1: }8 | + {1: }9 │{1: }9 | + {1: }10 │{1: }10 | {7:<onal-diff-screen-1 }{3:<l-diff-screen-1.2 }| | ]]) @@ -208,53 +208,53 @@ describe('Diff mode screen', function() feed(":set diffopt=filler<cr>") screen:expect([[ - {1:+ }{5:^+-- 4 lines: 1···}{3:│}{1:+ }{5:+-- 4 lines: 1··}| - {1: }5 {3:│}{1: }5 | - {1: }6 {3:│}{1: }6 | - {1: }7 {3:│}{1: }7 | - {1: }8 {3:│}{1: }8 | - {1: }9 {3:│}{1: }9 | - {1: }10 {3:│}{1: }10 | - {1: }{4:11 }{3:│}{1: }{2:-----------------}| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| + {1:+ }{5:^+-- 4 lines: 1···}│{1:+ }{5:+-- 4 lines: 1··}| + {1: }5 │{1: }5 | + {1: }6 │{1: }6 | + {1: }7 │{1: }7 | + {1: }8 │{1: }8 | + {1: }9 │{1: }9 | + {1: }10 │{1: }10 | + {1: }{4:11 }│{1: }{2:-----------------}| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| {7:<onal-diff-screen-1 }{3:<l-diff-screen-1.2 }| :set diffopt=filler | ]]) feed(":set diffopt+=internal<cr>") screen:expect([[ - {1:+ }{5:^+-- 4 lines: 1···}{3:│}{1:+ }{5:+-- 4 lines: 1··}| - {1: }5 {3:│}{1: }5 | - {1: }6 {3:│}{1: }6 | - {1: }7 {3:│}{1: }7 | - {1: }8 {3:│}{1: }8 | - {1: }9 {3:│}{1: }9 | - {1: }10 {3:│}{1: }10 | - {1: }{4:11 }{3:│}{1: }{2:-----------------}| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| + {1:+ }{5:^+-- 4 lines: 1···}│{1:+ }{5:+-- 4 lines: 1··}| + {1: }5 │{1: }5 | + {1: }6 │{1: }6 | + {1: }7 │{1: }7 | + {1: }8 │{1: }8 | + {1: }9 │{1: }9 | + {1: }10 │{1: }10 | + {1: }{4:11 }│{1: }{2:-----------------}| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| {7:<onal-diff-screen-1 }{3:<l-diff-screen-1.2 }| :set diffopt+=internal | ]]) screen:try_resize(40, 9) screen:expect([[ - {1:+ }{5:^+-- 4 lines: 1···}{3:│}{1:+ }{5:+-- 4 lines: 1··}| - {1: }5 {3:│}{1: }5 | - {1: }6 {3:│}{1: }6 | - {1: }7 {3:│}{1: }7 | - {1: }8 {3:│}{1: }8 | - {1: }9 {3:│}{1: }9 | - {1: }10 {3:│}{1: }10 | + {1:+ }{5:^+-- 4 lines: 1···}│{1:+ }{5:+-- 4 lines: 1··}| + {1: }5 │{1: }5 | + {1: }6 │{1: }6 | + {1: }7 │{1: }7 | + {1: }8 │{1: }8 | + {1: }9 │{1: }9 | + {1: }10 │{1: }10 | {7:<onal-diff-screen-1 }{3:<l-diff-screen-1.2 }| | ]]) @@ -267,40 +267,40 @@ describe('Diff mode screen', function() feed(':set diffopt=filler<cr>') screen:expect([[ - {1: }^1 {3:│}{1: }1 | - {1: }2 {3:│}{1: }2 | - {1: }3 {3:│}{1: }3 | - {1: }4 {3:│}{1: }4 | - {1: }{2:------------------}{3:│}{1: }{4:4 }| - {1: }5 {3:│}{1: }5 | - {1: }6 {3:│}{1: }6 | - {1: }7 {3:│}{1: }7 | - {1: }8 {3:│}{1: }8 | - {1: }9 {3:│}{1: }9 | - {1: }10 {3:│}{1: }10 | - {1: }{4:11 }{3:│}{1: }{2:-----------------}| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| + {1: }^1 │{1: }1 | + {1: }2 │{1: }2 | + {1: }3 │{1: }3 | + {1: }4 │{1: }4 | + {1: }{2:------------------}│{1: }{4:4 }| + {1: }5 │{1: }5 | + {1: }6 │{1: }6 | + {1: }7 │{1: }7 | + {1: }8 │{1: }8 | + {1: }9 │{1: }9 | + {1: }10 │{1: }10 | + {1: }{4:11 }│{1: }{2:-----------------}| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| {7:<onal-diff-screen-1 }{3:<l-diff-screen-1.2 }| :set diffopt=filler | ]]) feed(':set diffopt+=internal<cr>') screen:expect([[ - {1: }^1 {3:│}{1: }1 | - {1: }2 {3:│}{1: }2 | - {1: }3 {3:│}{1: }3 | - {1: }4 {3:│}{1: }4 | - {1: }{2:------------------}{3:│}{1: }{4:4 }| - {1: }5 {3:│}{1: }5 | - {1: }6 {3:│}{1: }6 | - {1: }7 {3:│}{1: }7 | - {1: }8 {3:│}{1: }8 | - {1: }9 {3:│}{1: }9 | - {1: }10 {3:│}{1: }10 | - {1: }{4:11 }{3:│}{1: }{2:-----------------}| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| + {1: }^1 │{1: }1 | + {1: }2 │{1: }2 | + {1: }3 │{1: }3 | + {1: }4 │{1: }4 | + {1: }{2:------------------}│{1: }{4:4 }| + {1: }5 │{1: }5 | + {1: }6 │{1: }6 | + {1: }7 │{1: }7 | + {1: }8 │{1: }8 | + {1: }9 │{1: }9 | + {1: }10 │{1: }10 | + {1: }{4:11 }│{1: }{2:-----------------}| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| {7:<onal-diff-screen-1 }{3:<l-diff-screen-1.2 }| :set diffopt+=internal | ]]) @@ -313,40 +313,40 @@ describe('Diff mode screen', function() feed(':set diffopt=filler<cr>') screen:expect([[ - {1: }^1 {3:│}{1: }1 | - {1: }2 {3:│}{1: }2 | - {1: }3 {3:│}{1: }3 | - {1: }4 {3:│}{1: }4 | - {1: }{4:4 }{3:│}{1: }{2:-----------------}| - {1: }5 {3:│}{1: }5 | - {1: }6 {3:│}{1: }6 | - {1: }7 {3:│}{1: }7 | - {1: }8 {3:│}{1: }8 | - {1: }9 {3:│}{1: }9 | - {1: }10 {3:│}{1: }10 | - {1: }{2:------------------}{3:│}{1: }{4:11 }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| + {1: }^1 │{1: }1 | + {1: }2 │{1: }2 | + {1: }3 │{1: }3 | + {1: }4 │{1: }4 | + {1: }{4:4 }│{1: }{2:-----------------}| + {1: }5 │{1: }5 | + {1: }6 │{1: }6 | + {1: }7 │{1: }7 | + {1: }8 │{1: }8 | + {1: }9 │{1: }9 | + {1: }10 │{1: }10 | + {1: }{2:------------------}│{1: }{4:11 }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| {7:<onal-diff-screen-1 }{3:<l-diff-screen-1.2 }| :set diffopt=filler | ]]) feed(':set diffopt+=internal<cr>') screen:expect([[ - {1: }^1 {3:│}{1: }1 | - {1: }2 {3:│}{1: }2 | - {1: }3 {3:│}{1: }3 | - {1: }4 {3:│}{1: }4 | - {1: }{4:4 }{3:│}{1: }{2:-----------------}| - {1: }5 {3:│}{1: }5 | - {1: }6 {3:│}{1: }6 | - {1: }7 {3:│}{1: }7 | - {1: }8 {3:│}{1: }8 | - {1: }9 {3:│}{1: }9 | - {1: }10 {3:│}{1: }10 | - {1: }{2:------------------}{3:│}{1: }{4:11 }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| + {1: }^1 │{1: }1 | + {1: }2 │{1: }2 | + {1: }3 │{1: }3 | + {1: }4 │{1: }4 | + {1: }{4:4 }│{1: }{2:-----------------}| + {1: }5 │{1: }5 | + {1: }6 │{1: }6 | + {1: }7 │{1: }7 | + {1: }8 │{1: }8 | + {1: }9 │{1: }9 | + {1: }10 │{1: }10 | + {1: }{2:------------------}│{1: }{4:11 }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| {7:<onal-diff-screen-1 }{3:<l-diff-screen-1.2 }| :set diffopt+=internal | ]]) @@ -413,40 +413,40 @@ int main(int argc, char **argv) reread() feed(':set diffopt=internal,filler<cr>') screen:expect([[ - {1: }^#include <stdio.h>{3:│}{1: }#include <stdio.h| - {1: } {3:│}{1: } | - {1: }{8:// Frobs foo heart}{3:│}{1: }{8:int fib(int n)}{9: }| - {1: }{4:int frobnitz(int f}{3:│}{1: }{2:-----------------}| - {1: }{ {3:│}{1: }{ | - {1: }{9: i}{8:nt i;}{9: }{3:│}{1: }{9: i}{8:f(n > 2)}{9: }| - {1: }{4: for(i = 0; i <}{3:│}{1: }{2:-----------------}| - {1: } { {3:│}{1: } { | - {1: }{9: }{8:printf("Yo}{3:│}{1: }{9: }{8:return fi}| - {1: }{4: printf("%d}{3:│}{1: }{2:-----------------}| - {1: } } {3:│}{1: } } | - {1: }{2:------------------}{3:│}{1: }{4: return 1; }| - {1: }} {3:│}{1: }} | - {1: } {3:│}{1: } | + {1: }^#include <stdio.h>│{1: }#include <stdio.h| + {1: } │{1: } | + {1: }{8:// Frobs foo heart}│{1: }{8:int fib(int n)}{9: }| + {1: }{4:int frobnitz(int f}│{1: }{2:-----------------}| + {1: }{ │{1: }{ | + {1: }{9: i}{8:nt i;}{9: }│{1: }{9: i}{8:f(n > 2)}{9: }| + {1: }{4: for(i = 0; i <}│{1: }{2:-----------------}| + {1: } { │{1: } { | + {1: }{9: }{8:printf("Yo}│{1: }{9: }{8:return fi}| + {1: }{4: printf("%d}│{1: }{2:-----------------}| + {1: } } │{1: } } | + {1: }{2:------------------}│{1: }{4: return 1; }| + {1: }} │{1: }} | + {1: } │{1: } | {7:<onal-diff-screen-1 }{3:<l-diff-screen-1.2 }| :set diffopt=internal,filler | ]]) feed('G') screen:expect([[ - {1: }{2:------------------}{3:│}{1: }{4:int frobnitz(int }| - {1: }{ {3:│}{1: }{ | - {1: }{9: i}{8:f(n > 1)}{9: }{3:│}{1: }{9: i}{8:nt i;}{9: }| - {1: }{2:------------------}{3:│}{1: }{4: for(i = 0; i }| - {1: } { {3:│}{1: } { | - {1: }{9: }{8:return fac}{3:│}{1: }{9: }{8:printf("%}| - {1: } } {3:│}{1: } } | - {1: }{4: return 1; }{3:│}{1: }{2:-----------------}| - {1: }} {3:│}{1: }} | - {1: } {3:│}{1: } | - {1: }int main(int argc,{3:│}{1: }int main(int argc| - {1: }{ {3:│}{1: }{ | - {1: }{9: frobnitz(f}{8:act}{9:(}{3:│}{1: }{9: frobnitz(f}{8:ib}{9:(}| - {1: }^} {3:│}{1: }} | + {1: }{2:------------------}│{1: }{4:int frobnitz(int }| + {1: }{ │{1: }{ | + {1: }{9: i}{8:f(n > 1)}{9: }│{1: }{9: i}{8:nt i;}{9: }| + {1: }{2:------------------}│{1: }{4: for(i = 0; i }| + {1: } { │{1: } { | + {1: }{9: }{8:return fac}│{1: }{9: }{8:printf("%}| + {1: } } │{1: } } | + {1: }{4: return 1; }│{1: }{2:-----------------}| + {1: }} │{1: }} | + {1: } │{1: } | + {1: }int main(int argc,│{1: }int main(int argc| + {1: }{ │{1: }{ | + {1: }{9: frobnitz(f}{8:act}{9:(}│{1: }{9: frobnitz(f}{8:ib}{9:(}| + {1: }^} │{1: }} | {7:<onal-diff-screen-1 }{3:<l-diff-screen-1.2 }| :set diffopt=internal,filler | ]]) @@ -456,40 +456,40 @@ int main(int argc, char **argv) reread() feed(':set diffopt=internal,filler,algorithm:patience<cr>') screen:expect([[ - {1: }^#include <stdio.h>{3:│}{1: }#include <stdio.h| - {1: } {3:│}{1: } | - {1: }{2:------------------}{3:│}{1: }{4:int fib(int n) }| - {1: }{2:------------------}{3:│}{1: }{4:{ }| - {1: }{2:------------------}{3:│}{1: }{4: if(n > 2) }| - {1: }{2:------------------}{3:│}{1: }{4: { }| - {1: }{2:------------------}{3:│}{1: }{4: return fi}| - {1: }{2:------------------}{3:│}{1: }{4: } }| - {1: }{2:------------------}{3:│}{1: }{4: return 1; }| - {1: }{2:------------------}{3:│}{1: }{4:} }| - {1: }{2:------------------}{3:│}{1: }{4: }| - {1: }// Frobs foo heart{3:│}{1: }// Frobs foo hear| - {1: }int frobnitz(int f{3:│}{1: }int frobnitz(int | - {1: }{ {3:│}{1: }{ | + {1: }^#include <stdio.h>│{1: }#include <stdio.h| + {1: } │{1: } | + {1: }{2:------------------}│{1: }{4:int fib(int n) }| + {1: }{2:------------------}│{1: }{4:{ }| + {1: }{2:------------------}│{1: }{4: if(n > 2) }| + {1: }{2:------------------}│{1: }{4: { }| + {1: }{2:------------------}│{1: }{4: return fi}| + {1: }{2:------------------}│{1: }{4: } }| + {1: }{2:------------------}│{1: }{4: return 1; }| + {1: }{2:------------------}│{1: }{4:} }| + {1: }{2:------------------}│{1: }{4: }| + {1: }// Frobs foo heart│{1: }// Frobs foo hear| + {1: }int frobnitz(int f│{1: }int frobnitz(int | + {1: }{ │{1: }{ | {7:<onal-diff-screen-1 }{3:<l-diff-screen-1.2 }| | ]]) feed('G') screen:expect([[ - {1: } {3:│}{1: } | - {1: }{4:int fact(int n) }{3:│}{1: }{2:-----------------}| - {1: }{4:{ }{3:│}{1: }{2:-----------------}| - {1: }{4: if(n > 1) }{3:│}{1: }{2:-----------------}| - {1: }{4: { }{3:│}{1: }{2:-----------------}| - {1: }{4: return fac}{3:│}{1: }{2:-----------------}| - {1: }{4: } }{3:│}{1: }{2:-----------------}| - {1: }{4: return 1; }{3:│}{1: }{2:-----------------}| - {1: }{4:} }{3:│}{1: }{2:-----------------}| - {1: }{4: }{3:│}{1: }{2:-----------------}| - {1: }int main(int argc,{3:│}{1: }int main(int argc| - {1: }{ {3:│}{1: }{ | - {1: }{9: frobnitz(f}{8:act}{9:(}{3:│}{1: }{9: frobnitz(f}{8:ib}{9:(}| - {1: }^} {3:│}{1: }} | + {1: } │{1: } | + {1: }{4:int fact(int n) }│{1: }{2:-----------------}| + {1: }{4:{ }│{1: }{2:-----------------}| + {1: }{4: if(n > 1) }│{1: }{2:-----------------}| + {1: }{4: { }│{1: }{2:-----------------}| + {1: }{4: return fac}│{1: }{2:-----------------}| + {1: }{4: } }│{1: }{2:-----------------}| + {1: }{4: return 1; }│{1: }{2:-----------------}| + {1: }{4:} }│{1: }{2:-----------------}| + {1: }{4: }│{1: }{2:-----------------}| + {1: }int main(int argc,│{1: }int main(int argc| + {1: }{ │{1: }{ | + {1: }{9: frobnitz(f}{8:act}{9:(}│{1: }{9: frobnitz(f}{8:ib}{9:(}| + {1: }^} │{1: }} | {7:<onal-diff-screen-1 }{3:<l-diff-screen-1.2 }| | ]]) @@ -499,40 +499,40 @@ int main(int argc, char **argv) reread() feed(':set diffopt=internal,filler,algorithm:histogram<cr>') screen:expect([[ - {1: }^#include <stdio.h>{3:│}{1: }#include <stdio.h| - {1: } {3:│}{1: } | - {1: }{2:------------------}{3:│}{1: }{4:int fib(int n) }| - {1: }{2:------------------}{3:│}{1: }{4:{ }| - {1: }{2:------------------}{3:│}{1: }{4: if(n > 2) }| - {1: }{2:------------------}{3:│}{1: }{4: { }| - {1: }{2:------------------}{3:│}{1: }{4: return fi}| - {1: }{2:------------------}{3:│}{1: }{4: } }| - {1: }{2:------------------}{3:│}{1: }{4: return 1; }| - {1: }{2:------------------}{3:│}{1: }{4:} }| - {1: }{2:------------------}{3:│}{1: }{4: }| - {1: }// Frobs foo heart{3:│}{1: }// Frobs foo hear| - {1: }int frobnitz(int f{3:│}{1: }int frobnitz(int | - {1: }{ {3:│}{1: }{ | + {1: }^#include <stdio.h>│{1: }#include <stdio.h| + {1: } │{1: } | + {1: }{2:------------------}│{1: }{4:int fib(int n) }| + {1: }{2:------------------}│{1: }{4:{ }| + {1: }{2:------------------}│{1: }{4: if(n > 2) }| + {1: }{2:------------------}│{1: }{4: { }| + {1: }{2:------------------}│{1: }{4: return fi}| + {1: }{2:------------------}│{1: }{4: } }| + {1: }{2:------------------}│{1: }{4: return 1; }| + {1: }{2:------------------}│{1: }{4:} }| + {1: }{2:------------------}│{1: }{4: }| + {1: }// Frobs foo heart│{1: }// Frobs foo hear| + {1: }int frobnitz(int f│{1: }int frobnitz(int | + {1: }{ │{1: }{ | {7:<onal-diff-screen-1 }{3:<l-diff-screen-1.2 }| | ]]) feed('G') screen:expect([[ - {1: } {3:│}{1: } | - {1: }{4:int fact(int n) }{3:│}{1: }{2:-----------------}| - {1: }{4:{ }{3:│}{1: }{2:-----------------}| - {1: }{4: if(n > 1) }{3:│}{1: }{2:-----------------}| - {1: }{4: { }{3:│}{1: }{2:-----------------}| - {1: }{4: return fac}{3:│}{1: }{2:-----------------}| - {1: }{4: } }{3:│}{1: }{2:-----------------}| - {1: }{4: return 1; }{3:│}{1: }{2:-----------------}| - {1: }{4:} }{3:│}{1: }{2:-----------------}| - {1: }{4: }{3:│}{1: }{2:-----------------}| - {1: }int main(int argc,{3:│}{1: }int main(int argc| - {1: }{ {3:│}{1: }{ | - {1: }{9: frobnitz(f}{8:act}{9:(}{3:│}{1: }{9: frobnitz(f}{8:ib}{9:(}| - {1: }^} {3:│}{1: }} | + {1: } │{1: } | + {1: }{4:int fact(int n) }│{1: }{2:-----------------}| + {1: }{4:{ }│{1: }{2:-----------------}| + {1: }{4: if(n > 1) }│{1: }{2:-----------------}| + {1: }{4: { }│{1: }{2:-----------------}| + {1: }{4: return fac}│{1: }{2:-----------------}| + {1: }{4: } }│{1: }{2:-----------------}| + {1: }{4: return 1; }│{1: }{2:-----------------}| + {1: }{4:} }│{1: }{2:-----------------}| + {1: }{4: }│{1: }{2:-----------------}| + {1: }int main(int argc,│{1: }int main(int argc| + {1: }{ │{1: }{ | + {1: }{9: frobnitz(f}{8:act}{9:(}│{1: }{9: frobnitz(f}{8:ib}{9:(}| + {1: }^} │{1: }} | {7:<onal-diff-screen-1 }{3:<l-diff-screen-1.2 }| | ]]) @@ -566,20 +566,20 @@ int main(int argc, char **argv) reread() feed(":set diffopt=internal,filler<cr>") screen:expect([[ - {1: }^def finalize(value{3:│}{1: }def finalize(valu| - {1: } {3:│}{1: } | - {1: } values.each do |{3:│}{1: } values.each do | - {1: }{2:------------------}{3:│}{1: }{4: v.prepare }| - {1: }{2:------------------}{3:│}{1: }{4: end }| - {1: }{2:------------------}{3:│}{1: }{4: }| - {1: }{2:------------------}{3:│}{1: }{4: values.each do }| - {1: } v.finalize {3:│}{1: } v.finalize | - {1: } end {3:│}{1: } end | - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| + {1: }^def finalize(value│{1: }def finalize(valu| + {1: } │{1: } | + {1: } values.each do |│{1: } values.each do | + {1: }{2:------------------}│{1: }{4: v.prepare }| + {1: }{2:------------------}│{1: }{4: end }| + {1: }{2:------------------}│{1: }{4: }| + {1: }{2:------------------}│{1: }{4: values.each do }| + {1: } v.finalize │{1: } v.finalize | + {1: } end │{1: } end | + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| {7:<onal-diff-screen-1 }{3:<l-diff-screen-1.2 }| :set diffopt=internal,filler | ]]) @@ -589,20 +589,20 @@ int main(int argc, char **argv) reread() feed(':set diffopt=internal,filler,indent-heuristic<cr>') screen:expect([[ - {1: }^def finalize(value{3:│}{1: }def finalize(valu| - {1: } {3:│}{1: } | - {1: }{2:------------------}{3:│}{1: }{4: values.each do }| - {1: }{2:------------------}{3:│}{1: }{4: v.prepare }| - {1: }{2:------------------}{3:│}{1: }{4: end }| - {1: }{2:------------------}{3:│}{1: }{4: }| - {1: } values.each do |{3:│}{1: } values.each do | - {1: } v.finalize {3:│}{1: } v.finalize | - {1: } end {3:│}{1: } end | - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| + {1: }^def finalize(value│{1: }def finalize(valu| + {1: } │{1: } | + {1: }{2:------------------}│{1: }{4: values.each do }| + {1: }{2:------------------}│{1: }{4: v.prepare }| + {1: }{2:------------------}│{1: }{4: end }| + {1: }{2:------------------}│{1: }{4: }| + {1: } values.each do |│{1: } values.each do | + {1: } v.finalize │{1: } v.finalize | + {1: } end │{1: } end | + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| {7:<onal-diff-screen-1 }{3:<l-diff-screen-1.2 }| | ]]) @@ -613,20 +613,20 @@ int main(int argc, char **argv) feed(':set diffopt=internal,filler,indent-heuristic,algorithm:patience<cr>') feed(':<cr>') screen:expect([[ - {1: }^def finalize(value{3:│}{1: }def finalize(valu| - {1: } {3:│}{1: } | - {1: }{2:------------------}{3:│}{1: }{4: values.each do }| - {1: }{2:------------------}{3:│}{1: }{4: v.prepare }| - {1: }{2:------------------}{3:│}{1: }{4: end }| - {1: }{2:------------------}{3:│}{1: }{4: }| - {1: } values.each do |{3:│}{1: } values.each do | - {1: } v.finalize {3:│}{1: } v.finalize | - {1: } end {3:│}{1: } end | - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| + {1: }^def finalize(value│{1: }def finalize(valu| + {1: } │{1: } | + {1: }{2:------------------}│{1: }{4: values.each do }| + {1: }{2:------------------}│{1: }{4: v.prepare }| + {1: }{2:------------------}│{1: }{4: end }| + {1: }{2:------------------}│{1: }{4: }| + {1: } values.each do |│{1: } values.each do | + {1: } v.finalize │{1: } v.finalize | + {1: } end │{1: } end | + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| {7:<onal-diff-screen-1 }{3:<l-diff-screen-1.2 }| : | ]]) @@ -640,40 +640,40 @@ int main(int argc, char **argv) feed(':set diffopt=filler<cr>') screen:expect([[ - {1:+ }{5:^+-- 10 lines: 1···}{3:│}{1:+ }{5:+-- 10 lines: 1··}| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| + {1:+ }{5:^+-- 10 lines: 1···}│{1:+ }{5:+-- 10 lines: 1··}| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| {7:<onal-diff-screen-1 }{3:<l-diff-screen-1.2 }| :set diffopt=filler | ]]) feed(':set diffopt+=internal<cr>') screen:expect([[ - {1:+ }{5:^+-- 10 lines: 1···}{3:│}{1:+ }{5:+-- 10 lines: 1··}| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| + {1:+ }{5:^+-- 10 lines: 1···}│{1:+ }{5:+-- 10 lines: 1··}| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| {7:<onal-diff-screen-1 }{3:<l-diff-screen-1.2 }| :set diffopt+=internal | ]]) @@ -686,40 +686,40 @@ int main(int argc, char **argv) feed(':set diffopt=filler<cr>') screen:expect([[ - {1:- }^ {3:│}{1:- } | - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| + {1:- }^ │{1:- } | + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| {7:<onal-diff-screen-1 }{3:<l-diff-screen-1.2 }| :set diffopt=filler | ]]) feed(':set diffopt+=internal<cr>') screen:expect([[ - {1:- }^ {3:│}{1:- } | - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| + {1:- }^ │{1:- } | + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| {7:<onal-diff-screen-1 }{3:<l-diff-screen-1.2 }| :set diffopt+=internal | ]]) @@ -732,40 +732,40 @@ int main(int argc, char **argv) feed(':set diffopt=filler,icase<cr>') screen:expect([[ - {1: }^a {3:│}{1: }A | - {1: }b {3:│}{1: }b | - {1: }{9:cd }{3:│}{1: }{9:cD}{8:e}{9: }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| + {1: }^a │{1: }A | + {1: }b │{1: }b | + {1: }{9:cd }│{1: }{9:cD}{8:e}{9: }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| {7:<onal-diff-screen-1 }{3:<l-diff-screen-1.2 }| :set diffopt=filler,icase | ]]) feed(':set diffopt+=internal<cr>') screen:expect([[ - {1: }^a {3:│}{1: }A | - {1: }b {3:│}{1: }b | - {1: }{9:cd }{3:│}{1: }{9:cD}{8:e}{9: }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| + {1: }^a │{1: }A | + {1: }b │{1: }b | + {1: }{9:cd }│{1: }{9:cD}{8:e}{9: }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| {7:<onal-diff-screen-1 }{3:<l-diff-screen-1.2 }| :set diffopt+=internal | ]]) @@ -784,20 +784,20 @@ int main(int argc, char **argv) reread() feed(':set diffopt=filler,iwhite<cr>') screen:expect([[ - {1: }^int main() {3:│}{1: }int main() | - {1: }{ {3:│}{1: }{ | - {1: }{2:------------------}{3:│}{1: }{4: if (0) }| - {1: }{2:------------------}{3:│}{1: }{4: { }| - {1: } printf("Hello, {3:│}{1: } printf("Hel| - {1: } return 0; {3:│}{1: } return 0; | - {1: }{2:------------------}{3:│}{1: }{4: } }| - {1: }} {3:│}{1: }} | - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| + {1: }^int main() │{1: }int main() | + {1: }{ │{1: }{ | + {1: }{2:------------------}│{1: }{4: if (0) }| + {1: }{2:------------------}│{1: }{4: { }| + {1: } printf("Hello, │{1: } printf("Hel| + {1: } return 0; │{1: } return 0; | + {1: }{2:------------------}│{1: }{4: } }| + {1: }} │{1: }} | + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| {7:<onal-diff-screen-1 }{3:<l-diff-screen-1.2 }| :set diffopt=filler,iwhite | ]]) @@ -807,20 +807,20 @@ int main(int argc, char **argv) reread() feed(':set diffopt=filler,iwhite,internal<cr>') screen:expect([[ - {1: }^int main() {3:│}{1: }int main() | - {1: }{ {3:│}{1: }{ | - {1: }{2:------------------}{3:│}{1: }{4: if (0) }| - {1: }{2:------------------}{3:│}{1: }{4: { }| - {1: } printf("Hello, {3:│}{1: } printf("Hel| - {1: } return 0; {3:│}{1: } return 0; | - {1: }{2:------------------}{3:│}{1: }{4: } }| - {1: }} {3:│}{1: }} | - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| + {1: }^int main() │{1: }int main() | + {1: }{ │{1: }{ | + {1: }{2:------------------}│{1: }{4: if (0) }| + {1: }{2:------------------}│{1: }{4: { }| + {1: } printf("Hello, │{1: } printf("Hel| + {1: } return 0; │{1: } return 0; | + {1: }{2:------------------}│{1: }{4: } }| + {1: }} │{1: }} | + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| {7:<onal-diff-screen-1 }{3:<l-diff-screen-1.2 }| :set diffopt=filler,iwhite,internal | ]]) @@ -838,20 +838,20 @@ int main(int argc, char **argv) reread() feed(':set diffopt=internal,filler,iblank<cr>') screen:expect([[ - {1: }^a {3:│}{1: }a | - {1: }{4: }{3:│}{1: }{2:-----------------}| - {1: }{4: }{3:│}{1: }{2:-----------------}| - {1: }cd {3:│}{1: }cd | - {1: }ef {3:│}{1: } | - {1: }{8:xxx}{9: }{3:│}{1: }ef | - {6:~ }{3:│}{1: }{8:yyy}{9: }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| + {1: }^a │{1: }a | + {1: }{4: }│{1: }{2:-----------------}| + {1: }{4: }│{1: }{2:-----------------}| + {1: }cd │{1: }cd | + {1: }ef │{1: } | + {1: }{8:xxx}{9: }│{1: }ef | + {6:~ }│{1: }{8:yyy}{9: }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| {7:<onal-diff-screen-1 }{3:<l-diff-screen-1.2 }| :set diffopt=internal,filler,iblank | ]]) @@ -862,20 +862,20 @@ int main(int argc, char **argv) feed(':set diffopt=internal,filler,iblank,iwhite<cr>') feed(':<cr>') screen:expect([[ - {1: }^a {3:│}{1: }a | - {1: } {3:│}{1: }cd | - {1: } {3:│}{1: } | - {1: }cd {3:│}{1: }ef | - {1: }ef {3:│}{1: }{8:yyy}{9: }| - {1: }{8:xxx}{9: }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| + {1: }^a │{1: }a | + {1: } │{1: }cd | + {1: } │{1: } | + {1: }cd │{1: }ef | + {1: }ef │{1: }{8:yyy}{9: }| + {1: }{8:xxx}{9: }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| {7:<onal-diff-screen-1 }{3:<l-diff-screen-1.2 }| : | ]]) @@ -886,20 +886,20 @@ int main(int argc, char **argv) feed(':set diffopt=internal,filler,iblank,iwhiteall<cr>') feed(':<cr>') screen:expect([[ - {1: }^a {3:│}{1: }a | - {1: } {3:│}{1: }cd | - {1: } {3:│}{1: } | - {1: }cd {3:│}{1: }ef | - {1: }ef {3:│}{1: }{8:yyy}{9: }| - {1: }{8:xxx}{9: }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| + {1: }^a │{1: }a | + {1: } │{1: }cd | + {1: } │{1: } | + {1: }cd │{1: }ef | + {1: }ef │{1: }{8:yyy}{9: }| + {1: }{8:xxx}{9: }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| {7:<onal-diff-screen-1 }{3:<l-diff-screen-1.2 }| : | ]]) @@ -910,20 +910,20 @@ int main(int argc, char **argv) feed(':set diffopt=internal,filler,iblank,iwhiteeol<cr>') feed(':<cr>') screen:expect([[ - {1: }^a {3:│}{1: }a | - {1: } {3:│}{1: }cd | - {1: } {3:│}{1: } | - {1: }cd {3:│}{1: }ef | - {1: }ef {3:│}{1: }{8:yyy}{9: }| - {1: }{8:xxx}{9: }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| + {1: }^a │{1: }a | + {1: } │{1: }cd | + {1: } │{1: } | + {1: }cd │{1: }ef | + {1: }ef │{1: }{8:yyy}{9: }| + {1: }{8:xxx}{9: }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| {7:<onal-diff-screen-1 }{3:<l-diff-screen-1.2 }| : | ]]) @@ -942,20 +942,20 @@ int main(int argc, char **argv) feed(':set diffopt=internal,filler,iwhiteeol<cr>') feed(':<cr>') screen:expect([[ - {1: }^a {3:│}{1: }a | - {1: }x {3:│}{1: }x | - {1: }{9:cd }{3:│}{1: }{9:c}{8: }{9:d }| - {1: }{9:ef }{3:│}{1: }{8: }{9:ef }| - {1: }{9:xx }{8: }{9:xx }{3:│}{1: }{9:xx xx }| - {1: }foo {3:│}{1: }foo | - {1: }{2:------------------}{3:│}{1: }{4: }| - {1: }bar {3:│}{1: }bar | - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| + {1: }^a │{1: }a | + {1: }x │{1: }x | + {1: }{9:cd }│{1: }{9:c}{8: }{9:d }| + {1: }{9:ef }│{1: }{8: }{9:ef }| + {1: }{9:xx }{8: }{9:xx }│{1: }{9:xx xx }| + {1: }foo │{1: }foo | + {1: }{2:------------------}│{1: }{4: }| + {1: }bar │{1: }bar | + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| {7:<onal-diff-screen-1 }{3:<l-diff-screen-1.2 }| : | ]]) @@ -966,20 +966,20 @@ int main(int argc, char **argv) feed(':set diffopt=internal,filler,iwhiteall<cr>') feed(':<cr>') screen:expect([[ - {1: }^a {3:│}{1: }a | - {1: }x {3:│}{1: }x | - {1: }cd {3:│}{1: }c d | - {1: }ef {3:│}{1: } ef | - {1: }xx xx {3:│}{1: }xx xx | - {1: }foo {3:│}{1: }foo | - {1: }{2:------------------}{3:│}{1: }{4: }| - {1: }bar {3:│}{1: }bar | - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| - {6:~ }{3:│}{6:~ }| + {1: }^a │{1: }a | + {1: }x │{1: }x | + {1: }cd │{1: }c d | + {1: }ef │{1: } ef | + {1: }xx xx │{1: }xx xx | + {1: }foo │{1: }foo | + {1: }{2:------------------}│{1: }{4: }| + {1: }bar │{1: }bar | + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| {7:<onal-diff-screen-1 }{3:<l-diff-screen-1.2 }| : | ]]) @@ -1029,14 +1029,14 @@ it('win_update redraws lines properly', function() command("windo diffthis") command("windo 1") screen:expect{grid=[[ - {13: }{16:-----------------------}{14:│}{13: }{15:^1 }| - {13: }{16:-----------------------}{14:│}{13: }{15: }| - {13: }{16:-----------------------}{14:│}{13: }{15: }| - {13: }2 {14:│}{13: }2 | - {13: }{17:2}{18:a }{14:│}{13: }{17:1}{18:a }| - {13: }{15:2b }{14:│}{13: }{16:----------------------}| - {13: } {14:│}{13: } | - {1:~ }{14:│}{1:~ }| + {13: }{16:-----------------------}│{13: }{15:^1 }| + {13: }{16:-----------------------}│{13: }{15: }| + {13: }{16:-----------------------}│{13: }{15: }| + {13: }2 │{13: }2 | + {13: }{17:2}{18:a }│{13: }{17:1}{18:a }| + {13: }{15:2b }│{13: }{16:----------------------}| + {13: } │{13: } | + {1:~ }│{1:~ }| {14:left [+] }{12:[No Name] [+] }| | ]]} @@ -1046,14 +1046,14 @@ it('win_update redraws lines properly', function() feed('<C-y>') feed('<C-y>') screen:expect{grid=[[ - {13: }{16:-----------------------}{14:│}{13: }{15:1 }| - {13: }{16:-----------------------}{14:│}{13: }{15: }| - {13: }{16:-----------------------}{14:│}{13: }{15:^ }| - {13: }2 {14:│}{13: }2 | - {13: }{17:2}{18:a }{14:│}{13: }{17:1}{18:a }| - {13: }{15:2b }{14:│}{13: }{16:----------------------}| - {13: } {14:│}{13: } | - {1:~ }{14:│}{1:~ }| + {13: }{16:-----------------------}│{13: }{15:1 }| + {13: }{16:-----------------------}│{13: }{15: }| + {13: }{16:-----------------------}│{13: }{15:^ }| + {13: }2 │{13: }2 | + {13: }{17:2}{18:a }│{13: }{17:1}{18:a }| + {13: }{15:2b }│{13: }{16:----------------------}| + {13: } │{13: } | + {1:~ }│{1:~ }| {14:left [+] }{12:[No Name] [+] }| | ]]} @@ -1075,10 +1075,8 @@ it('diff updates line numbers below filler lines', function() [9] = {background = Screen.colors.LightMagenta}, [10] = {bold = true, foreground = Screen.colors.Brown}, [11] = {foreground = Screen.colors.Brown}, - [12] = {foreground = Screen.colors.Brown, bold = true, background = Screen.colors.Red}; - [13] = {background = Screen.colors.Gray90}; }) - source([[ + exec([[ call setline(1, ['a', 'a', 'a', 'y', 'b', 'b', 'b', 'b', 'b']) vnew call setline(1, ['a', 'a', 'a', 'x', 'x', 'x', 'b', 'b', 'b', 'b', 'b']) @@ -1086,73 +1084,55 @@ it('diff updates line numbers below filler lines', function() setlocal number rnu cursorline cursorlineopt=number foldcolumn=0 ]]) screen:expect([[ - {1: }a {3:│}{10:1 }^a | - {1: }a {3:│}{11: 1 }a | - {1: }a {3:│}{11: 2 }a | - {1: }{8:x}{9: }{3:│}{11: 3 }{8:y}{9: }| - {1: }{4:x }{3:│}{11: }{2:----------------}| - {1: }{4:x }{3:│}{11: }{2:----------------}| - {1: }b {3:│}{11: 4 }b | - {1: }b {3:│}{11: 5 }b | - {1: }b {3:│}{11: 6 }b | - {1: }b {3:│}{11: 7 }b | - {1: }b {3:│}{11: 8 }b | - {6:~ }{3:│}{6:~ }| + {1: }a │{10:1 }^a | + {1: }a │{11: 1 }a | + {1: }a │{11: 2 }a | + {1: }{8:x}{9: }│{11: 3 }{8:y}{9: }| + {1: }{4:x }│{11: }{2:----------------}| + {1: }{4:x }│{11: }{2:----------------}| + {1: }b │{11: 4 }b | + {1: }b │{11: 5 }b | + {1: }b │{11: 6 }b | + {1: }b │{11: 7 }b | + {1: }b │{11: 8 }b | + {6:~ }│{6:~ }| {3:[No Name] [+] }{7:[No Name] [+] }| | ]]) feed('j') screen:expect([[ - {1: }a {3:│}{11: 1 }a | - {1: }a {3:│}{10:2 }^a | - {1: }a {3:│}{11: 1 }a | - {1: }{8:x}{9: }{3:│}{11: 2 }{8:y}{9: }| - {1: }{4:x }{3:│}{11: }{2:----------------}| - {1: }{4:x }{3:│}{11: }{2:----------------}| - {1: }b {3:│}{11: 3 }b | - {1: }b {3:│}{11: 4 }b | - {1: }b {3:│}{11: 5 }b | - {1: }b {3:│}{11: 6 }b | - {1: }b {3:│}{11: 7 }b | - {6:~ }{3:│}{6:~ }| + {1: }a │{11: 1 }a | + {1: }a │{10:2 }^a | + {1: }a │{11: 1 }a | + {1: }{8:x}{9: }│{11: 2 }{8:y}{9: }| + {1: }{4:x }│{11: }{2:----------------}| + {1: }{4:x }│{11: }{2:----------------}| + {1: }b │{11: 3 }b | + {1: }b │{11: 4 }b | + {1: }b │{11: 5 }b | + {1: }b │{11: 6 }b | + {1: }b │{11: 7 }b | + {6:~ }│{6:~ }| {3:[No Name] [+] }{7:[No Name] [+] }| | ]]) feed('j') screen:expect([[ - {1: }a {3:│}{11: 2 }a | - {1: }a {3:│}{11: 1 }a | - {1: }a {3:│}{10:3 }^a | - {1: }{8:x}{9: }{3:│}{11: 1 }{8:y}{9: }| - {1: }{4:x }{3:│}{11: }{2:----------------}| - {1: }{4:x }{3:│}{11: }{2:----------------}| - {1: }b {3:│}{11: 2 }b | - {1: }b {3:│}{11: 3 }b | - {1: }b {3:│}{11: 4 }b | - {1: }b {3:│}{11: 5 }b | - {1: }b {3:│}{11: 6 }b | - {6:~ }{3:│}{6:~ }| + {1: }a │{11: 2 }a | + {1: }a │{11: 1 }a | + {1: }a │{10:3 }^a | + {1: }{8:x}{9: }│{11: 1 }{8:y}{9: }| + {1: }{4:x }│{11: }{2:----------------}| + {1: }{4:x }│{11: }{2:----------------}| + {1: }b │{11: 2 }b | + {1: }b │{11: 3 }b | + {1: }b │{11: 4 }b | + {1: }b │{11: 5 }b | + {1: }b │{11: 6 }b | + {6:~ }│{6:~ }| {3:[No Name] [+] }{7:[No Name] [+] }| | ]]) - command("set signcolumn number tgc cursorline cursorlineopt=number,line") - command("hi CursorLineNr guibg=red") - screen:expect{grid=[[ - {1: }a {3:│}{11: 2 }a | - {1: }a {3:│}{11: 1 }a | - {1: }a {3:│}{12:3 }{13:^a }| - {1: }{8:x}{9: }{3:│}{11: 1 }{8:y}{9: }| - {1: }{4:x }{3:│}{11: }{2:----------------}| - {1: }{4:x }{3:│}{11: }{2:----------------}| - {1: }b {3:│}{11: 2 }b | - {1: }b {3:│}{11: 3 }b | - {1: }b {3:│}{11: 4 }b | - {1: }b {3:│}{11: 5 }b | - {1: }b {3:│}{11: 6 }b | - {6:~ }{3:│}{6:~ }| - {3:[No Name] [+] }{7:[No Name] [+] }| - signcolumn=auto | - ]]} end) it('Align the filler lines when changing text in diff mode', function() @@ -1169,7 +1149,7 @@ it('Align the filler lines when changing text in diff mode', function() [7] = {foreground = Screen.colors.Blue1, bold = true}; [8] = {reverse = true, bold = true}; }) - source([[ + exec([[ call setline(1, range(1, 15)) vnew call setline(1, range(9, 15)) @@ -1178,71 +1158,198 @@ it('Align the filler lines when changing text in diff mode', function() exe "normal Gl5\<C-E>" ]]) screen:expect{grid=[[ - {1: }{2:------------------}{3:│}{1: }{4:6 }| - {1: }{2:------------------}{3:│}{1: }{4:7 }| - {1: }{2:------------------}{3:│}{1: }{4:8 }| - {1: }9 {3:│}{1: }9 | - {1: }10 {3:│}{1: }10 | - {1: }11 {3:│}{1: }11 | - {1: }12 {3:│}{1: }12 | - {1: }13 {3:│}{1: }13 | - {1: }14 {3:│}{1: }14 | - {1:- }1^5 {3:│}{1:- }15 | - {7:~ }{3:│}{7:~ }| - {7:~ }{3:│}{7:~ }| - {7:~ }{3:│}{7:~ }| - {7:~ }{3:│}{7:~ }| - {7:~ }{3:│}{7:~ }| - {7:~ }{3:│}{7:~ }| - {7:~ }{3:│}{7:~ }| - {7:~ }{3:│}{7:~ }| + {1: }{2:------------------}│{1: }{4:6 }| + {1: }{2:------------------}│{1: }{4:7 }| + {1: }{2:------------------}│{1: }{4:8 }| + {1: }9 │{1: }9 | + {1: }10 │{1: }10 | + {1: }11 │{1: }11 | + {1: }12 │{1: }12 | + {1: }13 │{1: }13 | + {1: }14 │{1: }14 | + {1:- }1^5 │{1:- }15 | + {7:~ }│{7:~ }| + {7:~ }│{7:~ }| + {7:~ }│{7:~ }| + {7:~ }│{7:~ }| + {7:~ }│{7:~ }| + {7:~ }│{7:~ }| + {7:~ }│{7:~ }| + {7:~ }│{7:~ }| {8:[No Name] [+] }{3:[No Name] [+] }| | ]]} feed('ax<Esc>') screen:expect{grid=[[ - {1: }{2:------------------}{3:│}{1: }{4:6 }| - {1: }{2:------------------}{3:│}{1: }{4:7 }| - {1: }{2:------------------}{3:│}{1: }{4:8 }| - {1: }9 {3:│}{1: }9 | - {1: }10 {3:│}{1: }10 | - {1: }11 {3:│}{1: }11 | - {1: }12 {3:│}{1: }12 | - {1: }13 {3:│}{1: }13 | - {1: }14 {3:│}{1: }14 | - {1: }{5:15}{6:^x}{5: }{3:│}{1: }{5:15 }| - {7:~ }{3:│}{7:~ }| - {7:~ }{3:│}{7:~ }| - {7:~ }{3:│}{7:~ }| - {7:~ }{3:│}{7:~ }| - {7:~ }{3:│}{7:~ }| - {7:~ }{3:│}{7:~ }| - {7:~ }{3:│}{7:~ }| - {7:~ }{3:│}{7:~ }| + {1: }{2:------------------}│{1: }{4:6 }| + {1: }{2:------------------}│{1: }{4:7 }| + {1: }{2:------------------}│{1: }{4:8 }| + {1: }9 │{1: }9 | + {1: }10 │{1: }10 | + {1: }11 │{1: }11 | + {1: }12 │{1: }12 | + {1: }13 │{1: }13 | + {1: }14 │{1: }14 | + {1: }{5:15}{6:^x}{5: }│{1: }{5:15 }| + {7:~ }│{7:~ }| + {7:~ }│{7:~ }| + {7:~ }│{7:~ }| + {7:~ }│{7:~ }| + {7:~ }│{7:~ }| + {7:~ }│{7:~ }| + {7:~ }│{7:~ }| + {7:~ }│{7:~ }| {8:[No Name] [+] }{3:[No Name] [+] }| | ]]} feed('<C-W>lay<Esc>') screen:expect{grid=[[ - {1: }{2:-----------------}{3:│}{1: }{4:6 }| - {1: }{2:-----------------}{3:│}{1: }{4:7 }| - {1: }{2:-----------------}{3:│}{1: }{4:8 }| - {1: }9 {3:│}{1: }9 | - {1: }10 {3:│}{1: }10 | - {1: }11 {3:│}{1: }11 | - {1: }12 {3:│}{1: }12 | - {1: }13 {3:│}{1: }13 | - {1: }14 {3:│}{1: }14 | - {1: }{5:15}{6:x}{5: }{3:│}{1: }{5:15}{6:^y}{5: }| - {7:~ }{3:│}{7:~ }| - {7:~ }{3:│}{7:~ }| - {7:~ }{3:│}{7:~ }| - {7:~ }{3:│}{7:~ }| - {7:~ }{3:│}{7:~ }| - {7:~ }{3:│}{7:~ }| - {7:~ }{3:│}{7:~ }| - {7:~ }{3:│}{7:~ }| + {1: }{2:-----------------}│{1: }{4:6 }| + {1: }{2:-----------------}│{1: }{4:7 }| + {1: }{2:-----------------}│{1: }{4:8 }| + {1: }9 │{1: }9 | + {1: }10 │{1: }10 | + {1: }11 │{1: }11 | + {1: }12 │{1: }12 | + {1: }13 │{1: }13 | + {1: }14 │{1: }14 | + {1: }{5:15}{6:x}{5: }│{1: }{5:15}{6:^y}{5: }| + {7:~ }│{7:~ }| + {7:~ }│{7:~ }| + {7:~ }│{7:~ }| + {7:~ }│{7:~ }| + {7:~ }│{7:~ }| + {7:~ }│{7:~ }| + {7:~ }│{7:~ }| + {7:~ }│{7:~ }| {3:[No Name] [+] }{8:[No Name] [+] }| | ]]} end) + +it('diff mode works properly if file contains NUL bytes vim-patch:8.2.3925', function() + clear() + local screen = Screen.new(40, 20) + screen:set_default_attr_ids({ + [1] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.Gray}; + [2] = {reverse = true}; + [3] = {background = Screen.colors.LightBlue}; + [4] = {background = Screen.colors.LightMagenta}; + [5] = {background = Screen.colors.Red, bold = true}; + [6] = {foreground = Screen.colors.Blue, bold = true}; + [7] = {background = Screen.colors.Red, foreground = Screen.colors.Blue, bold = true}; + [8] = {reverse = true, bold = true}; + }) + screen:attach() + exec([[ + call setline(1, ['a', 'b', "c\n", 'd', 'e', 'f', 'g']) + vnew + call setline(1, ['A', 'b', 'c', 'd', 'E', 'f', 'g']) + windo diffthis + wincmd p + norm! gg0 + redraw! + ]]) + + -- Test using internal diff + screen:expect([[ + {1: }{5:^A}{4: }│{1: }{5:a}{4: }| + {1: }b │{1: }b | + {1: }{4:c }│{1: }{4:c}{7:^@}{4: }| + {1: }d │{1: }d | + {1: }{5:E}{4: }│{1: }{5:e}{4: }| + {1: }f │{1: }f | + {1: }g │{1: }g | + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {8:[No Name] [+] }{2:[No Name] [+] }| + | + ]]) + + -- Test using internal diff and case folding + command('set diffopt+=icase') + feed('<C-L>') + screen:expect([[ + {1: }^A │{1: }a | + {1: }b │{1: }b | + {1: }{4:c }│{1: }{4:c}{7:^@}{4: }| + {1: }d │{1: }d | + {1: }E │{1: }e | + {1: }f │{1: }f | + {1: }g │{1: }g | + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {8:[No Name] [+] }{2:[No Name] [+] }| + | + ]]) + + -- Test using external diff + command('set diffopt=filler') + feed('<C-L>') + screen:expect([[ + {1: }{5:^A}{4: }│{1: }{5:a}{4: }| + {1: }b │{1: }b | + {1: }{4:c }│{1: }{4:c}{7:^@}{4: }| + {1: }d │{1: }d | + {1: }{5:E}{4: }│{1: }{5:e}{4: }| + {1: }f │{1: }f | + {1: }g │{1: }g | + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {8:[No Name] [+] }{2:[No Name] [+] }| + | + ]]) + + -- Test using external diff and case folding + command('set diffopt+=filler,icase') + feed('<C-L>') + screen:expect([[ + {1: }^A │{1: }a | + {1: }b │{1: }b | + {1: }{4:c }│{1: }{4:c}{7:^@}{4: }| + {1: }d │{1: }d | + {1: }E │{1: }e | + {1: }f │{1: }f | + {1: }g │{1: }g | + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {6:~ }│{6:~ }| + {8:[No Name] [+] }{2:[No Name] [+] }| + | + ]]) +end) diff --git a/test/functional/ui/embed_spec.lua b/test/functional/ui/embed_spec.lua index 8218c8e12d..92f5beebf5 100644 --- a/test/functional/ui/embed_spec.lua +++ b/test/functional/ui/embed_spec.lua @@ -1,3 +1,5 @@ +local uv = require'luv' + local helpers = require('test.functional.helpers')(after_each) local Screen = require('test.functional.ui.screen') @@ -98,3 +100,49 @@ end describe('--embed UI on startup (ext_linegrid=true)', function() test_embed(true) end) describe('--embed UI on startup (ext_linegrid=false)', function() test_embed(false) end) + +describe('--embed UI', function() + it('can pass stdin', function() + local pipe = assert(uv.pipe()) + + local writer = assert(uv.new_pipe(false)) + writer:open(pipe.write) + + clear {args_rm={'--headless'}, io_extra=pipe.read} + + -- attach immediately after startup, for early UI + local screen = Screen.new(40, 8) + screen:attach {stdin_fd=3} + screen:set_default_attr_ids { + [1] = {bold = true, foreground = Screen.colors.Blue1}; + [2] = {bold = true}; + } + + writer:write "hello nvim\nfrom external input\n" + writer:shutdown(function() writer:close() end) + + screen:expect{grid=[[ + ^hello nvim | + from external input | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]} + + -- stdin (rpc input) still works + feed 'o' + screen:expect{grid=[[ + hello nvim | + ^ | + from external input | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {2:-- INSERT --} | + ]]} + end) +end) diff --git a/test/functional/ui/float_spec.lua b/test/functional/ui/float_spec.lua index 5f29261b17..50247ed214 100644 --- a/test/functional/ui/float_spec.lua +++ b/test/functional/ui/float_spec.lua @@ -8,6 +8,7 @@ local command, feed_command = helpers.command, helpers.feed_command local eval = helpers.eval local eq = helpers.eq local neq = helpers.neq +local expect = helpers.expect local exec_lua = helpers.exec_lua local insert = helpers.insert local meths = helpers.meths @@ -16,10 +17,12 @@ local funcs = helpers.funcs local run = helpers.run local pcall_err = helpers.pcall_err local tbl_contains = global_helpers.tbl_contains +local curbuf, curwin, curtab = helpers.curbuf, helpers.curwin, helpers.curtab describe('float window', function() before_each(function() clear() + command('hi VertSplit gui=reverse') end) local attrs = { [0] = {bold=true, foreground=Screen.colors.Blue}, @@ -83,8 +86,8 @@ describe('float window', function() local buf = meths.create_buf(false, false) meths.buf_set_lines(buf, 0, -1, true, {'the floatwin'}) local win = meths.open_win(buf, true, {relative='win', width=16, height=1, row=0, col=10}) - eq(pcall_err(funcs.win_execute, win, 'close'), 'Vim(close):E37: No write since last change (add ! to override)') - eq(pcall_err(funcs.win_execute, win, 'bdelete'), 'Vim(bdelete):E89: No write since last change for buffer 2 (add ! to override)') + eq('Vim(close):E37: No write since last change (add ! to override)', pcall_err(funcs.win_execute, win, 'close')) + eq('Vim(bdelete):E89: No write since last change for buffer 2 (add ! to override)', pcall_err(funcs.win_execute, win, 'bdelete')) funcs.win_execute(win, 'bwipe!') end) @@ -365,7 +368,7 @@ describe('float window', function() return vim.api.nvim_open_win(bufnr, false, opts) ]]) command('windo echo') - neq(eval('win_getid()'), winid) + neq(winid, eval('win_getid()')) end) it('is active after windo when focusable', function() @@ -382,7 +385,7 @@ describe('float window', function() return vim.api.nvim_open_win(bufnr, false, opts) ]]) command('windo echo') - eq(eval('win_getid()'), winid) + eq(winid, eval('win_getid()')) end) it('supports windo with focusable and non-focusable floats', function() @@ -417,6 +420,287 @@ describe('float window', function() eq(winids, eval('winids')) 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 + before_each(function() + insert('foo') + old_buf = curbuf().id + old_win = curwin().id + end) + describe('closing the last non-floating window gives E444', function() + before_each(function() + meths.open_win(old_buf, true, float_opts) + end) + it('if called from non-floating window', function() + meths.set_current_win(old_win) + eq('Vim:E444: Cannot close last window', + pcall_err(meths.win_close, old_win, false)) + end) + it('if called from floating window', function() + eq('Vim:E444: Cannot close last window', + pcall_err(meths.win_close, old_win, false)) + end) + end) + describe("deleting the last non-floating window's buffer", function() + describe('leaves one window with an empty buffer when there is only one buffer', function() + local same_buf_float + before_each(function() + same_buf_float = meths.open_win(old_buf, false, float_opts).id + end) + after_each(function() + eq(old_win, curwin().id) + expect('') + eq(1, #meths.list_wins()) + end) + it('if called from non-floating window', function() + meths.buf_delete(old_buf, {force = true}) + end) + it('if called from floating window', function() + meths.set_current_win(same_buf_float) + command('autocmd WinLeave * let g:win_leave = nvim_get_current_win()') + command('autocmd WinEnter * let g:win_enter = nvim_get_current_win()') + meths.buf_delete(old_buf, {force = true}) + eq(same_buf_float, eval('g:win_leave')) + eq(old_win, eval('g:win_enter')) + end) + end) + describe('closes other windows with that buffer when there are other buffers', function() + local same_buf_float, other_buf, other_buf_float + before_each(function() + same_buf_float = meths.open_win(old_buf, false, float_opts).id + other_buf = meths.create_buf(true, false).id + other_buf_float = meths.open_win(other_buf, true, float_opts).id + insert('bar') + meths.set_current_win(old_win) + end) + after_each(function() + eq(other_buf, curbuf().id) + expect('bar') + eq(2, #meths.list_wins()) + end) + it('if called from non-floating window', function() + meths.buf_delete(old_buf, {force = true}) + eq(old_win, curwin().id) + end) + it('if called from floating window with the same buffer', function() + meths.set_current_win(same_buf_float) + command('autocmd WinLeave * let g:win_leave = nvim_get_current_win()') + command('autocmd WinEnter * let g:win_enter = nvim_get_current_win()') + meths.buf_delete(old_buf, {force = true}) + eq(same_buf_float, eval('g:win_leave')) + eq(old_win, eval('g:win_enter')) + eq(old_win, curwin().id) + end) + -- TODO: this case is too hard to deal with + pending('if called from floating window with another buffer', function() + meths.set_current_win(other_buf_float) + meths.buf_delete(old_buf, {force = true}) + end) + end) + describe('creates an empty buffer when there is only one listed buffer', function() + local same_buf_float, unlisted_buf_float + before_each(function() + same_buf_float = meths.open_win(old_buf, false, float_opts).id + local unlisted_buf = meths.create_buf(true, false).id + unlisted_buf_float = meths.open_win(unlisted_buf, true, float_opts).id + insert('unlisted') + command('set nobuflisted') + meths.set_current_win(old_win) + end) + after_each(function() + expect('') + eq(2, #meths.list_wins()) + end) + it('if called from non-floating window', function() + meths.buf_delete(old_buf, {force = true}) + eq(old_win, curwin().id) + end) + it('if called from floating window with the same buffer', function() + meths.set_current_win(same_buf_float) + command('autocmd WinLeave * let g:win_leave = nvim_get_current_win()') + command('autocmd WinEnter * let g:win_enter = nvim_get_current_win()') + meths.buf_delete(old_buf, {force = true}) + eq(same_buf_float, eval('g:win_leave')) + eq(old_win, eval('g:win_enter')) + eq(old_win, curwin().id) + end) + -- TODO: this case is too hard to deal with + pending('if called from floating window with an unlisted buffer', function() + meths.set_current_win(unlisted_buf_float) + meths.buf_delete(old_buf, {force = true}) + end) + end) + end) + describe('with splits, deleting the last listed buffer creates an empty buffer', function() + describe('when a non-floating window has an unlisted buffer', function() + local same_buf_float + before_each(function() + command('botright vnew') + insert('unlisted') + command('set nobuflisted') + meths.set_current_win(old_win) + same_buf_float = meths.open_win(old_buf, false, float_opts).id + end) + after_each(function() + expect('') + eq(2, #meths.list_wins()) + end) + it('if called from non-floating window with the deleted buffer', function() + meths.buf_delete(old_buf, {force = true}) + eq(old_win, curwin().id) + end) + it('if called from floating window with the deleted buffer', function() + meths.set_current_win(same_buf_float) + meths.buf_delete(old_buf, {force = true}) + eq(same_buf_float, curwin().id) + end) + end) + end) + end) + + describe('with mulitple tabpages but only one listed buffer,', function() + local float_opts = {relative = 'editor', row = 1, col = 1, width = 1, height = 1} + local unlisted_buf, old_buf, old_win + before_each(function() + insert('unlisted') + command('set nobuflisted') + unlisted_buf = curbuf().id + command('tabnew') + insert('foo') + old_buf = curbuf().id + old_win = curwin().id + end) + describe('without splits, deleting the last listed buffer creates an empty buffer', function() + local same_buf_float + before_each(function() + meths.set_current_win(old_win) + same_buf_float = meths.open_win(old_buf, false, float_opts).id + end) + after_each(function() + expect('') + eq(2, #meths.list_wins()) + eq(2, #meths.list_tabpages()) + end) + it('if called from non-floating window', function() + meths.buf_delete(old_buf, {force = true}) + eq(old_win, curwin().id) + end) + it('if called from floating window with the same buffer', function() + meths.set_current_win(same_buf_float) + command('autocmd WinLeave * let g:win_leave = nvim_get_current_win()') + command('autocmd WinEnter * let g:win_enter = nvim_get_current_win()') + meths.buf_delete(old_buf, {force = true}) + eq(same_buf_float, eval('g:win_leave')) + eq(old_win, eval('g:win_enter')) + eq(old_win, curwin().id) + end) + end) + describe('with splits, deleting the last listed buffer creates an empty buffer', function() + local same_buf_float + before_each(function() + command('botright vsplit') + meths.set_current_buf(unlisted_buf) + meths.set_current_win(old_win) + same_buf_float = meths.open_win(old_buf, false, float_opts).id + end) + after_each(function() + expect('') + eq(3, #meths.list_wins()) + eq(2, #meths.list_tabpages()) + end) + it('if called from non-floating window with the deleted buffer', function() + meths.buf_delete(old_buf, {force = true}) + eq(old_win, curwin().id) + end) + it('if called from floating window with the deleted buffer', function() + meths.set_current_win(same_buf_float) + meths.buf_delete(old_buf, {force = true}) + eq(same_buf_float, curwin().id) + end) + end) + end) + + describe('with multiple tabpages and multiple listed buffers,', function() + local float_opts = {relative = 'editor', row = 1, col = 1, width = 1, height = 1} + local old_tabpage, old_buf, old_win + before_each(function() + old_tabpage = curtab().id + insert('oldtab') + command('tabnew') + old_buf = curbuf().id + old_win = curwin().id + end) + describe('closing the last non-floating window', function() + describe('closes the tabpage when all floating windows are closeable', function() + local same_buf_float + before_each(function() + same_buf_float = meths.open_win(old_buf, false, float_opts).id + end) + after_each(function() + eq(old_tabpage, curtab().id) + expect('oldtab') + eq(1, #meths.list_tabpages()) + end) + it('if called from non-floating window', function() + meths.win_close(old_win, false) + end) + it('if called from floating window', function() + meths.set_current_win(same_buf_float) + meths.win_close(old_win, false) + end) + end) + describe('gives E5601 when there are non-closeable floating windows', function() + local other_buf_float + before_each(function() + command('set nohidden') + local other_buf = meths.create_buf(true, false).id + other_buf_float = meths.open_win(other_buf, true, float_opts).id + insert('foo') + meths.set_current_win(old_win) + end) + it('if called from non-floating window', function() + eq('Vim:E5601: Cannot close window, only floating window would remain', + pcall_err(meths.win_close, old_win, false)) + end) + it('if called from floating window', function() + meths.set_current_win(other_buf_float) + eq('Vim:E5601: Cannot close window, only floating window would remain', + pcall_err(meths.win_close, old_win, false)) + end) + end) + end) + describe("deleting the last non-floating window's buffer", function() + describe('closes the tabpage when all floating windows are closeable', function() + local same_buf_float, other_buf, other_buf_float + before_each(function() + same_buf_float = meths.open_win(old_buf, false, float_opts).id + other_buf = meths.create_buf(true, false).id + other_buf_float = meths.open_win(other_buf, true, float_opts).id + meths.set_current_win(old_win) + end) + after_each(function() + eq(old_tabpage, curtab().id) + expect('oldtab') + eq(1, #meths.list_tabpages()) + end) + it('if called from non-floating window', function() + meths.buf_delete(old_buf, {force = false}) + end) + it('if called from floating window with the same buffer', function() + meths.set_current_win(same_buf_float) + meths.buf_delete(old_buf, {force = false}) + end) + -- TODO: this case is too hard to deal with + pending('if called from floating window with another buffer', function() + meths.set_current_win(other_buf_float) + meths.buf_delete(old_buf, {force = false}) + end) + end) + -- TODO: what to do when there are non-closeable floating windows? + end) + end) + local function with_ext_multigrid(multigrid) local screen before_each(function() @@ -3376,10 +3660,10 @@ describe('float window', function() screen:expect{grid=[[ ## grid 1 [2:----------------------------------------]| - {5:[No Name] }| - [5:----------------------------------------]| - [5:----------------------------------------]| - [5:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| {5:[Preview] }| [3:----------------------------------------]| ## grid 2 @@ -3390,10 +3674,6 @@ describe('float window', function() {17:f}{1:oo }| {17:b}{1:ar }| {1: }| - ## grid 5 - |1| {17:f}oo | - |2| {17:b}ar | - {0:~ }| ]], float_pos=expected_pos} else screen:expect([[ @@ -4169,7 +4449,7 @@ describe('float window', function() ## grid 4 {1:y }| {2:~ }| - ]], float_pos=expected_pos} + ]], float_pos=expected_pos} else screen:expect([[ ^x | @@ -4207,7 +4487,7 @@ describe('float window', function() ## grid 4 {1:^y }| {2:~ }| - ]], float_pos=expected_pos} + ]], float_pos=expected_pos} else screen:expect([[ x | @@ -4243,7 +4523,7 @@ describe('float window', function() ## grid 4 {1:y }| {2:~ }| - ]], float_pos=expected_pos} + ]], float_pos=expected_pos} else screen:expect([[ ^x | @@ -4283,7 +4563,7 @@ describe('float window', function() ## grid 4 {1:y }| {2:~ }| - ]], float_pos=expected_pos} + ]], float_pos=expected_pos} else screen:expect([[ ^x | @@ -4319,7 +4599,7 @@ describe('float window', function() ## grid 4 {1:y }| {2:~ }| - ]], float_pos=expected_pos} + ]], float_pos=expected_pos} else screen:expect([[ ^x | @@ -4357,7 +4637,7 @@ describe('float window', function() ## grid 4 {1:^y }| {2:~ }| - ]], float_pos=expected_pos} + ]], float_pos=expected_pos} else screen:expect([[ x | @@ -4393,7 +4673,7 @@ describe('float window', function() ## grid 4 {1:y }| {2:~ }| - ]], float_pos=expected_pos} + ]], float_pos=expected_pos} else screen:expect([[ ^x | @@ -4431,7 +4711,7 @@ describe('float window', function() ## grid 4 {1:^y }| {2:~ }| - ]], float_pos=expected_pos} + ]], float_pos=expected_pos} else meths.input_mouse('left', 'press', '', 0, 2, 5) screen:expect([[ @@ -4468,7 +4748,7 @@ describe('float window', function() ## grid 4 {1:y }| {2:~ }| - ]], float_pos=expected_pos} + ]], float_pos=expected_pos} else meths.input_mouse('left', 'press', '', 0, 0, 0) screen:expect([[ @@ -4510,7 +4790,7 @@ describe('float window', function() ## grid 4 {1:y }| {2:~ }| - ]], float_pos=expected_pos} + ]], float_pos=expected_pos} else meths.input_mouse('left', 'press', '', 0, 2, 5) screen:expect([[ @@ -4547,7 +4827,7 @@ describe('float window', function() ## grid 4 {1:y }| {2:~ }| - ]], float_pos=expected_pos, unchanged=true} + ]], float_pos=expected_pos, unchanged=true} else meths.input_mouse('left', 'press', '', 0, 0, 0) screen:expect([[ @@ -4562,7 +4842,6 @@ describe('float window', function() end end) - it("j", function() feed("<c-w>ji") -- INSERT to trigger screen change if multigrid then @@ -4587,7 +4866,7 @@ describe('float window', function() ## grid 4 {1:y }| {2:~ }| - ]], float_pos=expected_pos} + ]], float_pos=expected_pos} else screen:expect([[ ^x | @@ -4623,7 +4902,7 @@ describe('float window', function() ## grid 4 {1:^y }| {2:~ }| - ]], float_pos=expected_pos} + ]], float_pos=expected_pos} else screen:expect([[ x | @@ -4659,7 +4938,7 @@ describe('float window', function() ## grid 4 {1:y }| {2:~ }| - ]], float_pos=expected_pos} + ]], float_pos=expected_pos} else screen:expect([[ ^x | @@ -4698,7 +4977,7 @@ describe('float window', function() ## grid 4 {1:^y }| {2:~ }| - ]], float_pos=expected_pos} + ]], float_pos=expected_pos} else screen:expect([[ x | @@ -4886,7 +5165,7 @@ describe('float window', function() ## grid 4 {1:^y }| {2:~ }| - ]], float_pos=expected_pos} + ]], float_pos=expected_pos} else screen:expect([[ x | @@ -5067,7 +5346,7 @@ describe('float window', function() ## grid 5 ^x | {0:~ }| - ]], float_pos=expected_pos} + ]], float_pos=expected_pos} else screen:expect([[ ^x | @@ -5102,7 +5381,7 @@ describe('float window', function() ## grid 5 x | {0:~ }| - ]], float_pos=expected_pos} + ]], float_pos=expected_pos} else screen:expect([[ x | @@ -5137,7 +5416,7 @@ describe('float window', function() ## grid 5 x | {0:~ }| - ]], float_pos=expected_pos} + ]], float_pos=expected_pos} else screen:expect([[ x | @@ -5173,7 +5452,7 @@ describe('float window', function() ## grid 5 ^x | {0:~ }| - ]], float_pos=expected_pos} + ]], float_pos=expected_pos} else screen:expect([[ ^x | @@ -5210,7 +5489,7 @@ describe('float window', function() ## grid 5 ^y | {0:~ }| - ]], float_pos=expected_pos} + ]], float_pos=expected_pos} else screen:expect([[ ^y | @@ -5245,7 +5524,7 @@ describe('float window', function() ## grid 5 y | {0:~ }| - ]], float_pos=expected_pos} + ]], float_pos=expected_pos} else screen:expect([[ y | @@ -5280,7 +5559,7 @@ describe('float window', function() ## grid 5 y | {0:~ }| - ]], float_pos=expected_pos} + ]], float_pos=expected_pos} else screen:expect([[ y | @@ -5317,7 +5596,7 @@ describe('float window', function() ## grid 5 ^ | {0:~ }| - ]], float_pos=expected_pos} + ]], float_pos=expected_pos} else screen:expect([[ ^ | @@ -5354,7 +5633,7 @@ describe('float window', function() ## grid 5 ^ | {0:~ }| - ]], float_pos=expected_pos} + ]], float_pos=expected_pos} else screen:expect([[ ^ | @@ -5397,7 +5676,7 @@ describe('float window', function() {0:~ }| {0:~ }| {0:~ }| - ]], float_pos=expected_pos} + ]], float_pos=expected_pos} else screen:expect([[ ^x {5:│}x | @@ -5440,17 +5719,17 @@ describe('float window', function() {0:~ }| {0:~ }| {0:~ }| - ]], float_pos=expected_pos} + ]], float_pos=expected_pos} else - screen:expect([[ - ^ {5:│}x | - {0:~ }{5:│}{0:~ }| - {0:~ }{1:y }{0: }| - {0:~ }{2:~ }{0: }| - {0:~ }{5:│}{0:~ }| - {4:[No Name] }{5:[No Name] [+] }| - :vnew | - ]]) + screen:expect([[ + ^ {5:│}x | + {0:~ }{5:│}{0:~ }| + {0:~ }{1:y }{0: }| + {0:~ }{2:~ }{0: }| + {0:~ }{5:│}{0:~ }| + {4:[No Name] }{5:[No Name] [+] }| + :vnew | + ]]) end end) @@ -5483,7 +5762,7 @@ describe('float window', function() {0:~ }| {0:~ }| {0:~ }| - ]], float_pos=expected_pos} + ]], float_pos=expected_pos} else screen:expect([[ ^ {5:│}x | @@ -5553,7 +5832,7 @@ describe('float window', function() [4] = {{id = 1001}, "NW", 1, 2, 5, true}, [5] = {{id = 1002}, "NW", 1, 4, 8, true} }} - else + else screen:expect([[ x | {0:~ }| @@ -5574,7 +5853,7 @@ describe('float window', function() [2:----------------------------------------]| [2:----------------------------------------]| [2:----------------------------------------]| - {5:[No Name] [+] }| + [2:----------------------------------------]| [3:----------------------------------------]| ## grid 2 x | @@ -5582,6 +5861,7 @@ describe('float window', function() {0:~ }| {0:~ }| {0:~ }| + {0:~ }| ## grid 3 :quit | ## grid 4 @@ -5590,14 +5870,14 @@ describe('float window', function() ]], float_pos={ [4] = {{id = 1001}, "NW", 1, 2, 5, true}, }} - else + else screen:expect([[ x | {0:~ }| {0:~ }{1:^y }{0: }| {0:~ }{2:~ }{0: }| {0:~ }| - {5:[No Name] [+] }| + {0:~ }| :quit | ]]) end @@ -5659,7 +5939,7 @@ describe('float window', function() {0:~ }| ## grid 3 | - ]]} + ]]} else screen:expect([[ ^x | @@ -5699,7 +5979,7 @@ describe('float window', function() ## grid 4 {1:y }| {2:~ }| - ]], float_pos=expected_pos} + ]], float_pos=expected_pos} else screen:expect([[ x | @@ -5736,7 +6016,7 @@ describe('float window', function() ## grid 4 {1:^y }| {2:~ }| - ]], float_pos=expected_pos} + ]], float_pos=expected_pos} else screen:expect([[ x | @@ -5806,7 +6086,7 @@ describe('float window', function() {0:~ }| {0:~ }| {0:~ }| - ]]} + ]]} else screen:expect([[ ^x | @@ -5843,7 +6123,7 @@ describe('float window', function() ## grid 5 x | {0:~ }| - ]], float_pos=expected_pos} + ]], float_pos=expected_pos} else screen:expect([[ x | @@ -5880,7 +6160,7 @@ describe('float window', function() ## grid 5 x | {0:~ }| - ]], float_pos=expected_pos} + ]], float_pos=expected_pos} else screen:expect([[ x | @@ -5914,7 +6194,7 @@ describe('float window', function() ## grid 4 ^y | {0:~ }| - ]]} + ]]} else screen:expect([[ x | @@ -5950,7 +6230,7 @@ describe('float window', function() ## grid 4 ^y | {0:~ }| - ]], float_pos=expected_pos} + ]], float_pos=expected_pos} else eq("UI doesn't support external windows", pcall_err(meths.win_set_config, 0, {external=true, width=30, height=2})) @@ -5980,6 +6260,78 @@ describe('float window', function() end end) + it('J (float with border)', function() + meths.win_set_config(win, {relative='editor', width=20, height=2, row=2, col=5, border='single'}) + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [3:----------------------------------------]| + ## grid 2 + ^x | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + | + ## grid 4 + {5:┌────────────────────┐}| + {5:│}{1:y }{5:│}| + {5:│}{2:~ }{5:│}| + {5:└────────────────────┘}| + ]], float_pos=expected_pos} + else + screen:expect([[ + ^x | + {0:~ }| + {0:~ }{5:┌────────────────────┐}{0: }| + {0:~ }{5:│}{1:y }{5:│}{0: }| + {0:~ }{5:│}{2:~ }{5:│}{0: }| + {0:~ }{5:└────────────────────┘}{0: }| + | + ]]) + end + + feed("<c-w>w<c-w>J") + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + {5:[No Name] [+] }| + [4:----------------------------------------]| + [4:----------------------------------------]| + {4:[No Name] [+] }| + [3:----------------------------------------]| + ## grid 2 + x | + {0:~ }| + ## grid 3 + | + ## grid 4 + ^y | + {0:~ }| + ]]} + else + screen:expect([[ + x | + {0:~ }| + {5:[No Name] [+] }| + ^y | + {0:~ }| + {4:[No Name] [+] }| + | + ]]) + end + end) + it('movements with nested split layout', function() command("set hidden") feed("<c-w>s<c-w>v<c-w>b<c-w>v") @@ -6342,7 +6694,7 @@ describe('float window', function() it("left drag changes visual selection in float window", function() local buf = meths.create_buf(false,false) - meths.buf_set_lines(buf, 0, -1, true, {'foo', 'bar'}) + meths.buf_set_lines(buf, 0, -1, true, {'foo', 'bar', 'baz'}) meths.open_win(buf, false, {relative='editor', width=20, height=3, row=2, col=5}) if multigrid then screen:expect{grid=[[ @@ -6366,13 +6718,14 @@ describe('float window', function() ## grid 5 {1:foo }| {1:bar }| - {2:~ }| + {1:baz }| ]], float_pos={ [5] = {{id = 1002}, "NW", 1, 2, 5, true, 50}; }, win_viewport={ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1}; - [5] = {win = {id = 1002}, topline = 0, botline = 3, curline = 0, curcol = 0, linecount = 2}; + [5] = {win = {id = 1002}, topline = 0, botline = 3, curline = 0, curcol = 0, linecount = 3}; }} + meths.input_mouse('left', 'press', '', 5, 0, 0) screen:expect{grid=[[ ## grid 1 @@ -6395,13 +6748,14 @@ describe('float window', function() ## grid 5 {1:^foo }| {1:bar }| - {2:~ }| + {1:baz }| ]], float_pos={ [5] = {{id = 1002}, "NW", 1, 2, 5, true, 50}; }, win_viewport={ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1}; - [5] = {win = {id = 1002}, topline = 0, botline = 3, curline = 0, curcol = 0, linecount = 2}; + [5] = {win = {id = 1002}, topline = 0, botline = 3, curline = 0, curcol = 0, linecount = 3}; }} + meths.input_mouse('left', 'drag', '', 5, 1, 2) screen:expect{grid=[[ ## grid 1 @@ -6424,12 +6778,12 @@ describe('float window', function() ## grid 5 {27:foo}{1: }| {27:ba}{1:^r }| - {2:~ }| + {1:baz }| ]], float_pos={ [5] = {{id = 1002}, "NW", 1, 2, 5, true, 50}; }, win_viewport={ [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1}; - [5] = {win = {id = 1002}, topline = 0, botline = 3, curline = 1, curcol = 2, linecount = 2}; + [5] = {win = {id = 1002}, topline = 0, botline = 3, curline = 1, curcol = 2, linecount = 3}; }} else screen:expect{grid=[[ @@ -6437,7 +6791,7 @@ describe('float window', function() {0:~ }| {0:~ }{1:foo }{0: }| {0:~ }{1:bar }{0: }| - {0:~ }{2:~ }{0: }| + {0:~ }{1:baz }{0: }| {0:~ }| | ]]} @@ -6448,7 +6802,7 @@ describe('float window', function() {0:~ }| {0:~ }{1:^foo }{0: }| {0:~ }{1:bar }{0: }| - {0:~ }{2:~ }{0: }| + {0:~ }{1:baz }{0: }| {0:~ }| | ]]} @@ -6459,13 +6813,396 @@ describe('float window', function() {0:~ }| {0:~ }{27:foo}{1: }{0: }| {0:~ }{27:ba}{1:^r }{0: }| - {0:~ }{2:~ }{0: }| + {0:~ }{1:baz }{0: }| {0:~ }| {3:-- VISUAL --} | ]]} end end) + it("left drag changes visual selection in float window with border", function() + local buf = meths.create_buf(false,false) + meths.buf_set_lines(buf, 0, -1, true, {'foo', 'bar', 'baz'}) + meths.open_win(buf, false, {relative='editor', width=20, height=3, row=0, col=5, border='single'}) + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [3:----------------------------------------]| + ## grid 2 + ^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + | + ## grid 5 + {5:┌────────────────────┐}| + {5:│}{1:foo }{5:│}| + {5:│}{1:bar }{5:│}| + {5:│}{1:baz }{5:│}| + {5:└────────────────────┘}| + ]], float_pos={ + [5] = {{id = 1002}, "NW", 1, 0, 5, true, 50}; + }, win_viewport={ + [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1}; + [5] = {win = {id = 1002}, topline = 0, botline = 3, curline = 0, curcol = 0, linecount = 3}; + }} + + meths.input_mouse('left', 'press', '', 5, 1, 1) + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [3:----------------------------------------]| + ## grid 2 + | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + | + ## grid 5 + {5:┌────────────────────┐}| + {5:│}{1:^foo }{5:│}| + {5:│}{1:bar }{5:│}| + {5:│}{1:baz }{5:│}| + {5:└────────────────────┘}| + ]], float_pos={ + [5] = {{id = 1002}, "NW", 1, 0, 5, true, 50}; + }, win_viewport={ + [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1}; + [5] = {win = {id = 1002}, topline = 0, botline = 3, curline = 0, curcol = 0, linecount = 3}; + }} + + meths.input_mouse('left', 'drag', '', 5, 2, 3) + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [3:----------------------------------------]| + ## grid 2 + | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + {3:-- VISUAL --} | + ## grid 5 + {5:┌────────────────────┐}| + {5:│}{27:foo}{1: }{5:│}| + {5:│}{27:ba}{1:^r }{5:│}| + {5:│}{1:baz }{5:│}| + {5:└────────────────────┘}| + ]], float_pos={ + [5] = {{id = 1002}, "NW", 1, 0, 5, true, 50}; + }, win_viewport={ + [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1}; + [5] = {win = {id = 1002}, topline = 0, botline = 3, curline = 1, curcol = 2, linecount = 3}; + }} + else + screen:expect{grid=[[ + ^ {5:┌────────────────────┐} | + {0:~ }{5:│}{1:foo }{5:│}{0: }| + {0:~ }{5:│}{1:bar }{5:│}{0: }| + {0:~ }{5:│}{1:baz }{5:│}{0: }| + {0:~ }{5:└────────────────────┘}{0: }| + {0:~ }| + | + ]]} + + meths.input_mouse('left', 'press', '', 0, 1, 6) + screen:expect{grid=[[ + {5:┌────────────────────┐} | + {0:~ }{5:│}{1:^foo }{5:│}{0: }| + {0:~ }{5:│}{1:bar }{5:│}{0: }| + {0:~ }{5:│}{1:baz }{5:│}{0: }| + {0:~ }{5:└────────────────────┘}{0: }| + {0:~ }| + | + ]]} + + meths.input_mouse('left', 'drag', '', 0, 2, 8) + screen:expect{grid=[[ + {5:┌────────────────────┐} | + {0:~ }{5:│}{27:foo}{1: }{5:│}{0: }| + {0:~ }{5:│}{27:ba}{1:^r }{5:│}{0: }| + {0:~ }{5:│}{1:baz }{5:│}{0: }| + {0:~ }{5:└────────────────────┘}{0: }| + {0:~ }| + {3:-- VISUAL --} | + ]]} + end + end) + + it("left drag changes visual selection in float window with winbar", function() + local buf = meths.create_buf(false,false) + meths.buf_set_lines(buf, 0, -1, true, {'foo', 'bar', 'baz'}) + local float_win = meths.open_win(buf, false, {relative='editor', width=20, height=4, row=1, col=5}) + meths.win_set_option(float_win, 'winbar', 'floaty bar') + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [3:----------------------------------------]| + ## grid 2 + ^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + | + ## grid 5 + {3:floaty bar }| + {1:foo }| + {1:bar }| + {1:baz }| + ]], float_pos={ + [5] = {{id = 1002}, "NW", 1, 1, 5, true, 50}; + }, win_viewport={ + [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1}; + [5] = {win = {id = 1002}, topline = 0, botline = 3, curline = 0, curcol = 0, linecount = 3}; + }} + + meths.input_mouse('left', 'press', '', 5, 1, 0) + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [3:----------------------------------------]| + ## grid 2 + | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + | + ## grid 5 + {3:floaty bar }| + {1:^foo }| + {1:bar }| + {1:baz }| + ]], float_pos={ + [5] = {{id = 1002}, "NW", 1, 1, 5, true, 50}; + }, win_viewport={ + [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1}; + [5] = {win = {id = 1002}, topline = 0, botline = 3, curline = 0, curcol = 0, linecount = 3}; + }} + + meths.input_mouse('left', 'drag', '', 5, 2, 2) + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [3:----------------------------------------]| + ## grid 2 + | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + {3:-- VISUAL --} | + ## grid 5 + {3:floaty bar }| + {27:foo}{1: }| + {27:ba}{1:^r }| + {1:baz }| + ]], float_pos={ + [5] = {{id = 1002}, "NW", 1, 1, 5, true, 50}; + }, win_viewport={ + [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1}; + [5] = {win = {id = 1002}, topline = 0, botline = 3, curline = 1, curcol = 2, linecount = 3}; + }} + else + screen:expect{grid=[[ + ^ | + {0:~ }{3:floaty bar }{0: }| + {0:~ }{1:foo }{0: }| + {0:~ }{1:bar }{0: }| + {0:~ }{1:baz }{0: }| + {0:~ }| + | + ]]} + + meths.input_mouse('left', 'press', '', 0, 2, 5) + screen:expect{grid=[[ + | + {0:~ }{3:floaty bar }{0: }| + {0:~ }{1:^foo }{0: }| + {0:~ }{1:bar }{0: }| + {0:~ }{1:baz }{0: }| + {0:~ }| + | + ]]} + + meths.input_mouse('left', 'drag', '', 0, 3, 7) + screen:expect{grid=[[ + | + {0:~ }{3:floaty bar }{0: }| + {0:~ }{27:foo}{1: }{0: }| + {0:~ }{27:ba}{1:^r }{0: }| + {0:~ }{1:baz }{0: }| + {0:~ }| + {3:-- VISUAL --} | + ]]} + end + end) + + it('left drag changes visual selection if float window is turned into a split', function() + local buf = meths.create_buf(false,false) + meths.buf_set_lines(buf, 0, -1, true, {'foo', 'bar', 'baz'}) + meths.open_win(buf, true, {relative='editor', width=20, height=3, row=2, col=5}) + command('wincmd L') + if multigrid then + screen:expect([[ + ## grid 1 + [2:-------------------]{5:│}[5:--------------------]| + [2:-------------------]{5:│}[5:--------------------]| + [2:-------------------]{5:│}[5:--------------------]| + [2:-------------------]{5:│}[5:--------------------]| + [2:-------------------]{5:│}[5:--------------------]| + {5:[No Name] }{4:[No Name] [+] }| + [3:----------------------------------------]| + ## grid 2 + | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + | + ## grid 5 + ^foo | + bar | + baz | + {0:~ }| + {0:~ }| + ]]) + + meths.input_mouse('left', 'press', '', 5, 2, 2) + screen:expect([[ + ## grid 1 + [2:-------------------]{5:│}[5:--------------------]| + [2:-------------------]{5:│}[5:--------------------]| + [2:-------------------]{5:│}[5:--------------------]| + [2:-------------------]{5:│}[5:--------------------]| + [2:-------------------]{5:│}[5:--------------------]| + {5:[No Name] }{4:[No Name] [+] }| + [3:----------------------------------------]| + ## grid 2 + | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + | + ## grid 5 + foo | + bar | + ba^z | + {0:~ }| + {0:~ }| + ]]) + + meths.input_mouse('left', 'drag', '', 5, 1, 1) + screen:expect([[ + ## grid 1 + [2:-------------------]{5:│}[5:--------------------]| + [2:-------------------]{5:│}[5:--------------------]| + [2:-------------------]{5:│}[5:--------------------]| + [2:-------------------]{5:│}[5:--------------------]| + [2:-------------------]{5:│}[5:--------------------]| + {5:[No Name] }{4:[No Name] [+] }| + [3:----------------------------------------]| + ## grid 2 + | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + {3:-- VISUAL --} | + ## grid 5 + foo | + b^a{27:r} | + {27:baz} | + {0:~ }| + {0:~ }| + ]]) + else + screen:expect([[ + {5:│}^foo | + {0:~ }{5:│}bar | + {0:~ }{5:│}baz | + {0:~ }{5:│}{0:~ }| + {0:~ }{5:│}{0:~ }| + {5:[No Name] }{4:[No Name] [+] }| + | + ]]) + + meths.input_mouse('left', 'press', '', 0, 2, 22) + screen:expect([[ + {5:│}foo | + {0:~ }{5:│}bar | + {0:~ }{5:│}ba^z | + {0:~ }{5:│}{0:~ }| + {0:~ }{5:│}{0:~ }| + {5:[No Name] }{4:[No Name] [+] }| + | + ]]) + + meths.input_mouse('left', 'drag', '', 0, 1, 21) + screen:expect([[ + {5:│}foo | + {0:~ }{5:│}b^a{27:r} | + {0:~ }{5:│}{27:baz} | + {0:~ }{5:│}{0:~ }| + {0:~ }{5:│}{0:~ }| + {5:[No Name] }{4:[No Name] [+] }| + {3:-- VISUAL --} | + ]]) + end + end) + it("'winblend' option", function() screen:try_resize(50,9) screen:set_default_attr_ids({ @@ -7306,6 +8043,100 @@ describe('float window', function() ]]} end end) + + it('can use winbar', function() + local buf = meths.create_buf(false,false) + local win1 = meths.open_win(buf, false, {relative='editor', width=15, height=3, row=1, col=5}) + meths.win_set_option(win1, 'winbar', 'floaty bar') + + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [3:----------------------------------------]| + ## grid 2 + ^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + | + ## grid 4 + {3:floaty bar }| + {1: }| + {2:~ }| + ]], float_pos={ + [4] = {{id = 1001}, "NW", 1, 1, 5, true, 50}; + }, win_viewport={ + [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1}; + [4] = {win = {id = 1001}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1}; + }} + else + screen:expect{grid=[[ + ^ | + {0:~ }{3:floaty bar }{0: }| + {0:~ }{1: }{0: }| + {0:~ }{2:~ }{0: }| + {0:~ }| + {0:~ }| + | + ]]} + end + + -- resize and add a border + meths.win_set_config(win1, {relative='editor', width=15, height=4, row=0, col=4, border = 'single'}) + + if multigrid then + screen:expect{grid=[[ + ## grid 1 + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [2:----------------------------------------]| + [3:----------------------------------------]| + ## grid 2 + ^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + ## grid 3 + | + ## grid 4 + {5:┌───────────────┐}| + {5:│}{3:floaty bar }{5:│}| + {5:│}{1: }{5:│}| + {5:│}{2:~ }{5:│}| + {5:│}{2:~ }{5:│}| + {5:└───────────────┘}| + ]], float_pos={ + [4] = {{id = 1001}, "NW", 1, 0, 4, true, 50}; + }, win_viewport={ + [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1}; + [4] = {win = {id = 1001}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1}; + }} + else + screen:expect{grid=[[ + ^ {5:┌───────────────┐} | + {0:~ }{5:│}{3:floaty bar }{5:│}{0: }| + {0:~ }{5:│}{1: }{5:│}{0: }| + {0:~ }{5:│}{2:~ }{5:│}{0: }| + {0:~ }{5:│}{2:~ }{5:│}{0: }| + {0:~ }{5:└───────────────┘}{0: }| + | + ]]} + end + end) end describe('with ext_multigrid', function() diff --git a/test/functional/ui/fold_spec.lua b/test/functional/ui/fold_spec.lua index 3e0e15c2b7..394f2f5f49 100644 --- a/test/functional/ui/fold_spec.lua +++ b/test/functional/ui/fold_spec.lua @@ -21,12 +21,14 @@ local content1 = [[ describe("folded lines", function() before_each(function() clear() + command('hi VertSplit gui=reverse') end) local function with_ext_multigrid(multigrid) local screen before_each(function() clear() + command('hi VertSplit gui=reverse') screen = Screen.new(45, 8) screen:attach({rgb=true, ext_multigrid=multigrid}) screen:set_default_attr_ids({ @@ -1672,7 +1674,7 @@ describe("folded lines", function() end -- relax the maximum fdc thus fdc should expand to - -- accomodate the current number of folds + -- accommodate the current number of folds command("set foldcolumn=auto:4") if multigrid then screen:expect([[ @@ -1755,6 +1757,67 @@ describe("folded lines", function() end assert_alive() end) + + it('work correctly with :move #18668', function() + screen:try_resize(45, 12) + source([[ + set foldmethod=expr foldexpr=indent(v:lnum) + let content = ['', '', 'Line1', ' Line2', ' Line3', + \ 'Line4', ' Line5', ' Line6', + \ 'Line7', ' Line8', ' Line9'] + call append(0, content) + normal! zM + call cursor(4, 1) + move 2 + move 1 + ]]) + if multigrid then + screen:expect([[ + ## grid 1 + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [2:---------------------------------------------]| + [3:---------------------------------------------]| + ## grid 2 + | + {5:^+-- 2 lines: Line2··························}| + | + Line1 | + Line4 | + {5:+-- 2 lines: Line5··························}| + Line7 | + {5:+-- 2 lines: Line8··························}| + | + {1:~ }| + {1:~ }| + ## grid 3 + | + ]]) + else + screen:expect([[ + | + {5:^+-- 2 lines: Line2··························}| + | + Line1 | + Line4 | + {5:+-- 2 lines: Line5··························}| + Line7 | + {5:+-- 2 lines: Line8··························}| + | + {1:~ }| + {1:~ }| + | + ]]) + end + end) end describe("with ext_multigrid", function() diff --git a/test/functional/ui/highlight_spec.lua b/test/functional/ui/highlight_spec.lua index 255180bba8..946129b082 100644 --- a/test/functional/ui/highlight_spec.lua +++ b/test/functional/ui/highlight_spec.lua @@ -2,7 +2,7 @@ local helpers = require('test.functional.helpers')(after_each) local Screen = require('test.functional.ui.screen') local os = require('os') local clear, feed, insert = helpers.clear, helpers.feed, helpers.insert -local command = helpers.command +local command, exec = helpers.command, helpers.exec local eval, exc_exec = helpers.eval, helpers.exc_exec local feed_command, eq = helpers.feed_command, helpers.eq local curbufmeths = helpers.curbufmeths @@ -104,12 +104,12 @@ describe('highlight defaults', function() }) feed_command('sp', 'vsp', 'vsp') screen:expect([[ - ^ {2:│} {2:│} | - {0:~ }{2:│}{0:~ }{2:│}{0:~ }| - {0:~ }{2:│}{0:~ }{2:│}{0:~ }| - {0:~ }{2:│}{0:~ }{2:│}{0:~ }| - {0:~ }{2:│}{0:~ }{2:│}{0:~ }| - {0:~ }{2:│}{0:~ }{2:│}{0:~ }| + ^ │ │ | + {0:~ }│{0:~ }│{0:~ }| + {0:~ }│{0:~ }│{0:~ }| + {0:~ }│{0:~ }│{0:~ }| + {0:~ }│{0:~ }│{0:~ }| + {0:~ }│{0:~ }│{0:~ }| {1:[No Name] }{2:[No Name] [No Name] }| | {0:~ }| @@ -122,12 +122,12 @@ describe('highlight defaults', function() -- navigate to verify that the attributes are properly moved feed('<c-w>j') screen:expect([[ - {2:│} {2:│} | - {0:~ }{2:│}{0:~ }{2:│}{0:~ }| - {0:~ }{2:│}{0:~ }{2:│}{0:~ }| - {0:~ }{2:│}{0:~ }{2:│}{0:~ }| - {0:~ }{2:│}{0:~ }{2:│}{0:~ }| - {0:~ }{2:│}{0:~ }{2:│}{0:~ }| + │ │ | + {0:~ }│{0:~ }│{0:~ }| + {0:~ }│{0:~ }│{0:~ }| + {0:~ }│{0:~ }│{0:~ }| + {0:~ }│{0:~ }│{0:~ }| + {0:~ }│{0:~ }│{0:~ }| {2:[No Name] [No Name] [No Name] }| ^ | {0:~ }| @@ -142,12 +142,12 @@ describe('highlight defaults', function() -- (upstream vim has the same behavior) feed('<c-w>k<c-w>l') screen:expect([[ - {2:│}^ {2:│} | - {0:~ }{2:│}{0:~ }{2:│}{0:~ }| - {0:~ }{2:│}{0:~ }{2:│}{0:~ }| - {0:~ }{2:│}{0:~ }{2:│}{0:~ }| - {0:~ }{2:│}{0:~ }{2:│}{0:~ }| - {0:~ }{2:│}{0:~ }{2:│}{0:~ }| + │^ │ | + {0:~ }│{0:~ }│{0:~ }| + {0:~ }│{0:~ }│{0:~ }| + {0:~ }│{0:~ }│{0:~ }| + {0:~ }│{0:~ }│{0:~ }| + {0:~ }│{0:~ }│{0:~ }| {2:[No Name] }{1:[No Name] }{2:[No Name] }| | {0:~ }| @@ -159,12 +159,12 @@ describe('highlight defaults', function() ]]) feed('<c-w>l') screen:expect([[ - {2:│} {2:│}^ | - {0:~ }{2:│}{0:~ }{2:│}{0:~ }| - {0:~ }{2:│}{0:~ }{2:│}{0:~ }| - {0:~ }{2:│}{0:~ }{2:│}{0:~ }| - {0:~ }{2:│}{0:~ }{2:│}{0:~ }| - {0:~ }{2:│}{0:~ }{2:│}{0:~ }| + │ │^ | + {0:~ }│{0:~ }│{0:~ }| + {0:~ }│{0:~ }│{0:~ }| + {0:~ }│{0:~ }│{0:~ }| + {0:~ }│{0:~ }│{0:~ }| + {0:~ }│{0:~ }│{0:~ }| {2:[No Name] [No Name] }{1:[No Name] }| | {0:~ }| @@ -176,12 +176,12 @@ describe('highlight defaults', function() ]]) feed('<c-w>h<c-w>h') screen:expect([[ - ^ {2:│} {2:│} | - {0:~ }{2:│}{0:~ }{2:│}{0:~ }| - {0:~ }{2:│}{0:~ }{2:│}{0:~ }| - {0:~ }{2:│}{0:~ }{2:│}{0:~ }| - {0:~ }{2:│}{0:~ }{2:│}{0:~ }| - {0:~ }{2:│}{0:~ }{2:│}{0:~ }| + ^ │ │ | + {0:~ }│{0:~ }│{0:~ }| + {0:~ }│{0:~ }│{0:~ }| + {0:~ }│{0:~ }│{0:~ }| + {0:~ }│{0:~ }│{0:~ }| + {0:~ }│{0:~ }│{0:~ }| {1:[No Name] }{2:[No Name] [No Name] }| | {0:~ }| @@ -840,7 +840,7 @@ describe("'listchars' highlight", function() end) end) -describe('CursorLine highlight', function() +describe('CursorLine and CursorLineNr highlights', function() before_each(clear) it('overridden by Error, ColorColumn if fg not set', function() @@ -938,17 +938,18 @@ describe('CursorLine highlight', function() [2] = {foreground = Screen.colors.Yellow}; [3] = {foreground = Screen.colors.Red, background = Screen.colors.Green}; [4] = {foreground = Screen.colors.Green, background = Screen.colors.Red}; + [5] = {bold = true}, -- ModeMsg }) screen:attach() - feed_command('set wrap cursorline cursorlineopt=screenline') - feed_command('set showbreak=>>>') - feed_command('highlight clear NonText') - feed_command('highlight clear CursorLine') - feed_command('highlight NonText guifg=Yellow gui=NONE') - feed_command('highlight LineNr guifg=Red guibg=Green gui=NONE') - feed_command('highlight CursorLine guifg=Black guibg=White gui=NONE') - feed_command('highlight CursorLineNr guifg=Green guibg=Red gui=NONE') + command('set wrap cursorline cursorlineopt=screenline') + command('set showbreak=>>>') + command('highlight clear NonText') + command('highlight clear CursorLine') + command('highlight NonText guifg=Yellow gui=NONE') + command('highlight LineNr guifg=Red guibg=Green gui=NONE') + command('highlight CursorLine guifg=Black guibg=White gui=NONE') + command('highlight CursorLineNr guifg=Green guibg=Red gui=NONE') feed('30iø<esc>o<esc>30ia<esc>') @@ -978,7 +979,7 @@ describe('CursorLine highlight', function() ]]) -- CursorLineNr should not apply to line number when 'cursorlineopt' does not contain "number" - feed_command('set relativenumber numberwidth=2') + command('set relativenumber numberwidth=2') screen:expect([[ {3:0 }{1:øøøøøøøøøøøø^øøøøøø}| {3: }{2:>>>}øøøøøøøøøøøø | @@ -988,7 +989,7 @@ describe('CursorLine highlight', function() ]]) -- CursorLineNr should apply to line number when 'cursorlineopt' contains "number" - feed_command('set cursorlineopt+=number') + command('set cursorlineopt+=number') screen:expect([[ {4:0 }{1:øøøøøøøøøøøø^øøøøøø}| {3: }{2:>>>}øøøøøøøøøøøø | @@ -1020,6 +1021,44 @@ describe('CursorLine highlight', function() {3: }{2:>>>}{1:aaaaaaaaa^aaa }| | ]]) + + -- updated in Insert mode + feed('I') + screen:expect([[ + {3:1 }øøøøøøøøøøøøøøøøøø| + {3: }{2:>>>}øøøøøøøøøøøø | + {4:0 }{1:^aaaaaaaaaaaaaaaaaa}| + {3: }{2:>>>}aaaaaaaaaaaa | + {5:-- INSERT --} | + ]]) + + feed('<Esc>gg') + screen:expect([[ + {4:0 }{1:^øøøøøøøøøøøøøøøøøø}| + {3: }{2:>>>}øøøøøøøøøøøø | + {3:1 }aaaaaaaaaaaaaaaaaa| + {3: }{2:>>>}aaaaaaaaaaaa | + | + ]]) + + command('inoremap <F2> <Cmd>call cursor(1, 1)<CR>') + feed('A') + screen:expect([[ + {4:0 }øøøøøøøøøøøøøøøøøø| + {3: }{2:>>>}{1:øøøøøøøøøøøø^ }| + {3:1 }aaaaaaaaaaaaaaaaaa| + {3: }{2:>>>}aaaaaaaaaaaa | + {5:-- INSERT --} | + ]]) + + feed('<F2>') + screen:expect([[ + {4:0 }{1:^øøøøøøøøøøøøøøøøøø}| + {3: }{2:>>>}øøøøøøøøøøøø | + {3:1 }aaaaaaaaaaaaaaaaaa| + {3: }{2:>>>}aaaaaaaaaaaa | + {5:-- INSERT --} | + ]]) end) it('always updated. vim-patch:8.1.0849', function() @@ -1082,7 +1121,47 @@ describe('CursorLine highlight', function() ]]) end) - it('with split-windows in diff-mode', function() + it('is updated if cursor is moved up from timer vim-patch:8.2.4591', function() + local screen = Screen.new(50, 8) + screen:set_default_attr_ids({ + [1] = {background = Screen.colors.Gray90}, -- CursorLine + [2] = {bold = true, foreground = Screen.colors.Blue1}, -- NonText + }) + screen:attach() + exec([[ + call setline(1, ['aaaaa', 'bbbbb', 'ccccc', 'ddddd']) + set cursorline + call cursor(4, 1) + + func Func(timer) + call cursor(2, 1) + endfunc + + call timer_start(300, 'Func') + ]]) + screen:expect({grid = [[ + aaaaa | + bbbbb | + ccccc | + {1:^ddddd }| + {2:~ }| + {2:~ }| + {2:~ }| + | + ]], timeout = 100}) + screen:expect({grid = [[ + aaaaa | + {1:^bbbbb }| + ccccc | + ddddd | + {2:~ }| + {2:~ }| + {2:~ }| + | + ]]}) + end) + + it('with split windows in diff mode', function() local screen = Screen.new(50,12) screen:set_default_attr_ids({ [1] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.WebGray}, @@ -1094,7 +1173,6 @@ describe('CursorLine highlight', function() [7] = {background = Screen.colors.Red, foreground = Screen.colors.White}, [8] = {bold = true, foreground = Screen.colors.Blue1}, [9] = {bold = true, reverse = true}, - [10] = {bold = true}, }) screen:attach() @@ -1108,31 +1186,31 @@ describe('CursorLine highlight', function() feed('<esc>gg') command('windo diffthis') screen:expect([[ - {1: }{7:line 1 some text }{4:│}{1: }{7:^line 1 some text }| - {1: }{3:line 2 mo}{2:Re text!}{3: }{4:│}{1: }{3:line 2 mo}{2:re text}{3: }| - {1: }{5:extra line! }{4:│}{1: }{6:----------------------}| - {1: }extra line! {4:│}{1: }extra line! | - {1: }extra line! {4:│}{1: }extra line! | - {1: }last line ... {4:│}{1: }last line ... | - {1: } {4:│}{1: } | - {8:~ }{4:│}{8:~ }| - {8:~ }{4:│}{8:~ }| - {8:~ }{4:│}{8:~ }| + {1: }{7:line 1 some text }│{1: }{7:^line 1 some text }| + {1: }{3:line 2 mo}{2:Re text!}{3: }│{1: }{3:line 2 mo}{2:re text}{3: }| + {1: }{5:extra line! }│{1: }{6:----------------------}| + {1: }extra line! │{1: }extra line! | + {1: }extra line! │{1: }extra line! | + {1: }last line ... │{1: }last line ... | + {1: } │{1: } | + {8:~ }│{8:~ }| + {8:~ }│{8:~ }| + {8:~ }│{8:~ }| {4:[No Name] [+] }{9:[No Name] [+] }| | ]]) feed('jjjjj') screen:expect([[ - {1: }line 1 some text {4:│}{1: }line 1 some text | - {1: }{3:line 2 mo}{2:Re text!}{3: }{4:│}{1: }{3:line 2 mo}{2:re text}{3: }| - {1: }{5:extra line! }{4:│}{1: }{6:----------------------}| - {1: }extra line! {4:│}{1: }extra line! | - {1: }extra line! {4:│}{1: }extra line! | - {1: }last line ... {4:│}{1: }last line ... | - {1: }{7: }{4:│}{1: }{7:^ }| - {8:~ }{4:│}{8:~ }| - {8:~ }{4:│}{8:~ }| - {8:~ }{4:│}{8:~ }| + {1: }line 1 some text │{1: }line 1 some text | + {1: }{3:line 2 mo}{2:Re text!}{3: }│{1: }{3:line 2 mo}{2:re text}{3: }| + {1: }{5:extra line! }│{1: }{6:----------------------}| + {1: }extra line! │{1: }extra line! | + {1: }extra line! │{1: }extra line! | + {1: }last line ... │{1: }last line ... | + {1: }{7: }│{1: }{7:^ }| + {8:~ }│{8:~ }| + {8:~ }│{8:~ }| + {8:~ }│{8:~ }| {4:[No Name] [+] }{9:[No Name] [+] }| | ]]) @@ -1142,16 +1220,16 @@ describe('CursorLine highlight', function() command('hi CursorLine ctermbg=red ctermfg=NONE guibg=red guifg=NONE') feed('kkkk') screen:expect([[ - {1: }line 1 some text {4:│}{1: }line 1 some text | - {1: }{11:line 2 mo}{12:Re text!}{11: }{4:│}{1: }{11:^line 2 mo}{12:re text}{11: }| - {1: }{5:extra line! }{4:│}{1: }{6:----------------------}| - {1: }extra line! {4:│}{1: }extra line! | - {1: }extra line! {4:│}{1: }extra line! | - {1: }last line ... {4:│}{1: }last line ... | - {1: } {4:│}{1: } | - {8:~ }{4:│}{8:~ }| - {8:~ }{4:│}{8:~ }| - {8:~ }{4:│}{8:~ }| + {1: }line 1 some text │{1: }line 1 some text | + {1: }{11:line 2 mo}{12:Re text!}{11: }│{1: }{11:^line 2 mo}{12:re text}{11: }| + {1: }{5:extra line! }│{1: }{6:----------------------}| + {1: }extra line! │{1: }extra line! | + {1: }extra line! │{1: }extra line! | + {1: }last line ... │{1: }last line ... | + {1: } │{1: } | + {8:~ }│{8:~ }| + {8:~ }│{8:~ }| + {8:~ }│{8:~ }| {4:[No Name] [+] }{9:[No Name] [+] }| | ]], { @@ -1171,8 +1249,261 @@ describe('CursorLine highlight', function() background = Screen.colors.Red}, }) end) + + it('CursorLineNr shows correctly just below filler lines', function() + local screen = Screen.new(50,12) + screen:set_default_attr_ids({ + [1] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.WebGray}, + [2] = {background = Screen.colors.LightCyan1, bold = true, foreground = Screen.colors.Blue1}, + [3] = {reverse = true}, + [4] = {background = Screen.colors.LightBlue}, + [5] = {background = Screen.colors.Red, foreground = Screen.colors.White}, + [6] = {background = Screen.colors.White, bold = true, foreground = Screen.colors.Black}, + [7] = {bold = true, foreground = Screen.colors.Blue1}, + [8] = {bold = true, reverse = true}, + [9] = {foreground = Screen.colors.Brown}, + }) + screen:attach() + + command('hi CursorLine guibg=red guifg=white') + command('hi CursorLineNr guibg=white guifg=black gui=bold') + command('set cursorline number') + command('call setline(1, ["baz", "foo", "foo", "bar"])') + feed('2gg0') + command('vnew') + command('call setline(1, ["foo", "foo", "bar"])') + command('windo diffthis') + command('1wincmd w') + screen:expect([[ + {1: }{9: }{2:-------------------}│{1: }{9: 1 }{4:baz }| + {1: }{6: 1 }{5:^foo }│{1: }{6: 2 }{5:foo }| + {1: }{9: 2 }foo │{1: }{9: 3 }foo | + {1: }{9: 3 }bar │{1: }{9: 4 }bar | + {7:~ }│{7:~ }| + {7:~ }│{7:~ }| + {7:~ }│{7:~ }| + {7:~ }│{7:~ }| + {7:~ }│{7:~ }| + {7:~ }│{7:~ }| + {8:[No Name] [+] }{3:[No Name] [+] }| + | + ]]) + command('set cursorlineopt=number') + screen:expect([[ + {1: }{9: }{2:-------------------}│{1: }{9: 1 }{4:baz }| + {1: }{6: 1 }^foo │{1: }{6: 2 }{5:foo }| + {1: }{9: 2 }foo │{1: }{9: 3 }foo | + {1: }{9: 3 }bar │{1: }{9: 4 }bar | + {7:~ }│{7:~ }| + {7:~ }│{7:~ }| + {7:~ }│{7:~ }| + {7:~ }│{7:~ }| + {7:~ }│{7:~ }| + {7:~ }│{7:~ }| + {8:[No Name] [+] }{3:[No Name] [+] }| + | + ]]) + end) +end) + +describe('CursorColumn highlight', function() + local screen + before_each(function() + clear() + screen = Screen.new(50, 8) + screen:set_default_attr_ids({ + [1] = {background = Screen.colors.Gray90}, -- CursorColumn + [2] = {bold = true, foreground = Screen.colors.Blue1}, -- NonText + [3] = {bold = true}, -- ModeMsg + }) + screen:attach() + end) + + it('is updated when pressing "i" on a TAB character', function() + exec([[ + call setline(1, ['123456789', "a\tb"]) + set cursorcolumn + call cursor(2, 2) + ]]) + screen:expect([[ + 1234567{1:8}9 | + a ^ b | + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + | + ]]) + feed('i') + screen:expect([[ + 1{1:2}3456789 | + a^ b | + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + {3:-- INSERT --} | + ]]) + feed('<C-O>') + screen:expect([[ + 1234567{1:8}9 | + a ^ b | + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + {3:-- (insert) --} | + ]]) + feed('i') + screen:expect([[ + 1{1:2}3456789 | + a^ b | + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + {3:-- INSERT --} | + ]]) + end) + + it('is updated if cursor is moved from timer', function() + exec([[ + call setline(1, ['aaaaa', 'bbbbb', 'ccccc', 'ddddd']) + set cursorcolumn + call cursor(4, 5) + + func Func(timer) + call cursor(1, 1) + endfunc + + call timer_start(300, 'Func') + ]]) + screen:expect({grid = [[ + aaaa{1:a} | + bbbb{1:b} | + cccc{1:c} | + dddd^d | + {2:~ }| + {2:~ }| + {2:~ }| + | + ]], timeout = 100}) + screen:expect({grid = [[ + ^aaaaa | + {1:b}bbbb | + {1:c}cccc | + {1:d}dddd | + {2:~ }| + {2:~ }| + {2:~ }| + | + ]]}) + end) end) +describe('ColorColumn highlight', function() + local screen + + before_each(function() + clear() + screen = Screen.new(40, 15) + Screen:set_default_attr_ids({ + [1] = {background = Screen.colors.LightRed}, -- ColorColumn + [2] = {background = Screen.colors.Grey90}, -- CursorLine + [3] = {foreground = Screen.colors.Brown}, -- LineNr + [4] = {foreground = Screen.colors.Brown, bold = true}, -- CursorLineNr + [5] = {foreground = Screen.colors.Blue, bold = true}, -- NonText + -- NonText and ColorColumn + [6] = {foreground = Screen.colors.Blue, background = Screen.colors.LightRed, bold = true}, + [7] = {reverse = true, bold = true}, -- StatusLine + [8] = {reverse = true}, -- StatusLineNC + }) + screen:attach() + end) + + it('when entering a buffer vim-patch:8.1.2073', function() + exec([[ + set nohidden + split + edit X + call setline(1, ["1111111111","22222222222","3333333333"]) + set nomodified + set colorcolumn=3,9 + set number cursorline cursorlineopt=number + wincmd w + buf X + ]]) + screen:expect([[ + {4: 1 }11{1:1}11111{1:1}1 | + {3: 2 }22{1:2}22222{1:2}22 | + {3: 3 }33{1:3}33333{1:3}3 | + {5:~ }| + {5:~ }| + {5:~ }| + {8:X }| + {4: 1 }^11{1:1}11111{1:1}1 | + {3: 2 }22{1:2}22222{1:2}22 | + {3: 3 }33{1:3}33333{1:3}3 | + {5:~ }| + {5:~ }| + {5:~ }| + {7:X }| + | + ]]) + end) + + it("in 'breakindent' vim-patch:8.2.1689", function() + exec([[ + call setline(1, 'The quick brown fox jumped over the lazy dogs') + set co=40 linebreak bri briopt=shift:2 cc=40,41,43 + ]]) + screen:expect([[ + ^The quick brown fox jumped over the {1: }| + {1: } {1:l}azy dogs | + {5:~ }| + {5:~ }| + {5:~ }| + {5:~ }| + {5:~ }| + {5:~ }| + {5:~ }| + {5:~ }| + {5:~ }| + {5:~ }| + {5:~ }| + {5:~ }| + | + ]]) + end) + + it("in 'showbreak' vim-patch:8.2.1689", function() + exec([[ + call setline(1, 'The quick brown fox jumped over the lazy dogs') + set co=40 showbreak=+++>\\ cc=40,41,43 + ]]) + screen:expect([[ + ^The quick brown fox jumped over the laz{1:y}| + {6:+}{5:+}{6:+}{5:>\} dogs | + {5:~ }| + {5:~ }| + {5:~ }| + {5:~ }| + {5:~ }| + {5:~ }| + {5:~ }| + {5:~ }| + {5:~ }| + {5:~ }| + {5:~ }| + {5:~ }| + | + ]]) + end) +end) describe("MsgSeparator highlight and msgsep fillchar", function() local screen @@ -1378,6 +1709,46 @@ describe("'number' and 'relativenumber' highlight", function() | ]]) end) + + it('relative number highlight is updated if cursor is moved from timer', function() + local screen = Screen.new(50, 8) + screen:set_default_attr_ids({ + [1] = {foreground = Screen.colors.Brown}, -- LineNr + [2] = {bold = true, foreground = Screen.colors.Blue1}, -- NonText + }) + screen:attach() + exec([[ + call setline(1, ['aaaaa', 'bbbbb', 'ccccc', 'ddddd']) + set relativenumber + call cursor(4, 1) + + func Func(timer) + call cursor(1, 1) + endfunc + + call timer_start(300, 'Func') + ]]) + screen:expect({grid = [[ + {1: 3 }aaaaa | + {1: 2 }bbbbb | + {1: 1 }ccccc | + {1: 0 }^ddddd | + {2:~ }| + {2:~ }| + {2:~ }| + | + ]], timeout = 100}) + screen:expect({grid = [[ + {1: 0 }^aaaaa | + {1: 1 }bbbbb | + {1: 2 }ccccc | + {1: 3 }ddddd | + {2:~ }| + {2:~ }| + {2:~ }| + | + ]]}) + end) end) describe("'winhighlight' highlight", function() @@ -1478,8 +1849,7 @@ describe("'winhighlight' highlight", function() ]], unchanged=true} end) - - it('works local to the buffer', function() + it('works local to the window', function() insert("aa") command("split") command("setlocal winhl=Normal:Background1") @@ -1870,4 +2240,35 @@ describe("'winhighlight' highlight", function() | ]]} end) + + it('can override StatusLine and StatusLineNC', function() + command('set winhighlight=StatusLine:Background1,StatusLineNC:Background2') + command('split') + screen:expect([[ + ^ | + {0:~ }| + {0:~ }| + {1:[No Name] }| + | + {0:~ }| + {5:[No Name] }| + | + ]]) + end) + + it('can override WinBar and WinBarNC #19345', function() + command('setlocal winbar=foobar') + command('set winhighlight=WinBar:Background1,WinBarNC:Background2') + command('split') + screen:expect([[ + {1:foobar }| + ^ | + {0:~ }| + {3:[No Name] }| + {5:foobar }| + | + {4:[No Name] }| + | + ]]) + end) end) diff --git a/test/functional/ui/hlstate_spec.lua b/test/functional/ui/hlstate_spec.lua index 2a567b28ee..df7f34aa7f 100644 --- a/test/functional/ui/hlstate_spec.lua +++ b/test/functional/ui/hlstate_spec.lua @@ -5,7 +5,7 @@ local clear, insert = helpers.clear, helpers.insert local command = helpers.command local meths = helpers.meths local iswin = helpers.iswin -local nvim_dir = helpers.nvim_dir +local testprg = helpers.testprg local thelpers = require('test.functional.terminal.helpers') describe('ext_hlstate detailed highlights', function() @@ -14,6 +14,7 @@ describe('ext_hlstate detailed highlights', function() before_each(function() clear() command('syntax on') + command('hi VertSplit gui=reverse') screen = Screen.new(40, 8) screen:attach({ext_hlstate=true}) end) @@ -59,7 +60,7 @@ describe('ext_hlstate detailed highlights', function() it('work with cleared UI highlights', function() screen:set_default_attr_ids({ - [1] = {{}, {{hi_name = "VertSplit", ui_name = "VertSplit", kind = "ui"}}}, + [1] = {{}, {{hi_name = "Normal", ui_name = "WinSeparator", kind = "ui"}}}, [2] = {{bold = true, foreground = Screen.colors.Blue1}, {{hi_name = "NonText", ui_name = "EndOfBuffer", kind = "ui"}}}, [3] = {{bold = true, reverse = true}, @@ -179,6 +180,8 @@ describe('ext_hlstate detailed highlights', function() end) it("work with :terminal", function() + if helpers.pending_win32(pending) then return end + screen:set_default_attr_ids({ [1] = {{}, {{hi_name = "TermCursorNC", ui_name = "TermCursorNC", kind = "ui"}}}, [2] = {{foreground = tonumber('0x00ccff'), fg_indexed=true}, {{kind = "term"}}}, @@ -188,7 +191,7 @@ describe('ext_hlstate detailed highlights', function() [6] = {{foreground = tonumber('0x40ffff'), fg_indexed=true}, {5, 1}}, [7] = {{}, {{hi_name = "MsgArea", ui_name = "MsgArea", kind = "ui"}}}, }) - command('enew | call termopen(["'..nvim_dir..'/tty-test"])') + command(("enew | call termopen(['%s'])"):format(testprg('tty-test'))) screen:expect([[ ^tty ready | {1: } | diff --git a/test/functional/ui/inccommand_spec.lua b/test/functional/ui/inccommand_spec.lua index b6e2f2311f..d8dd546a8d 100644 --- a/test/functional/ui/inccommand_spec.lua +++ b/test/functional/ui/inccommand_spec.lua @@ -17,7 +17,7 @@ local source = helpers.source local poke_eventloop = helpers.poke_eventloop local nvim = helpers.nvim local sleep = helpers.sleep -local nvim_dir = helpers.nvim_dir +local testprg = helpers.testprg local assert_alive = helpers.assert_alive local default_text = [[ @@ -123,13 +123,11 @@ describe(":substitute, inccommand=split interactivity", function() it("no preview if invoked by feedkeys()", function() -- in a script... source([[:call feedkeys(":%s/tw/MO/g\<CR>")]]) - poke_eventloop() -- or interactively... - feed([[:call feedkeys(":%s/tw/MO/g\<CR>")<CR>]]) - poke_eventloop() + feed([[:call feedkeys(":%s/bs/BUU/g\<lt>CR>")<CR>]]) eq(1, eval("bufnr('$')")) -- sanity check: assert the buffer state - expect(default_text:gsub("tw", "MO")) + expect(default_text:gsub("tw", "MO"):gsub("bs", "BUU")) end) end) @@ -257,42 +255,70 @@ describe(":substitute, 'inccommand' preserves", function() end) end - for _, case in pairs{"", "split", "nosplit"} do - it("visual selection for non-previewable command (inccommand="..case..") #5888", function() + for _, case in ipairs({'', 'split', 'nosplit'}) do + it('previous substitute string ~ (inccommand='..case..') #12109', function() local screen = Screen.new(30,10) common_setup(screen, case, default_text) - feed('1G2V') - feed(':s') - screen:expect([[ - {vis:Inc substitution on} | - t{vis:wo lines} | - | - {15:~ }| - {15:~ }| - {15:~ }| - {15:~ }| - {15:~ }| - {15:~ }| - :'<,'>s^ | - ]]) + feed(':%s/Inc/SUB<CR>') + expect([[ + SUB substitution on + two lines + ]]) - feed('o') - screen:expect([[ - {vis:Inc substitution on} | - t{vis:wo lines} | - | - {15:~ }| - {15:~ }| - {15:~ }| - {15:~ }| - {15:~ }| - {15:~ }| - :'<,'>so^ | - ]]) + feed(':%s/line/') + poke_eventloop() + feed('~') + poke_eventloop() + feed('<CR>') + expect([[ + SUB substitution on + two SUBs + ]]) + + feed(':%s/sti/') + poke_eventloop() + feed('~') + poke_eventloop() + feed('B') + poke_eventloop() + feed('<CR>') + expect([[ + SUB subSUBBtution on + two SUBs + ]]) + + feed(':%s/ion/NEW<CR>') + expect([[ + SUB subSUBBtutNEW on + two SUBs + ]]) + + feed(':%s/two/') + poke_eventloop() + feed('N') + poke_eventloop() + feed('~') + poke_eventloop() + feed('<CR>') + expect([[ + SUB subSUBBtutNEW on + NNEW SUBs + ]]) + + feed(':%s/bS/') + poke_eventloop() + feed('~') + poke_eventloop() + feed('W') + poke_eventloop() + feed('<CR>') + expect([[ + SUB suNNEWWUBBtutNEW on + NNEW SUBs + ]]) end) end - end) describe(":substitute, 'inccommand' preserves undo", function() @@ -317,7 +343,7 @@ describe(":substitute, 'inccommand' preserves undo", function() } local function test_sub(substring, split, redoable) - clear() + command('bwipe!') feed_command("set inccommand=" .. split) insert("1") @@ -343,7 +369,7 @@ describe(":substitute, 'inccommand' preserves undo", function() end local function test_notsub(substring, split, redoable) - clear() + command('bwipe!') feed_command("set inccommand=" .. split) insert("1") @@ -377,7 +403,7 @@ describe(":substitute, 'inccommand' preserves undo", function() local function test_threetree(substring, split) - clear() + command('bwipe!') feed_command("set inccommand=" .. split) insert("1") @@ -429,6 +455,8 @@ describe(":substitute, 'inccommand' preserves undo", function() 2]]) end + before_each(clear) + it("at a non-leaf of the undo tree", function() for _, case in pairs(cases) do for _, str in pairs(substrings) do @@ -1279,6 +1307,108 @@ describe(":substitute, inccommand=split", function() ]]) end) + it([[preview changes correctly with c_CTRL-R_= and c_CTRL-\_e]], function() + feed('gg') + feed(":1,2s/t/X") + screen:expect([[ + Inc subs{12:X}itution on | + {12:X}wo lines | + Inc substitution on | + two lines | + | + {11:[No Name] [+] }| + |1| Inc subs{12:X}itution on | + |2| {12:X}wo lines | + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {10:[Preview] }| + :1,2s/t/X^ | + ]]) + + feed([[<C-R>='Y']]) + -- preview should be unchanged during c_CTRL-R_= editing + screen:expect([[ + Inc subs{12:X}itution on | + {12:X}wo lines | + Inc substitution on | + two lines | + | + {11:[No Name] [+] }| + |1| Inc subs{12:X}itution on | + |2| {12:X}wo lines | + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {10:[Preview] }| + ={1:'Y'}^ | + ]]) + + feed('<CR>') + -- preview should be changed by the result of the expression + screen:expect([[ + Inc subs{12:XY}itution on | + {12:XY}wo lines | + Inc substitution on | + two lines | + | + {11:[No Name] [+] }| + |1| Inc subs{12:XY}itution on | + |2| {12:XY}wo lines | + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {10:[Preview] }| + :1,2s/t/XY^ | + ]]) + + feed([[<C-\>e'echo']]) + -- preview should be unchanged during c_CTRL-\_e editing + screen:expect([[ + Inc subs{12:XY}itution on | + {12:XY}wo lines | + Inc substitution on | + two lines | + | + {11:[No Name] [+] }| + |1| Inc subs{12:XY}itution on | + |2| {12:XY}wo lines | + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {10:[Preview] }| + ={1:'echo'}^ | + ]]) + + feed('<CR>') + -- preview should be cleared if command is changed to a non-previewable one + screen:expect([[ + Inc substitution on | + two lines | + Inc substitution on | + two lines | + | + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + :echo^ | + ]]) + end) + end) describe("inccommand=nosplit", function() @@ -1582,10 +1712,12 @@ end) describe("'inccommand' and :cnoremap", function() local cases = { "", "split", "nosplit" } + local screen - local function refresh(case) + local function refresh(case, visual) clear() - common_setup(nil, case, default_text) + screen = visual and Screen.new(50,10) or nil + common_setup(screen, case, default_text) end it('work with remapped characters', function() @@ -1642,13 +1774,15 @@ describe("'inccommand' and :cnoremap", function() it('still works with a broken mapping', function() for _, case in pairs(cases) do - refresh(case) + refresh(case, true) feed_command("cnoremap <expr> x execute('bwipeout!')[-1].'x'") feed(":%s/tw/tox<enter>") + screen:expect{any=[[{14:^E565:]]} + feed('<c-c>') -- error thrown b/c of the mapping - neq(nil, eval('v:errmsg'):find('^E523:')) + neq(nil, eval('v:errmsg'):find('^E565:')) expect([[ Inc substitution on toxo lines @@ -1794,26 +1928,26 @@ describe("'inccommand' split windows", function() feed_command("split") feed(":%s/tw") screen:expect([[ - Inc substitution on {10:│}Inc substitution on| - {12:tw}o lines {10:│}{12:tw}o lines | - {10:│} | - {15:~ }{10:│}{15:~ }| - {15:~ }{10:│}{15:~ }| - {15:~ }{10:│}{15:~ }| - {15:~ }{10:│}{15:~ }| - {15:~ }{10:│}{15:~ }| - {15:~ }{10:│}{15:~ }| - {15:~ }{10:│}{15:~ }| - {15:~ }{10:│}{15:~ }| - {15:~ }{10:│}{15:~ }| - {15:~ }{10:│}{15:~ }| - {15:~ }{10:│}{15:~ }| - {11:[No Name] [+] }{10:│}{15:~ }| - Inc substitution on {10:│}{15:~ }| - {12:tw}o lines {10:│}{15:~ }| - {10:│}{15:~ }| - {15:~ }{10:│}{15:~ }| - {15:~ }{10:│}{15:~ }| + Inc substitution on │Inc substitution on| + {12:tw}o lines │{12:tw}o lines | + │ | + {15:~ }│{15:~ }| + {15:~ }│{15:~ }| + {15:~ }│{15:~ }| + {15:~ }│{15:~ }| + {15:~ }│{15:~ }| + {15:~ }│{15:~ }| + {15:~ }│{15:~ }| + {15:~ }│{15:~ }| + {15:~ }│{15:~ }| + {15:~ }│{15:~ }| + {15:~ }│{15:~ }| + {11:[No Name] [+] }│{15:~ }| + Inc substitution on │{15:~ }| + {12:tw}o lines │{15:~ }| + │{15:~ }| + {15:~ }│{15:~ }| + {15:~ }│{15:~ }| {10:[No Name] [+] [No Name] [+] }| |2| {12:tw}o lines | {15:~ }| @@ -1833,20 +1967,20 @@ describe("'inccommand' split windows", function() feed(":%s/tw") screen:expect([[ - Inc substitution on {10:│}Inc substitution on| - {12:tw}o lines {10:│}{12:tw}o lines | - {10:│} | - {15:~ }{10:│}{15:~ }| - {15:~ }{10:│}{15:~ }| - {15:~ }{10:│}{15:~ }| - {15:~ }{10:│}{15:~ }| - {15:~ }{10:│}{15:~ }| - {15:~ }{10:│}{15:~ }| - {15:~ }{10:│}{15:~ }| - {15:~ }{10:│}{15:~ }| - {15:~ }{10:│}{15:~ }| - {15:~ }{10:│}{15:~ }| - {15:~ }{10:│}{15:~ }| + Inc substitution on │Inc substitution on| + {12:tw}o lines │{12:tw}o lines | + │ | + {15:~ }│{15:~ }| + {15:~ }│{15:~ }| + {15:~ }│{15:~ }| + {15:~ }│{15:~ }| + {15:~ }│{15:~ }| + {15:~ }│{15:~ }| + {15:~ }│{15:~ }| + {15:~ }│{15:~ }| + {15:~ }│{15:~ }| + {15:~ }│{15:~ }| + {15:~ }│{15:~ }| {11:[No Name] [+] }{10:[No Name] [+] }| Inc substitution on | {12:tw}o lines | @@ -1917,6 +2051,16 @@ describe("'inccommand' split windows", function() end end) + it("don't open if there's not enough room", function() + refresh() + screen:try_resize(40, 3) + feed("gg:%s/tw") + screen:expect([[ + Inc substitution on | + {12:tw}o lines | + :%s/tw^ | + ]]) + end) end) describe("'inccommand' with 'gdefault'", function() @@ -2731,7 +2875,7 @@ it(':substitute with inccommand during :terminal activity', function() clear() command("set cmdwinheight=3") - feed([[:terminal "]]..nvim_dir..[[/shell-test" REP 5000 xxx<cr>]]) + feed(([[:terminal "%s" REP 5000 xxx<cr>]]):format(testprg('shell-test'))) command('file term') feed('G') -- Follow :terminal output. command('new') @@ -2773,6 +2917,62 @@ it(':substitute with inccommand, timer-induced :redraw #9777', function() ]]) end) +it(':substitute with inccommand, allows :redraw before first separator is typed #18857', function() + local screen = Screen.new(30,6) + clear() + common_setup(screen, 'split', 'foo bar baz\nbar baz fox\nbar foo baz') + command('hi! link NormalFloat CursorLine') + local float_buf = meths.create_buf(false, true) + meths.open_win(float_buf, false, { + relative = 'editor', height = 1, width = 5, row = 3, col = 0, focusable = false, + }) + feed(':%s') + screen:expect([[ + foo bar baz | + bar baz fox | + bar foo baz | + {16: }{15: }| + {15:~ }| + :%s^ | + ]]) + meths.buf_set_lines(float_buf, 0, -1, true, {'foo'}) + command('redraw') + screen:expect([[ + foo bar baz | + bar baz fox | + bar foo baz | + {16:foo }{15: }| + {15:~ }| + :%s^ | + ]]) +end) + +it(':substitute with inccommand, does not crash if range contains invalid marks', function() + local screen = Screen.new(30, 6) + clear() + common_setup(screen, 'split', 'test') + feed([[:'a,'bs]]) + screen:expect([[ + test | + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + :'a,'bs^ | + ]]) + -- v:errmsg shouldn't be set either before the first separator is typed + eq('', eval('v:errmsg')) + feed('/') + screen:expect([[ + test | + {15:~ }| + {15:~ }| + {15:~ }| + {15:~ }| + :'a,'bs/^ | + ]]) +end) + it(":substitute doesn't crash with inccommand, if undo is empty #12932", function() local screen = Screen.new(10,5) clear() @@ -2808,3 +3008,16 @@ it('long :%s/ with inccommand does not collapse cmdline', function() AAAAAAA^ | ]]) end) + +it("with 'inccommand' typing :filter doesn't segfault or leak memory #19057", function() + clear() + common_setup(nil, 'nosplit') + feed(':filter s') + assert_alive() + feed(' ') + assert_alive() + feed('h') + assert_alive() + feed('i') + assert_alive() +end) diff --git a/test/functional/ui/inccommand_user_spec.lua b/test/functional/ui/inccommand_user_spec.lua new file mode 100644 index 0000000000..b5816f6fe6 --- /dev/null +++ b/test/functional/ui/inccommand_user_spec.lua @@ -0,0 +1,356 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') +local clear = helpers.clear +local exec_lua = helpers.exec_lua +local insert = helpers.insert +local feed = helpers.feed +local command = helpers.command +local assert_alive = helpers.assert_alive + +-- Implements a :Replace command that works like :substitute. +local setup_replace_cmd = [[ + local function show_replace_preview(buf, use_preview_win, preview_ns, preview_buf, matches) + -- Find the width taken by the largest line number, used for padding the line numbers + local highest_lnum = math.max(matches[#matches][1], 1) + local highest_lnum_width = math.floor(math.log10(highest_lnum)) + local preview_buf_line = 0 + + vim.g.prevns = preview_ns + vim.g.prevbuf = preview_buf + + for _, match in ipairs(matches) do + local lnum = match[1] + local line_matches = match[2] + local prefix + + if use_preview_win then + prefix = string.format( + '|%s%d| ', + string.rep(' ', highest_lnum_width - math.floor(math.log10(lnum))), + lnum + ) + + vim.api.nvim_buf_set_lines( + preview_buf, + preview_buf_line, + preview_buf_line, + 0, + { prefix .. vim.api.nvim_buf_get_lines(buf, lnum - 1, lnum, false)[1] } + ) + end + + for _, line_match in ipairs(line_matches) do + vim.api.nvim_buf_add_highlight( + buf, + preview_ns, + 'Substitute', + lnum - 1, + line_match[1], + line_match[2] + ) + + if use_preview_win then + vim.api.nvim_buf_add_highlight( + preview_buf, + preview_ns, + 'Substitute', + preview_buf_line, + #prefix + line_match[1], + #prefix + line_match[2] + ) + end + end + + preview_buf_line = preview_buf_line + 1 + end + + if use_preview_win then + return 2 + else + return 1 + end + end + + local function do_replace(opts, preview, preview_ns, preview_buf) + local pat1 = opts.fargs[1] or '' + local pat2 = opts.fargs[2] or '' + local line1 = opts.line1 + local line2 = opts.line2 + + local buf = vim.api.nvim_get_current_buf() + local lines = vim.api.nvim_buf_get_lines(buf, line1 - 1, line2, 0) + local matches = {} + + for i, line in ipairs(lines) do + local startidx, endidx = 0, 0 + local line_matches = {} + local num = 1 + + while startidx ~= -1 do + local match = vim.fn.matchstrpos(line, pat1, 0, num) + startidx, endidx = match[2], match[3] + + if startidx ~= -1 then + line_matches[#line_matches+1] = { startidx, endidx } + end + + num = num + 1 + end + + if #line_matches > 0 then + matches[#matches+1] = { line1 + i - 1, line_matches } + end + end + + local new_lines = {} + + for _, match in ipairs(matches) do + local lnum = match[1] + local line_matches = match[2] + local line = lines[lnum - line1 + 1] + local pat_width_differences = {} + + -- If previewing, only replace the text in current buffer if pat2 isn't empty + -- Otherwise, always replace the text + if pat2 ~= '' or not preview then + if preview then + for _, line_match in ipairs(line_matches) do + local startidx, endidx = unpack(line_match) + local pat_match = line:sub(startidx + 1, endidx) + + pat_width_differences[#pat_width_differences+1] = + #vim.fn.substitute(pat_match, pat1, pat2, 'g') - #pat_match + end + end + + new_lines[lnum] = vim.fn.substitute(line, pat1, pat2, 'g') + end + + -- Highlight the matches if previewing + if preview then + local idx_offset = 0 + for i, line_match in ipairs(line_matches) do + local startidx, endidx = unpack(line_match) + -- Starting index of replacement text + local repl_startidx = startidx + idx_offset + -- Ending index of the replacement text (if pat2 isn't empty) + local repl_endidx + + if pat2 ~= '' then + repl_endidx = endidx + idx_offset + pat_width_differences[i] + else + repl_endidx = endidx + idx_offset + end + + if pat2 ~= '' then + idx_offset = idx_offset + pat_width_differences[i] + end + + line_matches[i] = { repl_startidx, repl_endidx } + end + end + end + + for lnum, line in pairs(new_lines) do + vim.api.nvim_buf_set_lines(buf, lnum - 1, lnum, false, { line }) + end + + if preview then + local lnum = vim.api.nvim_win_get_cursor(0)[1] + -- Use preview window only if preview buffer is provided and range isn't just the current line + local use_preview_win = (preview_buf ~= nil) and (line1 ~= lnum or line2 ~= lnum) + return show_replace_preview(buf, use_preview_win, preview_ns, preview_buf, matches) + end + end + + local function replace(opts) + do_replace(opts, false) + end + + local function replace_preview(opts, preview_ns, preview_buf) + return do_replace(opts, true, preview_ns, preview_buf) + end + + -- ":<range>Replace <pat1> <pat2>" + -- Replaces all occurences of <pat1> in <range> with <pat2> + vim.api.nvim_create_user_command( + 'Replace', + replace, + { nargs = '*', range = '%', addr = 'lines', + preview = replace_preview } + ) +]] + +describe("'inccommand' for user commands", function() + local screen + + before_each(function() + clear() + screen = Screen.new(40, 17) + screen:set_default_attr_ids({ + [1] = {background = Screen.colors.Yellow1}, + [2] = {foreground = Screen.colors.Blue1, bold = true}, + [3] = {reverse = true}, + [4] = {reverse = true, bold = true} + }) + screen:attach() + exec_lua(setup_replace_cmd) + command('set cmdwinheight=5') + insert[[ + text on line 1 + more text on line 2 + oh no, even more text + will the text ever stop + oh well + did the text stop + why won't it stop + make the text stop + ]] + end) + + it('works with inccommand=nosplit', function() + command('set inccommand=nosplit') + feed(':Replace text cats') + screen:expect([[ + {1:cats} on line 1 | + more {1:cats} on line 2 | + oh no, even more {1:cats} | + will the {1:cats} ever stop | + oh well | + did the {1:cats} stop | + why won't it stop | + make the {1:cats} stop | + | + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + :Replace text cats^ | + ]]) + end) + + it('works with inccommand=split', function() + command('set inccommand=split') + feed(':Replace text cats') + screen:expect([[ + {1:cats} on line 1 | + more {1:cats} on line 2 | + oh no, even more {1:cats} | + will the {1:cats} ever stop | + oh well | + did the {1:cats} stop | + why won't it stop | + make the {1:cats} stop | + | + {4:[No Name] [+] }| + |1| {1:cats} on line 1 | + |2| more {1:cats} on line 2 | + |3| oh no, even more {1:cats} | + |4| will the {1:cats} ever stop | + |6| did the {1:cats} stop | + {3:[Preview] }| + :Replace text cats^ | + ]]) + end) + + it('properly closes preview when inccommand=split', function() + command('set inccommand=split') + feed(':Replace text cats<Esc>') + screen:expect([[ + text on line 1 | + more text on line 2 | + oh no, even more text | + will the text ever stop | + oh well | + did the text stop | + why won't it stop | + make the text stop | + ^ | + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + | + ]]) + end) + + it('properly executes command when inccommand=split', function() + command('set inccommand=split') + feed(':Replace text cats<CR>') + screen:expect([[ + cats on line 1 | + more cats on line 2 | + oh no, even more cats | + will the cats ever stop | + oh well | + did the cats stop | + why won't it stop | + make the cats stop | + ^ | + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + :Replace text cats | + ]]) + end) + + it('shows preview window only when range is not current line', function() + command('set inccommand=split') + feed('gg:.Replace text cats') + screen:expect([[ + {1:cats} on line 1 | + more text on line 2 | + oh no, even more text | + will the text ever stop | + oh well | + did the text stop | + why won't it stop | + make the text stop | + | + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + {2:~ }| + :.Replace text cats^ | + ]]) + end) + + it('does not crash on ambiguous command #18825', function() + command('set inccommand=split') + command('command Reply echo 1') + feed(':R') + assert_alive() + feed('e') + assert_alive() + end) + + it('no crash if preview callback changes inccommand option', function() + command('set inccommand=nosplit') + exec_lua([[ + vim.api.nvim_create_user_command('Replace', function() end, { + nargs = '*', + preview = function() + vim.api.nvim_set_option('inccommand', 'split') + return 2 + end, + }) + ]]) + feed(':R') + assert_alive() + feed('e') + assert_alive() + end) +end) diff --git a/test/functional/ui/input_spec.lua b/test/functional/ui/input_spec.lua index ea8968a653..0f4e97088c 100644 --- a/test/functional/ui/input_spec.lua +++ b/test/functional/ui/input_spec.lua @@ -3,9 +3,12 @@ local clear, feed_command = helpers.clear, helpers.feed_command local feed, next_msg, eq = helpers.feed, helpers.next_msg, helpers.eq local command = helpers.command local expect = helpers.expect +local curbuf_contents = helpers.curbuf_contents local meths = helpers.meths local exec_lua = helpers.exec_lua local write_file = helpers.write_file +local funcs = helpers.funcs +local eval = helpers.eval local Screen = require('test.functional.ui.screen') before_each(clear) @@ -50,6 +53,8 @@ describe('mappings', function() add_mapping('<kenter>','<kenter>') add_mapping('<kcomma>','<kcomma>') add_mapping('<kequal>','<kequal>') + add_mapping('<f38>','<f38>') + add_mapping('<f63>','<f63>') end) it('ok', function() @@ -106,6 +111,8 @@ describe('mappings', function() check_mapping('<KPComma>','<kcomma>') check_mapping('<kequal>','<kequal>') check_mapping('<KPEquals>','<kequal>') + check_mapping('<f38>','<f38>') + check_mapping('<f63>','<f63>') end) it('support meta + multibyte char mapping', function() @@ -114,11 +121,196 @@ describe('mappings', function() end) end) -describe('input utf sequences that contain CSI/K_SPECIAL', function() +describe('input utf sequences that contain K_SPECIAL (0x80)', function() it('ok', function() feed('i…<esc>') expect('…') end) + + it('can be mapped', function() + command('inoremap … E280A6') + feed('i…<esc>') + expect('E280A6') + end) +end) + +describe('input utf sequences that contain CSI (0x9B)', function() + it('ok', function() + feed('iě<esc>') + expect('ě') + end) + + it('can be mapped', function() + command('inoremap ě C49B') + feed('iě<esc>') + expect('C49B') + end) +end) + +describe('input split utf sequences', function() + it('ok', function() + local str = '►' + feed('i' .. str:sub(1, 1)) + helpers.sleep(10) + feed(str:sub(2, 3)) + expect('►') + end) + + it('can be mapped', function() + command('inoremap ► E296BA') + local str = '►' + feed('i' .. str:sub(1, 1)) + helpers.sleep(10) + feed(str:sub(2, 3)) + expect('E296BA') + end) +end) + +describe('input pairs', function() + describe('<tab> / <c-i>', function() + it('ok', function() + feed('i<tab><c-i><esc>') + eq('\t\t', curbuf_contents()) + end) + + describe('can be mapped separately', function() + it('if <tab> is mapped after <c-i>', function() + command('inoremap <c-i> CTRL-I!') + command('inoremap <tab> TAB!') + feed('i<tab><c-i><esc>') + eq('TAB!CTRL-I!', curbuf_contents()) + end) + + it('if <tab> is mapped before <c-i>', function() + command('inoremap <tab> TAB!') + command('inoremap <c-i> CTRL-I!') + feed('i<tab><c-i><esc>') + eq('TAB!CTRL-I!', curbuf_contents()) + end) + end) + end) + + describe('<cr> / <c-m>', function() + it('ok', function() + feed('iunos<c-m>dos<cr>tres<esc>') + eq('unos\ndos\ntres', curbuf_contents()) + end) + + describe('can be mapped separately', function() + it('if <cr> is mapped after <c-m>', function() + command('inoremap <c-m> SNIPPET!') + command('inoremap <cr> , and then<cr>') + feed('iunos<c-m>dos<cr>tres<esc>') + eq('unosSNIPPET!dos, and then\ntres', curbuf_contents()) + end) + + it('if <cr> is mapped before <c-m>', function() + command('inoremap <cr> , and then<cr>') + command('inoremap <c-m> SNIPPET!') + feed('iunos<c-m>dos<cr>tres<esc>') + eq('unosSNIPPET!dos, and then\ntres', curbuf_contents()) + end) + end) + end) + + describe('<esc> / <c-[>', function() + it('ok', function() + feed('2adouble<c-[>asingle<esc>') + eq('doubledoublesingle', curbuf_contents()) + end) + + describe('can be mapped separately', function() + it('if <esc> is mapped after <c-[>', function() + command('inoremap <c-[> HALLOJ!') + command('inoremap <esc> ,<esc>') + feed('2adubbel<c-[>upp<esc>') + eq('dubbelHALLOJ!upp,dubbelHALLOJ!upp,', curbuf_contents()) + end) + + it('if <esc> is mapped before <c-[>', function() + command('inoremap <esc> ,<esc>') + command('inoremap <c-[> HALLOJ!') + feed('2adubbel<c-[>upp<esc>') + eq('dubbelHALLOJ!upp,dubbelHALLOJ!upp,', curbuf_contents()) + end) + end) + end) +end) + +it('Ctrl-6 is Ctrl-^ vim-patch:8.1.2333', function() + command('split aaa') + command('edit bbb') + feed('<C-6>') + eq('aaa', funcs.bufname()) +end) + +it('c_CTRL-R_CTRL-R, i_CTRL-R_CTRL-R, i_CTRL-G_CTRL-K work properly vim-patch:8.1.2346', function() + command('set timeoutlen=10') + + command([[let @a = 'aaa']]) + feed([[:let x = '<C-R><C-R>a'<CR>]]) + eq([[let x = 'aaa']], eval('@:')) + + feed('a<C-R><C-R>a<Esc>') + expect('aaa') + command('bwipe!') + + feed('axx<CR>yy<C-G><C-K>a<Esc>') + expect([[ + axx + yy]]) +end) + +it('typing a simplifiable key at hit-enter prompt triggers mapping vim-patch:8.2.0839', function() + local screen = Screen.new(60,8) + screen:set_default_attr_ids({ + [1] = {bold = true, foreground = Screen.colors.Blue}, -- NonText + [2] = {bold = true, reverse = true}, -- MsgSeparator + [3] = {bold = true, foreground = Screen.colors.SeaGreen}, -- MoreMsg + }) + screen:attach() + command([[nnoremap <C-6> <Cmd>echo 'hit ctrl-6'<CR>]]) + feed_command('ls') + screen:expect([[ + | + {1:~ }| + {1:~ }| + {1:~ }| + {2: }| + :ls | + 1 %a "[No Name]" line 1 | + {3:Press ENTER or type command to continue}^ | + ]]) + feed('<C-6>') + screen:expect([[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + hit ctrl-6 | + ]]) +end) + +it('mixing simplified and unsimplified keys can trigger mapping vim-patch:8.2.0916', function() + command('set timeoutlen=10') + command([[imap ' <C-W>]]) + command('imap <C-W><C-A> c-a') + feed([[a'<C-A>]]) + expect('c-a') +end) + +it('unsimplified mapping works when there was a partial match vim-patch:8.2.4504', function() + command('set timeoutlen=10') + command('nnoremap <C-J> a') + command('nnoremap <NL> x') + command('nnoremap <C-J>x <Nop>') + funcs.setline(1, 'x') + -- CTRL-J b should have trigger the <C-J> mapping and then insert "b" + feed('<C-J>b<Esc>') + expect('xb') end) describe('input non-printable chars', function() @@ -146,7 +338,7 @@ describe('input non-printable chars', function() {1:~ }| {1:~ }| {1:~ }| - "Xtest-overwrite" [noeol] 1L, 6C | + "Xtest-overwrite" [noeol] 1L, 6B | ]]) -- The timestamp is in second resolution, wait two seconds to be sure. @@ -237,3 +429,47 @@ describe("event processing and input", function() eq({'notification', 'stop', {}}, next_msg()) end) end) + +describe('display is updated', function() + local screen + before_each(function() + screen = Screen.new(60, 8) + screen:set_default_attr_ids({ + [1] = {bold = true, foreground = Screen.colors.Blue1}, -- NonText + [2] = {bold = true}, -- ModeMsg + }) + screen:attach() + end) + + it('in Insert mode after <Nop> mapping #17911', function() + command('imap <Plug>test <Nop>') + command('imap <F2> abc<CR><Plug>test') + feed('i<F2>') + screen:expect([[ + abc | + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {2:-- INSERT --} | + ]]) + end) + + it('in Insert mode after empty string <expr> mapping #17911', function() + command('imap <expr> <Plug>test ""') + command('imap <F2> abc<CR><Plug>test') + feed('i<F2>') + screen:expect([[ + abc | + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {2:-- INSERT --} | + ]]) + end) +end) diff --git a/test/functional/ui/messages_spec.lua b/test/functional/ui/messages_spec.lua index f038348253..00f126a1f2 100644 --- a/test/functional/ui/messages_spec.lua +++ b/test/functional/ui/messages_spec.lua @@ -12,6 +12,7 @@ local nvim_prog = helpers.nvim_prog local iswin = helpers.iswin local exc_exec = helpers.exc_exec local exec_lua = helpers.exec_lua +local poke_eventloop = helpers.poke_eventloop describe('ui/ext_messages', function() local screen @@ -30,6 +31,7 @@ describe('ui/ext_messages', function() [7] = {background = Screen.colors.Yellow}, [8] = {foreground = Screen.colors.Red}, [9] = {special = Screen.colors.Red, undercurl = true}, + [10] = {foreground = Screen.colors.Brown}; }) end) after_each(function() @@ -303,13 +305,25 @@ describe('ui/ext_messages', function() {1:~ }| {1:~ }| {1:~ }| - ]], messages={ + ]], msg_history={ {kind="echoerr", content={{"raa", 2}}}, {kind="echoerr", content={{"bork", 2}}}, {kind="echoerr", content={{"fail", 2}}}, {kind="echoerr", content={{"extrafail", 2}}}, {kind="echoerr", content={{"problem", 2}}} - }} + }, messages={{ + content = {{ "Press ENTER or type command to continue", 4 }}, + kind = "return_prompt" + }}} + + feed '<cr>' + screen:expect{grid=[[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ]]} end) it('shortmess-=S', function() @@ -454,11 +468,14 @@ describe('ui/ext_messages', function() alphpabe^t | {1:~ }| {1:~ }| - ]], messages={ - {kind="echomsg", content={{"stuff"}}}, - }, showmode={ - { "-- INSERT --", 3 } - }} + ]], msg_history={{ + content = {{ "stuff" }}, + kind = "echomsg", + }}, showmode={{ "-- INSERT --", 3 }}, + messages={{ + content = {{ "Press ENTER or type command to continue", 4}}, + kind = "return_prompt" + }}} end) it('&showmode with macro-recording message', function() @@ -684,12 +701,15 @@ describe('ui/ext_messages', function() {1:~ }| {1:~ }| {1:~ }| - ]], messages={ + ]], msg_history={ {kind="echomsg", content={{"howdy"}}}, {kind="", content={{"Type :qa and press <Enter> to exit Nvim"}}}, {kind="echoerr", content={{"bork", 2}}}, {kind="emsg", content={{"E117: Unknown function: nosuchfunction", 2}}} - }} + }, messages={{ + content = {{ "Press ENTER or type command to continue", 4}}, + kind = "return_prompt" + }}} end) it('implies ext_cmdline and ignores cmdheight', function() @@ -717,7 +737,6 @@ describe('ui/ext_messages', function() ]]) eq(0, eval('&cmdheight')) - -- normally this would be an error feed(':set cmdheight=0') screen:expect{grid=[[ ^ | @@ -839,9 +858,53 @@ stack traceback: {1:~ }| {1:~ }| ]]} - end) + it('supports nvim_echo messages with multiple attrs', function() + async_meths.echo({{'wow, ',"Search"}, {"such\n\nvery ", "ErrorMsg"}, {"color", "LineNr"}}, true, {}) + screen:expect{grid=[[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ]], messages={ + { content = { { "wow, ", 7 }, { "such\n\nvery ", 2 }, { "color", 10 } }, kind = "" } + }} + + feed ':ls<cr>' + screen:expect{grid=[[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ]], messages={ + { content = { { '\n 1 %a "[No Name]" line 1' } }, kind = "echomsg" } + }} + + feed ':messages<cr>' + screen:expect{grid=[[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ]], messages={ + { content = { { "Press ENTER or type command to continue", 4 } }, kind = "return_prompt" } + }, msg_history={ + { content = { { "wow, ", 7 }, { "such\n\nvery ", 2 }, { "color", 10 } }, kind = "echomsg" } + }} + + feed '<cr>' + screen:expect{grid=[[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ]]} + end) end) describe('ui/builtin messages', function() @@ -850,17 +913,19 @@ describe('ui/builtin messages', function() clear() screen = Screen.new(60, 7) screen:attach({rgb=true, ext_popupmenu=true}) - screen:set_default_attr_ids({ - [1] = {bold = true, foreground = Screen.colors.Blue1}, - [2] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red}, - [3] = {bold = true, reverse = true}, - [4] = {bold = true, foreground = Screen.colors.SeaGreen4}, - [5] = {foreground = Screen.colors.Blue1}, - [6] = {bold = true, foreground = Screen.colors.Magenta}, - [7] = {background = Screen.colors.Grey20}, - [8] = {reverse = true}, - [9] = {background = Screen.colors.LightRed} - }) + screen:set_default_attr_ids { + [1] = {bold = true, foreground = Screen.colors.Blue1}; + [2] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red}; + [3] = {bold = true, reverse = true}; + [4] = {bold = true, foreground = Screen.colors.SeaGreen4}; + [5] = {foreground = Screen.colors.Blue1}; + [6] = {bold = true, foreground = Screen.colors.Magenta}; + [7] = {background = Screen.colors.Grey20}; + [8] = {reverse = true}; + [9] = {background = Screen.colors.LightRed}; + [10] = {background = Screen.colors.Yellow}; + [11] = {foreground = Screen.colors.Brown}; + } end) it('supports multiline messages from rpc', function() @@ -1013,7 +1078,7 @@ vimComment xxx match /\s"[^\-:.%#=*].*$/ms=s+1,lc=1 excludenl contains=@vim end) it('redraws NOT_VALID correctly after message', function() - -- edge case: only one window was set NOT_VALID. Orginal report + -- edge case: only one window was set NOT_VALID. Original report -- used :make, but fake it using one command to set the current -- window NOT_VALID and another to show a long message. command("set more") @@ -1096,6 +1161,88 @@ vimComment xxx match /\s"[^\-:.%#=*].*$/ms=s+1,lc=1 excludenl contains=@vim {4:Press ENTER or type command to continue}^ | ]]} end) + + it('supports nvim_echo messages with multiple attrs', function() + async_meths.echo({{'wow, ',"Search"}, {"such\n\nvery ", "ErrorMsg"}, {"color", "LineNr"}}, true, {}) + screen:expect{grid=[[ + | + {1:~ }| + {3: }| + {10:wow, }{2:such} | + | + {2:very }{11:color} | + {4:Press ENTER or type command to continue}^ | + ]]} + + feed '<cr>' + screen:expect{grid=[[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]} + + feed ':messages<cr>' + screen:expect{grid=[[ + | + {1:~ }| + {3: }| + {10:wow, }{2:such} | + | + {2:very }{11:color} | + {4: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') + meths.buf_set_lines(0, 0, 0, true, {'aaa', 'bbb', 'ccc'}) + command('set display-=msgsep') + feed('gggQ<CR><CR>1<CR><CR>vi') + screen:expect([[ + Entering Ex mode. Type "visual" to go to Normal mode. | + {11: 2 }bbb | + {11: 3 }ccc | + :1 | + {11: 1 }aaa | + {11: 2 }bbb | + :vi^ | + ]]) + feed('<CR>') + screen:expect([[ + {11: 1 }aaa | + {11: 2 }^bbb | + {11: 3 }ccc | + {11: 4 } | + {1:~ }| + {1:~ }| + | + ]]) + command('set display+=msgsep') + feed('gggQ<CR><CR>1<CR><CR>vi') + screen:expect([[ + Entering Ex mode. Type "visual" to go to Normal mode. | + {11: 2 }bbb | + {11: 3 }ccc | + :1 | + {11: 1 }aaa | + {11: 2 }bbb | + :vi^ | + ]]) + feed('<CR>') + screen:expect([[ + {11: 1 }aaa | + {11: 2 }^bbb | + {11: 3 }ccc | + {11: 4 } | + {1:~ }| + {1:~ }| + | + ]]) + end) end) describe('ui/ext_messages', function() @@ -1111,6 +1258,8 @@ describe('ui/ext_messages', function() [3] = {bold = true}, [4] = {bold = true, foreground = Screen.colors.SeaGreen4}, [5] = {foreground = Screen.colors.Blue1}, + [6] = {reverse = true}, + [7] = {bold = true, reverse = true}, }) end) @@ -1202,6 +1351,107 @@ describe('ui/ext_messages', function() {content = { { "Press ENTER or type command to continue", 4 } }, kind = "return_prompt" } }} end) + + it('supports global statusline', function() + feed(":set laststatus=3<cr>") + feed(":sp<cr>") + feed(":set cmdheight<cr>") + screen:expect({grid=[[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ────────────────────────────────────────────────────────────────────────────────| + | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {7:[No Name] }| + ]], messages={ + {content = { { " cmdheight=0" } }, kind = "" } + }}) + + feed("<c-w>+") + feed(":set laststatus<cr>") + screen:expect({grid=[[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ────────────────────────────────────────────────────────────────────────────────| + | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {7:[No Name] }| + ]], messages={ + {content = { { " laststatus=3" } }, kind = "" } + }}) + + feed(":set mouse=a<cr>") + meths.input_mouse('left', 'press', '', 0, 12, 10) + poke_eventloop() + meths.input_mouse('left', 'drag', '', 0, 12, 10) + meths.input_mouse('left', 'drag', '', 0, 11, 10) + feed("<c-l>") + feed(":set cmdheight<cr>") + screen:expect({grid=[[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ────────────────────────────────────────────────────────────────────────────────| + | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {7:[No Name] }| + ]], messages={ + {content = { { " cmdheight=0" } }, kind = "" } + }}) + end) end) describe('ui/msg_puts_printf', function() @@ -1226,7 +1476,7 @@ describe('ui/msg_puts_printf', function() pending('Locale ja_JP.UTF-8 not supported', function() end) return elseif helpers.isCI() then - -- Fails non--Windows CI. Message catalog direcotry issue? + -- Fails non--Windows CI. Message catalog directory issue? pending('fails on unix CI', function() end) return end @@ -1281,7 +1531,7 @@ ullamco laboris nisi ut aliquip ex ea commodo consequat.]]) end) - it('can be quit', function() + it('can be quit with echon', function() screen:try_resize(25,5) feed(':echon join(map(range(0, &lines*10), "v:val"), "\\n")<cr>') screen:expect{grid=[[ @@ -1301,6 +1551,45 @@ aliquip ex ea commodo consequat.]]) ]]} end) + it('can be quit with Lua #11224 #16537', function() + -- NOTE: adds "4" to message history, although not displayed initially + -- (triggered the more prompt). + screen:try_resize(40,5) + feed(':lua for i=0,10 do print(i) end<cr>') + screen:expect{grid=[[ + 0 | + 1 | + 2 | + 3 | + {4:-- More --}^ | + ]]} + feed('q') + screen:expect{grid=[[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]} + feed(':mess<cr>') + screen:expect{grid=[[ + 0 | + 1 | + 2 | + 3 | + {4:-- More --}^ | + ]]} + feed('j') + screen:expect{grid=[[ + 1 | + 2 | + 3 | + 4 | + {4:Press ENTER or type command to continue}^ | + ]]} + feed('<cr>') + end) + it('handles wrapped lines with line scroll', function() feed(':lua error(_G.x)<cr>') screen:expect{grid=[[ diff --git a/test/functional/ui/mouse_spec.lua b/test/functional/ui/mouse_spec.lua index baacef358f..69b0d1ecec 100644 --- a/test/functional/ui/mouse_spec.lua +++ b/test/functional/ui/mouse_spec.lua @@ -32,7 +32,7 @@ describe('ui/mouse/input', function() [6] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red}, [7] = {bold = true, foreground = Screen.colors.SeaGreen4}, }) - command("set display-=msgsep") + command("set display-=msgsep mousemodel=extend") feed('itesting<cr>mouse<cr>support and selection<esc>') screen:expect([[ testing | @@ -595,54 +595,54 @@ describe('ui/mouse/input', function() feed('ifoo\nbar<esc>') screen:expect{grid=[[ - testing {4:│}testing | - mouse {4:│}mouse | - support and selection {4:│}support and selection | - {0:~ }{4:│}{0:~ }| - {0:~ }{4:│}{0:~ }| - {0:~ }{4:│[No Name] [+] }| - {0:~ }{4:│}foo{0:$} | - {0:~ }{4:│}ba^r{0:$} | - {0:~ }{4:│}{0:~ }| - {0:~ }{4:│}{0:~ }| - {0:~ }{4:│}{0:~ }| - {0:~ }{4:│}{0:~ }| + testing │testing | + mouse │mouse | + support and selection │support and selection | + {0:~ }│{0:~ }| + {0:~ }│{0:~ }| + {0:~ }│{4:[No Name] [+] }| + {0:~ }│foo{0:$} | + {0:~ }│ba^r{0:$} | + {0:~ }│{0:~ }| + {0:~ }│{0:~ }| + {0:~ }│{0:~ }| + {0:~ }│{0:~ }| {4:[No Name] [+] }{5:[No Name] [+] }| | ]]} meths.input_mouse('left', 'press', '', 0, 6, 27) screen:expect{grid=[[ - testing {4:│}testing | - mouse {4:│}mouse | - support and selection {4:│}support and selection | - {0:~ }{4:│}{0:~ }| - {0:~ }{4:│}{0:~ }| - {0:~ }{4:│[No Name] [+] }| - {0:~ }{4:│}^foo{0:$} | - {0:~ }{4:│}bar{0:$} | - {0:~ }{4:│}{0:~ }| - {0:~ }{4:│}{0:~ }| - {0:~ }{4:│}{0:~ }| - {0:~ }{4:│}{0:~ }| + testing │testing | + mouse │mouse | + support and selection │support and selection | + {0:~ }│{0:~ }| + {0:~ }│{0:~ }| + {0:~ }│{4:[No Name] [+] }| + {0:~ }│^foo{0:$} | + {0:~ }│bar{0:$} | + {0:~ }│{0:~ }| + {0:~ }│{0:~ }| + {0:~ }│{0:~ }| + {0:~ }│{0:~ }| {4:[No Name] [+] }{5:[No Name] [+] }| | ]]} meths.input_mouse('left', 'drag', '', 0, 7, 30) screen:expect{grid=[[ - testing {4:│}testing | - mouse {4:│}mouse | - support and selection {4:│}support and selection | - {0:~ }{4:│}{0:~ }| - {0:~ }{4:│}{0:~ }| - {0:~ }{4:│[No Name] [+] }| - {0:~ }{4:│}{1:foo}{3:$} | - {0:~ }{4:│}{1:bar}{0:^$} | - {0:~ }{4:│}{0:~ }| - {0:~ }{4:│}{0:~ }| - {0:~ }{4:│}{0:~ }| - {0:~ }{4:│}{0:~ }| + testing │testing | + mouse │mouse | + support and selection │support and selection | + {0:~ }│{0:~ }| + {0:~ }│{0:~ }| + {0:~ }│{4:[No Name] [+] }| + {0:~ }│{1:foo}{3:$} | + {0:~ }│{1:bar}{0:^$} | + {0:~ }│{0:~ }| + {0:~ }│{0:~ }| + {0:~ }│{0:~ }| + {0:~ }│{0:~ }| {4:[No Name] [+] }{5:[No Name] [+] }| {2:-- VISUAL --} | ]]} @@ -729,12 +729,12 @@ describe('ui/mouse/input', function() feed('k') feed_command('sp', 'vsp') screen:expect([[ - lines {4:│}lines | - to {4:│}to | - test {4:│}test | - ^mouse scrolling {4:│}mouse scrolling | - {4:│} | - {0:~ }{4:│}{0:~ }| + lines │lines | + to │to | + test │test | + ^mouse scrolling │mouse scrolling | + │ | + {0:~ }│{0:~ }| {5:[No Name] [+] }{4:[No Name] [+] }| to | test | @@ -750,12 +750,12 @@ describe('ui/mouse/input', function() feed('<ScrollWheelDown><0,0>') end screen:expect([[ - ^mouse scrolling {4:│}lines | - {4:│}to | - {0:~ }{4:│}test | - {0:~ }{4:│}mouse scrolling | - {0:~ }{4:│} | - {0:~ }{4:│}{0:~ }| + ^mouse scrolling │lines | + │to | + {0:~ }│test | + {0:~ }│mouse scrolling | + {0:~ }│ | + {0:~ }│{0:~ }| {5:[No Name] [+] }{4:[No Name] [+] }| to | test | @@ -771,12 +771,12 @@ describe('ui/mouse/input', function() feed('<ScrollWheelUp><27,0>') end screen:expect([[ - ^mouse scrolling {4:│}text | - {4:│}with | - {0:~ }{4:│}many | - {0:~ }{4:│}lines | - {0:~ }{4:│}to | - {0:~ }{4:│}test | + ^mouse scrolling │text | + │with | + {0:~ }│many | + {0:~ }│lines | + {0:~ }│to | + {0:~ }│test | {5:[No Name] [+] }{4:[No Name] [+] }| to | test | @@ -793,12 +793,12 @@ describe('ui/mouse/input', function() feed('<ScrollWheelUp><27,7><ScrollWheelUp>') end screen:expect([[ - ^mouse scrolling {4:│}text | - {4:│}with | - {0:~ }{4:│}many | - {0:~ }{4:│}lines | - {0:~ }{4:│}to | - {0:~ }{4:│}test | + ^mouse scrolling │text | + │with | + {0:~ }│many | + {0:~ }│lines | + {0:~ }│to | + {0:~ }│test | {5:[No Name] [+] }{4:[No Name] [+] }| Inserting | text | @@ -1571,4 +1571,23 @@ describe('ui/mouse/input', function() meths.set_option('winwidth', winwidth) meths.input_mouse('left', 'release', '', 0, 0, 0) end) + + it('scroll keys are not translated into multiclicks #6211 #6989', function() + meths.set_var('mouse_up', 0) + meths.set_var('mouse_up2', 0) + meths.set_var('mouse_up3', 0) + meths.set_var('mouse_up4', 0) + command('nnoremap <ScrollWheelUp> <Cmd>let g:mouse_up += 1<CR>') + command('nnoremap <2-ScrollWheelUp> <Cmd>let g:mouse_up2 += 1<CR>') + command('nnoremap <3-ScrollWheelUp> <Cmd>let g:mouse_up3 += 1<CR>') + command('nnoremap <4-ScrollWheelUp> <Cmd>let g:mouse_up4 += 1<CR>') + meths.input_mouse('wheel', 'up', '', 0, 0, 0) + meths.input_mouse('wheel', 'up', '', 0, 0, 0) + meths.input_mouse('wheel', 'up', '', 0, 0, 0) + meths.input_mouse('wheel', 'up', '', 0, 0, 0) + eq(4, meths.get_var('mouse_up')) + eq(0, meths.get_var('mouse_up2')) + eq(0, meths.get_var('mouse_up3')) + eq(0, meths.get_var('mouse_up4')) + end) end) diff --git a/test/functional/ui/multibyte_spec.lua b/test/functional/ui/multibyte_spec.lua index e6a79feadc..d4e237bcb4 100644 --- a/test/functional/ui/multibyte_spec.lua +++ b/test/functional/ui/multibyte_spec.lua @@ -6,6 +6,9 @@ local feed = helpers.feed local feed_command = helpers.feed_command local insert = helpers.insert local funcs = helpers.funcs +local meths = helpers.meths +local split = helpers.split +local dedent = helpers.dedent describe("multibyte rendering", function() local screen @@ -26,7 +29,7 @@ describe("multibyte rendering", function() ̊ x]]) feed("gg") - -- verify the modifier infact is alone + -- verify the modifier in fact is alone feed_command("ascii") screen:expect([[ ^ ̊ | @@ -115,6 +118,35 @@ describe("multibyte rendering", function() {4:-- INSERT --} | ]]) end) + + it('works with a lot of unicode (zalgo) text', function() + screen:try_resize(65, 10) + meths.buf_set_lines(0,0,-1,true, split(dedent [[ + L̓̉̑̒̌̚ơ̗̌̒̄̀ŕ̈̈̎̐̕è̇̅̄̄̐m̖̟̟̅̄̚ ̛̓̑̆̇̍i̗̟̞̜̅̐p̗̞̜̉̆̕s̟̜̘̍̑̏ū̟̞̎̃̉ḿ̘̙́́̐ ̖̍̌̇̉̚d̞̄̃̒̉̎ò́̌̌̂̐l̞̀̄̆̌̚ȯ̖̞̋̀̐r̓̇̌̃̃̚ ̗̘̀̏̍́s̜̀̎̎̑̕i̟̗̐̄̄̚t̝̎̆̓̐̒ ̘̇̔̓̊̚ȃ̛̟̗̏̅m̜̟̙̞̈̓é̘̞̟̔̆t̝̂̂̈̑̔,̜̜̖̅̄̍ ̛̗̊̓̆̚c̟̍̆̍̈̔ȯ̖̖̝̑̀n̜̟̎̊̃̚s̟̏̇̎̒̚e̙̐̈̓̌̚c̙̍̈̏̅̕ť̇̄̇̆̓e̛̓̌̈̓̈t̟̍̀̉̆̅u̝̞̎̂̄̚r̘̀̅̈̅̐ ̝̞̓́̇̉ã̏̀̆̅̕d̛̆̐̉̆̋ȉ̞̟̍̃̚p̛̜̊̍̂̓ȋ̏̅̃̋̚ṥ̛̏̃̕č̛̞̝̀̂í̗̘̌́̎n̔̎́̒̂̕ǧ̗̜̋̇̂ ̛̜̔̄̎̃ê̛̔̆̇̕l̘̝̏̐̊̏ĩ̛̍̏̏̄t̟̐́̀̐̎,̙̘̍̆̉̐ ̋̂̏̄̌̅s̙̓̌̈́̇e̛̗̋̒̎̏d̜̗̊̍̊̚ + ď̘̋̌̌̕ǒ̝̗̔̇̕ ̙̍́̄̄̉è̛̛̞̌̌i̜̖̐̈̆̚ȕ̇̈̓̃̓ŝ̛̞̙̉̋m̜̐̂̄̋̂ȯ̈̎̎̅̕d̜̙̓̔̋̑ ̞̗̄̂̂̚t̝̊́̃́̄e̛̘̜̞̓̑m̊̅̏̉̌̕p̛̈̂̇̀̐ỏ̙̘̈̉̔r̘̞̋̍̃̚ ̝̄̀̇̅̇ỉ̛̖̍̓̈n̛̛̝̎̕̕c̛̛̊̅́̐ĭ̗̓̀̍̐d̞̜̋̐̅̚i̟̙̇̄̊̄d̞̊̂̀̇̚ủ̝̉̑̃̕n̜̏̇̄̐̋ť̗̜̞̋̉ ̝̒̓̌̓̚ȕ̖̙̀̚̕t̖̘̎̉̂̌ ̛̝̄̍̌̂l̛̟̝̃̑̋á̛̝̝̔̅b̝̙̜̗̅̒ơ̖̌̒̄̆r̒̇̓̎̈̄e̛̛̖̅̏̇ ̖̗̜̝̃́e̛̛̘̅̔̌ẗ̛̙̗̐̕ ̖̟̇̋̌̈d̞̙̀̉̑̕ŏ̝̂́̐̑l̞̟̗̓̓̀ơ̘̎̃̄̂r̗̗̖̔̆̍ẻ̖̝̞̋̅ ̜̌̇̍̈̊m̈̉̇̄̒̀a̜̞̘̔̅̆g̗̖̈̃̈̉n̙̖̄̈̉̄â̛̝̜̄̃ ̛́̎̕̕̚ā̊́́̆̌l̟̙̞̃̒́i̖̇̎̃̀̋q̟̇̒̆́̊ủ́̌̇̑̚ã̛̘̉̐̚.̛́̏̐̍̊ + U̝̙̎̈̐̆t̜̍̌̀̔̏ ̞̉̍̇̈̃e̟̟̊̄̕̕n̝̜̒̓̆̕i̖̒̌̅̇̚m̞̊̃̔̊̂ ̛̜̊̎̄̂a̘̜̋̒̚̚d̟̊̎̇̂̍ ̜̖̏̑̉̕m̜̒̎̅̄̚i̝̖̓̂̍̕n̙̉̒̑̀̔ỉ̖̝̌̒́m̛̖̘̅̆̎ ̖̉̎̒̌̕v̖̞̀̔́̎e̖̙̗̒̎̉n̛̗̝̎̀̂ȉ̞̗̒̕̚ȧ̟̜̝̅̚m̆̉̐̐̇̈,̏̐̎́̍́ ̜̞̙̘̏̆q̙̖̙̅̓̂ủ̇́̀̔̚í̙̟̟̏̐s̖̝̍̏̂̇ ̛̘̋̈̕̕ń̛̞̜̜̎o̗̜̔̔̈̆s̞̘̘̄̒̋t̛̅̋́̔̈ȓ̓̒́̇̅ủ̜̄̃̒̍d̙̝̘̊̏̚ ̛̟̞̄́̔e̛̗̝̍̃̀x̞̖̃̄̂̅e̖̅̇̐̔̃r̗̞̖̔̎̚c̘̜̖̆̊̏ï̙̝̙̂̕t̖̏́̓̋̂ă̖̄̆̑̒t̜̟̍̉̑̏i̛̞̞̘̒̑ǒ̜̆̅̃̉ṅ̖̜̒̎̚ + u̗̞̓̔̈̏ĺ̟̝́̎̚l̛̜̅̌̎̆a̒̑̆̔̇̃m̜̗̈̊̎̚ċ̘̋̇̂̚ơ̟̖̊́̕ ̖̟̍̉̏̚l̙̔̓̀̅̏ä̞̗̘̙̅ḃ̟̎̄̃̕o̞̎̓̓̓̚r̗̜̊̓̈̒ï̗̜̃̃̅s̀̒̌̂̎̂ ̖̗̗̋̎̐n̝̟̝̘̄̚i̜̒̀̒̐̕s̘̘̄̊̃̀ī̘̜̏̌̕ ̗̖̞̐̈̒ư̙̞̄́̌t̟̘̖̙̊̚ ̌̅̋̆̚̚ä̇̊̇̕̕l̝̞̘̋̔̅i̍̋́̆̑̈q̛̆̐̈̐̚ư̏̆̊́̚î̜̝̑́̊p̗̓̅̑̆̏ ̆́̓̔̋̋e̟̊̋̏̓̚x̗̍̑̊̎̈ ̟̞̆̄̂̍ë̄̎̄̃̅a̛̜̅́̃̈ ̔̋̀̎̐̀c̖̖̍̀̒̂ơ̛̙̖̄̒m̘̔̍̏̆̕ḿ̖̙̝̏̂ȍ̓̋̈̀̕d̆̂̊̅̓̚o̖̔̌̑̚̕ ̙̆́̔̊̒c̖̘̖̀̄̍o̓̄̑̐̓̒ñ̞̒̎̈̚s̞̜̘̈̄̄e̙̊̀̇̌̋q̐̒̓́̔̃ư̗̟̔̔̚å̖̙̞̄̏t̛̙̟̒̇̏.̙̗̓̃̓̎ + D̜̖̆̏̌̌ư̑̃̌̍̕i̝̊̊̊̊̄s̛̙̒́̌̇ ̛̃̔̄̆̌ă̘̔̅̅̀ú̟̟̟̃̃t̟̂̄̈̈̃e̘̅̌̒̂̆ ̖̟̐̉̉̌î̟̟̙̜̇r̛̙̞̗̄̌ú̗̗̃̌̎r̛̙̘̉̊̕e̒̐̔̃̓̋ ̊̊̍̋̑̉d̛̝̙̉̀̓o̘̜̐̐̓̐l̞̋̌̆̍́o̊̊̐̃̃̚ṙ̛̖̘̃̕ ̞̊̀̍̒̕ȉ́̑̐̇̅ǹ̜̗̜̞̏ ̛̜̐̄̄̚r̜̖̈̇̅̋ĕ̗̉̃̔̚p̟̝̀̓̔̆r̜̈̆̇̃̃e̘̔̔̏̎̓h̗̒̉̑̆̚ė̛̘̘̈̐n̘̂̀̒̕̕d̗̅̂̋̅́ê̗̜̜̜̕r̟̋̄̐̅̂i̛̔̌̒̂̕t̛̗̓̎̀̎ ̙̗̀̉̂̚ȉ̟̗̐̓̚n̙̂̍̏̓̉ ̙̘̊̋̍̕v̜̖̀̎̆̐ő̜̆̉̃̎l̑̋̒̉̔̆ư̙̓̓́̚p̝̘̖̎̏̒t̛̘̝̞̂̓ȁ̘̆̔́̊t̖̝̉̒̐̎e̞̟̋̀̅̄ ̆̌̃̀̑̔v̝̘̝̍̀̇ȅ̝̊̄̓̕l̞̝̑̔̂̋ĭ̝̄̅̆̍t̝̜̉̂̈̇ + ē̟̊̇̕̚s̖̘̘̒̄̑s̛̘̀̊̆̇e̛̝̘̒̏̚ ̉̅̑̂̐̎c̛̟̙̎̋̓i̜̇̒̏̆̆l̟̄́̆̊̌l̍̊̋̃̆̌ủ̗̙̒̔̚m̛̘̘̖̅̍ ̖̙̈̎̂̕d̞̟̏̋̈̔ơ̟̝̌̃̄l̗̙̝̂̉̒õ̒̃̄̄̚ŕ̗̏̏̊̍ê̞̝̞̋̈ ̜̔̒̎̃̚e̞̟̞̒̃̄ư̖̏̄̑̃ ̛̗̜̄̓̎f̛̖̞̅̓̃ü̞̏̆̋̕g̜̝̞̑̑̆i̛̘̐̐̅̚à̜̖̌̆̎t̙̙̎̉̂̍ ̋̔̈̎̎̉n̞̓́̔̊̕ư̘̅̋̔̚l̗̍̒̄̀̚l̞̗̘̙̓̍â̘̔̒̎̚ ̖̓̋̉̃̆p̛̛̘̋̌̀ä̙̔́̒̕r̟̟̖̋̐̋ì̗̙̎̓̓ȃ̔̋̑̚̕t̄́̎̓̂̋ư̏̈̂̑̃r̖̓̋̊̚̚.̒̆̑̆̊̎ ̘̜̍̐̂̚E̞̅̐̇́̂x̄́̈̌̉̕ć̘̃̉̃̕è̘̂̑̏̑p̝̘̑̂̌̆t̔̐̅̍̌̂ȇ̞̈̐̚̕ű̝̞̜́̚ŕ̗̝̉̆́ + š̟́̔̏̀ȉ̝̟̝̏̅n̑̆̇̒̆̚t̝̒́̅̋̏ ̗̑̌̋̇̚ơ̙̗̟̆̅c̙̞̙̎̊̎c̘̟̍̔̊̊a̛̒̓̉́̐e̜̘̙̒̅̇ć̝̝̂̇̕ả̓̍̎̂̚t̗̗̗̟̒̃ ̘̒̓̐̇́c̟̞̉̐̓̄ȕ̙̗̅́̏p̛̍̋̈́̅i̖̓̒̍̈̄d̞̃̈̌̆̐a̛̗̝̎̋̉t̞̙̀̊̆̇a̛̙̒̆̉̚t̜̟̘̉̓̚ ̝̘̗̐̇̕n̛̘̑̏̂́ō̑̋̉̏́ň̞̊̆̄̃ ̙̙̙̜̄̏p̒̆̋̋̓̏r̖̖̅̉́̚ơ̜̆̑̈̚i̟̒̀̃̂̌d̛̏̃̍̋̚ë̖̞̙̗̓n̛̘̓̒̅̎t̟̗̙̊̆̚,̘̙̔̊̚̕ ̟̗̘̜̑̔s̜̝̍̀̓̌û̞̙̅̇́n̘̗̝̒̃̎t̗̅̀̅̊̈ ̗̖̅̅̀̄i̛̖̍̅̋̂n̙̝̓̓̎̚ ̞̋̅̋̃̚c̗̒̀̆̌̎ū̞̂̑̌̓ĺ̛̐̍̑́p̝̆̌̎̈̚a̖̙̒̅̈̌ ̝̝̜̂̈̀q̝̖̔̍̒̚ư̔̐̂̎̊ǐ̛̟̖̘̕ + o̖̜̔̋̅̚f̛̊̀̉́̕f̏̉̀̔̃̃i̘̍̎̐̔̎c̙̅̑̂̐̅ȋ̛̜̀̒̚a̋̍̇̏̀̋ ̖̘̒̅̃̒d̗̘̓̈̇̋é̝́̎̒̄š̙̒̊̉̋e̖̓̐̀̍̕r̗̞̂̅̇̄ù̘̇̐̉̀n̐̑̀̄̍̐t̟̀̂̊̄̚ ̟̝̂̍̏́m̜̗̈̂̏̚ő̞̊̑̇̒l̘̑̏́̔̄l̛̛̇̃̋̊i̓̋̒̃̉̌t̛̗̜̏̀̋ ̙̟̒̂̌̐a̙̝̔̆̏̅n̝̙̙̗̆̅i̍̔́̊̃̕m̖̝̟̒̍̚ ̛̃̃̑̌́ǐ̘̉̔̅̚d̝̗̀̌̏̒ ̖̝̓̑̊̚ȇ̞̟̖̌̕š̙̙̈̔̀t̂̉̒̍̄̄ ̝̗̊̋̌̄l̛̞̜̙̘̔å̝̍̂̍̅b̜̆̇̈̉̌ǒ̜̙̎̃̆r̝̀̄̍́̕ư̋̊́̊̕m̜̗̒̐̕̚.̟̘̀̒̌̚]], + '\n')) + + -- tests that we can handle overflow of the buffer + -- for redraw events (4096 bytes) gracefully + screen:expect{grid=[[ + ^L̓̉̑̒̌̚ơ̗̌̒̄̀ŕ̈̈̎̐̕è̇̅̄̄̐m̖̟̟̅̄̚ ̛̓̑̆̇̍i̗̟̞̜̅̐p̗̞̜̉̆̕s̟̜̘̍̑̏ū̟̞̎̃̉ḿ̘̙́́̐ ̖̍̌̇̉̚d̞̄̃̒̉̎ò́̌̌̂̐l̞̀̄̆̌̚ȯ̖̞̋̀̐r̓̇̌̃̃̚ ̗̘̀̏̍́s̜̀̎̎̑̕i̟̗̐̄̄̚t̝̎̆̓̐̒ ̘̇̔̓̊̚ȃ̛̟̗̏̅m̜̟̙̞̈̓é̘̞̟̔̆t̝̂̂̈̑̔,̜̜̖̅̄̍ ̛̗̊̓̆̚c̟̍̆̍̈̔ȯ̖̖̝̑̀n̜̟̎̊̃̚s̟̏̇̎̒̚e̙̐̈̓̌̚c̙̍̈̏̅̕ť̇̄̇̆̓e̛̓̌̈̓̈t̟̍̀̉̆̅u̝̞̎̂̄̚r̘̀̅̈̅̐ ̝̞̓́̇̉ã̏̀̆̅̕d̛̆̐̉̆̋ȉ̞̟̍̃̚p̛̜̊̍̂̓ȋ̏̅̃̋̚ṥ̛̏̃̕č̛̞̝̀̂í̗̘̌́̎n̔̎́̒̂̕ǧ̗̜̋̇̂ ̛̜̔̄̎̃ê̛̔̆̇̕l̘̝̏̐̊̏ĩ̛̍̏̏̄t̟̐́̀̐̎,̙̘̍̆̉̐ ̋̂̏̄̌̅s̙̓̌̈́̇e̛̗̋̒̎̏d̜̗̊̍̊̚ | + ď̘̋̌̌̕ǒ̝̗̔̇̕ ̙̍́̄̄̉è̛̛̞̌̌i̜̖̐̈̆̚ȕ̇̈̓̃̓ŝ̛̞̙̉̋m̜̐̂̄̋̂ȯ̈̎̎̅̕d̜̙̓̔̋̑ ̞̗̄̂̂̚t̝̊́̃́̄e̛̘̜̞̓̑m̊̅̏̉̌̕p̛̈̂̇̀̐ỏ̙̘̈̉̔r̘̞̋̍̃̚ ̝̄̀̇̅̇ỉ̛̖̍̓̈n̛̛̝̎̕̕c̛̛̊̅́̐ĭ̗̓̀̍̐d̞̜̋̐̅̚i̟̙̇̄̊̄d̞̊̂̀̇̚ủ̝̉̑̃̕n̜̏̇̄̐̋ť̗̜̞̋̉ ̝̒̓̌̓̚ȕ̖̙̀̚̕t̖̘̎̉̂̌ ̛̝̄̍̌̂l̛̟̝̃̑̋á̛̝̝̔̅b̝̙̜̗̅̒ơ̖̌̒̄̆r̒̇̓̎̈̄e̛̛̖̅̏̇ ̖̗̜̝̃́e̛̛̘̅̔̌ẗ̛̙̗̐̕ ̖̟̇̋̌̈d̞̙̀̉̑̕ŏ̝̂́̐̑l̞̟̗̓̓̀ơ̘̎̃̄̂r̗̗̖̔̆̍ẻ̖̝̞̋̅ ̜̌̇̍̈̊m̈̉̇̄̒̀a̜̞̘̔̅̆g̗̖̈̃̈̉n̙̖̄̈̉̄â̛̝̜̄̃ ̛́̎̕̕̚ā̊́́̆̌l̟̙̞̃̒́i̖̇̎̃̀̋q̟̇̒̆́̊ủ́̌̇̑̚ã̛̘̉̐̚.̛́̏̐̍̊ | + U̝̙̎̈̐̆t̜̍̌̀̔̏ ̞̉̍̇̈̃e̟̟̊̄̕̕n̝̜̒̓̆̕i̖̒̌̅̇̚m̞̊̃̔̊̂ ̛̜̊̎̄̂a̘̜̋̒̚̚d̟̊̎̇̂̍ ̜̖̏̑̉̕m̜̒̎̅̄̚i̝̖̓̂̍̕n̙̉̒̑̀̔ỉ̖̝̌̒́m̛̖̘̅̆̎ ̖̉̎̒̌̕v̖̞̀̔́̎e̖̙̗̒̎̉n̛̗̝̎̀̂ȉ̞̗̒̕̚ȧ̟̜̝̅̚m̆̉̐̐̇̈,̏̐̎́̍́ ̜̞̙̘̏̆q̙̖̙̅̓̂ủ̇́̀̔̚í̙̟̟̏̐s̖̝̍̏̂̇ ̛̘̋̈̕̕ń̛̞̜̜̎o̗̜̔̔̈̆s̞̘̘̄̒̋t̛̅̋́̔̈ȓ̓̒́̇̅ủ̜̄̃̒̍d̙̝̘̊̏̚ ̛̟̞̄́̔e̛̗̝̍̃̀x̞̖̃̄̂̅e̖̅̇̐̔̃r̗̞̖̔̎̚c̘̜̖̆̊̏ï̙̝̙̂̕t̖̏́̓̋̂ă̖̄̆̑̒t̜̟̍̉̑̏i̛̞̞̘̒̑ǒ̜̆̅̃̉ṅ̖̜̒̎̚ | + u̗̞̓̔̈̏ĺ̟̝́̎̚l̛̜̅̌̎̆a̒̑̆̔̇̃m̜̗̈̊̎̚ċ̘̋̇̂̚ơ̟̖̊́̕ ̖̟̍̉̏̚l̙̔̓̀̅̏ä̞̗̘̙̅ḃ̟̎̄̃̕o̞̎̓̓̓̚r̗̜̊̓̈̒ï̗̜̃̃̅s̀̒̌̂̎̂ ̖̗̗̋̎̐n̝̟̝̘̄̚i̜̒̀̒̐̕s̘̘̄̊̃̀ī̘̜̏̌̕ ̗̖̞̐̈̒ư̙̞̄́̌t̟̘̖̙̊̚ ̌̅̋̆̚̚ä̇̊̇̕̕l̝̞̘̋̔̅i̍̋́̆̑̈q̛̆̐̈̐̚ư̏̆̊́̚î̜̝̑́̊p̗̓̅̑̆̏ ̆́̓̔̋̋e̟̊̋̏̓̚x̗̍̑̊̎̈ ̟̞̆̄̂̍ë̄̎̄̃̅a̛̜̅́̃̈ ̔̋̀̎̐̀c̖̖̍̀̒̂ơ̛̙̖̄̒m̘̔̍̏̆̕ḿ̖̙̝̏̂ȍ̓̋̈̀̕d̆̂̊̅̓̚o̖̔̌̑̚̕ ̙̆́̔̊̒c̖̘̖̀̄̍o̓̄̑̐̓̒ñ̞̒̎̈̚s̞̜̘̈̄̄e̙̊̀̇̌̋q̐̒̓́̔̃ư̗̟̔̔̚å̖̙̞̄̏t̛̙̟̒̇̏.̙̗̓̃̓̎ | + D̜̖̆̏̌̌ư̑̃̌̍̕i̝̊̊̊̊̄s̛̙̒́̌̇ ̛̃̔̄̆̌ă̘̔̅̅̀ú̟̟̟̃̃t̟̂̄̈̈̃e̘̅̌̒̂̆ ̖̟̐̉̉̌î̟̟̙̜̇r̛̙̞̗̄̌ú̗̗̃̌̎r̛̙̘̉̊̕e̒̐̔̃̓̋ ̊̊̍̋̑̉d̛̝̙̉̀̓o̘̜̐̐̓̐l̞̋̌̆̍́o̊̊̐̃̃̚ṙ̛̖̘̃̕ ̞̊̀̍̒̕ȉ́̑̐̇̅ǹ̜̗̜̞̏ ̛̜̐̄̄̚r̜̖̈̇̅̋ĕ̗̉̃̔̚p̟̝̀̓̔̆r̜̈̆̇̃̃e̘̔̔̏̎̓h̗̒̉̑̆̚ė̛̘̘̈̐n̘̂̀̒̕̕d̗̅̂̋̅́ê̗̜̜̜̕r̟̋̄̐̅̂i̛̔̌̒̂̕t̛̗̓̎̀̎ ̙̗̀̉̂̚ȉ̟̗̐̓̚n̙̂̍̏̓̉ ̙̘̊̋̍̕v̜̖̀̎̆̐ő̜̆̉̃̎l̑̋̒̉̔̆ư̙̓̓́̚p̝̘̖̎̏̒t̛̘̝̞̂̓ȁ̘̆̔́̊t̖̝̉̒̐̎e̞̟̋̀̅̄ ̆̌̃̀̑̔v̝̘̝̍̀̇ȅ̝̊̄̓̕l̞̝̑̔̂̋ĭ̝̄̅̆̍t̝̜̉̂̈̇ | + ē̟̊̇̕̚s̖̘̘̒̄̑s̛̘̀̊̆̇e̛̝̘̒̏̚ ̉̅̑̂̐̎c̛̟̙̎̋̓i̜̇̒̏̆̆l̟̄́̆̊̌l̍̊̋̃̆̌ủ̗̙̒̔̚m̛̘̘̖̅̍ ̖̙̈̎̂̕d̞̟̏̋̈̔ơ̟̝̌̃̄l̗̙̝̂̉̒õ̒̃̄̄̚ŕ̗̏̏̊̍ê̞̝̞̋̈ ̜̔̒̎̃̚e̞̟̞̒̃̄ư̖̏̄̑̃ ̛̗̜̄̓̎f̛̖̞̅̓̃ü̞̏̆̋̕g̜̝̞̑̑̆i̛̘̐̐̅̚à̜̖̌̆̎t̙̙̎̉̂̍ ̋̔̈̎̎̉n̞̓́̔̊̕ư̘̅̋̔̚l̗̍̒̄̀̚l̞̗̘̙̓̍â̘̔̒̎̚ ̖̓̋̉̃̆p̛̛̘̋̌̀ä̙̔́̒̕r̟̟̖̋̐̋ì̗̙̎̓̓ȃ̔̋̑̚̕t̄́̎̓̂̋ư̏̈̂̑̃r̖̓̋̊̚̚.̒̆̑̆̊̎ ̘̜̍̐̂̚E̞̅̐̇́̂x̄́̈̌̉̕ć̘̃̉̃̕è̘̂̑̏̑p̝̘̑̂̌̆t̔̐̅̍̌̂ȇ̞̈̐̚̕ű̝̞̜́̚ŕ̗̝̉̆́ | + š̟́̔̏̀ȉ̝̟̝̏̅n̑̆̇̒̆̚t̝̒́̅̋̏ ̗̑̌̋̇̚ơ̙̗̟̆̅c̙̞̙̎̊̎c̘̟̍̔̊̊a̛̒̓̉́̐e̜̘̙̒̅̇ć̝̝̂̇̕ả̓̍̎̂̚t̗̗̗̟̒̃ ̘̒̓̐̇́c̟̞̉̐̓̄ȕ̙̗̅́̏p̛̍̋̈́̅i̖̓̒̍̈̄d̞̃̈̌̆̐a̛̗̝̎̋̉t̞̙̀̊̆̇a̛̙̒̆̉̚t̜̟̘̉̓̚ ̝̘̗̐̇̕n̛̘̑̏̂́ō̑̋̉̏́ň̞̊̆̄̃ ̙̙̙̜̄̏p̒̆̋̋̓̏r̖̖̅̉́̚ơ̜̆̑̈̚i̟̒̀̃̂̌d̛̏̃̍̋̚ë̖̞̙̗̓n̛̘̓̒̅̎t̟̗̙̊̆̚,̘̙̔̊̚̕ ̟̗̘̜̑̔s̜̝̍̀̓̌û̞̙̅̇́n̘̗̝̒̃̎t̗̅̀̅̊̈ ̗̖̅̅̀̄i̛̖̍̅̋̂n̙̝̓̓̎̚ ̞̋̅̋̃̚c̗̒̀̆̌̎ū̞̂̑̌̓ĺ̛̐̍̑́p̝̆̌̎̈̚a̖̙̒̅̈̌ ̝̝̜̂̈̀q̝̖̔̍̒̚ư̔̐̂̎̊ǐ̛̟̖̘̕ | + o̖̜̔̋̅̚f̛̊̀̉́̕f̏̉̀̔̃̃i̘̍̎̐̔̎c̙̅̑̂̐̅ȋ̛̜̀̒̚a̋̍̇̏̀̋ ̖̘̒̅̃̒d̗̘̓̈̇̋é̝́̎̒̄š̙̒̊̉̋e̖̓̐̀̍̕r̗̞̂̅̇̄ù̘̇̐̉̀n̐̑̀̄̍̐t̟̀̂̊̄̚ ̟̝̂̍̏́m̜̗̈̂̏̚ő̞̊̑̇̒l̘̑̏́̔̄l̛̛̇̃̋̊i̓̋̒̃̉̌t̛̗̜̏̀̋ ̙̟̒̂̌̐a̙̝̔̆̏̅n̝̙̙̗̆̅i̍̔́̊̃̕m̖̝̟̒̍̚ ̛̃̃̑̌́ǐ̘̉̔̅̚d̝̗̀̌̏̒ ̖̝̓̑̊̚ȇ̞̟̖̌̕š̙̙̈̔̀t̂̉̒̍̄̄ ̝̗̊̋̌̄l̛̞̜̙̘̔å̝̍̂̍̅b̜̆̇̈̉̌ǒ̜̙̎̃̆r̝̀̄̍́̕ư̋̊́̊̕m̜̗̒̐̕̚.̟̘̀̒̌̚ | + {1:~ }| + | + ]]} + end) end) describe('multibyte rendering: statusline', function() diff --git a/test/functional/ui/multigrid_spec.lua b/test/functional/ui/multigrid_spec.lua index 4e5e9c3a71..b30aa67fd3 100644 --- a/test/functional/ui/multigrid_spec.lua +++ b/test/functional/ui/multigrid_spec.lua @@ -77,18 +77,18 @@ describe('ext_multigrid', function() command('vsplit') screen:expect{grid=[[ ## grid 1 - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| {11:[No Name] }{12:[No Name] }| [3:-----------------------------------------------------]| ## grid 2 @@ -129,18 +129,18 @@ describe('ext_multigrid', function() command('split') screen:expect{grid=[[ ## grid 1 - [4:--------------------------]{12:│}[5:--------------------------]| - [4:--------------------------]{12:│}[5:--------------------------]| - [4:--------------------------]{12:│}[5:--------------------------]| - [4:--------------------------]{12:│}[5:--------------------------]| - [4:--------------------------]{12:│}[5:--------------------------]| - [4:--------------------------]{12:│}[5:--------------------------]| - [4:--------------------------]{12:│}{11:[No Name] }| - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| + [4:--------------------------]│[5:--------------------------]| + [4:--------------------------]│[5:--------------------------]| + [4:--------------------------]│[5:--------------------------]| + [4:--------------------------]│[5:--------------------------]| + [4:--------------------------]│[5:--------------------------]| + [4:--------------------------]│[5:--------------------------]| + [4:--------------------------]│{11:[No Name] }| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| {12:[No Name] [No Name] }| [3:-----------------------------------------------------]| ## grid 2 @@ -300,12 +300,12 @@ describe('ext_multigrid', function() command('vsp') screen:expect{grid=[[ ## grid 1 - [6:--------------------]{12:│}[5:----------------]{12:│}[4:---------------]| - [6:--------------------]{12:│}[5:----------------]{12:│}[4:---------------]| - [6:--------------------]{12:│}[5:----------------]{12:│}[4:---------------]| - [6:--------------------]{12:│}[5:----------------]{12:│}[4:---------------]| - [6:--------------------]{12:│}[5:----------------]{12:│}[4:---------------]| - [6:--------------------]{12:│}[5:----------------]{12:│}[4:---------------]| + [6:--------------------]│[5:----------------]│[4:---------------]| + [6:--------------------]│[5:----------------]│[4:---------------]| + [6:--------------------]│[5:----------------]│[4:---------------]| + [6:--------------------]│[5:----------------]│[4:---------------]| + [6:--------------------]│[5:----------------]│[4:---------------]| + [6:--------------------]│[5:----------------]│[4:---------------]| {11:[No Name] }{12:[No Name] [No Name] }| [2:-----------------------------------------------------]| [2:-----------------------------------------------------]| @@ -347,12 +347,12 @@ describe('ext_multigrid', function() insert('hello') screen:expect{grid=[[ ## grid 1 - [6:--------------------]{12:│}[5:----------------]{12:│}[4:---------------]| - [6:--------------------]{12:│}[5:----------------]{12:│}[4:---------------]| - [6:--------------------]{12:│}[5:----------------]{12:│}[4:---------------]| - [6:--------------------]{12:│}[5:----------------]{12:│}[4:---------------]| - [6:--------------------]{12:│}[5:----------------]{12:│}[4:---------------]| - [6:--------------------]{12:│}[5:----------------]{12:│}[4:---------------]| + [6:--------------------]│[5:----------------]│[4:---------------]| + [6:--------------------]│[5:----------------]│[4:---------------]| + [6:--------------------]│[5:----------------]│[4:---------------]| + [6:--------------------]│[5:----------------]│[4:---------------]| + [6:--------------------]│[5:----------------]│[4:---------------]| + [6:--------------------]│[5:----------------]│[4:---------------]| {11:[No Name] [+] }{12:[No Name] [+] [No Name] [+] }| [2:-----------------------------------------------------]| [2:-----------------------------------------------------]| @@ -467,18 +467,18 @@ describe('ext_multigrid', function() command('vsp') screen:expect{grid=[[ ## grid 1 - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| {11:[No Name] }{12:[No Name] }| [3:-----------------------------------------------------]| ## grid 2 @@ -516,18 +516,18 @@ describe('ext_multigrid', function() command('vertical resize 10') screen:expect{grid=[[ ## grid 1 - [4:----------]{12:│}[2:------------------------------------------]| - [4:----------]{12:│}[2:------------------------------------------]| - [4:----------]{12:│}[2:------------------------------------------]| - [4:----------]{12:│}[2:------------------------------------------]| - [4:----------]{12:│}[2:------------------------------------------]| - [4:----------]{12:│}[2:------------------------------------------]| - [4:----------]{12:│}[2:------------------------------------------]| - [4:----------]{12:│}[2:------------------------------------------]| - [4:----------]{12:│}[2:------------------------------------------]| - [4:----------]{12:│}[2:------------------------------------------]| - [4:----------]{12:│}[2:------------------------------------------]| - [4:----------]{12:│}[2:------------------------------------------]| + [4:----------]│[2:------------------------------------------]| + [4:----------]│[2:------------------------------------------]| + [4:----------]│[2:------------------------------------------]| + [4:----------]│[2:------------------------------------------]| + [4:----------]│[2:------------------------------------------]| + [4:----------]│[2:------------------------------------------]| + [4:----------]│[2:------------------------------------------]| + [4:----------]│[2:------------------------------------------]| + [4:----------]│[2:------------------------------------------]| + [4:----------]│[2:------------------------------------------]| + [4:----------]│[2:------------------------------------------]| + [4:----------]│[2:------------------------------------------]| {11:<No Name] }{12:[No Name] }| [3:-----------------------------------------------------]| ## grid 2 @@ -565,18 +565,18 @@ describe('ext_multigrid', function() command('sp') screen:expect{grid=[[ ## grid 1 - [5:--------------------------]{12:│}[2:--------------------------]| - [5:--------------------------]{12:│}[2:--------------------------]| - [5:--------------------------]{12:│}[2:--------------------------]| - [5:--------------------------]{12:│}[2:--------------------------]| - [5:--------------------------]{12:│}[2:--------------------------]| - [5:--------------------------]{12:│}[2:--------------------------]| - {11:[No Name] }{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| + [5:--------------------------]│[2:--------------------------]| + [5:--------------------------]│[2:--------------------------]| + [5:--------------------------]│[2:--------------------------]| + [5:--------------------------]│[2:--------------------------]| + [5:--------------------------]│[2:--------------------------]| + [5:--------------------------]│[2:--------------------------]| + {11:[No Name] }│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| {12:[No Name] [No Name] }| [3:-----------------------------------------------------]| ## grid 2 @@ -611,18 +611,18 @@ describe('ext_multigrid', function() insert('hello') screen:expect{grid=[[ ## grid 1 - [5:--------------------------]{12:│}[2:--------------------------]| - [5:--------------------------]{12:│}[2:--------------------------]| - [5:--------------------------]{12:│}[2:--------------------------]| - [5:--------------------------]{12:│}[2:--------------------------]| - [5:--------------------------]{12:│}[2:--------------------------]| - [5:--------------------------]{12:│}[2:--------------------------]| - {11:[No Name] [+] }{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| + [5:--------------------------]│[2:--------------------------]| + [5:--------------------------]│[2:--------------------------]| + [5:--------------------------]│[2:--------------------------]| + [5:--------------------------]│[2:--------------------------]| + [5:--------------------------]│[2:--------------------------]| + [5:--------------------------]│[2:--------------------------]| + {11:[No Name] [+] }│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| {12:[No Name] [+] [No Name] [+] }| [3:-----------------------------------------------------]| ## grid 2 @@ -659,18 +659,18 @@ describe('ext_multigrid', function() command('vsp') screen:expect{grid=[[ ## grid 1 - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| {11:[No Name] }{12:[No Name] }| [3:-----------------------------------------------------]| ## grid 2 @@ -1056,12 +1056,12 @@ describe('ext_multigrid', function() command('vsp') screen:expect{grid=[[ ## grid 1 - [5:--------------------------]{12:│}[4:--------------------------]| - [5:--------------------------]{12:│}[4:--------------------------]| - [5:--------------------------]{12:│}[4:--------------------------]| - [5:--------------------------]{12:│}[4:--------------------------]| - [5:--------------------------]{12:│}[4:--------------------------]| - [5:--------------------------]{12:│}[4:--------------------------]| + [5:--------------------------]│[4:--------------------------]| + [5:--------------------------]│[4:--------------------------]| + [5:--------------------------]│[4:--------------------------]| + [5:--------------------------]│[4:--------------------------]| + [5:--------------------------]│[4:--------------------------]| + [5:--------------------------]│[4:--------------------------]| {11:[No Name] }{12:[No Name] }| [2:-----------------------------------------------------]| [2:-----------------------------------------------------]| @@ -1097,12 +1097,12 @@ describe('ext_multigrid', function() feed(":echoerr 'very' | echoerr 'much' | echoerr 'fail'<cr>") screen:expect{grid=[[ ## grid 1 - [5:--------------------------]{12:│}[4:--------------------------]| - [5:--------------------------]{12:│}[4:--------------------------]| - [5:--------------------------]{12:│}[4:--------------------------]| - [5:--------------------------]{12:│}[4:--------------------------]| - [5:--------------------------]{12:│}[4:--------------------------]| - [5:--------------------------]{12:│}[4:--------------------------]| + [5:--------------------------]│[4:--------------------------]| + [5:--------------------------]│[4:--------------------------]| + [5:--------------------------]│[4:--------------------------]| + [5:--------------------------]│[4:--------------------------]| + [5:--------------------------]│[4:--------------------------]| + [5:--------------------------]│[4:--------------------------]| {11:[No Name] }{12:[No Name] }| [2:-----------------------------------------------------]| [2:-----------------------------------------------------]| @@ -1141,12 +1141,12 @@ describe('ext_multigrid', function() feed('<cr>') screen:expect{grid=[[ ## grid 1 - [5:--------------------------]{12:│}[4:--------------------------]| - [5:--------------------------]{12:│}[4:--------------------------]| - [5:--------------------------]{12:│}[4:--------------------------]| - [5:--------------------------]{12:│}[4:--------------------------]| - [5:--------------------------]{12:│}[4:--------------------------]| - [5:--------------------------]{12:│}[4:--------------------------]| + [5:--------------------------]│[4:--------------------------]| + [5:--------------------------]│[4:--------------------------]| + [5:--------------------------]│[4:--------------------------]| + [5:--------------------------]│[4:--------------------------]| + [5:--------------------------]│[4:--------------------------]| + [5:--------------------------]│[4:--------------------------]| {11:[No Name] }{12:[No Name] }| [2:-----------------------------------------------------]| [2:-----------------------------------------------------]| @@ -1242,12 +1242,12 @@ describe('ext_multigrid', function() feed("<c-c>") screen:expect{grid=[[ ## grid 1 - [5:--------------------------]{12:│}[4:--------------------------]| - [5:--------------------------]{12:│}[4:--------------------------]| - [5:--------------------------]{12:│}[4:--------------------------]| - [5:--------------------------]{12:│}[4:--------------------------]| - [5:--------------------------]{12:│}[4:--------------------------]| - [5:--------------------------]{12:│}[4:--------------------------]| + [5:--------------------------]│[4:--------------------------]| + [5:--------------------------]│[4:--------------------------]| + [5:--------------------------]│[4:--------------------------]| + [5:--------------------------]│[4:--------------------------]| + [5:--------------------------]│[4:--------------------------]| + [5:--------------------------]│[4:--------------------------]| {11:[No Name] }{12:[No Name] }| [2:-----------------------------------------------------]| [2:-----------------------------------------------------]| @@ -1285,18 +1285,18 @@ describe('ext_multigrid', function() command('vsp') screen:expect{grid=[[ ## grid 1 - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| {11:[No Name] }{12:[No Name] }| [3:-----------------------------------------------------]| ## grid 2 @@ -1453,17 +1453,17 @@ describe('ext_multigrid', function() screen:expect{grid=[[ ## grid 1 {7: }{18:2}{7: [No Name] }{16: }{17:2}{16: [No Name] }{12: }{16:X}| - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| {11:[No Name] }{12:[No Name] }| [3:-----------------------------------------------------]| ## grid 2 @@ -1637,18 +1637,18 @@ describe('ext_multigrid', function() command('tabclose') screen:expect{grid=[[ ## grid 1 - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| {11:[No Name] }{12:[No Name] }| [3:-----------------------------------------------------]| ## grid 2 @@ -1915,7 +1915,7 @@ describe('ext_multigrid', function() {1:~ }| ]]} - meths.input_mouse('left', 'press', '', 1,6, 20) + meths.input_mouse('left', 'press', '', 1, 6, 20) -- TODO(bfredl): "batching" input_mouse is formally not supported yet. -- Normally it should work fine in async context when nvim is not blocked, -- but add a poke_eventloop be sure. @@ -1960,13 +1960,13 @@ describe('ext_multigrid', function() [4:-----------------------------------------------------]| [4:-----------------------------------------------------]| {12:[No Name] [+] }| - [5:--------------------------]{12:│}[2:--------------------------]| - [5:--------------------------]{12:│}[2:--------------------------]| - [5:--------------------------]{12:│}[2:--------------------------]| - [5:--------------------------]{12:│}[2:--------------------------]| - [5:--------------------------]{12:│}[2:--------------------------]| - [5:--------------------------]{12:│}[2:--------------------------]| - [5:--------------------------]{12:│}[2:--------------------------]| + [5:--------------------------]│[2:--------------------------]| + [5:--------------------------]│[2:--------------------------]| + [5:--------------------------]│[2:--------------------------]| + [5:--------------------------]│[2:--------------------------]| + [5:--------------------------]│[2:--------------------------]| + [5:--------------------------]│[2:--------------------------]| + [5:--------------------------]│[2:--------------------------]| {11:[No Name] [+] }{12:[No Name] [+] }| [3:-----------------------------------------------------]| ## grid 2 @@ -2002,13 +2002,13 @@ describe('ext_multigrid', function() [4:-----------------------------------------------------]| [4:-----------------------------------------------------]| {12:[No Name] [+] }| - [5:------------------------------]{12:│}[2:----------------------]| - [5:------------------------------]{12:│}[2:----------------------]| - [5:------------------------------]{12:│}[2:----------------------]| - [5:------------------------------]{12:│}[2:----------------------]| - [5:------------------------------]{12:│}[2:----------------------]| - [5:------------------------------]{12:│}[2:----------------------]| - [5:------------------------------]{12:│}[2:----------------------]| + [5:------------------------------]│[2:----------------------]| + [5:------------------------------]│[2:----------------------]| + [5:------------------------------]│[2:----------------------]| + [5:------------------------------]│[2:----------------------]| + [5:------------------------------]│[2:----------------------]| + [5:------------------------------]│[2:----------------------]| + [5:------------------------------]│[2:----------------------]| {11:[No Name] [+] }{12:[No Name] [+] }| [3:-----------------------------------------------------]| ## grid 2 @@ -2049,18 +2049,18 @@ describe('ext_multigrid', function() screen:expect{grid=[[ ## grid 1 - [4:--------------------------]{12:│}[5:--------------------------]| - [4:--------------------------]{12:│}[5:--------------------------]| - [4:--------------------------]{12:│}[5:--------------------------]| - [4:--------------------------]{12:│}[5:--------------------------]| - [4:--------------------------]{12:│}[5:--------------------------]| - [4:--------------------------]{12:│}[5:--------------------------]| - [4:--------------------------]{12:│}{11:[No Name] [+] }| - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| - [4:--------------------------]{12:│}[2:--------------------------]| + [4:--------------------------]│[5:--------------------------]| + [4:--------------------------]│[5:--------------------------]| + [4:--------------------------]│[5:--------------------------]| + [4:--------------------------]│[5:--------------------------]| + [4:--------------------------]│[5:--------------------------]| + [4:--------------------------]│[5:--------------------------]| + [4:--------------------------]│{11:[No Name] [+] }| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| + [4:--------------------------]│[2:--------------------------]| {12:[No Name] [No Name] }| [3:-----------------------------------------------------]| ## grid 2 @@ -2092,7 +2092,6 @@ describe('ext_multigrid', function() {1:~ }| {1:~ }| ]]} - end) it('has viewport information', function() @@ -2369,4 +2368,223 @@ describe('ext_multigrid', function() [2] = {win = {id = 1000}, topline = 6, botline = 12, curline = 10, curcol = 1, linecount = 11}, }} end) + + it('with winbar', function() + command 'split' + command 'setlocal winbar=very\\ bar' + screen:expect{grid=[[ + ## grid 1 + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + {11:[No Name] }| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + {12:[No Name] }| + [3:-----------------------------------------------------]| + ## grid 2 + | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ## grid 3 + | + ## grid 4 + {7:very bar }| + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ]], win_viewport={ + [2] = {win = {id = 1000}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1}; + [4] = {win = {id = 1001}, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1}; + }} + end) + + it('with winbar dragging statusline with mouse works correctly', function() + meths.set_option('winbar', 'Set Up The Bars') + command('split') + screen:expect([[ + ## grid 1 + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + {11:[No Name] }| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + {12:[No Name] }| + [3:-----------------------------------------------------]| + ## grid 2 + {7:Set Up The Bars }| + | + {1:~ }| + {1:~ }| + {1:~ }| + ## grid 3 + | + ## grid 4 + {7:Set Up The Bars }| + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ]]) + + meths.input_mouse('left', 'press', '', 1, 6, 20) + poke_eventloop() + meths.input_mouse('left', 'drag', '', 1, 7, 20) + screen:expect([[ + ## grid 1 + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + {11:[No Name] }| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + {12:[No Name] }| + [3:-----------------------------------------------------]| + ## grid 2 + {7:Set Up The Bars }| + | + {1:~ }| + {1:~ }| + ## grid 3 + | + ## grid 4 + {7:Set Up The Bars }| + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ]]) + + meths.input_mouse('left', 'drag', '', 1, 4, 20) + screen:expect([[ + ## grid 1 + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + {11:[No Name] }| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + {12:[No Name] }| + [3:-----------------------------------------------------]| + ## grid 2 + {7:Set Up The Bars }| + | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ## grid 3 + | + ## grid 4 + {7:Set Up The Bars }| + ^ | + {1:~ }| + {1:~ }| + ]]) + + meths.input_mouse('left', 'press', '', 1, 12, 10) + poke_eventloop() + meths.input_mouse('left', 'drag', '', 1, 10, 10) + screen:expect([[ + ## grid 1 + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + {11:[No Name] }| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + {12:[No Name] }| + [3:-----------------------------------------------------]| + [3:-----------------------------------------------------]| + [3:-----------------------------------------------------]| + ## grid 2 + {7:Set Up The Bars }| + | + {1:~ }| + {1:~ }| + {1:~ }| + ## grid 3 + | + | + | + ## grid 4 + {7:Set Up The Bars }| + ^ | + {1:~ }| + {1:~ }| + ]]) + eq(3, meths.get_option('cmdheight')) + + meths.input_mouse('left', 'drag', '', 1, 12, 10) + screen:expect([[ + ## grid 1 + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + [4:-----------------------------------------------------]| + {11:[No Name] }| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + [2:-----------------------------------------------------]| + {12:[No Name] }| + [3:-----------------------------------------------------]| + ## grid 2 + {7:Set Up The Bars }| + | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ## grid 3 + | + ## grid 4 + {7:Set Up The Bars }| + ^ | + {1:~ }| + {1:~ }| + ]]) + eq(1, meths.get_option('cmdheight')) + end) end) diff --git a/test/functional/ui/options_spec.lua b/test/functional/ui/options_spec.lua index 2f113f6ac6..c2b0bcdb64 100644 --- a/test/functional/ui/options_spec.lua +++ b/test/functional/ui/options_spec.lua @@ -4,6 +4,7 @@ local clear = helpers.clear local command = helpers.command local eq = helpers.eq local shallowcopy = helpers.shallowcopy +local eval = helpers.eval describe('UI receives option updates', function() local screen @@ -62,17 +63,18 @@ describe('UI receives option updates', function() end screen:attach() screen:expect(function() - eq({'mouse_off'}, evs) + eq({'mouse_on'}, evs) end) - command("set mouse=nvi") + command("set mouse=") + command("set mouse&") screen:expect(function() - eq({'mouse_off','mouse_on'}, evs) + eq({'mouse_on','mouse_off', 'mouse_on'}, evs) end) screen:detach() - eq({'mouse_off','mouse_on'}, evs) + eq({'mouse_on','mouse_off', 'mouse_on'}, evs) screen:attach() screen:expect(function() - eq({'mouse_off','mouse_on','mouse_on'}, evs) + eq({'mouse_on','mouse_off','mouse_on', 'mouse_on'}, evs) end) end) @@ -86,6 +88,12 @@ describe('UI receives option updates', function() eq(expected, screen.options) end) + command("set pumblend=-1") + expected.pumblend = 0 + screen:expect(function() + eq(expected, screen.options) + end) + command("set guifont=Comic\\ Sans") expected.guifont = "Comic Sans" screen:expect(function() @@ -168,3 +176,42 @@ describe('UI receives option updates', function() it('from startup options with --headless', function() startup_test(true) end) it('from startup options with --embed', function() startup_test(false) end) end) + +describe('UI can set terminal option', function() + local screen + before_each(function() + -- by default we implicity "--cmd 'set bg=light'" which ruins everything + clear{args_rm={'--cmd'}} + screen = Screen.new(20,5) + end) + + it('term_background', function() + eq('dark', eval '&background') + + screen:attach {term_background='light'} + eq('light', eval '&background') + end) + + it("term_background but not if 'background' already set by user", function() + eq('dark', eval '&background') + command 'set background=dark' + + screen:attach {term_background='light'} + + eq('dark', eval '&background') + end) + + it('term_name', function() + eq('nvim', eval '&term') + + screen:attach {term_name='xterm'} + eq('xterm', eval '&term') + end) + + it('term_colors', function() + eq('256', eval '&t_Co') + + screen:attach {term_colors=8} + eq('8', eval '&t_Co') + end) +end) diff --git a/test/functional/ui/output_spec.lua b/test/functional/ui/output_spec.lua index 50e5dfac84..71c6410013 100644 --- a/test/functional/ui/output_spec.lua +++ b/test/functional/ui/output_spec.lua @@ -9,6 +9,7 @@ local feed_command = helpers.feed_command local iswin = helpers.iswin local clear = helpers.clear local command = helpers.command +local testprg = helpers.testprg local nvim_dir = helpers.nvim_dir local has_powershell = helpers.has_powershell local set_shell_powershell = helpers.set_shell_powershell @@ -54,7 +55,7 @@ describe("shell command :!", function() if 'openbsd' == helpers.uname() then pending('FIXME #10804') end - child_session.feed_data(":!"..nvim_dir.."/shell-test REP 30001 foo\n") + child_session.feed_data((":!%s REP 30001 foo\n"):format(testprg('shell-test'))) -- If we observe any line starting with a dot, then throttling occurred. -- Avoid false failure on slow systems. @@ -207,12 +208,7 @@ describe("shell command :!", function() it('handles multibyte sequences split over buffer boundaries', function() command('cd '..nvim_dir) - local cmd - if iswin() then - cmd = '!shell-test UTF-8 ' - else - cmd = '!./shell-test UTF-8' - end + local cmd = iswin() and '!shell-test UTF-8 ' or '!./shell-test UTF-8' feed_command(cmd) -- Note: only the first example of split composed char works screen:expect([[ diff --git a/test/functional/ui/popupmenu_spec.lua b/test/functional/ui/popupmenu_spec.lua index 4fc5c389e5..7b0005bcf1 100644 --- a/test/functional/ui/popupmenu_spec.lua +++ b/test/functional/ui/popupmenu_spec.lua @@ -10,6 +10,8 @@ local funcs = helpers.funcs local get_pathsep = helpers.get_pathsep local eq = helpers.eq local pcall_err = helpers.pcall_err +local exec_lua = helpers.exec_lua +local exec = helpers.exec describe('ui/ext_popupmenu', function() local screen @@ -25,6 +27,7 @@ describe('ui/ext_popupmenu', function() [5] = {bold = true, foreground = Screen.colors.SeaGreen}, [6] = {background = Screen.colors.WebGray}, [7] = {background = Screen.colors.LightMagenta}, + [8] = {foreground = Screen.colors.Red}, }) source([[ function! TestComplete() abort @@ -369,6 +372,111 @@ describe('ui/ext_popupmenu', function() {1:~ }| {2:-- INSERT --} | ]]) + + command('iunmap <f1>') + command('iunmap <f2>') + command('iunmap <f3>') + exec_lua([[ + vim.keymap.set('i', '<f1>', function() vim.api.nvim_select_popupmenu_item(2, true, false, {}) end) + vim.keymap.set('i', '<f2>', function() vim.api.nvim_select_popupmenu_item(-1, false, false, {}) end) + vim.keymap.set('i', '<f3>', function() vim.api.nvim_select_popupmenu_item(1, false, true, {}) end) + ]]) + feed('<C-r>=TestComplete()<CR>') + screen:expect([[ + | + foo^ | + {6:fo x the foo }{1: }| + {7:bar }{1: }| + {7:spam }{1: }| + {1:~ }| + {1:~ }| + {2:-- INSERT --} | + ]]) + + feed('<f1>') + screen:expect([[ + | + spam^ | + {7:fo x the foo }{1: }| + {7:bar }{1: }| + {6:spam }{1: }| + {1:~ }| + {1:~ }| + {2:-- INSERT --} | + ]]) + + feed('<f2>') + screen:expect([[ + | + spam^ | + {7:fo x the foo }{1: }| + {7:bar }{1: }| + {7:spam }{1: }| + {1:~ }| + {1:~ }| + {2:-- INSERT --} | + ]]) + + feed('<f3>') + screen:expect([[ + | + bar^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {2:-- INSERT --} | + ]]) + + feed('<esc>ddiaa bb cc<cr>') + feed('<c-x><c-n>') + screen:expect([[ + aa bb cc | + aa^ | + {6:aa }{1: }| + {7:bb }{1: }| + {7:cc }{1: }| + {1:~ }| + {1:~ }| + {2:-- Keyword Local completion (^N^P) }{5:match 1 of 3} | + ]]) + + feed('<f1>') + screen:expect([[ + aa bb cc | + cc^ | + {7:aa }{1: }| + {7:bb }{1: }| + {6:cc }{1: }| + {1:~ }| + {1:~ }| + {2:-- Keyword Local completion (^N^P) }{5:match 3 of 3} | + ]]) + + feed('<f2>') + screen:expect([[ + aa bb cc | + cc^ | + {7:aa }{1: }| + {7:bb }{1: }| + {7:cc }{1: }| + {1:~ }| + {1:~ }| + {2:-- Keyword Local completion (^N^P) }{8:Back at original} | + ]]) + + feed('<f3>') + screen:expect([[ + aa bb cc | + bb^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {2:-- INSERT --} | + ]]) end) local function source_complete_month() @@ -846,72 +954,72 @@ describe('builtin popupmenu', function() insert('aaa aab aac\n') feed(':vsplit<cr>') screen:expect([[ - aaa aab aac {3:│}aaa aab aac| - ^ {3:│} | - {1:~ }{3:│}{1:~ }| - {1:~ }{3:│}{1:~ }| - {1:~ }{3:│}{1:~ }| - {1:~ }{3:│}{1:~ }| - {1:~ }{3:│}{1:~ }| - {1:~ }{3:│}{1:~ }| - {1:~ }{3:│}{1:~ }| - {1:~ }{3:│}{1:~ }| - {1:~ }{3:│}{1:~ }| - {1:~ }{3:│}{1:~ }| - {1:~ }{3:│}{1:~ }| - {1:~ }{3:│}{1:~ }| - {1:~ }{3:│}{1:~ }| - {1:~ }{3:│}{1:~ }| - {1:~ }{3:│}{1:~ }| - {1:~ }{3:│}{1:~ }| + aaa aab aac │aaa aab aac| + ^ │ | + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| {4:[No Name] [+] }{3:<Name] [+] }| :vsplit | ]]) feed('ibbb a<c-x><c-n>') screen:expect([[ - aaa aab aac {3:│}aaa aab aac| - bbb aaa^ {3:│}bbb aaa | - {1:~ }{s: aaa }{1: }{3:│}{1:~ }| - {1:~ }{n: aab }{1: }{3:│}{1:~ }| - {1:~ }{n: aac }{1: }{3:│}{1:~ }| - {1:~ }{3:│}{1:~ }| - {1:~ }{3:│}{1:~ }| - {1:~ }{3:│}{1:~ }| - {1:~ }{3:│}{1:~ }| - {1:~ }{3:│}{1:~ }| - {1:~ }{3:│}{1:~ }| - {1:~ }{3:│}{1:~ }| - {1:~ }{3:│}{1:~ }| - {1:~ }{3:│}{1:~ }| - {1:~ }{3:│}{1:~ }| - {1:~ }{3:│}{1:~ }| - {1:~ }{3:│}{1:~ }| - {1:~ }{3:│}{1:~ }| + aaa aab aac │aaa aab aac| + bbb aaa^ │bbb aaa | + {1:~ }{s: aaa }{1: }│{1:~ }| + {1:~ }{n: aab }{1: }│{1:~ }| + {1:~ }{n: aac }{1: }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| {4:[No Name] [+] }{3:<Name] [+] }| {2:-- }{5:match 1 of 3} | ]]) feed('<esc><c-w><c-w>oc a<c-x><c-n>') screen:expect([[ - aaa aab aac{3:│}aaa aab aac | - bbb aaa {3:│}bbb aaa | - c aaa {3:│}c aaa^ | - {1:~ }{3:│}{1:~}{s: aaa }{1: }| - {1:~ }{3:│}{1:~}{n: aab }{1: }| - {1:~ }{3:│}{1:~}{n: aac }{1: }| - {1:~ }{3:│}{1:~ }| - {1:~ }{3:│}{1:~ }| - {1:~ }{3:│}{1:~ }| - {1:~ }{3:│}{1:~ }| - {1:~ }{3:│}{1:~ }| - {1:~ }{3:│}{1:~ }| - {1:~ }{3:│}{1:~ }| - {1:~ }{3:│}{1:~ }| - {1:~ }{3:│}{1:~ }| - {1:~ }{3:│}{1:~ }| - {1:~ }{3:│}{1:~ }| - {1:~ }{3:│}{1:~ }| + aaa aab aac│aaa aab aac | + bbb aaa │bbb aaa | + c aaa │c aaa^ | + {1:~ }│{1:~}{s: aaa }{1: }| + {1:~ }│{1:~}{n: aab }{1: }| + {1:~ }│{1:~}{n: aac }{1: }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| {3:<Name] [+] }{4:[No Name] [+] }| {2:-- }{5:match 1 of 3} | ]]) @@ -1845,7 +1953,7 @@ describe('builtin popupmenu', function() ]]) end) - it('wildoptions=pum with scrolled mesages ', function() + it('wildoptions=pum with scrolled messages ', function() screen:try_resize(40,10) command('set wildmenu') command('set wildoptions=pum') @@ -2084,7 +2192,7 @@ describe('builtin popupmenu', function() {20:-- Keyword Local completion (^N^P) }{21:match 1 of 65} | ]]) - -- can disable blending for indiviual attribute. For instance current + -- can disable blending for individual attribute. For instance current -- selected item. (also tests that `hi Pmenu*` take immediate effect) command('hi PMenuSel blend=0') screen:expect([[ @@ -2252,6 +2360,103 @@ describe('builtin popupmenu', function() {2:-- INSERT --} | ]]) end) + + it('supports mousemodel=popup', function() + screen:try_resize(32, 6) + exec([[ + call setline(1, 'popup menu test') + set mouse=a mousemodel=popup + + aunmenu PopUp + menu PopUp.foo :let g:menustr = 'foo'<CR> + menu PopUp.bar :let g:menustr = 'bar'<CR> + menu PopUp.baz :let g:menustr = 'baz'<CR> + ]]) + meths.input_mouse('right', 'press', '', 0, 0, 4) + screen:expect([[ + ^popup menu test | + {1:~ }{n: foo }{1: }| + {1:~ }{n: bar }{1: }| + {1:~ }{n: baz }{1: }| + {1:~ }| + | + ]]) + feed('<Down>') + screen:expect([[ + ^popup menu test | + {1:~ }{s: foo }{1: }| + {1:~ }{n: bar }{1: }| + {1:~ }{n: baz }{1: }| + {1:~ }| + | + ]]) + feed('<Down>') + screen:expect([[ + ^popup menu test | + {1:~ }{n: foo }{1: }| + {1:~ }{s: bar }{1: }| + {1:~ }{n: baz }{1: }| + {1:~ }| + | + ]]) + feed('<CR>') + screen:expect([[ + ^popup menu test | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + :let g:menustr = 'bar' | + ]]) + eq('bar', meths.get_var('menustr')) + meths.input_mouse('right', 'press', '', 0, 1, 20) + screen:expect([[ + ^popup menu test | + {1:~ }| + {1:~ }{n: foo }{1: }| + {1:~ }{n: bar }{1: }| + {1:~ }{n: baz }{1: }| + :let g:menustr = 'bar' | + ]]) + meths.input_mouse('left', 'press', '', 0, 4, 22) + screen:expect([[ + ^popup menu test | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + :let g:menustr = 'baz' | + ]]) + eq('baz', meths.get_var('menustr')) + meths.input_mouse('right', 'press', '', 0, 0, 4) + screen:expect([[ + ^popup menu test | + {1:~ }{n: foo }{1: }| + {1:~ }{n: bar }{1: }| + {1:~ }{n: baz }{1: }| + {1:~ }| + :let g:menustr = 'baz' | + ]]) + meths.input_mouse('right', 'drag', '', 0, 3, 6) + screen:expect([[ + ^popup menu test | + {1:~ }{n: foo }{1: }| + {1:~ }{n: bar }{1: }| + {1:~ }{s: baz }{1: }| + {1:~ }| + :let g:menustr = 'baz' | + ]]) + meths.input_mouse('right', 'release', '', 0, 1, 6) + screen:expect([[ + ^popup menu test | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + :let g:menustr = 'foo' | + ]]) + eq('foo', meths.get_var('menustr')) + end) end) describe('builtin popupmenu with ui/ext_multigrid', function() @@ -2343,4 +2548,121 @@ describe('builtin popupmenu with ui/ext_multigrid', function() {n: 哦哦哦哦哦哦哦哦哦>}{s: }| ]], float_pos={[4] = {{id = -1}, 'NW', 2, 1, 11, false, 100}}}) end) + + it('supports mousemodel=popup', function() + screen:try_resize(32, 6) + exec([[ + call setline(1, 'popup menu test') + set mouse=a mousemodel=popup + + aunmenu PopUp + menu PopUp.foo :let g:menustr = 'foo'<CR> + menu PopUp.bar :let g:menustr = 'bar'<CR> + menu PopUp.baz :let g:menustr = 'baz'<CR> + ]]) + meths.input_mouse('right', 'press', '', 2, 1, 20) + screen:expect({grid=[[ + ## grid 1 + [2:--------------------------------]| + [2:--------------------------------]| + [2:--------------------------------]| + [2:--------------------------------]| + [2:--------------------------------]| + [3:--------------------------------]| + ## grid 2 + ^popup menu test | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ## grid 3 + | + ## grid 4 + {n: foo }| + {n: bar }| + {n: baz }| + ]], float_pos={[4] = {{id = -1}, 'NW', 2, 2, 19, false, 100}}}) + meths.input_mouse('left', 'press', '', 4, 2, 2) + screen:expect({grid=[[ + ## grid 1 + [2:--------------------------------]| + [2:--------------------------------]| + [2:--------------------------------]| + [2:--------------------------------]| + [2:--------------------------------]| + [3:--------------------------------]| + ## grid 2 + ^popup menu test | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ## grid 3 + :let g:menustr = 'baz' | + ]]}) + eq('baz', meths.get_var('menustr')) + meths.input_mouse('right', 'press', '', 2, 0, 4) + screen:expect({grid=[[ + ## grid 1 + [2:--------------------------------]| + [2:--------------------------------]| + [2:--------------------------------]| + [2:--------------------------------]| + [2:--------------------------------]| + [3:--------------------------------]| + ## grid 2 + ^popup menu test | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ## grid 3 + :let g:menustr = 'baz' | + ## grid 4 + {n: foo }| + {n: bar }| + {n: baz }| + ]], float_pos={[4] = {{id = -1}, 'NW', 2, 1, 3, false, 100}}}) + meths.input_mouse('right', 'drag', '', 2, 3, 6) + screen:expect({grid=[[ + ## grid 1 + [2:--------------------------------]| + [2:--------------------------------]| + [2:--------------------------------]| + [2:--------------------------------]| + [2:--------------------------------]| + [3:--------------------------------]| + ## grid 2 + ^popup menu test | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ## grid 3 + :let g:menustr = 'baz' | + ## grid 4 + {n: foo }| + {n: bar }| + {s: baz }| + ]], float_pos={[4] = {{id = -1}, 'NW', 2, 1, 3, false, 100}}}) + meths.input_mouse('right', 'release', '', 2, 1, 6) + screen:expect({grid=[[ + ## grid 1 + [2:--------------------------------]| + [2:--------------------------------]| + [2:--------------------------------]| + [2:--------------------------------]| + [2:--------------------------------]| + [3:--------------------------------]| + ## grid 2 + ^popup menu test | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + ## grid 3 + :let g:menustr = 'foo' | + ]]}) + eq('foo', meths.get_var('menustr')) + end) end) diff --git a/test/functional/ui/screen.lua b/test/functional/ui/screen.lua index 61f19c3794..ea98705394 100644 --- a/test/functional/ui/screen.lua +++ b/test/functional/ui/screen.lua @@ -75,7 +75,7 @@ local busted = require('busted') local deepcopy = helpers.deepcopy local shallowcopy = helpers.shallowcopy local concat_tables = helpers.concat_tables -local request, run_session = helpers.request, helpers.run_session +local run_session = helpers.run_session local eq = helpers.eq local dedent = helpers.dedent local get_session = helpers.get_session @@ -90,8 +90,6 @@ end local Screen = {} Screen.__index = Screen -local debug_screen - local default_timeout_factor = 1 if os.getenv('VALGRIND') then default_timeout_factor = default_timeout_factor * 3 @@ -123,18 +121,6 @@ do Screen.colornames = colornames end -function Screen.debug(command) - if not command then - command = 'pynvim -n -c ' - end - command = command .. request('vim_eval', '$NVIM_LISTEN_ADDRESS') - if debug_screen then - debug_screen:close() - end - debug_screen = io.popen(command, 'r') - debug_screen:read() -end - function Screen.new(width, height) if not width then width = 53 @@ -179,6 +165,7 @@ function Screen.new(width, height) _width = width, _height = height, _grids = {}, + _grid_win_extmarks = {}, _cursor = { grid = 1, row = 1, col = 1 }, @@ -255,7 +242,7 @@ end -- canonical order of ext keys, used to generate asserts local ext_keys = { 'popupmenu', 'cmdline', 'cmdline_block', 'wildmenu_items', 'wildmenu_pos', - 'messages', 'showmode', 'showcmd', 'ruler', 'float_pos', 'win_viewport' + 'messages', 'msg_history', 'showmode', 'showcmd', 'ruler', 'float_pos', 'win_viewport' } -- Asserts that the screen state eventually matches an expected state. @@ -278,6 +265,8 @@ local ext_keys = { -- attributes in the final state are an error. -- Use screen:set_default_attr_ids() to define attributes for many -- expect() calls. +-- extmarks: Expected win_extmarks accumulated for the grids. For each grid, +-- the win_extmark messages are accumulated into an array. -- condition: Function asserting some arbitrary condition. Return value is -- ignored, throw an error (use eq() or similar) to signal failure. -- any: Lua pattern string expected to match a screen line. NB: the @@ -320,7 +309,7 @@ function Screen:expect(expected, attr_ids, ...) assert(not (attr_ids ~= nil)) local is_key = {grid=true, attr_ids=true, condition=true, mouse_enabled=true, any=true, mode=true, unchanged=true, intermediate=true, - reset=true, timeout=true, request_cb=true, hl_groups=true} + reset=true, timeout=true, request_cb=true, hl_groups=true, extmarks=true} for _, v in ipairs(ext_keys) do is_key[v] = true end @@ -394,7 +383,7 @@ function Screen:expect(expected, attr_ids, ...) for i, row in ipairs(expected_rows) do msg_expected_rows[i] = row local m = (row ~= actual_rows[i] and row:match('{MATCH:(.*)}') or nil) - if row ~= actual_rows[i] and (not m or not actual_rows[i]:match(m)) then + if row ~= actual_rows[i] and (not m or not (actual_rows[i] and actual_rows[i]:match(m))) then msg_expected_rows[i] = '*' .. msg_expected_rows[i] if i <= #actual_rows then actual_rows[i] = '*' .. actual_rows[i] @@ -459,6 +448,25 @@ screen:redraw_debug() to show all intermediate screen states. ]]) end end end + + if expected.extmarks ~= nil then + for gridid, expected_marks in pairs(expected.extmarks) do + local stored_marks = self._grid_win_extmarks[gridid] + if stored_marks == nil then + return 'no win_extmark for grid '..tostring(gridid) + end + local status, res = pcall(eq, expected_marks, stored_marks, "extmarks for grid "..tostring(gridid)) + if not status then + return tostring(res) + end + end + for gridid, _ in pairs(self._grid_win_extmarks) do + local expected_marks = expected.extmarks[gridid] + if expected_marks == nil then + return 'unexpected win_extmark for grid '..tostring(gridid) + end + end + end end, expected) end @@ -544,7 +552,7 @@ function Screen:_wait(check, flags) elseif not checked then err = check() if not err and flags.unchanged then - -- expecting NO screen change: use a shorter timout + -- expecting NO screen change: use a shorter timeout success_seen = true end end @@ -576,16 +584,16 @@ to the test if they make sense. print([[ warning: Screen changes were received after the expected state. This indicates -indeterminism in the test. Try adding screen:expect(...) (or wait()) between -asynchronous (feed(), nvim_input()) and synchronous API calls. +indeterminism in the test. Try adding screen:expect(...) (or poke_eventloop()) +between asynchronous (feed(), nvim_input()) and synchronous API calls. - Use screen:redraw_debug() to investigate; it may find relevant intermediate states that should be added to the test to make it more robust. - If the purpose of the test is to assert state after some user input sent with feed(), adding screen:expect() before the feed() will help to ensure the input is sent when Nvim is in a predictable state. This is preferable - to wait(), for being closer to real user interaction. - - wait() can trigger redraws and consequently generate more indeterminism. - Try removing wait(). + to poke_eventloop(), for being closer to real user interaction. + - poke_eventloop() can trigger redraws and thus generate more indeterminism. + Try removing poke_eventloop(). ]]) did_warn = true end @@ -703,6 +711,7 @@ function Screen:_reset() self.cmdline_block = {} self.wildmenu_items = nil self.wildmenu_pos = nil + self._grid_win_extmarks = {} end function Screen:_handle_mode_info_set(cursor_style_enabled, mode_info) @@ -803,6 +812,13 @@ function Screen:_handle_win_close(grid) self.float_pos[grid] = nil end +function Screen:_handle_win_extmark(grid, ...) + if self._grid_win_extmarks[grid] == nil then + self._grid_win_extmarks[grid] = {} + end + table.insert(self._grid_win_extmarks[grid], {...}) +end + function Screen:_handle_busy_start() self._busy = true end @@ -1067,6 +1083,10 @@ function Screen:_handle_msg_history_show(entries) self.msg_history = entries end +function Screen:_handle_msg_history_clear() + self.msg_history = {} +end + function Screen:_clear_block(grid, top, bot, left, right) for i = top, bot do self:_clear_row_section(grid, i, left, right) @@ -1155,7 +1175,7 @@ function Screen:_extstate_repr(attr_state) local msg_history = {} for i, entry in ipairs(self.msg_history) do - messages[i] = {kind=entry[1], content=self:_chunks_repr(entry[2], attr_state)} + msg_history[i] = {kind=entry[1], content=self:_chunks_repr(entry[2], attr_state)} end local win_viewport = (next(self.win_viewport) and self.win_viewport) or nil @@ -1560,9 +1580,10 @@ end function Screen:_equal_attrs(a, b) return a.bold == b.bold and a.standout == b.standout and a.underline == b.underline and a.undercurl == b.undercurl and - a.italic == b.italic and a.reverse == b.reverse and - a.foreground == b.foreground and a.background == b.background and - a.special == b.special and a.blend == b.blend and + a.underdouble == b.underdouble and a.underdotted == b.underdotted and + a.underdashed == b.underdashed and a.italic == b.italic and + a.reverse == b.reverse and a.foreground == b.foreground and + a.background == b.background and a.special == b.special and a.blend == b.blend and a.strikethrough == b.strikethrough and a.fg_indexed == b.fg_indexed and a.bg_indexed == b.bg_indexed end diff --git a/test/functional/ui/screen_basic_spec.lua b/test/functional/ui/screen_basic_spec.lua index 958e137f65..6c872e52d3 100644 --- a/test/functional/ui/screen_basic_spec.lua +++ b/test/functional/ui/screen_basic_spec.lua @@ -6,6 +6,7 @@ local insert = helpers.insert local eq = helpers.eq local eval = helpers.eval local iswin = helpers.iswin +local funcs, meths, exec_lua = helpers.funcs, helpers.meths, helpers.exec_lua describe('screen', function() local screen @@ -127,14 +128,67 @@ local function screen_tests(linegrid) end) it('has correct default title with named file', function() - local expected = (iswin() and 'myfile (C:\\mydir) - NVIM' - or 'myfile (/mydir) - NVIM') + local expected = (iswin() and 'myfile (C:\\mydir) - NVIM' or 'myfile (/mydir) - NVIM') command('set title') command(iswin() and 'file C:\\mydir\\myfile' or 'file /mydir/myfile') screen:expect(function() eq(expected, screen.title) end) end) + + describe('is not changed by', function() + local file1 = iswin() and 'C:\\mydir\\myfile1' or '/mydir/myfile1' + local file2 = iswin() and 'C:\\mydir\\myfile2' or '/mydir/myfile2' + local expected = (iswin() and 'myfile1 (C:\\mydir) - NVIM' or 'myfile1 (/mydir) - NVIM') + local buf2 + + before_each(function() + command('edit '..file1) + buf2 = funcs.bufadd(file2) + command('set title') + end) + + it('calling setbufvar() to set an option in a hidden buffer from i_CTRL-R', function() + command([[inoremap <F2> <C-R>=setbufvar(]]..buf2..[[, '&autoindent', 1) ? '' : ''<CR>]]) + feed('i<F2><Esc>') + command('redraw!') + screen:expect(function() + eq(expected, screen.title) + end) + end) + + it('an RPC call to nvim_buf_set_option in a hidden buffer', function() + meths.buf_set_option(buf2, 'autoindent', true) + command('redraw!') + screen:expect(function() + eq(expected, screen.title) + end) + end) + + it('a Lua callback calling nvim_buf_set_option in a hidden buffer', function() + exec_lua(string.format([[ + vim.schedule(function() + vim.api.nvim_buf_set_option(%d, 'autoindent', true) + end) + ]], buf2)) + command('redraw!') + screen:expect(function() + eq(expected, screen.title) + end) + end) + + it('a Lua callback calling nvim_buf_call in a hidden buffer', function() + exec_lua(string.format([[ + vim.schedule(function() + vim.api.nvim_buf_call(%d, function() end) + end) + ]], buf2)) + command('redraw!') + screen:expect(function() + eq(expected, screen.title) + end) + end) + end) end) describe(':set icon', function() @@ -272,12 +326,12 @@ local function screen_tests(linegrid) command('vsp') command('vsp') screen:expect([[ - ^ {3:│} {3:│} | - {0:~ }{3:│}{0:~ }{3:│}{0:~ }| - {0:~ }{3:│}{0:~ }{3:│}{0:~ }| - {0:~ }{3:│}{0:~ }{3:│}{0:~ }| - {0:~ }{3:│}{0:~ }{3:│}{0:~ }| - {0:~ }{3:│}{0:~ }{3:│}{0:~ }| + ^ │ │ | + {0:~ }│{0:~ }│{0:~ }| + {0:~ }│{0:~ }│{0:~ }| + {0:~ }│{0:~ }│{0:~ }| + {0:~ }│{0:~ }│{0:~ }| + {0:~ }│{0:~ }│{0:~ }| {1:[No Name] }{3:[No Name] [No Name] }| | {0:~ }| @@ -289,12 +343,12 @@ local function screen_tests(linegrid) ]]) insert('hello') screen:expect([[ - hell^o {3:│}hello {3:│}hello | - {0:~ }{3:│}{0:~ }{3:│}{0:~ }| - {0:~ }{3:│}{0:~ }{3:│}{0:~ }| - {0:~ }{3:│}{0:~ }{3:│}{0:~ }| - {0:~ }{3:│}{0:~ }{3:│}{0:~ }| - {0:~ }{3:│}{0:~ }{3:│}{0:~ }| + hell^o │hello │hello | + {0:~ }│{0:~ }│{0:~ }| + {0:~ }│{0:~ }│{0:~ }| + {0:~ }│{0:~ }│{0:~ }| + {0:~ }│{0:~ }│{0:~ }| + {0:~ }│{0:~ }│{0:~ }| {1:[No Name] [+] }{3:[No Name] [+] [No Name] [+] }| hello | {0:~ }| @@ -315,12 +369,12 @@ local function screen_tests(linegrid) command('vsp') insert('hello') screen:expect([[ - hell^o {3:│}hello {3:│}hello | - {0:~ }{3:│}{0:~ }{3:│}{0:~ }| - {0:~ }{3:│}{0:~ }{3:│}{0:~ }| - {0:~ }{3:│}{0:~ }{3:│}{0:~ }| - {0:~ }{3:│}{0:~ }{3:│}{0:~ }| - {0:~ }{3:│}{0:~ }{3:│}{0:~ }| + hell^o │hello │hello | + {0:~ }│{0:~ }│{0:~ }| + {0:~ }│{0:~ }│{0:~ }| + {0:~ }│{0:~ }│{0:~ }| + {0:~ }│{0:~ }│{0:~ }| + {0:~ }│{0:~ }│{0:~ }| {1:[No Name] [+] }{3:[No Name] [+] [No Name] [+] }| hello | {0:~ }| @@ -352,12 +406,12 @@ local function screen_tests(linegrid) command('tabprevious') screen:expect([[ {2: }{6:4}{2:+ [No Name] }{4: + [No Name] }{3: }{4:X}| - hell^o {3:│}hello {3:│}hello | - {0:~ }{3:│}{0:~ }{3:│}{0:~ }| - {0:~ }{3:│}{0:~ }{3:│}{0:~ }| - {0:~ }{3:│}{0:~ }{3:│}{0:~ }| - {0:~ }{3:│}{0:~ }{3:│}{0:~ }| - {0:~ }{3:│}{0:~ }{3:│}{0:~ }| + hell^o │hello │hello | + {0:~ }│{0:~ }│{0:~ }| + {0:~ }│{0:~ }│{0:~ }| + {0:~ }│{0:~ }│{0:~ }| + {0:~ }│{0:~ }│{0:~ }| + {0:~ }│{0:~ }│{0:~ }| {1:[No Name] [+] }{3:[No Name] [+] [No Name] [+] }| hello | {0:~ }| @@ -466,36 +520,36 @@ local function screen_tests(linegrid) command('vsplit') screen:expect([[ - ^foo {3:│}foo | - foo {3:│}foo | - foo {3:│}foo | - foo {3:│}foo | - foo {3:│}foo | - foo {3:│}foo | - foo {3:│}foo | - foo {3:│}foo | - foo {3:│}foo | - foo {3:│}foo | - foo {3:│}foo | - foo {3:│}foo | + ^foo │foo | + foo │foo | + foo │foo | + foo │foo | + foo │foo | + foo │foo | + foo │foo | + foo │foo | + foo │foo | + foo │foo | + foo │foo | + foo │foo | {1:[No Name] [+] }{3:[No Name] [+] }| | ]]) feed('<PageDown>') screen:expect([[ - ^foo {3:│}foo | - foo {3:│}foo | - foo {3:│}foo | - foo {3:│}foo | - bar {3:│}foo | - bar {3:│}foo | - bar {3:│}foo | - bar {3:│}foo | - bar {3:│}foo | - bar {3:│}foo | - bar {3:│}foo | - bar {3:│}foo | + ^foo │foo | + foo │foo | + foo │foo | + foo │foo | + bar │foo | + bar │foo | + bar │foo | + bar │foo | + bar │foo | + bar │foo | + bar │foo | + bar │foo | {1:[No Name] [+] }{3:[No Name] [+] }| | ]]) @@ -694,12 +748,12 @@ local function screen_tests(linegrid) command('vsp') command('vsp') screen:expect([[ - and {3:│}and {3:│}and | - clearing {3:│}clearing {3:│}clearing | - in {3:│}in {3:│}in | - split {3:│}split {3:│}split | - windows {3:│}windows {3:│}windows | - ^ {3:│} {3:│} | + and │and │and | + clearing │clearing │clearing | + in │in │in | + split │split │split | + windows │windows │windows | + ^ │ │ | {1:[No Name] [+] }{3:[No Name] [+] [No Name] [+] }| clearing | in | @@ -714,12 +768,12 @@ local function screen_tests(linegrid) it('only affects the current scroll region', function() feed('6k') screen:expect([[ - ^scrolling {3:│}and {3:│}and | - and {3:│}clearing {3:│}clearing | - clearing {3:│}in {3:│}in | - in {3:│}split {3:│}split | - split {3:│}windows {3:│}windows | - windows {3:│} {3:│} | + ^scrolling │and │and | + and │clearing │clearing | + clearing │in │in | + in │split │split | + split │windows │windows | + windows │ │ | {1:[No Name] [+] }{3:[No Name] [+] [No Name] [+] }| clearing | in | @@ -731,12 +785,12 @@ local function screen_tests(linegrid) ]]) feed('<c-w>l') screen:expect([[ - scrolling {3:│}and {3:│}and | - and {3:│}clearing {3:│}clearing | - clearing {3:│}in {3:│}in | - in {3:│}split {3:│}split | - split {3:│}windows {3:│}windows | - windows {3:│}^ {3:│} | + scrolling │and │and | + and │clearing │clearing | + clearing │in │in | + in │split │split | + split │windows │windows | + windows │^ │ | {3:[No Name] [+] }{1:[No Name] [+] }{3:<Name] [+] }| clearing | in | @@ -748,12 +802,12 @@ local function screen_tests(linegrid) ]]) feed('gg') screen:expect([[ - scrolling {3:│}^Inserting {3:│}and | - and {3:│}text {3:│}clearing | - clearing {3:│}with {3:│}in | - in {3:│}many {3:│}split | - split {3:│}lines {3:│}windows | - windows {3:│}to {3:│} | + scrolling │^Inserting │and | + and │text │clearing | + clearing │with │in | + in │many │split | + split │lines │windows | + windows │to │ | {3:[No Name] [+] }{1:[No Name] [+] }{3:<Name] [+] }| clearing | in | @@ -765,12 +819,12 @@ local function screen_tests(linegrid) ]]) feed('7j') screen:expect([[ - scrolling {3:│}with {3:│}and | - and {3:│}many {3:│}clearing | - clearing {3:│}lines {3:│}in | - in {3:│}to {3:│}split | - split {3:│}test {3:│}windows | - windows {3:│}^scrolling {3:│} | + scrolling │with │and | + and │many │clearing | + clearing │lines │in | + in │to │split | + split │test │windows | + windows │^scrolling │ | {3:[No Name] [+] }{1:[No Name] [+] }{3:<Name] [+] }| clearing | in | @@ -782,12 +836,12 @@ local function screen_tests(linegrid) ]]) feed('2j') screen:expect([[ - scrolling {3:│}lines {3:│}and | - and {3:│}to {3:│}clearing | - clearing {3:│}test {3:│}in | - in {3:│}scrolling {3:│}split | - split {3:│}and {3:│}windows | - windows {3:│}^clearing {3:│} | + scrolling │lines │and | + and │to │clearing | + clearing │test │in | + in │scrolling │split | + split │and │windows | + windows │^clearing │ | {3:[No Name] [+] }{1:[No Name] [+] }{3:<Name] [+] }| clearing | in | @@ -799,12 +853,12 @@ local function screen_tests(linegrid) ]]) feed('5k') screen:expect([[ - scrolling {3:│}^lines {3:│}and | - and {3:│}to {3:│}clearing | - clearing {3:│}test {3:│}in | - in {3:│}scrolling {3:│}split | - split {3:│}and {3:│}windows | - windows {3:│}clearing {3:│} | + scrolling │^lines │and | + and │to │clearing | + clearing │test │in | + in │scrolling │split | + split │and │windows | + windows │clearing │ | {3:[No Name] [+] }{1:[No Name] [+] }{3:<Name] [+] }| clearing | in | @@ -816,12 +870,12 @@ local function screen_tests(linegrid) ]]) feed('k') screen:expect([[ - scrolling {3:│}^many {3:│}and | - and {3:│}lines {3:│}clearing | - clearing {3:│}to {3:│}in | - in {3:│}test {3:│}split | - split {3:│}scrolling {3:│}windows | - windows {3:│}and {3:│} | + scrolling │^many │and | + and │lines │clearing | + clearing │to │in | + in │test │split | + split │scrolling │windows | + windows │and │ | {3:[No Name] [+] }{1:[No Name] [+] }{3:<Name] [+] }| clearing | in | diff --git a/test/functional/ui/searchhl_spec.lua b/test/functional/ui/searchhl_spec.lua index 5540b3c2dc..c5c88323a2 100644 --- a/test/functional/ui/searchhl_spec.lua +++ b/test/functional/ui/searchhl_spec.lua @@ -5,7 +5,8 @@ local command = helpers.command local feed_command = helpers.feed_command local eq = helpers.eq local eval = helpers.eval -local nvim_dir = helpers.nvim_dir +local funcs = helpers.funcs +local testprg = helpers.testprg describe('search highlighting', function() local screen @@ -43,7 +44,7 @@ describe('search highlighting', function() insert("some text\nmore text") feed_command('1,2fold') feed("gg/text") - screen:expect([[ + screen:expect{grid=[[ {6:+-- 2 lines: some text·················}| {1:~ }| {1:~ }| @@ -51,7 +52,9 @@ describe('search highlighting', function() {1:~ }| {1:~ }| /text^ | - ]]) + ]], win_viewport={ + [2] = {win = {id = 1000}, topline = 0, botline = 3, curline = 0, curcol = 9, linecount = 2}; + }} end) it('works', function() @@ -109,6 +112,146 @@ describe('search highlighting', function() ]]) end) + describe('CurSearch highlight', function() + before_each(function() + screen:set_default_attr_ids({ + [1] = {background = Screen.colors.Yellow}, -- Search + [2] = {foreground = Screen.colors.White, background = Screen.colors.Black}, -- CurSearch + [3] = {foreground = Screen.colors.Red}, -- WarningMsg + }) + command('highlight CurSearch guibg=Black guifg=White') + end) + + it('works for match under cursor', function() + insert([[ + There is no way that a bee should be + able to fly. Its wings are too small + to get its fat little body off the + ground. The bee, of course, flies + anyway because bees don't care what + humans think is impossible.]]) + + feed('/bee<CR>') + screen:expect{grid=[[ + There is no way that a {2:^bee} should be | + able to fly. Its wings are too small | + to get its fat little body off the | + ground. The {1:bee}, of course, flies | + anyway because {1:bee}s don't care what | + humans think is impossible. | + {3:search hit BOTTOM, continuing at TOP} | + ]]} + + feed('nn') + screen:expect{grid=[[ + There is no way that a {1:bee} should be | + able to fly. Its wings are too small | + to get its fat little body off the | + ground. The {1:bee}, of course, flies | + anyway because {2:^bee}s don't care what | + humans think is impossible. | + /bee | + ]]} + + feed('N') + screen:expect{grid=[[ + There is no way that a {1:bee} should be | + able to fly. Its wings are too small | + to get its fat little body off the | + ground. The {2:^bee}, of course, flies | + anyway because {1:bee}s don't care what | + humans think is impossible. | + ?bee | + ]]} + end) + + it('works for multiline match', function() + command([[call setline(1, ['one', 'foo', 'bar', 'baz', 'foo the foo and foo', 'bar'])]]) + feed('gg/foo<CR>') + screen:expect([[ + one | + {2:^foo} | + bar | + baz | + {1:foo} the {1:foo} and {1:foo} | + bar | + /foo | + ]]) + feed('n') + screen:expect([[ + one | + {1:foo} | + bar | + baz | + {2:^foo} the {1:foo} and {1:foo} | + bar | + /foo | + ]]) + feed('n') + screen:expect([[ + one | + {1:foo} | + bar | + baz | + {1:foo} the {2:^foo} and {1:foo} | + bar | + /foo | + ]]) + feed('n') + screen:expect([[ + one | + {1:foo} | + bar | + baz | + {1:foo} the {1:foo} and {2:^foo} | + bar | + /foo | + ]]) + command([[call setline(5, 'foo')]]) + feed('0?<CR>') + screen:expect([[ + one | + {2:^foo} | + bar | + baz | + {1:foo} | + bar | + ?foo | + ]]) + feed('gg/foo\\nbar<CR>') + screen:expect([[ + one | + {2:^foo} | + {2:bar} | + baz | + {1:foo} | + {1:bar} | + /foo\nbar | + ]]) + command([[call setline(1, ['---', 'abcdefg', 'hijkl', '---', 'abcdefg', 'hijkl'])]]) + feed('gg/efg\\nhij<CR>') + screen:expect([[ + --- | + abcd{2:^efg} | + {2:hij}kl | + --- | + abcd{1:efg} | + {1:hij}kl | + /efg\nhij | + ]]) + feed('n') + screen:expect([[ + --- | + abcd{1:efg} | + {1:hij}kl | + --- | + abcd{2:^efg} | + {2:hij}kl | + /efg\nhij | + ]]) + end) + end) + it('highlights after EOL', function() insert("\n\n\n\n\n\n") @@ -163,7 +306,7 @@ describe('search highlighting', function() end) it('is preserved during :terminal activity', function() - feed([[:terminal "]]..nvim_dir..[[/shell-test" REP 5000 foo<cr>]]) + feed((':terminal "%s" REP 5000 foo<cr>'):format(testprg('shell-test'))) feed(':file term<CR>') feed('G') -- Follow :terminal output. @@ -179,100 +322,101 @@ describe('search highlighting', function() end) it('works with incsearch', function() - feed_command('set hlsearch') - feed_command('set incsearch') + command('set hlsearch') + command('set incsearch') + command('set laststatus=0') insert([[ the first line - in a little file - ]]) + in a little file]]) + command('vsplit') feed("gg/li") screen:expect([[ - the first {3:li}ne | - in a {2:li}ttle file | - | - {1:~ }| - {1:~ }| - {1:~ }| + the first {3:li}ne │the first {2:li}ne | + in a {2:li}ttle file │in a {2:li}ttle file | + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| /li^ | ]]) -- check that consecutive matches are caught by C-g/C-t feed("<C-g>") screen:expect([[ - the first {2:li}ne | - in a {3:li}ttle file | - | - {1:~ }| - {1:~ }| - {1:~ }| + the first {2:li}ne │the first {2:li}ne | + in a {3:li}ttle file │in a {2:li}ttle file | + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| /li^ | ]]) feed("<C-t>") screen:expect([[ - the first {3:li}ne | - in a {2:li}ttle file | - | - {1:~ }| - {1:~ }| - {1:~ }| + the first {3:li}ne │the first {2:li}ne | + in a {2:li}ttle file │in a {2:li}ttle file | + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| /li^ | ]]) feed("t") screen:expect([[ - the first line | - in a {3:lit}tle file | - | - {1:~ }| - {1:~ }| - {1:~ }| + the first line │the first line | + in a {3:lit}tle file │in a {2:lit}tle file | + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| /lit^ | ]]) feed("<cr>") screen:expect([[ - the first line | - in a {2:^lit}tle file | - | - {1:~ }| - {1:~ }| - {1:~ }| + the first line │the first line | + in a {2:^lit}tle file │in a {2:lit}tle file | + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| /lit | ]]) feed("/fir") screen:expect([[ - the {3:fir}st line | - in a little file | - | - {1:~ }| - {1:~ }| - {1:~ }| + the {3:fir}st line │the {2:fir}st line | + in a little file │in a little file | + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| /fir^ | ]]) -- incsearch have priority over hlsearch feed("<esc>/ttle") screen:expect([[ - the first line | - in a li{3:ttle} file | - | - {1:~ }| - {1:~ }| - {1:~ }| + the first line │the first line | + in a li{3:ttle} file │in a li{2:ttle} file | + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| /ttle^ | ]]) -- cancelling search resets to the old search term feed('<esc>') screen:expect([[ - the first line | - in a {2:^lit}tle file | - | - {1:~ }| - {1:~ }| - {1:~ }| + the first line │the first line | + in a {2:^lit}tle file │in a {2:lit}tle file | + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| | ]]) eq('lit', eval('@/')) @@ -280,91 +424,78 @@ describe('search highlighting', function() -- cancelling inc search restores the hl state feed(':noh<cr>') screen:expect([[ - the first line | - in a ^little file | - | - {1:~ }| - {1:~ }| - {1:~ }| + the first line │the first line | + in a ^little file │in a little file | + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| :noh | ]]) feed('/first') screen:expect([[ - the {3:first} line | - in a little file | - | - {1:~ }| - {1:~ }| - {1:~ }| + the {3:first} line │the {2:first} line | + in a little file │in a little file | + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| /first^ | ]]) feed('<esc>') screen:expect([[ - the first line | - in a ^little file | - | - {1:~ }| - {1:~ }| - {1:~ }| + the first line │the first line | + in a ^little file │in a little file | + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| | ]]) -- test that pressing C-g in an empty command line does not move the cursor - feed('/<C-g>') - screen:expect([[ - the first line | - in a little file | - | - {1:~ }| - {1:~ }| - {1:~ }| - /^ | - ]]) - - -- same, for C-t - feed('<ESC>') - screen:expect([[ - the first line | - in a ^little file | - | - {1:~ }| - {1:~ }| - {1:~ }| - | - ]]) - feed('/<C-t>') - screen:expect([[ - the first line | - in a little file | - | - {1:~ }| - {1:~ }| - {1:~ }| - /^ | - ]]) + feed('gg0') + command([[let @/ = 'i']]) + -- moves to next match of previous search pattern, just like /<cr> + feed('/<c-g><cr>') + eq({0, 1, 6, 0}, funcs.getpos('.')) + -- moves to next match of previous search pattern, just like /<cr> + feed('/<cr>') + eq({0, 1, 12, 0}, funcs.getpos('.')) + -- moves to next match of previous search pattern, just like /<cr> + feed('/<c-t><cr>') + eq({0, 2, 1, 0}, funcs.getpos('.')) -- 8.0.1304, test that C-g and C-t works with incsearch and empty pattern feed('<esc>/fi<CR>') + screen:expect([[ + the {2:fi}rst line │the {2:fi}rst line | + in a little {2:^fi}le │in a little {2:fi}le | + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + /fi | + ]]) feed('//') screen:expect([[ - the {3:fi}rst line | - in a little {2:fi}le | - | - {1:~ }| - {1:~ }| - {1:~ }| + the {3:fi}rst line │the {2:fi}rst line | + in a little {2:fi}le │in a little {2:fi}le | + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| //^ | ]]) - feed('<C-g>') screen:expect([[ - the {2:fi}rst line | - in a little {3:fi}le | - | - {1:~ }| - {1:~ }| - {1:~ }| + the {2:fi}rst line │the {2:fi}rst line | + in a little {3:fi}le │in a little {2:fi}le | + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| //^ | ]]) end) @@ -439,19 +570,20 @@ describe('search highlighting', function() end) it('works with matchadd and syntax', function() - screen:set_default_attr_ids( { - [1] = {bold=true, foreground=Screen.colors.Blue}, - [2] = {background = colors.Yellow}, - [3] = {reverse = true}, - [4] = {foreground = colors.Red}, - [5] = {bold = true, background = colors.Green}, - [6] = {italic = true, background = colors.Magenta}, - [7] = {bold = true, background = colors.Yellow}, - } ) + screen:set_default_attr_ids { + [1] = {bold=true, foreground=Screen.colors.Blue}; + [2] = {background = colors.Yellow}; + [3] = {reverse = true}; + [4] = {foreground = colors.Red}; + [5] = {bold = true, background = colors.Green}; + [6] = {italic = true, background = colors.Magenta}; + [7] = {bold = true, background = colors.Yellow}; + [8] = {foreground = Screen.colors.Blue4, background = Screen.colors.LightGray}; + } feed_command('set hlsearch') - insert([[ + insert [[ very special text - ]]) + ]] feed_command("syntax on") feed_command("highlight MyGroup guibg=Green gui=bold") feed_command("highlight MyGroup2 guibg=Magenta gui=italic") @@ -461,7 +593,7 @@ describe('search highlighting', function() -- searchhl and matchadd matches are exclusive, only the highest priority -- is used (and matches with lower priorities are not combined) feed_command("/ial te") - screen:expect([[ + screen:expect{grid=[[ very {5:spec^ial}{2: te}{6:xt} | | {1:~ }| @@ -469,10 +601,21 @@ describe('search highlighting', function() {1:~ }| {1:~ }| {4:search hit BOTTOM, continuing at TOP} | - ]]) + ]], win_viewport={ + [2] = {win = {id = 1000}, topline = 0, botline = 3, curline = 0, curcol = 11, linecount = 2}; + }} - -- check hilights work also in folds + -- check highlights work also in folds feed("zf4j") + screen:expect{grid=[[ + {8:^+-- 2 lines: very special text·········}| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {4:search hit BOTTOM, continuing at TOP} | + ]]} command("%foldopen") screen:expect([[ very {5:spec^ial}{2: te}{6:xt} | diff --git a/test/functional/ui/sign_spec.lua b/test/functional/ui/sign_spec.lua index dcd31cfdb7..dbc92ca222 100644 --- a/test/functional/ui/sign_spec.lua +++ b/test/functional/ui/sign_spec.lua @@ -446,7 +446,7 @@ describe('Signs', function() {1:>>>>>>>>}{6: 1 }a | {2: }{6: 2 }b | {2: }{6: 3 }c | - {2: }{6:^ 4 } | + {2: }{6: 4 }^ | {0:~ }| {0:~ }| {0:~ }| @@ -468,7 +468,7 @@ describe('Signs', function() {1:>>>>>>>>>>}{6: 1 }a | {2: }{6: 2 }b | {2: }{6: 3 }c | - {2: ^ }{6: 4 } | + {2: }{6: 4 }^ | {0:~ }| {0:~ }| {0:~ }| @@ -482,7 +482,7 @@ describe('Signs', function() ]]) end) - it('ignores signs with no icon and text when calculting the signcolumn width', function() + it('ignores signs with no icon and text when calculating the signcolumn width', function() feed('ia<cr>b<cr>c<cr><esc>') command('set number') command('set signcolumn=auto:2') @@ -512,7 +512,33 @@ describe('Signs', function() {1:>>}{6: 1 }a | {2: }{6: 2 }b | {2: }{6: 3 }c | - {2: }{6: ^4 } | + {2: }{6: 4 }^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]) + end) + + it('shows the line number when signcolumn=number but no marks on a line have text', function() + feed('ia<cr>b<cr>c<cr><esc>') + command('set number signcolumn=number') + command('sign define pietSearch text=>> texthl=Search numhl=Error') + command('sign define pietError text= texthl=Search numhl=Error') + command('sign place 1 line=1 name=pietSearch buffer=1') + command('sign place 2 line=2 name=pietError buffer=1') + -- no signcolumn, line number for "a" is Search, for "b" is Error, for "c" is LineNr + screen:expect([[ + {1: >> }a | + {8: 2 }b | + {6: 3 }c | + {6: 4 }^ | {0:~ }| {0:~ }| {0:~ }| diff --git a/test/functional/ui/statusline_spec.lua b/test/functional/ui/statusline_spec.lua new file mode 100644 index 0000000000..1e1066d48a --- /dev/null +++ b/test/functional/ui/statusline_spec.lua @@ -0,0 +1,342 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') +local clear = helpers.clear +local command = helpers.command +local feed = helpers.feed +local eq = helpers.eq +local funcs = helpers.funcs +local meths = helpers.meths +local exec = helpers.exec +local exec_lua = helpers.exec_lua +local eval = helpers.eval + +describe('statusline clicks', function() + local screen + + before_each(function() + clear() + screen = Screen.new(40, 8) + screen:attach() + command('set laststatus=2 mousemodel=extend') + exec([=[ + function! MyClickFunc(minwid, clicks, button, mods) + let mods = trim(a:mods) + if mods ==# '' + let g:testvar = printf("%d %d %s", a:minwid, a:clicks, a:button) + else + let g:testvar = printf("%d %d %s %s", a:minwid, a:clicks, a:button, mods) + endif + endfunction + ]=]) + end) + + it('works', function() + meths.set_option('statusline', 'Not clicky stuff %0@MyClickFunc@Clicky stuff%T') + meths.input_mouse('left', 'press', '', 0, 6, 17) + eq('0 1 l', eval("g:testvar")) + meths.input_mouse('right', 'press', '', 0, 6, 17) + eq('0 1 r', eval("g:testvar")) + end) + + it('works for winbar', function() + meths.set_option('winbar', 'Not clicky stuff %0@MyClickFunc@Clicky stuff%T') + meths.input_mouse('left', 'press', '', 0, 0, 17) + eq('0 1 l', eval("g:testvar")) + meths.input_mouse('right', 'press', '', 0, 0, 17) + eq('0 1 r', eval("g:testvar")) + end) + + it('works for winbar in floating window', function() + meths.open_win(0, true, { width=30, height=4, relative='editor', row=1, col=5, + border = "single" }) + meths.set_option_value('winbar', 'Not clicky stuff %0@MyClickFunc@Clicky stuff%T', + { scope = 'local' }) + meths.input_mouse('left', 'press', '', 0, 2, 23) + eq('0 1 l', eval("g:testvar")) + end) + + it('works when there are multiple windows', function() + command('split') + meths.set_option('statusline', 'Not clicky stuff %0@MyClickFunc@Clicky stuff%T') + meths.set_option('winbar', 'Not clicky stuff %0@MyClickFunc@Clicky stuff%T') + meths.input_mouse('left', 'press', '', 0, 0, 17) + eq('0 1 l', eval("g:testvar")) + meths.input_mouse('right', 'press', '', 0, 4, 17) + eq('0 1 r', eval("g:testvar")) + meths.input_mouse('middle', 'press', '', 0, 3, 17) + eq('0 1 m', eval("g:testvar")) + meths.input_mouse('left', 'press', '', 0, 6, 17) + eq('0 1 l', eval("g:testvar")) + end) + + it('works with Lua function', function() + exec_lua([[ + function clicky_func(minwid, clicks, button, mods) + vim.g.testvar = string.format("%d %d %s", minwid, clicks, button) + end + ]]) + meths.set_option('statusline', 'Not clicky stuff %0@v:lua.clicky_func@Clicky stuff%T') + meths.input_mouse('left', 'press', '', 0, 6, 17) + eq('0 1 l', eval("g:testvar")) + end) + + it('ignores unsupported click items', function() + command('tabnew | tabprevious') + meths.set_option('statusline', '%2TNot clicky stuff%T') + meths.input_mouse('left', 'press', '', 0, 6, 0) + eq(1, meths.get_current_tabpage().id) + meths.set_option('statusline', '%2XNot clicky stuff%X') + meths.input_mouse('left', 'press', '', 0, 6, 0) + eq(2, #meths.list_tabpages()) + end) + + it("right click works when statusline isn't focused #18994", function() + meths.set_option('statusline', 'Not clicky stuff %0@MyClickFunc@Clicky stuff%T') + meths.input_mouse('right', 'press', '', 0, 6, 17) + eq('0 1 r', eval("g:testvar")) + meths.input_mouse('right', 'press', '', 0, 6, 17) + eq('0 2 r', eval("g:testvar")) + end) + + it("click works with modifiers #18994", function() + meths.set_option('statusline', 'Not clicky stuff %0@MyClickFunc@Clicky stuff%T') + meths.input_mouse('right', 'press', 's', 0, 6, 17) + eq('0 1 r s', eval("g:testvar")) + meths.input_mouse('left', 'press', 's', 0, 6, 17) + eq('0 1 l s', eval("g:testvar")) + end) +end) + +describe('global statusline', function() + local screen + + before_each(function() + clear() + screen = Screen.new(60, 16) + screen:attach() + screen:set_default_attr_ids({ + [1] = {bold = true, foreground = Screen.colors.Blue}; + [2] = {bold = true, reverse = true}; + [3] = {bold = true}; + [4] = {reverse = true}; + }) + command('set laststatus=3') + command('set ruler') + end) + + it('works', function() + screen:expect([[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {2:[No Name] 0,0-1 All}| + | + ]]) + + feed('i<CR><CR>') + screen:expect([[ + | + | + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {2:[No Name] [+] 3,1 All}| + {3:-- INSERT --} | + ]]) + end) + + it('works with splits', function() + command('vsplit | split | vsplit | vsplit | wincmd l | split | 2wincmd l | split') + screen:expect([[ + │ │ │^ | + {1:~ }│{1:~ }│{1:~}│{1:~ }| + {1:~ }│{1:~ }│{1:~}│{1:~ }| + {1:~ }│{1:~ }│{1:~}│{1:~ }| + {1:~ }├────────────────┤{1:~}│{1:~ }| + {1:~ }│ │{1:~}│{1:~ }| + {1:~ }│{1:~ }│{1:~}│{1:~ }| + {1:~ }│{1:~ }│{1:~}├────────────────────| + {1:~ }│{1:~ }│{1:~}│ | + ────────────────────┴────────────────┴─┤{1:~ }| + │{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {2:[No Name] 0,0-1 All}| + | + ]]) + end) + + it('works when switching between values of laststatus', function() + command('set laststatus=1') + screen:expect([[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + 0,0-1 All | + ]]) + + command('set laststatus=3') + screen:expect([[ + ^ | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {2:[No Name] 0,0-1 All}| + | + ]]) + + command('vsplit | split | vsplit | vsplit | wincmd l | split | 2wincmd l | split') + command('set laststatus=2') + screen:expect([[ + │ │ │^ | + {1:~ }│{1:~ }│{1:~}│{1:~ }| + {1:~ }│{1:~ }│{1:~}│{1:~ }| + {1:~ }│{1:~ }│{1:~}│{1:~ }| + {1:~ }│{4:< Name] 0,0-1 }│{1:~}│{1:~ }| + {1:~ }│ │{1:~}│{1:~ }| + {1:~ }│{1:~ }│{1:~}│{1:~ }| + {1:~ }│{1:~ }│{1:~}│{2:<No Name] 0,0-1 All}| + {1:~ }│{1:~ }│{1:~}│ | + {4:<No Name] 0,0-1 All < Name] 0,0-1 <}│{1:~ }| + │{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {4:[No Name] 0,0-1 All <No Name] 0,0-1 All}| + | + ]]) + + command('set laststatus=3') + screen:expect([[ + │ │ │^ | + {1:~ }│{1:~ }│{1:~}│{1:~ }| + {1:~ }│{1:~ }│{1:~}│{1:~ }| + {1:~ }│{1:~ }│{1:~}│{1:~ }| + {1:~ }├────────────────┤{1:~}│{1:~ }| + {1:~ }│ │{1:~}│{1:~ }| + {1:~ }│{1:~ }│{1:~}│{1:~ }| + {1:~ }│{1:~ }│{1:~}├────────────────────| + {1:~ }│{1:~ }│{1:~}│ | + ────────────────────┴────────────────┴─┤{1:~ }| + │{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {2:[No Name] 0,0-1 All}| + | + ]]) + + command('set laststatus=0') + screen:expect([[ + │ │ │^ | + {1:~ }│{1:~ }│{1:~}│{1:~ }| + {1:~ }│{1:~ }│{1:~}│{1:~ }| + {1:~ }│{1:~ }│{1:~}│{1:~ }| + {1:~ }│{4:< Name] 0,0-1 }│{1:~}│{1:~ }| + {1:~ }│ │{1:~}│{1:~ }| + {1:~ }│{1:~ }│{1:~}│{1:~ }| + {1:~ }│{1:~ }│{1:~}│{2:<No Name] 0,0-1 All}| + {1:~ }│{1:~ }│{1:~}│ | + {4:<No Name] 0,0-1 All < Name] 0,0-1 <}│{1:~ }| + │{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + 0,0-1 All | + ]]) + + command('set laststatus=3') + screen:expect([[ + │ │ │^ | + {1:~ }│{1:~ }│{1:~}│{1:~ }| + {1:~ }│{1:~ }│{1:~}│{1:~ }| + {1:~ }│{1:~ }│{1:~}│{1:~ }| + {1:~ }├────────────────┤{1:~}│{1:~ }| + {1:~ }│ │{1:~}│{1:~ }| + {1:~ }│{1:~ }│{1:~}│{1:~ }| + {1:~ }│{1:~ }│{1:~}├────────────────────| + {1:~ }│{1:~ }│{1:~}│ | + ────────────────────┴────────────────┴─┤{1:~ }| + │{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {1:~ }│{1:~ }| + {2:[No Name] 0,0-1 All}| + | + ]]) + end) + + it('win_move_statusline() can reduce cmdheight to 1', function() + eq(1, meths.get_option('cmdheight')) + funcs.win_move_statusline(0, -1) + eq(2, meths.get_option('cmdheight')) + funcs.win_move_statusline(0, -1) + eq(3, meths.get_option('cmdheight')) + funcs.win_move_statusline(0, 1) + eq(2, meths.get_option('cmdheight')) + funcs.win_move_statusline(0, 1) + eq(1, meths.get_option('cmdheight')) + end) + + it('mouse dragging can reduce cmdheight to 1', function() + command('set mouse=a') + meths.input_mouse('left', 'press', '', 0, 14, 10) + eq(1, meths.get_option('cmdheight')) + meths.input_mouse('left', 'drag', '', 0, 13, 10) + eq(2, meths.get_option('cmdheight')) + meths.input_mouse('left', 'drag', '', 0, 12, 10) + eq(3, meths.get_option('cmdheight')) + meths.input_mouse('left', 'drag', '', 0, 13, 10) + eq(2, meths.get_option('cmdheight')) + meths.input_mouse('left', 'drag', '', 0, 14, 10) + eq(1, meths.get_option('cmdheight')) + meths.input_mouse('left', 'drag', '', 0, 15, 10) + eq(0, meths.get_option('cmdheight')) + meths.input_mouse('left', 'drag', '', 0, 14, 10) + eq(1, meths.get_option('cmdheight')) + end) +end) diff --git a/test/functional/ui/syntax_conceal_spec.lua b/test/functional/ui/syntax_conceal_spec.lua index 4e1852162f..f790597140 100644 --- a/test/functional/ui/syntax_conceal_spec.lua +++ b/test/functional/ui/syntax_conceal_spec.lua @@ -3,6 +3,8 @@ local Screen = require('test.functional.ui.screen') local clear, feed, command = helpers.clear, helpers.feed, helpers.command local eq = helpers.eq local insert = helpers.insert +local poke_eventloop = helpers.poke_eventloop +local expect_exit = helpers.expect_exit describe('Screen', function() local screen @@ -255,6 +257,40 @@ describe('Screen', function() ]]) end) end) -- a region of text (implicit concealing) + + it("cursor position is correct when entering Insert mode with cocu=ni #13916", function() + insert([[foobarfoobarfoobar]]) + -- move to end of line + feed("$") + command("set concealcursor=ni") + command("syn match Foo /foobar/ conceal cchar=&") + screen:expect([[ + {1:&&&}^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]) + feed("i") + -- cursor should stay in place, not jump to column 16 + screen:expect([[ + {1:&&&}^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {4:-- INSERT --} | + ]]) + end) end) -- match and conceal describe("let the conceal level be", function() @@ -911,14 +947,64 @@ describe('Screen', function() {0:~ }| | ]]} - eq(grid_lines, {{2, 0, {{'c', 0, 3}}}}) + eq({{2, 0, {{'c', 0, 3}}}}, grid_lines) + end) + + it('K_EVENT should not cause extra redraws with concealcursor #13196', function() + command('set conceallevel=1') + command('set concealcursor=nv') + command('set redrawdebug+=nodelta') + + insert([[ + aaa + bbb + ccc + ]]) + screen:expect{grid=[[ + aaa | + bbb | + ccc | + ^ | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]} + + -- XXX: hack to get notifications, and check only a single line is + -- updated. Could use next_msg() also. + local orig_handle_grid_line = screen._handle_grid_line + local grid_lines = {} + function screen._handle_grid_line(self, grid, row, col, items) + table.insert(grid_lines, {row, col, items}) + orig_handle_grid_line(self, grid, row, col, items) + end + feed('k') + screen:expect{grid=[[ + aaa | + bbb | + ^ccc | + | + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + {0:~ }| + | + ]]} + eq({{2, 0, {{'c', 0, 3}}}}, grid_lines) + poke_eventloop() -- causes K_EVENT key + screen:expect_unchanged() + eq({{2, 0, {{'c', 0, 3}}}}, grid_lines) end) -- Copy of Test_cursor_column_in_concealed_line_after_window_scroll in -- test/functional/ui/syntax_conceal_spec.lua. describe('concealed line after window scroll', function() after_each(function() - command(':qall!') + expect_exit(command, ':qall!') os.remove('Xcolesearch') end) diff --git a/test/functional/ui/wildmode_spec.lua b/test/functional/ui/wildmode_spec.lua index 65c6fabfa8..98398bc7a1 100644 --- a/test/functional/ui/wildmode_spec.lua +++ b/test/functional/ui/wildmode_spec.lua @@ -7,7 +7,7 @@ local meths = helpers.meths local eq = helpers.eq local eval = helpers.eval local retry = helpers.retry -local nvim_dir = helpers.nvim_dir +local testprg = helpers.testprg describe("'wildmenu'", function() local screen @@ -114,7 +114,7 @@ describe("'wildmenu'", function() it('is preserved during :terminal activity', function() command('set wildmenu wildmode=full') command('set scrollback=4') - feed([[:terminal "]]..nvim_dir..[[/shell-test" REP 5000 !terminal_output!<cr>]]) + feed((':terminal "%s" REP 5000 !terminal_output!<cr>'):format(testprg('shell-test'))) feed('G') -- Follow :terminal output. feed([[:sign <Tab>]]) -- Invoke wildmenu. -- NB: in earlier versions terminal output was redrawn during cmdline mode. diff --git a/test/functional/ui/winbar_spec.lua b/test/functional/ui/winbar_spec.lua new file mode 100644 index 0000000000..92a6ab2e84 --- /dev/null +++ b/test/functional/ui/winbar_spec.lua @@ -0,0 +1,580 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') +local clear = helpers.clear +local command = helpers.command +local insert = helpers.insert +local meths = helpers.meths +local eq = helpers.eq +local poke_eventloop = helpers.poke_eventloop +local feed = helpers.feed +local pcall_err = helpers.pcall_err + +describe('winbar', function() + local screen + + before_each(function() + clear() + screen = Screen.new(60, 13) + screen:attach() + screen:set_default_attr_ids({ + [1] = {bold = true}, + [2] = {reverse = true}, + [3] = {bold = true, foreground = Screen.colors.Blue}, + [4] = {bold = true, reverse = true}, + [5] = {bold = true, foreground = Screen.colors.Red}, + [6] = {foreground = Screen.colors.Blue}, + [7] = {background = Screen.colors.LightGrey}, + [8] = {background = Screen.colors.LightMagenta}, + [9] = {bold = true, foreground = Screen.colors.Blue, background = Screen.colors.LightMagenta}, + [10] = {background = Screen.colors.LightGrey, underline = true}, + [11] = {background = Screen.colors.LightGrey, underline = true, bold = true, foreground = Screen.colors.Magenta}, + }) + meths.set_option('winbar', 'Set Up The Bars') + end) + + it('works', function() + screen:expect([[ + {1:Set Up The Bars }| + ^ | + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + | + ]]) + end) + + it('works with custom \'fillchars\' value', function() + command('set fillchars=wbr:+') + screen:expect([[ + {1:Set Up The Bars+++++++++++++++++++++++++++++++++++++++++++++}| + ^ | + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + | + ]]) + end) + + it('works with custom highlight', function() + command('hi WinBar guifg=red') + screen:expect([[ + {5:Set Up The Bars }| + ^ | + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + | + ]]) + end) + + it('works with splits', function() + command('hi WinBar guifg=red') + command('hi WinBarNC guifg=blue') + command('belowright vsplit | split | split') + screen:expect([[ + {6:Set Up The Bars }│{5:Set Up The Bars }| + │^ | + {3:~ }│{3:~ }| + {3:~ }│{4:[No Name] }| + {3:~ }│{6:Set Up The Bars }| + {3:~ }│ | + {3:~ }│{3:~ }| + {3:~ }│{2:[No Name] }| + {3:~ }│{6:Set Up The Bars }| + {3:~ }│ | + {3:~ }│{3:~ }| + {2:[No Name] [No Name] }| + | + ]]) + end) + + it('works when switching value of \'winbar\'', function() + command('belowright vsplit | split | split | set winbar=') + screen:expect([[ + │^ | + {3:~ }│{3:~ }| + {3:~ }│{3:~ }| + {3:~ }│{4:[No Name] }| + {3:~ }│ | + {3:~ }│{3:~ }| + {3:~ }│{3:~ }| + {3:~ }│{2:[No Name] }| + {3:~ }│ | + {3:~ }│{3:~ }| + {3:~ }│{3:~ }| + {2:[No Name] [No Name] }| + | + ]]) + command('set winbar=All\\ Your\\ Bar\\ Are\\ Belong\\ To\\ Us') + screen:expect([[ + {1:All Your Bar Are Belong To Us}│{1:All Your Bar Are Belong To Us }| + │^ | + {3:~ }│{3:~ }| + {3:~ }│{4:[No Name] }| + {3:~ }│{1:All Your Bar Are Belong To Us }| + {3:~ }│ | + {3:~ }│{3:~ }| + {3:~ }│{2:[No Name] }| + {3:~ }│{1:All Your Bar Are Belong To Us }| + {3:~ }│ | + {3:~ }│{3:~ }| + {2:[No Name] [No Name] }| + | + ]]) + command('set winbar=Changed\\ winbar') + screen:expect([[ + {1:Changed winbar }│{1:Changed winbar }| + │^ | + {3:~ }│{3:~ }| + {3:~ }│{4:[No Name] }| + {3:~ }│{1:Changed winbar }| + {3:~ }│ | + {3:~ }│{3:~ }| + {3:~ }│{2:[No Name] }| + {3:~ }│{1:Changed winbar }| + {3:~ }│ | + {3:~ }│{3:~ }| + {2:[No Name] [No Name] }| + | + ]]) + end) + + it('can be ruler', function() + insert [[ + just some + random text]] + meths.set_option('winbar', 'Hello, I am a ruler: %l,%c') + screen:expect{grid=[[ + {1:Hello, I am a ruler: 2,11 }| + just some | + random tex^t | + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + | + ]]} + feed 'b' + screen:expect{grid=[[ + {1:Hello, I am a ruler: 2,8 }| + just some | + random ^text | + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + | + ]]} + feed 'k' + screen:expect{grid=[[ + {1:Hello, I am a ruler: 1,8 }| + just so^me | + random text | + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + | + ]]} + end) + + it('works with laststatus=3', function() + command('set laststatus=3') + screen:expect([[ + {1:Set Up The Bars }| + ^ | + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + {4:[No Name] }| + | + ]]) + command('belowright vsplit | split | split') + screen:expect([[ + {1:Set Up The Bars }│{1:Set Up The Bars }| + │^ | + {3:~ }│{3:~ }| + {3:~ }├──────────────────────────────| + {3:~ }│{1:Set Up The Bars }| + {3:~ }│ | + {3:~ }│{3:~ }| + {3:~ }├──────────────────────────────| + {3:~ }│{1:Set Up The Bars }| + {3:~ }│ | + {3:~ }│{3:~ }| + {4:[No Name] }| + | + ]]) + -- Test for issue #18791 + command('tabnew') + screen:expect([[ + {10: }{11:4}{10: [No Name] }{1: [No Name] }{2: }{10:X}| + {1:Set Up The Bars }| + ^ | + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + {4:[No Name] }| + | + ]]) + end) + + it('mouse click and drag work correctly in buffer', function() + insert([[ + line 1 + line 2 + line 3 + line 4 + line -42 + line i + line sin(theta) + line 8]]) + + meths.input_mouse('left', 'press', '', 0, 5, 1) + screen:expect([[ + {1:Set Up The Bars }| + line 1 | + line 2 | + line 3 | + line 4 | + l^ine -42 | + line i | + line sin(theta) | + line 8 | + {3:~ }| + {3:~ }| + {3:~ }| + | + ]]) + eq({5, 1}, meths.win_get_cursor(0)) + + meths.input_mouse('left', 'drag', '', 0, 6, 2) + screen:expect([[ + {1:Set Up The Bars }| + line 1 | + line 2 | + line 3 | + line 4 | + l{7:ine -42} | + {7:li}^ne i | + line sin(theta) | + line 8 | + {3:~ }| + {3:~ }| + {3:~ }| + {1:-- VISUAL --} | + ]]) + eq({6, 2}, meths.win_get_cursor(0)) + + meths.input_mouse('left', 'drag', '', 0, 1, 2) + screen:expect([[ + {1:Set Up The Bars }| + li^n{7:e 1} | + {7:line 2} | + {7:line 3} | + {7:line 4} | + {7:li}ne -42 | + line i | + line sin(theta) | + line 8 | + {3:~ }| + {3:~ }| + {3:~ }| + {1:-- VISUAL --} | + ]]) + eq({1, 2}, meths.win_get_cursor(0)) + + meths.input_mouse('left', 'drag', '', 0, 0, 2) + screen:expect_unchanged() + eq({1, 2}, meths.win_get_cursor(0)) + end) + + it('dragging statusline with mouse works correctly', function() + command('split') + screen:expect([[ + {1:Set Up The Bars }| + ^ | + {3:~ }| + {3:~ }| + {3:~ }| + {4:[No Name] }| + {1:Set Up The Bars }| + | + {3:~ }| + {3:~ }| + {3:~ }| + {2:[No Name] }| + | + ]]) + + meths.input_mouse('left', 'press', '', 1, 5, 10) + poke_eventloop() + meths.input_mouse('left', 'drag', '', 1, 6, 10) + screen:expect([[ + {1:Set Up The Bars }| + ^ | + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + {4:[No Name] }| + {1:Set Up The Bars }| + | + {3:~ }| + {3:~ }| + {2:[No Name] }| + | + ]]) + + meths.input_mouse('left', 'drag', '', 1, 4, 10) + screen:expect([[ + {1:Set Up The Bars }| + ^ | + {3:~ }| + {3:~ }| + {4:[No Name] }| + {1:Set Up The Bars }| + | + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + {2:[No Name] }| + | + ]]) + + meths.input_mouse('left', 'press', '', 1, 11, 10) + poke_eventloop() + meths.input_mouse('left', 'drag', '', 1, 9, 10) + screen:expect([[ + {1:Set Up The Bars }| + ^ | + {3:~ }| + {3:~ }| + {4:[No Name] }| + {1:Set Up The Bars }| + | + {3:~ }| + {3:~ }| + {2:[No Name] }| + | + | + | + ]]) + eq(3, meths.get_option('cmdheight')) + + meths.input_mouse('left', 'drag', '', 1, 11, 10) + screen:expect([[ + {1:Set Up The Bars }| + ^ | + {3:~ }| + {3:~ }| + {4:[No Name] }| + {1:Set Up The Bars }| + | + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + {2:[No Name] }| + | + ]]) + eq(1, meths.get_option('cmdheight')) + end) + + it('properly equalizes window height for window-local value', function() + command('set equalalways | set winbar= | setlocal winbar=a | split') + command('setlocal winbar= | split') + command('setlocal winbar=b | split') + screen:expect([[ + {1:b }| + ^ | + {4:[No Name] }| + {1:b }| + | + {2:[No Name] }| + | + {3:~ }| + {2:[No Name] }| + {1:a }| + | + {2:[No Name] }| + | + ]]) + end) + + it('requires window-local value for floating windows', function() + local win = meths.open_win(0, false, { relative = 'editor', row = 2, col = 10, height = 7, + width = 30 }) + meths.set_option_value('winbar', 'bar', {}) + screen:expect{grid=[[ + {1:bar }| + ^ | + {3:~ }{8: }{3: }| + {3:~ }{9:~ }{3: }| + {3:~ }{9:~ }{3: }| + {3:~ }{9:~ }{3: }| + {3:~ }{9:~ }{3: }| + {3:~ }{9:~ }{3: }| + {3:~ }{9:~ }{3: }| + {3:~ }| + {3:~ }| + {3:~ }| + | + ]]} + meths.set_option_value('winbar', 'floaty bar', { scope = 'local', win = win.id }) + screen:expect{grid=[[ + {1:bar }| + ^ | + {3:~ }{1:floaty bar }{3: }| + {3:~ }{8: }{3: }| + {3:~ }{9:~ }{3: }| + {3:~ }{9:~ }{3: }| + {3:~ }{9:~ }{3: }| + {3:~ }{9:~ }{3: }| + {3:~ }{9:~ }{3: }| + {3:~ }| + {3:~ }| + {3:~ }| + | + ]]} + end) + + it('works correctly when moving a split', function() + screen:try_resize(45, 6) + command('set winbar=') + command('vsplit') + command('setlocal winbar=foo') + screen:expect([[ + {1:foo }│ | + ^ │{3:~ }| + {3:~ }│{3:~ }| + {3:~ }│{3:~ }| + {4:[No Name] }{2:[No Name] }| + | + ]]) + + command('wincmd L') + screen:expect([[ + │{1:foo }| + {3:~ }│^ | + {3:~ }│{3:~ }| + {3:~ }│{3:~ }| + {2:[No Name] }{4:[No Name] }| + | + ]]) + + command('wincmd w') + command('wincmd L') + screen:expect([[ + {1:foo }│^ | + │{3:~ }| + {3:~ }│{3:~ }| + {3:~ }│{3:~ }| + {2:[No Name] }{4:[No Name] }| + | + ]]) + end) + + it('properly resizes window when there is no space in it', function() + command('set winbar= | 1split') + screen:expect([[ + ^ | + {4:[No Name] }| + | + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + {2:[No Name] }| + | + ]]) + command('set winbar=a') + screen:expect([[ + {1:a }| + ^ | + {4:[No Name] }| + {1:a }| + | + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + {3:~ }| + {2:[No Name] }| + | + ]]) + end) + + it('cannot be added unless there is room', function() + command('set winbar= | split | split | split | split | split') + screen:expect([[ + ^ | + {4:[No Name] }| + | + {2:[No Name] }| + | + {2:[No Name] }| + | + {2:[No Name] }| + | + {2:[No Name] }| + | + {2:[No Name] }| + | + ]]) + eq('Vim(set):E36: Not enough room', pcall_err(command, 'set winbar=test')) + end) +end) diff --git a/test/functional/vimscript/api_functions_spec.lua b/test/functional/vimscript/api_functions_spec.lua index d07e74d40e..8ca245f61a 100644 --- a/test/functional/vimscript/api_functions_spec.lua +++ b/test/functional/vimscript/api_functions_spec.lua @@ -49,7 +49,8 @@ describe('eval-API', function() it('cannot change texts if textlocked', function() command("autocmd TextYankPost <buffer> ++once call nvim_buf_set_lines(0, 0, -1, v:false, [])") - eq('Vim(call):E5555: API call: E523: Not allowed here', pcall_err(command, "normal! yy")) + eq('Vim(call):E5555: API call: E565: Not allowed to change text or change window', + pcall_err(command, "normal! yy")) end) it("use buffer numbers and windows ids as handles", function() diff --git a/test/functional/vimscript/eval_spec.lua b/test/functional/vimscript/eval_spec.lua index e1459ab5b8..0c2ca8de78 100644 --- a/test/functional/vimscript/eval_spec.lua +++ b/test/functional/vimscript/eval_spec.lua @@ -5,7 +5,7 @@ -- null_spec.lua -- operators_spec.lua -- --- Tests for the Vimscript |functions| library should live in: +-- Tests for the Vimscript |builtin-functions| library should live in: -- test/functional/vimscript/<funcname>_spec.lua -- test/functional/vimscript/functions_spec.lua diff --git a/test/functional/vimscript/executable_spec.lua b/test/functional/vimscript/executable_spec.lua index 048a65188d..b4162b2336 100644 --- a/test/functional/vimscript/executable_spec.lua +++ b/test/functional/vimscript/executable_spec.lua @@ -17,6 +17,21 @@ describe('executable()', function() eq(1, call('executable', 'false')) end) + if iswin() then + it('exepath respects shellslash', function() + command('let $PATH = fnamemodify("./test/functional/fixtures/bin", ":p")') + eq([[test\functional\fixtures\bin\null.CMD]], call('fnamemodify', call('exepath', 'null'), ':.')) + command('set shellslash') + eq('test/functional/fixtures/bin/null.CMD', call('fnamemodify', call('exepath', 'null'), ':.')) + end) + + it('stdpath respects shellslash', function() + eq([[build\Xtest_xdg\share\nvim-data]], call('fnamemodify', call('stdpath', 'data'), ':.')) + command('set shellslash') + eq('build/Xtest_xdg/share/nvim-data', call('fnamemodify', call('stdpath', 'data'), ':.')) + end) + end + it('fails for invalid values', function() for _, input in ipairs({'v:null', 'v:true', 'v:false', '{}', '[]'}) do eq('Vim(call):E928: String required', exc_exec('call executable('..input..')')) diff --git a/test/functional/vimscript/execute_spec.lua b/test/functional/vimscript/execute_spec.lua index e21c71dc7f..a733b098f5 100644 --- a/test/functional/vimscript/execute_spec.lua +++ b/test/functional/vimscript/execute_spec.lua @@ -153,7 +153,7 @@ describe('execute()', function() function! Test3() echo 1234 let x = execute('echoerr "abcdef"', 'silent!') - echon 'ABCD' + echon 'ABCDXZYZ' endfunction " test 4: silenced echoerr goes as usual @@ -214,7 +214,7 @@ describe('execute()', function() ~ | ~ | ~ | - 1234ABCD | + 1234ABCDXZYZ | ]]) feed([[:call Test4()<cr>]]) diff --git a/test/functional/vimscript/functions_spec.lua b/test/functional/vimscript/functions_spec.lua index 0ad7fd8010..20c1400030 100644 --- a/test/functional/vimscript/functions_spec.lua +++ b/test/functional/vimscript/functions_spec.lua @@ -1,4 +1,4 @@ --- Tests for misc Vimscript |functions|. +-- Tests for misc Vimscript |builtin-functions|. -- -- If a function is non-trivial, consider moving its spec to: -- test/functional/vimscript/<funcname>_spec.lua diff --git a/test/functional/vimscript/has_spec.lua b/test/functional/vimscript/has_spec.lua index c03fd13e0c..4d9b226434 100644 --- a/test/functional/vimscript/has_spec.lua +++ b/test/functional/vimscript/has_spec.lua @@ -68,4 +68,11 @@ describe('has()', function() eq(0, funcs.has('wsl')) end end) + + it('does not change v:shell_error', function() + local nvim_prog = helpers.nvim_prog + funcs.system({nvim_prog, '-es', '+73cquit'}) + funcs.has('python3') -- use a call whose implementation shells out + eq(73, funcs.eval('v:shell_error')) + end) end) diff --git a/test/functional/vimscript/input_spec.lua b/test/functional/vimscript/input_spec.lua index 14c02f9eb2..554d15e550 100644 --- a/test/functional/vimscript/input_spec.lua +++ b/test/functional/vimscript/input_spec.lua @@ -9,6 +9,7 @@ local source = helpers.source local command = helpers.command local exc_exec = helpers.exc_exec local nvim_async = helpers.nvim_async +local NIL = helpers.NIL local screen @@ -200,6 +201,15 @@ describe('input()', function() feed(':let var = input({"cancelreturn": "BAR"})<CR>') feed('<Esc>') eq('BAR', meths.get_var('var')) + feed(':let var = input({"cancelreturn": []})<CR>') + feed('<Esc>') + eq({}, meths.get_var('var')) + feed(':let var = input({"cancelreturn": v:false})<CR>') + feed('<Esc>') + eq(false, meths.get_var('var')) + feed(':let var = input({"cancelreturn": v:null})<CR>') + feed('<Esc>') + eq(NIL, meths.get_var('var')) end) it('supports default string', function() feed(':let var = input("", "DEF1")<CR>') @@ -220,8 +230,6 @@ describe('input()', function() eq('Vim(call):E730: using List as a String', exc_exec('call input({"prompt": []})')) eq('Vim(call):E730: using List as a String', - exc_exec('call input({"cancelreturn": []})')) - eq('Vim(call):E730: using List as a String', exc_exec('call input({"default": []})')) eq('Vim(call):E730: using List as a String', exc_exec('call input({"completion": []})')) @@ -418,8 +426,6 @@ describe('inputdialog()', function() eq('Vim(call):E730: using List as a String', exc_exec('call inputdialog({"prompt": []})')) eq('Vim(call):E730: using List as a String', - exc_exec('call inputdialog({"cancelreturn": []})')) - eq('Vim(call):E730: using List as a String', exc_exec('call inputdialog({"default": []})')) eq('Vim(call):E730: using List as a String', exc_exec('call inputdialog({"completion": []})')) diff --git a/test/functional/vimscript/lang_spec.lua b/test/functional/vimscript/lang_spec.lua index d5254986ab..90437f2ee1 100644 --- a/test/functional/vimscript/lang_spec.lua +++ b/test/functional/vimscript/lang_spec.lua @@ -11,6 +11,7 @@ describe('vimscript', function() return end source([[ + let s:foo = 1 func! <sid>_dummy_function() echo 1 endfunc diff --git a/test/functional/vimscript/let_spec.lua b/test/functional/vimscript/let_spec.lua index 4ff4090a18..ca1b5e8907 100644 --- a/test/functional/vimscript/let_spec.lua +++ b/test/functional/vimscript/let_spec.lua @@ -5,9 +5,10 @@ local clear = helpers.clear local command = helpers.command local eval = helpers.eval local meths = helpers.meths +local exec = helpers.exec local exec_capture = helpers.exec_capture local source = helpers.source -local nvim_dir = helpers.nvim_dir +local testprg = helpers.testprg before_each(clear) @@ -47,33 +48,33 @@ describe(':let', function() end) it("multibyte env var #8398 #9267", function() - command("let $NVIM_TEST = 'AìaB'") - eq('AìaB', eval('$NVIM_TEST')) - command("let $NVIM_TEST = 'AaあB'") - eq('AaあB', eval('$NVIM_TEST')) + command("let $NVIM_TEST_LET = 'AìaB'") + eq('AìaB', eval('$NVIM_TEST_LET')) + command("let $NVIM_TEST_LET = 'AaあB'") + eq('AaあB', eval('$NVIM_TEST_LET')) local mbyte = [[\p* .ม .ม .ม .ม่ .ม่ .ม่ ֹ ֹ ֹ .ֹ .ֹ .ֹ ֹֻ ֹֻ ֹֻ .ֹֻ .ֹֻ .ֹֻ ֹֻ ֹֻ ֹֻ .ֹֻ .ֹֻ .ֹֻ ֹ ֹ ֹ .ֹ .ֹ .ֹ ֹ ֹ ֹ .ֹ .ֹ .ֹ ֹֻ ֹֻ .ֹֻ .ֹֻ .ֹֻ a a a ca ca ca à à à]] - command("let $NVIM_TEST = '"..mbyte.."'") - eq(mbyte, eval('$NVIM_TEST')) + command("let $NVIM_TEST_LET = '"..mbyte.."'") + eq(mbyte, eval('$NVIM_TEST_LET')) end) it("multibyte env var to child process #8398 #9267", function() - local cmd_get_child_env = "let g:env_from_child = system(['"..nvim_dir.."/printenv-test', 'NVIM_TEST'])" - command("let $NVIM_TEST = 'AìaB'") + local cmd_get_child_env = ("let g:env_from_child = system(['%s', 'NVIM_TEST_LET'])"):format(testprg('printenv-test')) + command("let $NVIM_TEST_LET = 'AìaB'") command(cmd_get_child_env) - eq(eval('$NVIM_TEST'), eval('g:env_from_child')) + eq(eval('$NVIM_TEST_LET'), eval('g:env_from_child')) - command("let $NVIM_TEST = 'AaあB'") + command("let $NVIM_TEST_LET = 'AaあB'") command(cmd_get_child_env) - eq(eval('$NVIM_TEST'), eval('g:env_from_child')) + eq(eval('$NVIM_TEST_LET'), eval('g:env_from_child')) local mbyte = [[\p* .ม .ม .ม .ม่ .ม่ .ม่ ֹ ֹ ֹ .ֹ .ֹ .ֹ ֹֻ ֹֻ ֹֻ .ֹֻ .ֹֻ .ֹֻ ֹֻ ֹֻ ֹֻ .ֹֻ .ֹֻ .ֹֻ ֹ ֹ ֹ .ֹ .ֹ .ֹ ֹ ֹ ֹ .ֹ .ֹ .ֹ ֹֻ ֹֻ .ֹֻ .ֹֻ .ֹֻ a a a ca ca ca à à à]] - command("let $NVIM_TEST = '"..mbyte.."'") + command("let $NVIM_TEST_LET = '"..mbyte.."'") command(cmd_get_child_env) - eq(eval('$NVIM_TEST'), eval('g:env_from_child')) + eq(eval('$NVIM_TEST_LET'), eval('g:env_from_child')) end) it("release of list assigned to l: variable does not trigger assertion #12387, #12430", function() @@ -91,3 +92,19 @@ describe(':let', function() eq(1, eval('1')) end) end) + +describe(':let and :const', function() + it('have the same output when called without arguments', function() + eq(exec_capture('let'), exec_capture('const')) + end) + + it('can be used in sandbox', function() + exec([[ + func Func() + let l:foo = 'foo' + const l:bar = 'bar' + endfunc + sandbox call Func() + ]]) + end) +end) diff --git a/test/functional/vimscript/msgpack_functions_spec.lua b/test/functional/vimscript/msgpack_functions_spec.lua index 837b629858..cab67d77e4 100644 --- a/test/functional/vimscript/msgpack_functions_spec.lua +++ b/test/functional/vimscript/msgpack_functions_spec.lua @@ -5,6 +5,7 @@ local eval, eq = helpers.eval, helpers.eq local command = helpers.command local nvim = helpers.nvim local exc_exec = helpers.exc_exec +local iswin = helpers.iswin describe('msgpack*() functions', function() before_each(clear) @@ -466,6 +467,11 @@ describe('msgpackparse() function', function() eval(cmd) eval(cmd) -- do it again (try to force segfault) local api_info = eval(cmd) -- do it again + if iswin() then + helpers.assert_alive() + pending('msgpackparse() has a bug on windows') + return + end eq({'error_types', 'functions', 'types', 'ui_events', 'ui_options', 'version'}, api_info) end) diff --git a/test/functional/vimscript/reltime_spec.lua b/test/functional/vimscript/reltime_spec.lua index d87943e485..6d661402a6 100644 --- a/test/functional/vimscript/reltime_spec.lua +++ b/test/functional/vimscript/reltime_spec.lua @@ -12,7 +12,7 @@ describe('reltimestr(), reltimefloat()', function() local later = reltime() local elapsed = reltime(now) - neq(reltimestr(elapsed), '0.0') + neq('0.0', reltimestr(elapsed)) ok(reltimefloat(elapsed) > 0.0) -- original vim test for < 0.1, but easily fails on travis ok(nil ~= string.match(reltimestr(elapsed), "0%.")) @@ -26,7 +26,7 @@ describe('reltimestr(), reltimefloat()', function() eq(0.0, reltimefloat(same)) local differs = reltime(now, later) - neq(reltimestr(differs), '0.0') + neq('0.0', reltimestr(differs)) ok(reltimefloat(differs) > 0.0) -- original vim test for < 0.1, but easily fails on travis ok(nil ~= string.match(reltimestr(differs), "0%.")) diff --git a/test/functional/vimscript/screenchar_spec.lua b/test/functional/vimscript/screenchar_spec.lua new file mode 100644 index 0000000000..767e3c57ef --- /dev/null +++ b/test/functional/vimscript/screenchar_spec.lua @@ -0,0 +1,69 @@ +local helpers = require('test.functional.helpers')(after_each) +local clear, eq, neq = helpers.clear, helpers.eq, helpers.neq +local command, meths, funcs = helpers.command, helpers.meths, helpers.funcs +local tbl_deep_extend = helpers.tbl_deep_extend + +-- Set up two overlapping floating windows +local setup_floating_windows = function() + local base_opts = { + relative = 'editor', + height = 1, + width = 2, + anchor = 'NW', + style = 'minimal', + border = 'none', + } + + local bufnr_1 = meths.create_buf(false, true) + meths.buf_set_lines(bufnr_1, 0, -1, true, { 'aa' }) + local opts_1 = tbl_deep_extend('force', { row = 0, col = 0, zindex = 11 }, base_opts) + meths.open_win(bufnr_1, false, opts_1) + + local bufnr_2 = meths.create_buf(false, true) + meths.buf_set_lines(bufnr_2, 0, -1, true, { 'bb' }) + local opts_2 = tbl_deep_extend('force', { row = 0, col = 1, zindex = 10 }, base_opts) + meths.open_win(bufnr_2, false, opts_2) + + command('redraw') +end + +describe('screenchar() and family respect floating windows', function() + before_each(function() + clear() + -- These commands result into visible text `aabc`. + -- `aab` - from floating windows, `c` - from text in regular window. + meths.buf_set_lines(0, 0, -1, true, { 'cccc' }) + setup_floating_windows() + end) + + it('screenattr()', function() + local attr_1 = funcs.screenattr(1, 1) + local attr_2 = funcs.screenattr(1, 2) + local attr_3 = funcs.screenattr(1, 3) + local attr_4 = funcs.screenattr(1, 4) + eq(attr_1, attr_2) + eq(attr_1, attr_3) + neq(attr_1, attr_4) + end) + + it('screenchar()', function() + eq(97, funcs.screenchar(1, 1)) + eq(97, funcs.screenchar(1, 2)) + eq(98, funcs.screenchar(1, 3)) + eq(99, funcs.screenchar(1, 4)) + end) + + it('screenchars()', function() + eq({ 97 }, funcs.screenchars(1, 1)) + eq({ 97 }, funcs.screenchars(1, 2)) + eq({ 98 }, funcs.screenchars(1, 3)) + eq({ 99 }, funcs.screenchars(1, 4)) + end) + + it('screenstring()', function() + eq('a', funcs.screenstring(1, 1)) + eq('a', funcs.screenstring(1, 2)) + eq('b', funcs.screenstring(1, 3)) + eq('c', funcs.screenstring(1, 4)) + end) +end) diff --git a/test/functional/vimscript/server_spec.lua b/test/functional/vimscript/server_spec.lua index 238d1aeb0f..6e95459630 100644 --- a/test/functional/vimscript/server_spec.lua +++ b/test/functional/vimscript/server_spec.lua @@ -1,11 +1,11 @@ local helpers = require('test.functional.helpers')(after_each) local eq, neq, eval = helpers.eq, helpers.neq, helpers.eval -local command = helpers.command local clear, funcs, meths = helpers.clear, helpers.funcs, helpers.meths local iswin = helpers.iswin local ok = helpers.ok local matches = helpers.matches local pcall_err = helpers.pcall_err +local mkdir = helpers.mkdir local function clear_serverlist() for _, server in pairs(funcs.serverlist()) do @@ -14,29 +14,38 @@ local function clear_serverlist() end describe('server', function() - before_each(clear) + it('serverstart() stores sockets in $XDG_RUNTIME_DIR', function() + local dir = 'Xtest_xdg_run' + mkdir(dir) + clear({ env={ XDG_RUNTIME_DIR=dir } }) + matches(dir, funcs.stdpath('run')) + if not iswin() then + matches(dir, funcs.serverstart()) + end + end) - it('serverstart() sets $NVIM_LISTEN_ADDRESS on first invocation', function() - -- Unset $NVIM_LISTEN_ADDRESS - command('let $NVIM_LISTEN_ADDRESS = ""') + it('serverstart(), serverstop() does not set $NVIM', function() + clear() local s = eval('serverstart()') assert(s ~= nil and s:len() > 0, "serverstart() returned empty") - eq(s, eval('$NVIM_LISTEN_ADDRESS')) + eq('', eval('$NVIM')) + eq('', eval('$NVIM_LISTEN_ADDRESS')) eq(1, eval("serverstop('"..s.."')")) eq('', eval('$NVIM_LISTEN_ADDRESS')) end) it('sets new v:servername if $NVIM_LISTEN_ADDRESS is invalid', function() clear({env={NVIM_LISTEN_ADDRESS='.'}}) - eq('.', eval('$NVIM_LISTEN_ADDRESS')) + -- Cleared on startup. + eq('', eval('$NVIM_LISTEN_ADDRESS')) local servers = funcs.serverlist() eq(1, #servers) - ok(string.len(servers[1]) > 4) -- Like /tmp/nvim…/… or \\.\pipe\… + 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() + it('sets v:servername at startup or if all servers were stopped', function() + clear() local initial_server = meths.get_vvar('servername') assert(initial_server ~= nil and initial_server:len() > 0, 'v:servername was not initialized') @@ -55,19 +64,23 @@ describe('server', function() eq(1, funcs.serverstop(funcs.serverlist()[1])) eq('', meths.get_vvar('servername')) - -- v:servername will take the next available server. + -- v:servername and $NVIM take the next available server. local servername = (iswin() and [[\\.\pipe\Xtest-functional-server-pipe]] - or 'Xtest-functional-server-socket') + or './Xtest-functional-server-socket') funcs.serverstart(servername) eq(servername, meths.get_vvar('servername')) + -- Not set in the current process, only in children. + eq('', eval('$NVIM')) end) it('serverstop() returns false for invalid input', function() + clear() eq(0, eval("serverstop('')")) eq(0, eval("serverstop('bogus-socket-name')")) end) - it('parses endpoints correctly', function() + it('parses endpoints', function() + clear() clear_serverlist() eq({}, funcs.serverlist()) @@ -102,19 +115,24 @@ describe('server', function() eq(expected, funcs.serverlist()) clear_serverlist() + -- Address without slashes is a "name" which is appended to a generated path. #8519 + matches([[.*[/\\]xtest1%.2%.3%.4[^/\\]*]], funcs.serverstart('xtest1.2.3.4')) + clear_serverlist() + eq('Vim:Failed to start server: invalid argument', pcall_err(funcs.serverstart, '127.0.0.1:65536')) -- invalid port eq({}, funcs.serverlist()) end) it('serverlist() returns the list of servers', function() + clear() -- There should already be at least one server. local n = eval('len(serverlist())') -- Add some servers. local servs = (iswin() and { [[\\.\pipe\Xtest-pipe0934]], [[\\.\pipe\Xtest-pipe4324]] } - or { [[Xtest-pipe0934]], [[Xtest-pipe4324]] }) + or { [[./Xtest-pipe0934]], [[./Xtest-pipe4324]] }) for _, s in ipairs(servs) do eq(s, eval("serverstart('"..s.."')")) end @@ -136,7 +154,6 @@ end) describe('startup --listen', function() it('validates', function() clear() - local cmd = { unpack(helpers.nvim_argv) } table.insert(cmd, '--listen') matches('nvim.*: Argument missing after: "%-%-listen"', funcs.system(cmd)) @@ -148,9 +165,13 @@ describe('startup --listen', function() it('sets v:servername, overrides $NVIM_LISTEN_ADDRESS', function() local addr = (iswin() and [[\\.\pipe\Xtest-listen-pipe]] - or 'Xtest-listen-pipe') - clear({ env={ NVIM_LISTEN_ADDRESS='Xtest-env-pipe' }, + or './Xtest-listen-pipe') + clear({ env={ NVIM_LISTEN_ADDRESS='./Xtest-env-pipe' }, args={ '--listen', addr } }) eq(addr, meths.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[^/\\]*]], meths.get_vvar('servername')) end) end) diff --git a/test/functional/vimscript/setpos_spec.lua b/test/functional/vimscript/setpos_spec.lua index 935f387bcc..02e550dcc0 100644 --- a/test/functional/vimscript/setpos_spec.lua +++ b/test/functional/vimscript/setpos_spec.lua @@ -24,41 +24,41 @@ describe('setpos() function', function() end) it('can set the current cursor position', function() setpos(".", {0, 2, 1, 0}) - eq(getpos("."), {0, 2, 1, 0}) + eq({0, 2, 1, 0}, getpos(".")) setpos(".", {2, 1, 1, 0}) - eq(getpos("."), {0, 1, 1, 0}) + eq({0, 1, 1, 0}, getpos(".")) local ret = exc_exec('call setpos(".", [1, 1, 1, 0])') eq(0, ret) end) it('can set lowercase marks in the current buffer', function() setpos("'d", {0, 2, 1, 0}) - eq(getpos("'d"), {0, 2, 1, 0}) + eq({0, 2, 1, 0}, getpos("'d")) command('undo') command('call setpos("\'d", [2, 3, 1, 0])') - eq(getpos("'d"), {0, 3, 1, 0}) + eq({0, 3, 1, 0}, getpos("'d")) end) it('can set lowercase marks in other buffers', function() local retval = setpos("'d", {1, 2, 1, 0}) eq(0, retval) setpos("'d", {1, 2, 1, 0}) - eq(getpos("'d"), {0, 0, 0, 0}) + eq({0, 0, 0, 0}, getpos("'d")) command('wincmd w') - eq(eval('bufnr("%")'), 1) - eq(getpos("'d"), {0, 2, 1, 0}) + eq(1, eval('bufnr("%")')) + eq({0, 2, 1, 0}, getpos("'d")) end) it("fails when setting a mark in a buffer that doesn't exist", function() local retval = setpos("'d", {3, 2, 1, 0}) eq(-1, retval) - eq(getpos("'d"), {0, 0, 0, 0}) + eq({0, 0, 0, 0}, getpos("'d")) retval = setpos("'D", {3, 2, 1, 0}) eq(-1, retval) - eq(getpos("'D"), {0, 0, 0, 0}) + eq({0, 0, 0, 0}, getpos("'D")) end) it('can set uppercase marks', function() setpos("'D", {2, 2, 3, 0}) - eq(getpos("'D"), {2, 2, 3, 0}) + eq({2, 2, 3, 0}, getpos("'D")) -- Can set a mark in another buffer setpos("'D", {1, 2, 2, 0}) - eq(getpos("'D"), {1, 2, 2, 0}) + eq({1, 2, 2, 0}, getpos("'D")) end) end) diff --git a/test/functional/vimscript/system_spec.lua b/test/functional/vimscript/system_spec.lua index 24a1f05390..c915556c57 100644 --- a/test/functional/vimscript/system_spec.lua +++ b/test/functional/vimscript/system_spec.lua @@ -1,11 +1,13 @@ local helpers = require('test.functional.helpers')(after_each) local assert_alive = helpers.assert_alive -local nvim_dir = helpers.nvim_dir +local testprg = helpers.testprg local eq, call, clear, eval, feed_command, feed, nvim = helpers.eq, helpers.call, helpers.clear, helpers.eval, helpers.feed_command, helpers.feed, helpers.nvim local command = helpers.command +local insert = helpers.insert +local expect = helpers.expect local exc_exec = helpers.exc_exec local iswin = helpers.iswin local os_kill = helpers.os_kill @@ -30,10 +32,6 @@ describe('system()', function() before_each(clear) describe('command passed as a List', function() - local function printargs_path() - return nvim_dir..'/printargs-test' .. (iswin() and '.exe' or '') - end - it('throws error if cmd[0] is not executable', function() eq("Vim:E475: Invalid value for argument cmd: 'this-should-not-exist' is not executable", pcall_err(call, 'system', { 'this-should-not-exist' })) @@ -66,23 +64,23 @@ describe('system()', function() it('quotes arguments correctly #5280', function() local out = call('system', - { printargs_path(), [[1]], [[2 "3]], [[4 ' 5]], [[6 ' 7']] }) + { testprg('printargs-test'), [[1]], [[2 "3]], [[4 ' 5]], [[6 ' 7']] }) eq(0, eval('v:shell_error')) eq([[arg1=1;arg2=2 "3;arg3=4 ' 5;arg4=6 ' 7';]], out) - out = call('system', { printargs_path(), [['1]], [[2 "3]] }) + out = call('system', { testprg('printargs-test'), [['1]], [[2 "3]] }) eq(0, eval('v:shell_error')) eq([[arg1='1;arg2=2 "3;]], out) - out = call('system', { printargs_path(), "A\nB" }) + out = call('system', { testprg('printargs-test'), "A\nB" }) eq(0, eval('v:shell_error')) eq("arg1=A\nB;", out) end) it('calls executable in $PATH', function() - if 0 == eval("executable('python')") then pending("missing `python`") end - eq("foo\n", eval([[system(['python', '-c', 'print("foo")'])]])) + if 0 == eval("executable('python3')") then pending("missing `python3`") end + eq("foo\n", eval([[system(['python3', '-c', 'print("foo")'])]])) eq(0, eval('v:shell_error')) end) @@ -167,7 +165,7 @@ describe('system()', function() end end) - it('works with powershell', function() + it('with powershell', function() helpers.set_shell_powershell() eq('a\nb\n', eval([[system('Write-Output a b')]])) eq('C:\\\n', eval([[system('cd c:\; (Get-Location).Path')]])) @@ -175,12 +173,11 @@ describe('system()', function() end) end - it('works with powershell w/ UTF-8 text (#13713)', function() + it('powershell w/ UTF-8 text #13713', function() if not helpers.has_powershell() then - pending("not tested; powershell was not found", function() end) + pending("powershell not found", function() end) return end - -- Should work with recommended config used in helper helpers.set_shell_powershell() eq('ああ\n', eval([[system('Write-Output "ああ"')]])) -- Sanity test w/ default encoding @@ -268,7 +265,7 @@ describe('system()', function() :call system("for /L %I in (1,0,2) do @echo y") |]] or [[ :call system("yes") |]])) - feed('<c-c>') + feed('foo<c-c>') screen:expect([[ ^ | ~ | @@ -286,6 +283,49 @@ describe('system()', function() Type :qa and press <Enter> to exit Nvim | ]]) end) + + it('`yes` interrupted with mapped CTRL-C', function() + command('nnoremap <C-C> i') + feed(':call system("' .. (iswin() + and 'for /L %I in (1,0,2) do @echo y' + or 'yes') .. '")<cr>') + screen:expect([[ + | + ~ | + ~ | + ~ | + ~ | + ~ | + ~ | + ~ | + ~ | + ~ | + ~ | + ~ | + ~ | +]] .. (iswin() + and [[ + :call system("for /L %I in (1,0,2) do @echo y") |]] + or [[ + :call system("yes") |]])) + feed('foo<c-c>') + screen:expect([[ + ^ | + ~ | + ~ | + ~ | + ~ | + ~ | + ~ | + ~ | + ~ | + ~ | + ~ | + ~ | + ~ | + -- INSERT -- | + ]]) + end) end) describe('passing no input', function() @@ -387,7 +427,7 @@ describe('system()', function() end) it("with a program that doesn't close stdout will exit properly after passing input", function() - local out = eval(string.format("system('%s', 'clip-data')", nvim_dir..'/streams-test')) + local out = eval(string.format("system('%s', 'clip-data')", testprg('streams-test'))) assert(out:sub(0, 5) == 'pid: ', out) os_kill(out:match("%d+")) end) @@ -527,7 +567,7 @@ describe('systemlist()', function() end) -- Unlike `system()` which uses SOH to represent NULs, with `systemlist()` - -- input and ouput are the same. + -- input and output are the same. describe('with linefeed characters inside list items', function() it('converts linefeed characters to NULs', function() eq({'l1\np2', 'line2\na\nb', 'l3'}, @@ -566,17 +606,16 @@ describe('systemlist()', function() end) it("with a program that doesn't close stdout will exit properly after passing input", function() - local out = eval(string.format("systemlist('%s', 'clip-data')", nvim_dir..'/streams-test')) + local out = eval(string.format("systemlist('%s', 'clip-data')", testprg('streams-test'))) assert(out[1]:sub(0, 5) == 'pid: ', out) os_kill(out[1]:match("%d+")) end) - it('works with powershell w/ UTF-8 text (#13713)', function() + it('powershell w/ UTF-8 text #13713', function() if not helpers.has_powershell() then - pending("not tested; powershell was not found", function() end) + pending("powershell not found", function() end) return end - -- Should work with recommended config used in helper helpers.set_shell_powershell() eq({iswin() and 'あ\r' or 'あ'}, eval([[systemlist('Write-Output あ')]])) -- Sanity test w/ default encoding @@ -587,3 +626,31 @@ describe('systemlist()', function() end) end) + +describe('shell :!', function() + before_each(clear) + + it(':{range}! with powershell filter/redirect #16271', function() + local screen = Screen.new(500, 8) + screen:attach() + local found = helpers.set_shell_powershell(true) + insert([[ + 3 + 1 + 4 + 2]]) + feed(':4verbose %!sort<cr>') + screen:expect{ + any=[[Executing command: .?Start%-Process sort %-RedirectStandardInput .* %-RedirectStandardOutput .* %-NoNewWindow %-Wait]] + } + feed('<CR>') + if found then + -- Not using fake powershell, so we can test the result. + expect([[ + 1 + 2 + 3 + 4]]) + end + end) +end) diff --git a/test/functional/vimscript/timer_spec.lua b/test/functional/vimscript/timer_spec.lua index e45b64422f..5463cfb234 100644 --- a/test/functional/vimscript/timer_spec.lua +++ b/test/functional/vimscript/timer_spec.lua @@ -96,7 +96,7 @@ describe('timers', function() nvim_async("command", "let g:val = 0 | let g:c = getchar()") retry(nil, nil, function() local val = eval("g:val") - ok(val >= 2, "expected >= 2, got: "..tostring(val)) + ok(val >= 2, '>= 2', tostring(val)) eq(0, eval("getchar(1)")) end) feed("c") @@ -272,4 +272,12 @@ describe('timers', function() ]] eq("Vim(call):E48: Not allowed in sandbox", exc_exec("sandbox call timer_start(0, 'Scary')")) end) + + it('can be triggered after an empty string <expr> mapping #17257', function() + local screen = Screen.new(40, 6) + screen:attach() + command([=[imap <expr> <F2> [timer_start(0, { _ -> execute("throw 'x'", "") }), ''][-1]]=]) + feed('i<F2>') + screen:expect({any='E605: Exception not caught: x'}) + end) end) diff --git a/test/helpers.lua b/test/helpers.lua index 87431e4342..7ec9beea92 100644 --- a/test/helpers.lua +++ b/test/helpers.lua @@ -4,7 +4,7 @@ local assert = require('luassert') local luv = require('luv') local lfs = require('lfs') local relpath = require('pl.path').relpath -local Paths = require('test.config.paths') +local Paths = require('test.cmakeconfig.paths') assert:set_parameter('TableFormatLevel', 100) @@ -40,10 +40,6 @@ function module.popen_r(...) return io.popen(module.argss_to_cmd(...), 'r') end -function module.popen_w(...) - return io.popen(module.argss_to_cmd(...), 'w') -end - -- sleeps the test runner (_not_ the nvim instance) function module.sleep(ms) luv.sleep(ms) @@ -55,42 +51,31 @@ local check_logs_useless_lines = { ['See README_MISSING_SYSCALL_OR_IOCTL for guidance']=3, } ---- Invokes `fn` and includes the tail of `logfile` in the error message if it ---- fails. ---- ----@param logfile Log file, defaults to $NVIM_LOG_FILE or '.nvimlog' ----@param fn Function to invoke ----@param ... Function arguments -local function dumplog(logfile, fn, ...) - -- module.validate({ - -- logfile={logfile,'s',true}, - -- fn={fn,'f',false}, - -- }) - local status, rv = pcall(fn, ...) - if status == false then - logfile = logfile or os.getenv('NVIM_LOG_FILE') or '.nvimlog' - local logtail = module.read_nvim_log(logfile) - error(string.format('%s\n%s', tostring(rv), logtail)) - end +function module.eq(expected, actual, context) + return assert.are.same(expected, actual, context) end -function module.eq(expected, actual, context, logfile) - return dumplog(logfile, assert.are.same, expected, actual, context) +function module.neq(expected, actual, context) + return assert.are_not.same(expected, actual, context) end -function module.neq(expected, actual, context, logfile) - return dumplog(logfile, assert.are_not.same, expected, actual, context) -end -function module.ok(res, msg, logfile) - return dumplog(logfile, assert.is_true, res, msg) + +--- Asserts that `cond` is true, or prints a message. +--- +--- @param cond (boolean) expression to assert +--- @param expected (any) description of expected result +--- @param actual (any) description of actual result +function module.ok(cond, expected, actual) + assert((not expected and not actual) or (expected and actual), 'if "expected" is given, "actual" is also required') + local msg = expected and ('expected %s, got: %s'):format(expected, tostring(actual)) or nil + return assert(cond, msg) end --- TODO(bfredl): this should "failure" not "error" (issue with dumplog() ) local function epicfail(state, arguments, _) state.failure_message = arguments[1] return false end assert:register("assertion", "epicfail", epicfail) -function module.fail(msg, logfile) - return dumplog(logfile, assert.epicfail, msg) +function module.fail(msg) + return assert.epicfail(msg) end function module.matches(pat, actual) @@ -100,20 +85,33 @@ function module.matches(pat, actual) error(string.format('Pattern does not match.\nPattern:\n%s\nActual:\n%s', pat, actual)) end ---- Asserts that `pat` matches one or more lines in the tail of $NVIM_LOG_FILE. +--- Asserts that `pat` matches (or *not* if inverse=true) any line in the tail of `logfile`. --- ----@param pat (string) Lua pattern to search for in the log file. ----@param logfile (string, default=$NVIM_LOG_FILE) full path to log file. -function module.assert_log(pat, logfile) +---@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 inverse (boolean) Assert that the pattern does NOT match. +function module.assert_log(pat, logfile, nrlines, inverse) logfile = logfile or os.getenv('NVIM_LOG_FILE') or '.nvimlog' - local nrlines = 10 + assert(logfile ~= nil, 'no logfile') + nrlines = nrlines or 10 + inverse = inverse or false local lines = module.read_file_list(logfile, -nrlines) or {} + local msg = string.format('Pattern %q %sfound in log (last %d lines): %s:\n%s', + pat, (inverse and '' or 'not '), nrlines, logfile, ' '..table.concat(lines, '\n ')) for _,line in ipairs(lines) do - if line:match(pat) then return end + if line:match(pat) then + if inverse then error(msg) else return end + end end - local logtail = module.read_nvim_log(logfile) - error(string.format('Pattern %q not found in log (last %d lines): %s:\n%s', - pat, nrlines, logfile, logtail)) + if not inverse then error(msg) end +end + +--- Asserts that `pat` does NOT matche any line in the tail of `logfile`. +--- +--- @see assert_log +function module.assert_nolog(pat, logfile, nrlines) + return module.assert_log(pat, logfile, nrlines, true) end -- Invokes `fn` and returns the error string (with truncated paths), or raises @@ -271,7 +269,7 @@ module.uname = (function() return platform end - if os.getenv("SYSTEM_NAME") then -- From CMAKE_SYSTEM_NAME. + if os.getenv("SYSTEM_NAME") then -- From CMAKE_HOST_SYSTEM_NAME. platform = string.lower(os.getenv("SYSTEM_NAME")) return platform end @@ -307,6 +305,7 @@ local function tmpdir_is_local(dir) return not not (dir and string.find(dir, 'Xtest')) end +--- Creates a new temporary file for use by tests. module.tmpname = (function() local seq = 0 local tmpdir = tmpdir_get() @@ -314,7 +313,8 @@ module.tmpname = (function() if tmpdir_is_local(tmpdir) then -- Cannot control os.tmpname() dir, so hack our own tmpname() impl. seq = seq + 1 - local fname = tmpdir..'/nvim-test-lua-'..seq + -- "…/Xtest_tmpdir/T42.7" + local fname = ('%s/%s.%d'):format(tmpdir, (_G._nvim_test_id or 'nvim-test'), seq) io.open(fname, 'w'):close() return fname else @@ -409,17 +409,6 @@ function module.check_cores(app, force) end end -function module.which(exe) - local pipe = module.popen_r('which', exe) - local ret = pipe:read('*a') - pipe:close() - if ret == '' then - return nil - else - return ret:sub(1, -2) - end -end - function module.repeated_read_cmd(...) for _ = 1, 10 do local stream = module.popen_r(...) @@ -801,12 +790,10 @@ end function module.isCI(name) local any = (name == nil) - assert(any or name == 'appveyor' or name == 'travis' or name == 'sourcehut' or name == 'github') - local av = ((any or name == 'appveyor') and nil ~= os.getenv('APPVEYOR')) - local tr = ((any or name == 'travis') and nil ~= os.getenv('TRAVIS')) + assert(any or name == 'sourcehut' or name == 'github') local sh = ((any or name == 'sourcehut') and nil ~= os.getenv('SOURCEHUT')) local gh = ((any or name == 'github') and nil ~= os.getenv('GITHUB_ACTIONS')) - return tr or av or sh or gh + return sh or gh end @@ -815,7 +802,7 @@ end function module.read_nvim_log(logfile, ci_rename) logfile = logfile or os.getenv('NVIM_LOG_FILE') or '.nvimlog' local is_ci = module.isCI() - local keep = is_ci and 999 or 10 + local keep = is_ci and 100 or 10 local lines = module.read_file_list(logfile, -keep) or {} local log = (('-'):rep(78)..'\n' ..string.format('$NVIM_LOG_FILE: %s\n', logfile) diff --git a/test/symbolic/klee/nvim/keymap.c b/test/symbolic/klee/nvim/keymap.c index 8bc597d7fe..26cdd9db47 100644 --- a/test/symbolic/klee/nvim/keymap.c +++ b/test/symbolic/klee/nvim/keymap.c @@ -1,7 +1,7 @@ #include <stdbool.h> #include "nvim/types.h" -#include "nvim/keymap.h" +#include "nvim/keycodes.h" #include "nvim/ascii.h" #include "nvim/eval/typval.h" diff --git a/test/symbolic/klee/run.sh b/test/symbolic/klee/run.sh index 0234a935b5..97ce42c31b 100755 --- a/test/symbolic/klee/run.sh +++ b/test/symbolic/klee/run.sh @@ -54,7 +54,7 @@ main() { includes="$includes -I$PROJECT_SOURCE_DIR/src" includes="$includes -I$PROJECT_BINARY_DIR/src/nvim/auto" includes="$includes -I$PROJECT_BINARY_DIR/include" - includes="$includes -I$PROJECT_BINARY_DIR/config" + includes="$includes -I$PROJECT_BINARY_DIR/cmake.config" includes="$includes -I/host-includes" local defines= diff --git a/test/symbolic/klee/viml_expressions_lexer.c b/test/symbolic/klee/viml_expressions_lexer.c index ee7dc312e9..03c9d66ca4 100644 --- a/test/symbolic/klee/viml_expressions_lexer.c +++ b/test/symbolic/klee/viml_expressions_lexer.c @@ -17,7 +17,7 @@ #include "nvim/charset.c" #include "nvim/garray.c" #include "nvim/gettext.c" -#include "nvim/keymap.c" +#include "nvim/keycodes.c" #include "nvim/viml/parser/expressions.c" #define INPUT_SIZE 7 diff --git a/test/symbolic/klee/viml_expressions_parser.c b/test/symbolic/klee/viml_expressions_parser.c index 9a876ed3fa..b0e1d71127 100644 --- a/test/symbolic/klee/viml_expressions_parser.c +++ b/test/symbolic/klee/viml_expressions_parser.c @@ -17,7 +17,7 @@ #include "nvim/garray.c" #include "nvim/gettext.c" #include "nvim/viml/parser/expressions.c" -#include "nvim/keymap.c" +#include "nvim/keycodes.c" #define INPUT_SIZE 50 diff --git a/test/unit/buffer_spec.lua b/test/unit/buffer_spec.lua index 3692e19379..204d713fb7 100644 --- a/test/unit/buffer_spec.lua +++ b/test/unit/buffer_spec.lua @@ -17,22 +17,22 @@ describe('buffer functions', function() return buffer.buflist_new(c_file, c_file, 1, flags) end - local close_buffer = function(win, buf, action, abort_if_last) - return buffer.close_buffer(win, buf, action, abort_if_last) + local close_buffer = function(win, buf, action, abort_if_last, ignore_abort) + return buffer.close_buffer(win, buf, action, abort_if_last, ignore_abort) end local path1 = 'test_file_path' local path2 = 'file_path_test' local path3 = 'path_test_file' - before_each(function() + setup(function() -- create the files io.open(path1, 'w').close() io.open(path2, 'w').close() io.open(path3, 'w').close() end) - after_each(function() + teardown(function() os.remove(path1) os.remove(path2) os.remove(path3) @@ -53,7 +53,7 @@ describe('buffer functions', function() itp('should view a closed and hidden buffer as valid', function() local buf = buflist_new(path1, buffer.BLN_LISTED) - close_buffer(NULL, buf, 0, 0) + close_buffer(NULL, buf, 0, 0, 0) eq(true, buffer.buf_valid(buf)) end) @@ -61,7 +61,7 @@ describe('buffer functions', function() itp('should view a closed and unloaded buffer as valid', function() local buf = buflist_new(path1, buffer.BLN_LISTED) - close_buffer(NULL, buf, buffer.DOBUF_UNLOAD, 0) + close_buffer(NULL, buf, buffer.DOBUF_UNLOAD, 0, 0) eq(true, buffer.buf_valid(buf)) end) @@ -69,7 +69,7 @@ describe('buffer functions', function() itp('should view a closed and wiped buffer as invalid', function() local buf = buflist_new(path1, buffer.BLN_LISTED) - close_buffer(NULL, buf, buffer.DOBUF_WIPE, 0) + close_buffer(NULL, buf, buffer.DOBUF_WIPE, 0, 0) eq(false, buffer.buf_valid(buf)) end) @@ -90,7 +90,7 @@ describe('buffer functions', function() eq(buf.handle, buflist_findpat(path1, ONLY_LISTED)) - close_buffer(NULL, buf, buffer.DOBUF_WIPE, 0) + close_buffer(NULL, buf, buffer.DOBUF_WIPE, 0, 0) end) itp('should prefer to match the start of a file path', function() @@ -102,9 +102,9 @@ describe('buffer functions', function() eq(buf2.handle, buflist_findpat("file", ONLY_LISTED)) eq(buf3.handle, buflist_findpat("path", ONLY_LISTED)) - close_buffer(NULL, buf1, buffer.DOBUF_WIPE, 0) - close_buffer(NULL, buf2, buffer.DOBUF_WIPE, 0) - close_buffer(NULL, buf3, buffer.DOBUF_WIPE, 0) + close_buffer(NULL, buf1, buffer.DOBUF_WIPE, 0, 0) + close_buffer(NULL, buf2, buffer.DOBUF_WIPE, 0, 0) + close_buffer(NULL, buf3, buffer.DOBUF_WIPE, 0, 0) end) itp('should prefer to match the end of a file over the middle', function() @@ -118,7 +118,7 @@ describe('buffer functions', function() --} --{ When: We close buf2 - close_buffer(NULL, buf2, buffer.DOBUF_WIPE, 0) + close_buffer(NULL, buf2, buffer.DOBUF_WIPE, 0, 0) -- And: Open buf1, which has 'file' in the middle of its name local buf1 = buflist_new(path1, buffer.BLN_LISTED) @@ -127,8 +127,8 @@ describe('buffer functions', function() eq(buf3.handle, buflist_findpat("file", ONLY_LISTED)) --} - close_buffer(NULL, buf1, buffer.DOBUF_WIPE, 0) - close_buffer(NULL, buf3, buffer.DOBUF_WIPE, 0) + close_buffer(NULL, buf1, buffer.DOBUF_WIPE, 0, 0) + close_buffer(NULL, buf3, buffer.DOBUF_WIPE, 0, 0) end) itp('should match a unique fragment of a file path', function() @@ -138,9 +138,9 @@ describe('buffer functions', function() eq(buf3.handle, buflist_findpat("_test_", ONLY_LISTED)) - close_buffer(NULL, buf1, buffer.DOBUF_WIPE, 0) - close_buffer(NULL, buf2, buffer.DOBUF_WIPE, 0) - close_buffer(NULL, buf3, buffer.DOBUF_WIPE, 0) + close_buffer(NULL, buf1, buffer.DOBUF_WIPE, 0, 0) + close_buffer(NULL, buf2, buffer.DOBUF_WIPE, 0, 0) + close_buffer(NULL, buf3, buffer.DOBUF_WIPE, 0, 0) end) itp('should include / ignore unlisted buffers based on the flag.', function() @@ -152,7 +152,7 @@ describe('buffer functions', function() --} --{ When: We unlist the buffer - close_buffer(NULL, buf3, buffer.DOBUF_DEL, 0) + close_buffer(NULL, buf3, buffer.DOBUF_DEL, 0, 0) -- Then: It should not find the buffer when searching only listed buffers eq(-1, buflist_findpat("_test_", ONLY_LISTED)) @@ -162,7 +162,7 @@ describe('buffer functions', function() --} --{ When: We wipe the buffer - close_buffer(NULL, buf3, buffer.DOBUF_WIPE, 0) + close_buffer(NULL, buf3, buffer.DOBUF_WIPE, 0, 0) -- Then: It should not find the buffer at all eq(-1, buflist_findpat("_test_", ONLY_LISTED)) @@ -180,7 +180,7 @@ describe('buffer functions', function() --} --{ When: The first buffer is unlisted - close_buffer(NULL, buf1, buffer.DOBUF_DEL, 0) + close_buffer(NULL, buf1, buffer.DOBUF_DEL, 0, 0) -- Then: The second buffer is preferred because -- unlisted buffers are not allowed @@ -194,7 +194,7 @@ describe('buffer functions', function() --} --{ When: We unlist the second buffer - close_buffer(NULL, buf2, buffer.DOBUF_DEL, 0) + close_buffer(NULL, buf2, buffer.DOBUF_DEL, 0, 0) -- Then: The first buffer is preferred again -- because buf1 matches better which takes precedence @@ -205,8 +205,8 @@ describe('buffer functions', function() eq(-1, buflist_findpat("test", ONLY_LISTED)) --} - close_buffer(NULL, buf1, buffer.DOBUF_WIPE, 0) - close_buffer(NULL, buf2, buffer.DOBUF_WIPE, 0) + close_buffer(NULL, buf1, buffer.DOBUF_WIPE, 0, 0) + close_buffer(NULL, buf2, buffer.DOBUF_WIPE, 0, 0) end) end) @@ -249,7 +249,7 @@ describe('buffer functions', function() -- -- @param arg Options can be placed in an optional dictionary 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 + -- .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, @@ -264,7 +264,7 @@ describe('buffer functions', function() local fillchar = option.fillchar or (' '):byte() local expected_cell_count = option.expected_cell_count or statusline_cell_count - local expected_byte_length = option.expected_byte_length or expected_cell_count + local expected_byte_length = option.expected_byte_length or #expected_stl itp(description, function() if option.file_name then @@ -312,12 +312,12 @@ describe('buffer functions', function() statusline_test('should put fillchar `~` in between text', 10, 'abc%=def', 'abc~~~~def', {fillchar=('~'):byte()}) + statusline_test('should put fillchar `━` in between text', 10, + 'abc%=def', 'abc━━━━def', + {fillchar=0x2501}) statusline_test('should handle zero-fillchar as a space', 10, 'abcde%=', 'abcde ', {fillchar=0}) - statusline_test('should handle multibyte-fillchar as a dash', 10, - 'abcde%=', 'abcde-----', - {fillchar=0x80}) statusline_test('should print the tail file name', 80, '%t', 'buffer_spec.lua', {file_name='test/unit/buffer_spec.lua', expected_cell_count=15}) @@ -366,70 +366,86 @@ describe('buffer functions', function() statusline_test('should ignore trailing %', 3, 'abc%', 'abc') - -- alignment testing - statusline_test('should right align when using =', 20, - 'neo%=vim', 'neo vim') - statusline_test('should, when possible, center text when using %=text%=', 20, - 'abc%=neovim%=def', 'abc neovim def') - statusline_test('should handle uneven spacing in the buffer when using %=text%=', 20, - 'abc%=neo_vim%=def', 'abc neo_vim def') - statusline_test('should have equal spaces even with non-equal sides when using =', 20, - 'foobar%=test%=baz', 'foobar test baz') - statusline_test('should have equal spaces even with longer right side when using =', 20, - 'a%=test%=longtext', 'a test longtext') - statusline_test('should handle an empty left side when using ==', 20, - '%=test%=baz', ' test baz') - statusline_test('should handle an empty right side when using ==', 20, - 'foobar%=test%=', 'foobar test ') - statusline_test('should handle consecutive empty ==', 20, - '%=%=test%=', ' test ') - statusline_test('should handle an = alone', 20, - '%=', ' ') - statusline_test('should right align text when it is alone with =', 20, - '%=foo', ' foo') - statusline_test('should left align text when it is alone with =', 20, - 'foo%=', 'foo ') - - statusline_test('should approximately center text when using %=text%=', 21, - 'abc%=neovim%=def', 'abc neovim def') - statusline_test('should completely fill the buffer when using %=text%=', 21, - 'abc%=neo_vim%=def', 'abc neo_vim def') - statusline_test('should have equal spaces even with non-equal sides when using =', 21, - 'foobar%=test%=baz', 'foobar test baz') - statusline_test('should have equal spaces even with longer right side when using =', 21, - 'a%=test%=longtext', 'a test longtext') - statusline_test('should handle an empty left side when using ==', 21, - '%=test%=baz', ' test baz') - statusline_test('should handle an empty right side when using ==', 21, - 'foobar%=test%=', 'foobar test ') - - statusline_test('should quadrant the text when using 3 %=', 40, - 'abcd%=n%=eovim%=ef', 'abcd n eovim ef') - statusline_test('should work well with %t', 40, - '%t%=right_aligned', 'buffer_spec.lua right_aligned', + -- alignment testing with fillchar + local function statusline_test_align (description, + statusline_cell_count, + input_stl, + expected_stl, + arg) + arg = arg or {} + statusline_test(description .. ' without fillchar', + statusline_cell_count, input_stl, expected_stl:gsub('%~', ' '), arg) + arg.fillchar = ('!'):byte() + statusline_test(description .. ' with fillchar `!`', + statusline_cell_count, input_stl, expected_stl:gsub('%~', '!'), arg) + arg.fillchar = 0x2501 + statusline_test(description .. ' with fillchar `━`', + statusline_cell_count, input_stl, expected_stl:gsub('%~', '━'), arg) + end + + statusline_test_align('should right align when using =', 20, + 'neo%=vim', 'neo~~~~~~~~~~~~~~vim') + statusline_test_align('should, when possible, center text when using %=text%=', 20, + 'abc%=neovim%=def', 'abc~~~~neovim~~~~def') + statusline_test_align('should handle uneven spacing in the buffer when using %=text%=', 20, + 'abc%=neo_vim%=def', 'abc~~~neo_vim~~~~def') + statusline_test_align('should have equal spaces even with non-equal sides when using =', 20, + 'foobar%=test%=baz', 'foobar~~~test~~~~baz') + statusline_test_align('should have equal spaces even with longer right side when using =', 20, + 'a%=test%=longtext', 'a~~~test~~~~longtext') + statusline_test_align('should handle an empty left side when using ==', 20, + '%=test%=baz', '~~~~~~test~~~~~~~baz') + statusline_test_align('should handle an empty right side when using ==', 20, + 'foobar%=test%=', 'foobar~~~~~test~~~~~') + statusline_test_align('should handle consecutive empty ==', 20, + '%=%=test%=', '~~~~~~~~~~test~~~~~~') + statusline_test_align('should handle an = alone', 20, + '%=', '~~~~~~~~~~~~~~~~~~~~') + statusline_test_align('should right align text when it is alone with =', 20, + '%=foo', '~~~~~~~~~~~~~~~~~foo') + statusline_test_align('should left align text when it is alone with =', 20, + 'foo%=', 'foo~~~~~~~~~~~~~~~~~') + + statusline_test_align('should approximately center text when using %=text%=', 21, + 'abc%=neovim%=def', 'abc~~~~neovim~~~~~def') + statusline_test_align('should completely fill the buffer when using %=text%=', 21, + 'abc%=neo_vim%=def', 'abc~~~~neo_vim~~~~def') + statusline_test_align('should have equal spacing even with non-equal sides when using =', 21, + 'foobar%=test%=baz', 'foobar~~~~test~~~~baz') + statusline_test_align('should have equal spacing even with longer right side when using =', 21, + 'a%=test%=longtext', 'a~~~~test~~~~longtext') + statusline_test_align('should handle an empty left side when using ==', 21, + '%=test%=baz', '~~~~~~~test~~~~~~~baz') + statusline_test_align('should handle an empty right side when using ==', 21, + 'foobar%=test%=', 'foobar~~~~~test~~~~~~') + + statusline_test_align('should quadrant the text when using 3 %=', 40, + 'abcd%=n%=eovim%=ef', 'abcd~~~~~~~~~n~~~~~~~~~eovim~~~~~~~~~~ef') + statusline_test_align('should work well with %t', 40, + '%t%=right_aligned', 'buffer_spec.lua~~~~~~~~~~~~right_aligned', {file_name='test/unit/buffer_spec.lua'}) - statusline_test('should work well with %t and regular text', 40, - 'l%=m_l %t m_r%=r', 'l m_l buffer_spec.lua m_r r', + statusline_test_align('should work well with %t and regular text', 40, + 'l%=m_l %t m_r%=r', 'l~~~~~~~m_l buffer_spec.lua m_r~~~~~~~~r', {file_name='test/unit/buffer_spec.lua'}) - statusline_test('should work well with %=, %t, %L, and %l', 40, - '%t %= %L %= %l', 'buffer_spec.lua 1 0', + statusline_test_align('should work well with %=, %t, %L, and %l', 40, + '%t %= %L %= %l', 'buffer_spec.lua ~~~~~~~~~ 1 ~~~~~~~~~~ 0', {file_name='test/unit/buffer_spec.lua'}) - statusline_test('should quadrant the text when using 3 %=', 41, - 'abcd%=n%=eovim%=ef', 'abcd n eovim ef') - statusline_test('should work well with %t', 41, - '%t%=right_aligned', 'buffer_spec.lua right_aligned', + statusline_test_align('should quadrant the text when using 3 %=', 41, + 'abcd%=n%=eovim%=ef', 'abcd~~~~~~~~~n~~~~~~~~~eovim~~~~~~~~~~~ef') + statusline_test_align('should work well with %t', 41, + '%t%=right_aligned', 'buffer_spec.lua~~~~~~~~~~~~~right_aligned', {file_name='test/unit/buffer_spec.lua'}) - statusline_test('should work well with %t and regular text', 41, - 'l%=m_l %t m_r%=r', 'l m_l buffer_spec.lua m_r r', + statusline_test_align('should work well with %t and regular text', 41, + 'l%=m_l %t m_r%=r', 'l~~~~~~~~m_l buffer_spec.lua m_r~~~~~~~~r', {file_name='test/unit/buffer_spec.lua'}) - statusline_test('should work well with %=, %t, %L, and %l', 41, - '%t %= %L %= %l', 'buffer_spec.lua 1 0', + statusline_test_align('should work well with %=, %t, %L, and %l', 41, + '%t %= %L %= %l', 'buffer_spec.lua ~~~~~~~~~~ 1 ~~~~~~~~~~ 0', {file_name='test/unit/buffer_spec.lua'}) - statusline_test('should work with 10 %=', 50, + statusline_test_align('should work with 10 %=', 50, 'aaaa%=b%=c%=d%=e%=fg%=hi%=jk%=lmnop%=qrstuv%=wxyz', - 'aaaa b c d e fg hi jk lmnop qrstuv wxyz') + 'aaaa~~b~~c~~d~~e~~fg~~hi~~jk~~lmnop~~qrstuv~~~wxyz') -- stl item testing local tabline = '' @@ -452,11 +468,10 @@ describe('buffer functions', function() -- multi-byte testing statusline_test('should handle multibyte characters', 10, - 'Ĉ%=x', 'Ĉ x', - {expected_byte_length=11}) + 'Ĉ%=x', 'Ĉ x') statusline_test('should handle multibyte characters and different fillchars', 10, 'Ą%=mid%=end', 'Ą@mid@@end', - {fillchar=('@'):byte(), expected_byte_length=11}) + {fillchar=('@'):byte()}) -- escaping % testing statusline_test('should handle escape of %', 4, 'abc%%', 'abc%') diff --git a/test/unit/fixtures/rbuffer.c b/test/unit/fixtures/rbuffer.c index 3f4062fa18..efa7ab1986 100644 --- a/test/unit/fixtures/rbuffer.c +++ b/test/unit/fixtures/rbuffer.c @@ -15,7 +15,7 @@ void ut_rbuffer_each_read_chunk(RBuffer *buf, each_ptr_cb cb) void ut_rbuffer_each_write_chunk(RBuffer *buf, each_ptr_cb cb) { - RBUFFER_UNTIL_FULL(buf, wptr, wcnt) { + RBUFFER_UNTIL_FULL(buf, wptr, wcnt) { // -V1044 cb(wptr, wcnt); rbuffer_produced(buf, wcnt); } diff --git a/test/unit/helpers.lua b/test/unit/helpers.lua index 465b553693..29ea0235be 100644 --- a/test/unit/helpers.lua +++ b/test/unit/helpers.lua @@ -2,7 +2,7 @@ local ffi = require('ffi') local formatc = require('test.unit.formatc') local Set = require('test.unit.set') local Preprocess = require('test.unit.preprocess') -local Paths = require('test.config.paths') +local Paths = require('test.cmakeconfig.paths') local global_helpers = require('test.helpers') local assert = require('luassert') local say = require('say') @@ -96,6 +96,7 @@ local init = only_separate(function() c.func(unpack(c.args)) end libnvim.time_init() + libnvim.fs_init() libnvim.event_init() libnvim.early_init(nil) if child_calls_mod then @@ -778,7 +779,8 @@ local function cppimport(path) return cimport(Paths.test_source_path .. '/test/includes/pre/' .. path) end -cimport('./src/nvim/types.h', './src/nvim/main.h', './src/nvim/os/time.h') +cimport('./src/nvim/types.h', './src/nvim/main.h', './src/nvim/os/time.h', + './src/nvim/os/fs.h') local function conv_enum(etab, eval) local n = tonumber(eval) diff --git a/test/unit/keymap_spec.lua b/test/unit/keycodes_spec.lua index 595a19eb17..5bf27c9232 100644 --- a/test/unit/keymap_spec.lua +++ b/test/unit/keycodes_spec.lua @@ -5,9 +5,10 @@ local ffi = helpers.ffi local eq = helpers.eq local neq = helpers.neq -local keymap = helpers.cimport("./src/nvim/keymap.h") +local keymap = helpers.cimport('./src/nvim/keycodes.h') +local NULL = helpers.NULL -describe('keymap.c', function() +describe('keycodes.c', function() describe('find_special_key()', function() local srcp = ffi.new('const unsigned char *[1]') @@ -15,12 +16,12 @@ describe('keymap.c', function() itp('no keycode', function() srcp[0] = 'abc' - eq(0, keymap.find_special_key(srcp, 3, modp, false, false, false)) + eq(0, keymap.find_special_key(srcp, 3, modp, 0, NULL)) end) itp('keycode with multiple modifiers', function() srcp[0] = '<C-M-S-A>' - neq(0, keymap.find_special_key(srcp, 9, modp, false, false, false)) + neq(0, keymap.find_special_key(srcp, 9, modp, 0, NULL)) neq(0, modp[0]) end) @@ -28,22 +29,22 @@ describe('keymap.c', function() -- Compare other capitalizations to this. srcp[0] = '<C-A>' local all_caps_key = - keymap.find_special_key(srcp, 5, modp, false, false, false) + keymap.find_special_key(srcp, 5, modp, 0, NULL) local all_caps_mod = modp[0] srcp[0] = '<C-a>' eq(all_caps_key, - keymap.find_special_key(srcp, 5, modp, false, false, false)) + keymap.find_special_key(srcp, 5, modp, 0, NULL)) eq(all_caps_mod, modp[0]) srcp[0] = '<c-A>' eq(all_caps_key, - keymap.find_special_key(srcp, 5, modp, false, false, false)) + keymap.find_special_key(srcp, 5, modp, 0, NULL)) eq(all_caps_mod, modp[0]) srcp[0] = '<c-a>' eq(all_caps_key, - keymap.find_special_key(srcp, 5, modp, false, false, false)) + keymap.find_special_key(srcp, 5, modp, 0, NULL)) eq(all_caps_mod, modp[0]) end) @@ -51,20 +52,20 @@ describe('keymap.c', function() -- Unescaped with in_string=false srcp[0] = '<C-">' eq(string.byte('"'), - keymap.find_special_key(srcp, 5, modp, false, false, false)) + keymap.find_special_key(srcp, 5, modp, 0, NULL)) -- Unescaped with in_string=true - eq(0, keymap.find_special_key(srcp, 5, modp, false, false, true)) + eq(0, keymap.find_special_key(srcp, 5, modp, keymap.FSK_IN_STRING, NULL)) -- Escaped with in_string=false srcp[0] = '<C-\\">' -- Should fail because the key is invalid -- (more than 1 non-modifier character). - eq(0, keymap.find_special_key(srcp, 6, modp, false, false, false)) + eq(0, keymap.find_special_key(srcp, 6, modp, 0, NULL)) -- Escaped with in_string=true eq(string.byte('"'), - keymap.find_special_key(srcp, 6, modp, false, false, true)) + keymap.find_special_key(srcp, 6, modp, keymap.FSK_IN_STRING, NULL)) end) end) diff --git a/test/unit/marktree_spec.lua b/test/unit/marktree_spec.lua index 10d02d2eb4..3c96bc5f58 100644 --- a/test/unit/marktree_spec.lua +++ b/test/unit/marktree_spec.lua @@ -32,11 +32,11 @@ local function shadoworder(tree, shadow, iter, giveorder) local mark = lib.marktree_itr_current(iter) local id = tonumber(mark.id) local spos = shadow[id] - if (mark.row ~= spos[1] or mark.col ~= spos[2]) then - error("invalid pos for "..id..":("..mark.row..", "..mark.col..") instead of ("..spos[1]..", "..spos[2]..")") + if (mark.pos.row ~= spos[1] or mark.pos.col ~= spos[2]) then + error("invalid pos for "..id..":("..mark.pos.row..", "..mark.pos.col..") instead of ("..spos[1]..", "..spos[2]..")") end - if mark.right_gravity ~= spos[3] then - error("invalid gravity for "..id..":("..mark.row..", "..mark.col..")") + if lib.mt_right_test(mark) ~= spos[3] then + error("invalid gravity for "..id..":("..mark.pos.row..", "..mark.pos.col..")") end if count > 0 then if not pos_leq(last, spos) then @@ -87,7 +87,21 @@ local function dosplice(tree, shadow, start, old_extent, new_extent) shadowsplice(shadow, start, old_extent, new_extent) end +local last_id = nil + +local function put(tree, row, col, gravitate) + last_id = last_id + 1 + local my_id = last_id + + lib.marktree_put_test(tree, my_id, row, col, gravitate); + return my_id +end + describe('marktree', function() + before_each(function() + last_id = 0 + end) + itp('works', function() local tree = ffi.new("MarkTree[1]") -- zero initialized by luajit local shadow = {} @@ -97,7 +111,7 @@ describe('marktree', function() for i = 1,100 do for j = 1,100 do local gravitate = (i%2) > 0 - local id = tonumber(lib.marktree_put(tree, j, i, gravitate, 0)) + local id = put(tree, j, i, gravitate) ok(id > 0) eq(nil, shadow[id]) shadow[id] = {j,i,gravitate} @@ -115,12 +129,12 @@ describe('marktree', function() eq({}, id2pos) for i,ipos in pairs(shadow) do - local pos = lib.marktree_lookup(tree, i, iter) - eq(ipos[1], pos.row) - eq(ipos[2], pos.col) + local p = lib.marktree_lookup_ns(tree, -1, i, false, iter) + eq(ipos[1], p.pos.row) + eq(ipos[2], p.pos.col) local k = lib.marktree_itr_current(iter) - eq(ipos[1], k.row) - eq(ipos[2], k.col, ipos[1]) + eq(ipos[1], k.pos.row) + eq(ipos[2], k.pos.col, ipos[1]) lib.marktree_itr_next(tree, iter) -- TODO(bfredl): use id2pos to check neighbour? -- local k2 = lib.marktree_itr_current(iter) @@ -130,8 +144,8 @@ describe('marktree', function() lib.marktree_itr_get(tree, ipos[1], ipos[2], iter) local k = lib.marktree_itr_current(iter) eq(i, tonumber(k.id)) - eq(ipos[1], k.row) - eq(ipos[2], k.col) + eq(ipos[1], k.pos.row) + eq(ipos[2], k.pos.col) end ok(lib.marktree_itr_first(tree, iter)) @@ -146,8 +160,8 @@ describe('marktree', function() lib.marktree_itr_get(tree, i, 50+ci, iter) local k = lib.marktree_itr_current(iter) local id = tonumber(k.id) - eq(shadow[id][1], k.row) - eq(shadow[id][2], k.col) + eq(shadow[id][1], k.pos.row) + eq(shadow[id][2], k.pos.col) lib.marktree_del_itr(tree, iter, false) shadow[id] = nil end @@ -191,7 +205,7 @@ describe('marktree', function() -- https://github.com/neovim/neovim/pull/14719 lib.marktree_clear(tree) for i = 1,20 do - lib.marktree_put(tree, i, i, false, 0) + put(tree, i, i, false) end lib.marktree_itr_get(tree, 10, 10, iter) diff --git a/test/unit/os/env_spec.lua b/test/unit/os/env_spec.lua index a0e02b6624..71177f4c65 100644 --- a/test/unit/os/env_spec.lua +++ b/test/unit/os/env_spec.lua @@ -154,7 +154,7 @@ describe('env.c', function() local value = 'TESTVALUE' os_setenv(name, value, 1) eq(OK, os_unsetenv(name)) - neq(os_getenv(name), value) + neq(value, os_getenv(name)) -- Depending on the platform the var might be unset or set as '' assert.True(os_getenv(name) == nil or os_getenv(name) == '') if os_getenv(name) == nil then @@ -266,7 +266,7 @@ describe('env.c', function() itp('does not crash #3725', function() local name_out = ffi.new('char[100]') - cimp.os_get_user_name(name_out, 100) + cimp.os_get_username(name_out, 100) local curuser = ffi.string(name_out) local src = to_cstr("~"..curuser.."/Vcs/django-rest-framework/rest_framework/renderers.py") diff --git a/test/unit/os/users_spec.lua b/test/unit/os/users_spec.lua index f92413c7de..679e76fae1 100644 --- a/test/unit/os/users_spec.lua +++ b/test/unit/os/users_spec.lua @@ -48,10 +48,10 @@ describe('users function', function() end) end) - describe('os_get_user_name', function() + describe('os_get_username', function() itp('should write the username into the buffer and return OK', function() local name_out = ffi.new('char[100]') - eq(OK, users.os_get_user_name(name_out, 100)) + eq(OK, users.os_get_username(name_out, 100)) eq(current_username, ffi.string(name_out)) end) end) @@ -73,18 +73,18 @@ describe('users function', function() end) end) - describe('os_get_user_directory', function() + describe('os_get_userdir', function() itp('should return NULL if called with NULL', function() - eq(NULL, users.os_get_user_directory(NULL)) + eq(NULL, users.os_get_userdir(NULL)) end) itp('should return $HOME for the current user', function() local home = os.getenv('HOME') - eq(home, ffi.string((users.os_get_user_directory(current_username)))) + eq(home, ffi.string((users.os_get_userdir(current_username)))) end) itp('should return NULL if the user is not found', function() - eq(NULL, users.os_get_user_directory('neovim_user_not_found_test')) + eq(NULL, users.os_get_userdir('neovim_user_not_found_test')) end) end) end) diff --git a/test/unit/path_spec.lua b/test/unit/path_spec.lua index 15ce59747e..fb476397e6 100644 --- a/test/unit/path_spec.lua +++ b/test/unit/path_spec.lua @@ -626,4 +626,20 @@ describe('path.c', function() eq(false, path_with_extension('/some/path/file', 'lua')) end) end) + + describe('path_with_url', function() + itp('scheme is alpha and inner hyphen only', function() + local function path_with_url(fname) + return cimp.path_with_url(to_cstr(fname)) + end + eq(1, path_with_url([[test://xyz/foo/b0]])) + eq(2, path_with_url([[test:\\xyz\foo\b0]])) + eq(0, path_with_url([[test+abc://xyz/foo/b1]])) + eq(0, path_with_url([[test_abc://xyz/foo/b2]])) + eq(1, path_with_url([[test-abc://xyz/foo/b3]])) + eq(2, path_with_url([[test-abc:\\xyz\foo\b3]])) + eq(0, path_with_url([[-test://xyz/foo/b4]])) + eq(0, path_with_url([[test-://xyz/foo/b5]])) + end) + end) end) diff --git a/test/unit/search_spec.lua b/test/unit/search_spec.lua index 3c2d485e0e..ce37ebfc3a 100644 --- a/test/unit/search_spec.lua +++ b/test/unit/search_spec.lua @@ -5,6 +5,8 @@ local to_cstr = helpers.to_cstr local eq = helpers.eq local search = helpers.cimport("./src/nvim/search.h") +local globals = helpers.cimport('./src/nvim/globals.h') +local ffi = helpers.ffi itp('pat_has_uppercase', function() -- works on empty string @@ -31,3 +33,25 @@ itp('pat_has_uppercase', function() eq(false, search.pat_has_uppercase(to_cstr("aa\\%Ab"))) eq(true, search.pat_has_uppercase(to_cstr("aab\\%AU"))) end) + +describe('search_regcomp', function() + local search_regcomp = function(pat, pat_save, pat_use, options ) + local regmatch = ffi.new("regmmatch_T") + local fail = search.search_regcomp(to_cstr(pat), pat_save, pat_use, options, regmatch) + return fail, regmatch + end + + local get_search_pat = function() + return helpers.internalize(search.get_search_pat()) + end + + itp("accepts regexp pattern with invalid utf", function() + --crafted to call reverse_text with invalid utf + globals.curwin.w_onebuf_opt.wo_rl = 1 + globals.curwin.w_onebuf_opt.wo_rlc = to_cstr('s') + globals.cmdmod.cmod_flags = globals.CMOD_KEEPPATTERNS + local fail = search_regcomp("a\192", 0,0,0) + eq(1, fail) + eq("\192a", get_search_pat()) + end) +end) diff --git a/test/unit/strings_spec.lua b/test/unit/strings_spec.lua index e54c82b26a..b2c839f25c 100644 --- a/test/unit/strings_spec.lua +++ b/test/unit/strings_spec.lua @@ -138,3 +138,50 @@ describe('vim_strchr()', function() eq(nil, vim_strchr('«\237\175\191\237\188\128»', 0x10FF00)) end) end) + +describe('strcase_save()' , function() + local strcase_save = function(input_string, upper) + local res = strings.strcase_save(to_cstr(input_string), upper) + return ffi.string(res) + end + + itp('decodes overlong encoded characters.', function() + eq("A", strcase_save("\xc1\x81", true)) + eq("a", strcase_save("\xc1\x81", false)) + end) +end) + +describe("reverse_text", function() + local reverse_text = function(str) + return helpers.internalize(strings.reverse_text(to_cstr(str))) + end + + itp("handles empty string", function() + eq("", reverse_text("")) + end) + + itp("handles simple cases", function() + eq("a", reverse_text("a")) + eq("ba", reverse_text("ab")) + end) + + itp("handles multibyte characters", function() + eq("bα", reverse_text("αb")) + eq("Yötön yö", reverse_text("öy nötöY")) + end) + + itp("handles combining chars", function() + local utf8_COMBINING_RING_ABOVE = "\204\138" + local utf8_COMBINING_RING_BELOW = "\204\165" + eq("bba" .. utf8_COMBINING_RING_ABOVE .. utf8_COMBINING_RING_BELOW .. "aa", + reverse_text("aaa" .. utf8_COMBINING_RING_ABOVE .. utf8_COMBINING_RING_BELOW .. "bb")) + end) + + itp("treats invalid utf as separate characters", function() + eq("\192ba", reverse_text("ab\192")) + end) + + itp("treats an incomplete utf continuation sequence as valid", function() + eq("\194ba", reverse_text("ab\194")) + end) +end) diff --git a/test/unit/tempfile_spec.lua b/test/unit/tempfile_spec.lua index c05abfd640..44bd19c1d2 100644 --- a/test/unit/tempfile_spec.lua +++ b/test/unit/tempfile_spec.lua @@ -24,7 +24,7 @@ describe('tempfile related functions', function() end describe('vim_gettempdir', function() - itp('returns path to Neovim own temp directory', function() + itp('returns path to Nvim own temp directory', function() local dir = vim_gettempdir() assert.True(dir ~= nil and dir:len() > 0) -- os_file_is_writable returns 2 for a directory which we have rights @@ -36,9 +36,7 @@ describe('tempfile related functions', function() end) itp('returns the same directory on each call', function() - local dir1 = vim_gettempdir() - local dir2 = vim_gettempdir() - eq(dir1, dir2) + eq(vim_gettempdir(), vim_gettempdir()) end) end) @@ -54,12 +52,10 @@ describe('tempfile related functions', function() end) itp('generate different names on each call', function() - local fst = vim_tempname() - local snd = vim_tempname() - neq(fst, snd) + neq(vim_tempname(), vim_tempname()) end) - itp('generate file name in Neovim own temp directory', function() + itp('generate file name in Nvim own temp directory', function() local dir = vim_gettempdir() local file = vim_tempname() eq(string.sub(file, 1, string.len(dir)), dir) diff --git a/test/unit/viml/expressions/parser_spec.lua b/test/unit/viml/expressions/parser_spec.lua index 8342044b5e..51a703b593 100644 --- a/test/unit/viml/expressions/parser_spec.lua +++ b/test/unit/viml/expressions/parser_spec.lua @@ -48,6 +48,7 @@ local predefined_hl_defs = { TermCursor=true, VertSplit=true, WildMenu=true, + WinSeparator=true, EndOfBuffer=true, QuickFixLine=true, Substitute=true, |