diff options
author | Josh Rahm <joshuarahm@gmail.com> | 2025-02-05 23:09:29 +0000 |
---|---|---|
committer | Josh Rahm <joshuarahm@gmail.com> | 2025-02-05 23:09:29 +0000 |
commit | d5f194ce780c95821a855aca3c19426576d28ae0 (patch) | |
tree | d45f461b19f9118ad2bb1f440a7a08973ad18832 /test | |
parent | c5d770d311841ea5230426cc4c868e8db27300a8 (diff) | |
parent | 44740e561fc93afe3ebecfd3618bda2d2abeafb0 (diff) | |
download | rneovim-rahm.tar.gz rneovim-rahm.tar.bz2 rneovim-rahm.zip |
Diffstat (limited to 'test')
173 files changed, 13194 insertions, 3023 deletions
diff --git a/test/README.md b/test/README.md index d1b053fca3..5b225980a2 100644 --- a/test/README.md +++ b/test/README.md @@ -138,6 +138,163 @@ Debugging tests Then put `screen:snapshot_util()` anywhere in your test. See the comments in `test/functional/ui/screen.lua` for more info. +Debugging Lua test code +----------------------- + +Debugging Lua test code is a bit involved. Get your shopping list ready, you'll +need to install and configure: + +1. [nvim-dap](https://github.com/mfussenegger/nvim-dap) +2. [local-lua-debugger-vscode](https://github.com/mfussenegger/nvim-dap/wiki/Debug-Adapter-installation#local-lua-debugger-vscode) +3. [nlua](https://github.com/mfussenegger/nlua) +4. [one-small-step-for-vimkind](https://github.com/jbyuki/one-small-step-for-vimkind) (called `osv`) +5. A `nbusted` command in `$PATH`. This command can be a copy of `busted` with + `exec '/usr/bin/lua5.1'"` replaced with `"exec '/usr/bin/nlua'"` (or the + path to your `nlua`) + + +The setup roughly looks like this: + +``` + ┌─────────────────────────┐ + │ nvim used for debugging │◄────┐ + └─────────────────────────┘ │ + │ │ + ▼ │ + ┌─────────────────┐ │ + │ local-lua-debug │ │ + └─────────────────┘ │ + │ │ + ▼ │ + ┌─────────┐ │ + │ nbusted │ │ + └─────────┘ │ + │ │ + ▼ │ + ┌───────────┐ │ + │ test-case │ │ + └───────────┘ │ + │ │ + ▼ │ + ┌────────────────────┐ │ + │ nvim test-instance │ │ + └────────────────────┘ │ + │ ┌─────┐ │ + └──►│ osv │─────────────────┘ + └─────┘ +``` + + +With these installed you can use a configuration like this: + + +```lua +local dap = require("dap") + + +local function free_port() + local tcp = vim.loop.new_tcp() + assert(tcp) + tcp:bind('127.0.0.1', 0) + local port = tcp:getsockname().port + tcp:shutdown() + tcp:close() + return port +end + + +local name = "nvim-test-case" -- arbitrary name +local config = { + name = name, + + -- value of type must match the key used in `dap.adapters["local-lua"] = ...` from step 2) + type = "local-lua", + + request = "launch", + cwd = "${workspaceFolder}", + program = { + command = "nbusted", + }, + args = { + "--ignore-lua", + "--lazy", + "--helper=test/functional/preload.lua", + "--lpath=build/?.lua", + "--lpath=?.lua", + + -- path to file to debug, could be replaced with a hardcoded string + function() + return vim.api.nvim_buf_get_name(0) + end, + + -- You can filter to specific test-case by adding: + -- '--filter="' .. test_case_name .. '"', + }, + env = { + OSV_PORT = free_port + } +} + +-- Whenever the config is used it needs to launch a second debug session that attaches to `osv` +-- This makes it possible to step into `exec_lua` code blocks +setmetatable(config, { + + __call = function(c) + ---@param session dap.Session + dap.listeners.after.event_initialized["nvim_debug"] = function(session) + if session.config.name ~= name then + return + end + dap.listeners.after.event_initialized["nvim_debug"] = nil + vim.defer_fn(function() + dap.run({ + name = "attach-osv", + type = "nlua", -- value must match the `dap.adapters` definition key for osv + request = "attach", + port = session.config.env.OSV_PORT, + }) + end, 500) + end + + return c + end, +}) + +``` + +You can either add this configuration to your `dap.configurations.lua` list as +described in `:help dap-configuration` or create it dynamically in a +user-command or function and call it directly via `dap.run(config)`. The latter +is useful if you use tree-sitter to find the test case around a cursor location +with a query like the following and set the `--filter` property to it. + +```query +(function_call + name: (identifier) @name (#any-of? @name "describe" "it") + arguments: (arguments + (string) @str + ) +) +``` + +Limitations: + +- You need to add the following boilerplate to each spec file where you want to + be able to stop at breakpoints within the test-case code: + +``` +if os.getenv("LOCAL_LUA_DEBUGGER_VSCODE") == "1" then + require("lldebugger").start() +end +``` + +This is a [local-lua-debugger +limitation](https://github.com/tomblind/local-lua-debugger-vscode?tab=readme-ov-file#busted) + +- You cannot step into code of files which get baked into the nvim binary like + the `shared.lua`. + + Filtering Tests --------------- @@ -379,3 +536,6 @@ Number; !must be defined to function properly): - `NVIM_TEST_MAXTRACE` (U) (N): specifies maximum number of trace lines to keep. Default is 1024. + +- `OSV_PORT`: (F): launches `osv` listening on the given port within nvim test + instances. diff --git a/test/benchmark/decor_spec.lua b/test/benchmark/decor_spec.lua new file mode 100644 index 0000000000..1b7e763a09 --- /dev/null +++ b/test/benchmark/decor_spec.lua @@ -0,0 +1,140 @@ +local n = require('test.functional.testnvim')() +local Screen = require('test.functional.ui.screen') +local exec_lua = n.exec_lua + +describe('decor perf', function() + before_each(n.clear) + + it('can handle long lines', function() + Screen.new(100, 101) + + local result = exec_lua [==[ + local ephemeral_pattern = { + { 0, 4, 'Comment', 11 }, + { 0, 3, 'Keyword', 12 }, + { 1, 2, 'Label', 12 }, + { 0, 1, 'String', 21 }, + { 1, 3, 'Function', 21 }, + { 2, 10, 'Label', 8 }, + } + + local regular_pattern = { + { 4, 5, 'String', 12 }, + { 1, 4, 'Function', 2 }, + } + + for _, list in ipairs({ ephemeral_pattern, regular_pattern }) do + for _, p in ipairs(list) do + p[3] = vim.api.nvim_get_hl_id_by_name(p[3]) + end + end + + local text = ('abcdefghijklmnopqrstuvwxyz0123'):rep(333) + local line_len = #text + vim.api.nvim_buf_set_lines(0, 0, 0, false, { text }) + + local ns = vim.api.nvim_create_namespace('decor_spec.lua') + vim.api.nvim_buf_clear_namespace(0, ns, 0, -1) + vim.api.nvim_win_set_cursor(0, { 1, 0 }) + + local ps, pe + local function add_pattern(pattern, ephemeral) + ps = vim.uv.hrtime() + local i = 0 + while i < line_len - 10 do + for _, p in ipairs(pattern) do + vim.api.nvim_buf_set_extmark(0, ns, 0, i + p[1], { + end_row = 0, + end_col = i + p[2], + hl_group = p[3], + priority = p[4], + ephemeral = ephemeral, + }) + end + i = i + 5 + end + pe = vim.uv.hrtime() + end + + vim.api.nvim_set_decoration_provider(ns, { + on_win = function() + return true + end, + on_line = function() + add_pattern(ephemeral_pattern, true) + end, + }) + + add_pattern(regular_pattern, false) + + local total = {} + local provider = {} + for i = 1, 100 do + local tic = vim.uv.hrtime() + vim.cmd'redraw!' + local toc = vim.uv.hrtime() + table.insert(total, toc - tic) + table.insert(provider, pe - ps) + end + + return { total, provider } + ]==] + + local total, provider = unpack(result) + table.sort(total) + table.sort(provider) + + local ms = 1 / 1000000 + local function fmt(stats) + return string.format( + 'min, 25%%, median, 75%%, max:\n\t%0.1fms,\t%0.1fms,\t%0.1fms,\t%0.1fms,\t%0.1fms', + stats[1] * ms, + stats[1 + math.floor(#stats * 0.25)] * ms, + stats[1 + math.floor(#stats * 0.5)] * ms, + stats[1 + math.floor(#stats * 0.75)] * ms, + stats[#stats] * ms + ) + end + + print('\nTotal ' .. fmt(total) .. '\nDecoration provider: ' .. fmt(provider)) + end) + + it('can handle full screen of highlighting', function() + Screen.new(100, 51) + + local result = exec_lua(function() + local long_line = 'local a={' .. ('a=5,'):rep(22) .. '}' + local lines = {} + for _ = 1, 50 do + table.insert(lines, long_line) + end + vim.api.nvim_buf_set_lines(0, 0, 0, false, lines) + vim.api.nvim_win_set_cursor(0, { 1, 0 }) + vim.treesitter.start(0, 'lua') + + local total = {} + for _ = 1, 100 do + local tic = vim.uv.hrtime() + vim.cmd 'redraw!' + local toc = vim.uv.hrtime() + table.insert(total, toc - tic) + end + + return { total } + end) + + local total = unpack(result) + table.sort(total) + + local ms = 1 / 1000000 + local res = string.format( + 'min, 25%%, median, 75%%, max:\n\t%0.1fms,\t%0.1fms,\t%0.1fms,\t%0.1fms,\t%0.1fms', + total[1] * ms, + total[1 + math.floor(#total * 0.25)] * ms, + total[1 + math.floor(#total * 0.5)] * ms, + total[1 + math.floor(#total * 0.75)] * ms, + total[#total] * ms + ) + print('\nTotal ' .. res) + end) +end) diff --git a/test/benchmark/text_spec.lua b/test/benchmark/text_spec.lua new file mode 100644 index 0000000000..9cfeaf765b --- /dev/null +++ b/test/benchmark/text_spec.lua @@ -0,0 +1,52 @@ +describe('vim.text', function() + --- @param t number[] + local function mean(t) + assert(#t > 0) + local sum = 0 + for _, v in ipairs(t) do + sum = sum + v + end + return sum / #t + end + + --- @param t number[] + local function median(t) + local len = #t + if len % 2 == 0 then + return t[len / 2] + end + return t[(len + 1) / 2] + end + + --- @param f fun(t: number[]): table<number, number|string|table> + local function measure(f, input, N) + local stats = {} ---@type number[] + for _ = 1, N do + local tic = vim.uv.hrtime() + f(input) + local toc = vim.uv.hrtime() + stats[#stats + 1] = (toc - tic) / 1000000 + end + table.sort(stats) + print( + string.format( + '\nN: %d, Min: %0.6f ms, Max: %0.6f ms, Median: %0.6f ms, Mean: %0.6f ms', + N, + math.min(unpack(stats)), + math.max(unpack(stats)), + median(stats), + mean(stats) + ) + ) + end + + local input, output = string.rep('😂', 2 ^ 16), string.rep('F09F9882', 2 ^ 16) + + it('hexencode', function() + measure(vim.text.hexencode, input, 100) + end) + + it('hexdecode', function() + measure(vim.text.hexdecode, output, 100) + end) +end) diff --git a/test/client/msgpack_rpc_stream.lua b/test/client/rpc_stream.lua index 7131940a58..9f2672bcf9 100644 --- a/test/client/msgpack_rpc_stream.lua +++ b/test/client/rpc_stream.lua @@ -1,34 +1,41 @@ +--- +--- Reading/writing of msgpack over any of the stream types from `uv_stream.lua`. +--- Does not implement the RPC protocol, see `session.lua` for that. +--- + local mpack = vim.mpack local Response = {} Response.__index = Response -function Response.new(msgpack_rpc_stream, request_id) +function Response.new(rpc_stream, request_id) return setmetatable({ - _msgpack_rpc_stream = msgpack_rpc_stream, + _rpc_stream = rpc_stream, _request_id = request_id, }, Response) end function Response:send(value, is_error) - local data = self._msgpack_rpc_stream._session:reply(self._request_id) + local data = self._rpc_stream._session:reply(self._request_id) if is_error then - data = data .. self._msgpack_rpc_stream._pack(value) - data = data .. self._msgpack_rpc_stream._pack(mpack.NIL) + data = data .. self._rpc_stream._pack(value) + data = data .. self._rpc_stream._pack(mpack.NIL) else - data = data .. self._msgpack_rpc_stream._pack(mpack.NIL) - data = data .. self._msgpack_rpc_stream._pack(value) + data = data .. self._rpc_stream._pack(mpack.NIL) + data = data .. self._rpc_stream._pack(value) end - self._msgpack_rpc_stream._stream:write(data) + self._rpc_stream._stream:write(data) end ---- @class test.MsgpackRpcStream +--- Nvim msgpack RPC stream. +--- +--- @class test.RpcStream --- @field private _stream test.Stream --- @field private __pack table -local MsgpackRpcStream = {} -MsgpackRpcStream.__index = MsgpackRpcStream +local RpcStream = {} +RpcStream.__index = RpcStream -function MsgpackRpcStream.new(stream) +function RpcStream.new(stream) return setmetatable({ _stream = stream, _pack = mpack.Packer(), @@ -50,10 +57,10 @@ function MsgpackRpcStream.new(stream) }, }), }), - }, MsgpackRpcStream) + }, RpcStream) end -function MsgpackRpcStream:write(method, args, response_cb) +function RpcStream:write(method, args, response_cb) local data if response_cb then assert(type(response_cb) == 'function') @@ -66,10 +73,10 @@ function MsgpackRpcStream:write(method, args, response_cb) self._stream:write(data) end -function MsgpackRpcStream:read_start(request_cb, notification_cb, eof_cb) +function RpcStream:read_start(on_request, on_notification, on_eof) self._stream:read_start(function(data) if not data then - return eof_cb() + return on_eof() end local type, id_or_cb, method_or_error, args_or_result local pos = 1 @@ -78,9 +85,9 @@ function MsgpackRpcStream:read_start(request_cb, notification_cb, eof_cb) type, id_or_cb, method_or_error, args_or_result, pos = self._session:receive(data, pos) if type == 'request' or type == 'notification' then if type == 'request' then - request_cb(method_or_error, args_or_result, Response.new(self, id_or_cb)) + on_request(method_or_error, args_or_result, Response.new(self, id_or_cb)) else - notification_cb(method_or_error, args_or_result) + on_notification(method_or_error, args_or_result) end elseif type == 'response' then if method_or_error == mpack.NIL then @@ -94,12 +101,12 @@ function MsgpackRpcStream:read_start(request_cb, notification_cb, eof_cb) end) end -function MsgpackRpcStream:read_stop() +function RpcStream:read_stop() self._stream:read_stop() end -function MsgpackRpcStream:close(signal) +function RpcStream:close(signal) self._stream:close(signal) end -return MsgpackRpcStream +return RpcStream diff --git a/test/client/session.lua b/test/client/session.lua index f1f46c5efe..a5839e012a 100644 --- a/test/client/session.lua +++ b/test/client/session.lua @@ -1,12 +1,18 @@ +--- +--- Nvim msgpack-RPC protocol session. Manages requests/notifications/responses. +--- + local uv = vim.uv -local MsgpackRpcStream = require('test.client.msgpack_rpc_stream') +local RpcStream = require('test.client.rpc_stream') +--- Nvim msgpack-RPC protocol session. Manages requests/notifications/responses. +--- --- @class test.Session ---- @field private _pending_messages string[] ---- @field private _msgpack_rpc_stream test.MsgpackRpcStream +--- @field private _pending_messages string[] Requests/notifications received from the remote end. +--- @field private _rpc_stream test.RpcStream --- @field private _prepare uv.uv_prepare_t --- @field private _timer uv.uv_timer_t ---- @field private _is_running boolean +--- @field private _is_running boolean true during `Session:run()` scope. --- @field exec_lua_setup boolean local Session = {} Session.__index = Session @@ -51,9 +57,10 @@ local function coroutine_exec(func, ...) end)) end +--- Creates a new msgpack-RPC session. function Session.new(stream) return setmetatable({ - _msgpack_rpc_stream = MsgpackRpcStream.new(stream), + _rpc_stream = RpcStream.new(stream), _pending_messages = {}, _prepare = uv.new_prepare(), _timer = uv.new_timer(), @@ -91,10 +98,13 @@ function Session:next_message(timeout) return table.remove(self._pending_messages, 1) end +--- Sends a notification to the RPC endpoint. function Session:notify(method, ...) - self._msgpack_rpc_stream:write(method, { ... }) + self._rpc_stream:write(method, { ... }) end +--- Sends a request to the RPC endpoint. +--- --- @param method string --- @param ... any --- @return boolean, table @@ -114,8 +124,16 @@ function Session:request(method, ...) return true, result end ---- Runs the event loop. +--- Processes incoming RPC requests/notifications until exhausted. +--- +--- TODO(justinmk): luaclient2 avoids this via uvutil.cb_wait() + uvutil.add_idle_call()? +--- +--- @param request_cb function Handles requests from the sever to the local end. +--- @param notification_cb function Handles notifications from the sever to the local end. +--- @param setup_cb function +--- @param timeout number function Session:run(request_cb, notification_cb, setup_cb, timeout) + --- Handles an incoming request. local function on_request(method, args, response) coroutine_exec(request_cb, method, args, function(status, result, flag) if status then @@ -126,6 +144,7 @@ function Session:run(request_cb, notification_cb, setup_cb, timeout) end) end + --- Handles an incoming notification. local function on_notification(method, args) coroutine_exec(notification_cb, method, args) end @@ -160,39 +179,45 @@ function Session:close(signal) if not self._prepare:is_closing() then self._prepare:close() end - self._msgpack_rpc_stream:close(signal) + self._rpc_stream:close(signal) self.closed = true end +--- Sends a request to the RPC endpoint, without blocking (schedules a coroutine). function Session:_yielding_request(method, args) return coroutine.yield(function(co) - self._msgpack_rpc_stream:write(method, args, function(err, result) + self._rpc_stream:write(method, args, function(err, result) resume(co, err, result) end) end) end +--- Sends a request to the RPC endpoint, and blocks (polls event loop) until a response is received. function Session:_blocking_request(method, args) local err, result + -- Invoked when a request is received from the remote end. local function on_request(method_, args_, response) table.insert(self._pending_messages, { 'request', method_, args_, response }) end + -- Invoked when a notification is received from the remote end. local function on_notification(method_, args_) table.insert(self._pending_messages, { 'notification', method_, args_ }) end - self._msgpack_rpc_stream:write(method, args, function(e, r) + self._rpc_stream:write(method, args, function(e, r) err = e result = r uv.stop() end) + -- Poll for incoming requests/notifications received from the remote end. self:_run(on_request, on_notification) return (err or self.eof_err), result end +--- Polls for incoming requests/notifications received from the remote end. function Session:_run(request_cb, notification_cb, timeout) if type(timeout) == 'number' then self._prepare:start(function() @@ -202,14 +227,21 @@ function Session:_run(request_cb, notification_cb, timeout) self._prepare:stop() end) end - self._msgpack_rpc_stream:read_start(request_cb, notification_cb, function() + self._rpc_stream:read_start(request_cb, notification_cb, function() uv.stop() - self.eof_err = { 1, 'EOF was received from Nvim. Likely the Nvim process crashed.' } + + --- @diagnostic disable-next-line: invisible + local stderr = self._rpc_stream._stream.stderr --[[@as string?]] + -- See if `ProcStream.stderr` has anything useful. + stderr = '' ~= ((stderr or ''):match('^%s*(.*%S)') or '') and ' stderr:\n' .. stderr or '' + + self.eof_err = { 1, 'EOF was received from Nvim. Likely the Nvim process crashed.' .. stderr } end) uv.run() self._prepare:stop() self._timer:stop() - self._msgpack_rpc_stream:read_stop() + self._rpc_stream:read_stop() end +--- Nvim msgpack-RPC session. return Session diff --git a/test/client/uv_stream.lua b/test/client/uv_stream.lua index adf002ba1e..6e1a6995be 100644 --- a/test/client/uv_stream.lua +++ b/test/client/uv_stream.lua @@ -1,3 +1,8 @@ +--- +--- Basic stream types. +--- See `rpc_stream.lua` for the msgpack layer. +--- + local uv = vim.uv --- @class test.Stream @@ -6,6 +11,8 @@ local uv = vim.uv --- @field read_stop fun(self) --- @field close fun(self, signal?: string) +--- Stream over given pipes. +--- --- @class vim.StdioStream : test.Stream --- @field private _in uv.uv_pipe_t --- @field private _out uv.uv_pipe_t @@ -45,6 +52,8 @@ function StdioStream:close() self._out:close() end +--- Stream over a named pipe or TCP socket. +--- --- @class test.SocketStream : test.Stream --- @field package _stream_error? string --- @field package _socket uv.uv_pipe_t @@ -109,26 +118,54 @@ function SocketStream:close() uv.close(self._socket) end ---- @class test.ChildProcessStream : test.Stream +--- Stream over child process stdio. +--- +--- @class test.ProcStream : test.Stream --- @field private _proc uv.uv_process_t --- @field private _pid integer --- @field private _child_stdin uv.uv_pipe_t --- @field private _child_stdout uv.uv_pipe_t +--- @field private _child_stderr uv.uv_pipe_t +--- Collects stdout (if `collect_text=true`). Treats data as text (CRLF converted to LF). +--- @field stdout string +--- Collects stderr as raw data. +--- @field stderr string +--- Gets stderr+stdout as text (CRLF converted to LF). +--- @field output fun(): string +--- @field stdout_eof boolean +--- @field stderr_eof boolean +--- Collects text into the `stdout` field. +--- @field collect_text boolean +--- Exit code --- @field status integer --- @field signal integer -local ChildProcessStream = {} -ChildProcessStream.__index = ChildProcessStream +local ProcStream = {} +ProcStream.__index = ProcStream +--- Starts child process specified by `argv`. +--- --- @param argv string[] --- @param env string[]? --- @param io_extra uv.uv_pipe_t? ---- @return test.ChildProcessStream -function ChildProcessStream.spawn(argv, env, io_extra) +--- @return test.ProcStream +function ProcStream.spawn(argv, env, io_extra) local self = setmetatable({ - _child_stdin = uv.new_pipe(false), - _child_stdout = uv.new_pipe(false), + collect_text = false, + output = function(self) + if not self.collect_text then + error('set collect_text=true') + end + return (self.stderr .. self.stdout):gsub('\r\n', '\n') + end, + stdout = '', + stderr = '', + stdout_eof = false, + stderr_eof = false, + _child_stdin = assert(uv.new_pipe(false)), + _child_stdout = assert(uv.new_pipe(false)), + _child_stderr = assert(uv.new_pipe(false)), _exiting = false, - }, ChildProcessStream) + }, ProcStream) local prog = argv[1] local args = {} --- @type string[] for i = 2, #argv do @@ -136,13 +173,14 @@ function ChildProcessStream.spawn(argv, env, io_extra) end --- @diagnostic disable-next-line:missing-fields self._proc, self._pid = uv.spawn(prog, { - stdio = { self._child_stdin, self._child_stdout, 1, io_extra }, + stdio = { self._child_stdin, self._child_stdout, self._child_stderr, io_extra }, args = args, --- @diagnostic disable-next-line:assign-type-mismatch env = env, }, function(status, signal) - self.status = status self.signal = signal + -- "Abort" exit may not set status; force to nonzero in that case. + self.status = (0 ~= (status or 0) or 0 == (signal or 0)) and status or (128 + (signal or 0)) end) if not self._proc then @@ -153,24 +191,54 @@ function ChildProcessStream.spawn(argv, env, io_extra) return self end -function ChildProcessStream:write(data) +function ProcStream:write(data) self._child_stdin:write(data) end -function ChildProcessStream:read_start(cb) - self._child_stdout:read_start(function(err, chunk) - if err then - error(err) +function ProcStream:on_read(stream, cb, err, chunk) + if err then + error(err) -- stream read failed? + elseif chunk then + -- Always collect stderr, in case it gives useful info on failure. + if stream == 'stderr' then + self.stderr = self.stderr .. chunk --[[@as string]] + elseif stream == 'stdout' and self.collect_text then + -- Set `stdout` and convert CRLF => LF. + self.stdout = (self.stdout .. chunk):gsub('\r\n', '\n') end + else + -- stderr_eof/stdout_eof + self[stream .. '_eof'] = true ---@type boolean + end + + -- Handler provided by the caller. + if cb then cb(chunk) + end +end + +--- Collects output until the process exits. +function ProcStream:wait() + while not (self.stdout_eof and self.stderr_eof and (self.status or self.signal)) do + uv.run('once') + end +end + +function ProcStream:read_start(on_stdout, on_stderr) + self._child_stdout:read_start(function(err, chunk) + self:on_read('stdout', on_stdout, err, chunk) + end) + self._child_stderr:read_start(function(err, chunk) + self:on_read('stderr', on_stderr, err, chunk) end) end -function ChildProcessStream:read_stop() +function ProcStream:read_stop() self._child_stdout:read_stop() + self._child_stderr:read_stop() end -function ChildProcessStream:close(signal) +function ProcStream:close(signal) if self._closed then return end @@ -178,6 +246,7 @@ function ChildProcessStream:close(signal) self:read_stop() self._child_stdin:close() self._child_stdout:close() + self._child_stderr:close() if type(signal) == 'string' then self._proc:kill('sig' .. signal) end @@ -189,6 +258,6 @@ end return { StdioStream = StdioStream, - ChildProcessStream = ChildProcessStream, + ProcStream = ProcStream, SocketStream = SocketStream, } diff --git a/test/functional/api/autocmd_spec.lua b/test/functional/api/autocmd_spec.lua index 3f9883f43f..64b28e7bf7 100644 --- a/test/functional/api/autocmd_spec.lua +++ b/test/functional/api/autocmd_spec.lua @@ -273,54 +273,72 @@ describe('autocmd api', function() eq({}, api.nvim_get_autocmds({ event = 'User', pattern = 'Test' })) end) - it('receives an args table', function() + local function test_autocmd_args(event) + local function get_amatch(pat) + return event == 'User' and pat or vim.fs.normalize(n.fn.fnamemodify(pat, ':p')) + end + local group_id = api.nvim_create_augroup('TestGroup', {}) -- Having an existing autocmd calling expand("<afile>") shouldn't change args #18964 - api.nvim_create_autocmd('User', { + api.nvim_create_autocmd(event, { group = 'TestGroup', pattern = 'Te*', command = 'call expand("<afile>")', }) - local autocmd_id = exec_lua [[ - return vim.api.nvim_create_autocmd("User", { + local autocmd_id = exec_lua(([[ + return vim.api.nvim_create_autocmd(%q, { group = "TestGroup", pattern = "Te*", callback = function(args) vim.g.autocmd_args = args end, }) - ]] + ]]):format(event)) - api.nvim_exec_autocmds('User', { pattern = 'Test pattern' }) + local exec_pat = 'Test pattern' + local amatch = get_amatch(exec_pat) + api.nvim_exec_autocmds(event, { pattern = exec_pat }) eq({ id = autocmd_id, group = group_id, - event = 'User', - match = 'Test pattern', - file = 'Test pattern', + event = event, + match = amatch, + file = exec_pat, buf = 1, }, api.nvim_get_var('autocmd_args')) -- Test without a group - autocmd_id = exec_lua [[ - return vim.api.nvim_create_autocmd("User", { + autocmd_id = exec_lua(([[ + return vim.api.nvim_create_autocmd(%q, { pattern = "*", callback = function(args) vim.g.autocmd_args = args end, }) - ]] + ]]):format(event)) - api.nvim_exec_autocmds('User', { pattern = 'some_pat' }) + exec_pat = 'some_pat' + amatch = get_amatch(exec_pat) + api.nvim_exec_autocmds(event, { pattern = exec_pat }) eq({ id = autocmd_id, group = nil, - event = 'User', - match = 'some_pat', - file = 'some_pat', + event = event, + match = amatch, + file = exec_pat, buf = 1, }, api.nvim_get_var('autocmd_args')) + end + + describe('receives correct args table', function() + it('for event that takes non-file pattern', function() + test_autocmd_args('User') + end) + + it('for event that takes file pattern', function() + test_autocmd_args('BufEnter') + end) end) it('can receive arbitrary data', function() @@ -881,6 +899,89 @@ describe('autocmd api', function() eq([[:echo "Buffer"]], normalized_aus[1].command) end) end) + + describe('id', function() + it('gets events by ID', function() + local id = api.nvim_create_autocmd('BufEnter', { + command = 'echo "hello"', + }) + eq({ + { + buflocal = false, + command = 'echo "hello"', + event = 'BufEnter', + id = id, + once = false, + pattern = '*', + }, + }, api.nvim_get_autocmds({ id = id })) + end) + + it('gets events by ID by other filters', function() + local group_name = 'NVIM_GET_AUTOCMDS_ID' + local group = api.nvim_create_augroup(group_name, { clear = true }) + local id = api.nvim_create_autocmd('BufEnter', { + command = 'set number', + group = group, + }) + api.nvim_create_autocmd('WinEnter', { + group = group, + command = 'set cot&', + }) + eq({ + { + buflocal = false, + command = 'set number', + event = 'BufEnter', + group = group, + group_name = group_name, + id = id, + once = false, + pattern = '*', + }, + }, api.nvim_get_autocmds({ id = id, group = group })) + end) + + it('gets events by ID and a specific event', function() + local id = api.nvim_create_autocmd('InsertEnter', { command = 'set number' }) + api.nvim_create_autocmd('InsertEnter', { command = 'set wrap' }) + eq({ + { + buflocal = false, + command = 'set number', + event = 'InsertEnter', + id = id, + once = false, + pattern = '*', + }, + }, api.nvim_get_autocmds({ id = id, event = 'InsertEnter' })) + end) + + it('gets events by ID and a specific pattern', function() + local id = api.nvim_create_autocmd('InsertEnter', { + pattern = '*.c', + command = 'set number', + }) + api.nvim_create_autocmd('InsertEnter', { + pattern = '*.c', + command = 'set wrap', + }) + eq({ + { + buflocal = false, + command = 'set number', + event = 'InsertEnter', + id = id, + once = false, + pattern = '*.c', + }, + }, api.nvim_get_autocmds({ id = id, pattern = '*.c' })) + end) + + it('empty result when id does not found', function() + eq({}, api.nvim_get_autocmds({ id = 255 })) + end) + end) end) describe('nvim_exec_autocmds', function() diff --git a/test/functional/api/buffer_updates_spec.lua b/test/functional/api/buffer_updates_spec.lua index 527394bfd1..489f601d31 100644 --- a/test/functional/api/buffer_updates_spec.lua +++ b/test/functional/api/buffer_updates_spec.lua @@ -879,7 +879,8 @@ describe('API: buffer events:', function() it('when :terminal lines change', function() local buffer_lines = {} local expected_lines = {} - fn.termopen({ nvim_prog, '-u', 'NONE', '-i', 'NONE', '-n', '-c', 'set shortmess+=A' }, { + fn.jobstart({ nvim_prog, '-u', 'NONE', '-i', 'NONE', '-n', '-c', 'set shortmess+=A' }, { + term = true, env = { VIMRUNTIME = os.getenv('VIMRUNTIME') }, }) diff --git a/test/functional/api/command_spec.lua b/test/functional/api/command_spec.lua index a16c6a88e3..fabd9be6d6 100644 --- a/test/functional/api/command_spec.lua +++ b/test/functional/api/command_spec.lua @@ -651,6 +651,11 @@ describe('nvim_create_user_command', function() api.nvim_set_current_buf(bufnr) command('Hello') assert_alive() + eq( + 'Invalid buffer id: 1234', + pcall_err(api.nvim_buf_create_user_command, 1234, 'Hello', '', {}) + ) + assert_alive() end) it('can use a Lua complete function', function() @@ -771,5 +776,9 @@ describe('nvim_del_user_command', function() command('Hello') api.nvim_buf_del_user_command(0, 'Hello') matches('Not an editor command: Hello', pcall_err(command, 'Hello')) + eq('Invalid command (not found): Hello', pcall_err(api.nvim_buf_del_user_command, 0, 'Hello')) + eq('Invalid command (not found): Bye', pcall_err(api.nvim_buf_del_user_command, 0, 'Bye')) + eq('Invalid buffer id: 1234', pcall_err(api.nvim_buf_del_user_command, 1234, 'Hello')) + assert_alive() end) end) diff --git a/test/functional/api/deprecated_spec.lua b/test/functional/api/deprecated_spec.lua new file mode 100644 index 0000000000..2efcfda873 --- /dev/null +++ b/test/functional/api/deprecated_spec.lua @@ -0,0 +1,21 @@ +-- Island of misfit toys. +--- @diagnostic disable: deprecated + +local t = require('test.testutil') +local n = require('test.functional.testnvim')() + +describe('deprecated', function() + before_each(n.clear) + + describe('nvim_notify', function() + it('can notify a info message', function() + n.api.nvim_notify('hello world', 2, {}) + end) + + it('can be overridden', function() + n.command('lua vim.notify = function(...) return 42 end') + t.eq(42, n.api.nvim_exec_lua("return vim.notify('Hello world')", {})) + n.api.nvim_notify('hello world', 4, {}) + end) + end) +end) diff --git a/test/functional/api/extmark_spec.lua b/test/functional/api/extmark_spec.lua index 43be0c0e43..8a4aea1efe 100644 --- a/test/functional/api/extmark_spec.lua +++ b/test/functional/api/extmark_spec.lua @@ -249,7 +249,7 @@ describe('API/extmarks', function() set_extmark(ns, 2, 1, 0, { right_gravity = false }) eq({ { 1, 0, 0 }, { 2, 1, 0 } }, get_extmarks(ns, { 0, 0 }, { -1, -1 })) feed('u') - eq({ { 1, 0, 0 }, { 2, 1, 0 } }, get_extmarks(ns, { 0, 0 }, { -1, -1 })) + eq({ { 1, 0, 0 }, { 2, 0, 0 } }, get_extmarks(ns, { 0, 0 }, { -1, -1 })) api.nvim_buf_clear_namespace(0, ns, 0, -1) end) @@ -1731,7 +1731,7 @@ describe('API/extmarks', function() -- mark with invalidate is removed command('d2') screen:expect([[ - S2^aaa bbb ccc | + {7:S2}^aaa bbb ccc | {7: }aaa bbb ccc |*3 {7: } | | @@ -1739,9 +1739,9 @@ describe('API/extmarks', function() -- mark is restored with undo_restore == true command('silent undo') screen:expect([[ - S1{7: }^aaa bbb ccc | - S2S1aaa bbb ccc | - S2{7: }aaa bbb ccc | + {7:S1 }^aaa bbb ccc | + {7:S2S1}aaa bbb ccc | + {7:S2 }aaa bbb ccc | {7: }aaa bbb ccc |*2 | ]]) @@ -1794,6 +1794,16 @@ describe('API/extmarks', function() eq({}, get_extmark_by_id(ns, 4, {})) end) + it('no crash checking invalidated flag of sign pair end key #31856', function() + api.nvim_buf_set_lines(0, 0, 1, false, { '', '' }) + api.nvim_set_option_value('signcolumn', 'auto:2', {}) + set_extmark(ns, 1, 0, 0, { sign_text = 'S1', invalidate = true, end_row = 0 }) + set_extmark(ns, 2, 1, 0, { sign_text = 'S2', end_row = 1 }) + command('d') + api.nvim_buf_clear_namespace(0, ns, 0, -1) + n.assert_alive() + end) + it('can set a URL', function() local url1 = 'https://example.com' local url2 = 'http://127.0.0.1' diff --git a/test/functional/api/highlight_spec.lua b/test/functional/api/highlight_spec.lua index dd0611f184..8f98ae3650 100644 --- a/test/functional/api/highlight_spec.lua +++ b/test/functional/api/highlight_spec.lua @@ -309,6 +309,15 @@ describe('API: set highlight', function() eq({ underdotted = true }, api.nvim_get_hl_by_name('Test_hl', true)) end) + it('can set all underline cterm attributes #31385', function() + local ns = get_ns() + local attrs = { 'underline', 'undercurl', 'underdouble', 'underdotted', 'underdashed' } + for _, attr in ipairs(attrs) do + api.nvim_set_hl(ns, 'Test_' .. attr, { cterm = { [attr] = true } }) + eq({ [attr] = true }, api.nvim_get_hl_by_name('Test_' .. attr, false)) + end + end) + it('can set a highlight in the global namespace', function() api.nvim_set_hl(0, 'Test_hl', highlight2_config) eq( @@ -710,4 +719,18 @@ describe('API: set/get highlight namespace', function() api.nvim_win_set_hl_ns(0, ns) eq(ns, api.nvim_get_hl_ns({ winid = 0 })) end) + + it('setting namespace takes priority over &winhighlight', function() + command('set winhighlight=Visual:Search') + n.insert('foobar') + local ns = api.nvim_create_namespace('') + api.nvim_win_set_hl_ns(0, ns) + eq(ns, api.nvim_get_hl_ns({ winid = 0 })) + command('enew') -- switching buffer keeps namespace #30904 + eq(ns, api.nvim_get_hl_ns({ winid = 0 })) + command('set winhighlight=') + eq(ns, api.nvim_get_hl_ns({ winid = 0 })) + command('set winhighlight=Visual:Search') + eq(ns, api.nvim_get_hl_ns({ winid = 0 })) + end) end) diff --git a/test/functional/api/server_requests_spec.lua b/test/functional/api/server_requests_spec.lua index bdd340f6c6..c022ba28de 100644 --- a/test/functional/api/server_requests_spec.lua +++ b/test/functional/api/server_requests_spec.lua @@ -9,7 +9,6 @@ local nvim_prog, command, fn = n.nvim_prog, n.command, n.fn local source, next_msg = n.source, n.next_msg local ok = t.ok local api = n.api -local spawn, merge_args = n.spawn, n.merge_args local set_session = n.set_session local pcall_err = t.pcall_err local assert_alive = n.assert_alive @@ -281,10 +280,9 @@ describe('server -> client', function() end) describe('connecting to another (peer) nvim', function() - local nvim_argv = merge_args(n.nvim_argv, { '--headless' }) local function connect_test(server, mode, address) local serverpid = fn.getpid() - local client = spawn(nvim_argv, false, nil, true) + local client = n.new_session(true) set_session(client) local clientpid = fn.getpid() @@ -312,7 +310,7 @@ describe('server -> client', function() end it('via named pipe', function() - local server = spawn(nvim_argv) + local server = n.new_session(false) set_session(server) local address = fn.serverlist()[1] local first = string.sub(address, 1, 1) @@ -321,7 +319,7 @@ describe('server -> client', function() end) it('via ipv4 address', function() - local server = spawn(nvim_argv) + local server = n.new_session(false) set_session(server) local status, address = pcall(fn.serverstart, '127.0.0.1:') if not status then @@ -332,7 +330,7 @@ describe('server -> client', function() end) it('via ipv6 address', function() - local server = spawn(nvim_argv) + local server = n.new_session(false) set_session(server) local status, address = pcall(fn.serverstart, '::1:') if not status then @@ -343,7 +341,7 @@ describe('server -> client', function() end) it('via hostname', function() - local server = spawn(nvim_argv) + local server = n.new_session(false) set_session(server) local address = fn.serverstart('localhost:') eq('localhost:', string.sub(address, 1, 10)) @@ -351,10 +349,10 @@ describe('server -> client', function() end) it('does not crash on receiving UI events', function() - local server = spawn(nvim_argv) + local server = n.new_session(false) set_session(server) local address = fn.serverlist()[1] - local client = spawn(nvim_argv, false, nil, true) + local client = n.new_session(true) set_session(client) local id = fn.sockconnect('pipe', address, { rpc = true }) diff --git a/test/functional/api/version_spec.lua b/test/functional/api/version_spec.lua index 617786eb2d..68c4ef7503 100644 --- a/test/functional/api/version_spec.lua +++ b/test/functional/api/version_spec.lua @@ -43,7 +43,7 @@ describe("api_info()['version']", function() eq(0, fn.has('nvim-' .. major .. '.' .. minor .. '.' .. (patch + 1))) eq(0, fn.has('nvim-' .. major .. '.' .. (minor + 1) .. '.' .. patch)) eq(0, fn.has('nvim-' .. (major + 1) .. '.' .. minor .. '.' .. patch)) - assert(build == nil or type(build) == 'string') + assert(build == vim.NIL or type(build) == 'string') end) end) @@ -93,28 +93,28 @@ describe('api metadata', function() local function clean_level_0(metadata) for _, f in ipairs(metadata.functions) do f.can_fail = nil - f.async = nil + f.async = nil -- XXX: renamed to "fast". f.receives_channel_id = nil f.since = 0 end end - local api_info, compat, stable, api_level + local api_info --[[@type table]] + local compat --[[@type integer]] + local stable --[[@type integer]] + local api_level --[[@type integer]] local old_api = {} setup(function() clear() -- Ensure a session before requesting api_info. + --[[@type { version: {api_compatible: integer, api_level: integer, api_prerelease: boolean} }]] api_info = api.nvim_get_api_info()[2] compat = api_info.version.api_compatible api_level = api_info.version.api_level - if api_info.version.api_prerelease then - stable = api_level - 1 - else - stable = api_level - end + stable = api_info.version.api_prerelease and api_level - 1 or api_level for level = compat, stable do local path = ('test/functional/fixtures/api_level_' .. tostring(level) .. '.mpack') - old_api[level] = read_mpack_file(path) + old_api[level] = read_mpack_file(path) --[[@type table]] if old_api[level] == nil then local errstr = 'missing metadata fixture for stable level ' .. level .. '. ' if level == api_level and not api_info.version.api_prerelease then diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index 3f1e378bc1..3aa9ba49d5 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -96,12 +96,19 @@ describe('API', function() assert_alive() end) - it('input is processed first when followed immediately by non-fast events', function() + it('input is processed first if followed immediately by non-fast events', function() api.nvim_set_current_line('ab') async_meths.nvim_input('x') async_meths.nvim_exec_lua('_G.res1 = vim.api.nvim_get_current_line()', {}) async_meths.nvim_exec_lua('_G.res2 = vim.api.nvim_get_current_line()', {}) eq({ 'b', 'b' }, exec_lua('return { _G.res1, _G.res2 }')) + -- Also test with getchar() + async_meths.nvim_command('let g:getchar = 1 | call getchar() | let g:getchar = 0') + eq(1, api.nvim_get_var('getchar')) + async_meths.nvim_input('x') + async_meths.nvim_exec_lua('_G.res1 = vim.g.getchar', {}) + async_meths.nvim_exec_lua('_G.res2 = vim.g.getchar', {}) + eq({ 0, 0 }, exec_lua('return { _G.res1, _G.res2 }')) end) it('does not set CA_COMMAND_BUSY #7254', function() @@ -695,7 +702,7 @@ describe('API', function() pcall_err(request, 'nvim_call_dict_function', 42, 'f', { 1, 2 }) ) eq( - 'Failed to evaluate dict expression', + 'Vim:E121: Undefined variable: foo', pcall_err(request, 'nvim_call_dict_function', 'foo', 'f', { 1, 2 }) ) eq('dict not found', pcall_err(request, 'nvim_call_dict_function', '42', 'f', { 1, 2 })) @@ -781,18 +788,6 @@ describe('API', function() end) end) - describe('nvim_notify', function() - it('can notify a info message', function() - api.nvim_notify('hello world', 2, {}) - end) - - it('can be overridden', function() - command('lua vim.notify = function(...) return 42 end') - eq(42, api.nvim_exec_lua("return vim.notify('Hello world')", {})) - api.nvim_notify('hello world', 4, {}) - end) - end) - describe('nvim_input', function() it('Vimscript error: does NOT fail, updates v:errmsg', function() local status, _ = pcall(api.nvim_input, ':call bogus_fn()<CR>') @@ -1770,6 +1765,11 @@ describe('API', function() end) it('validation', function() + eq("Unknown option 'foobar'", pcall_err(api.nvim_set_option_value, 'foobar', 'baz', {})) + eq( + "Unknown option 'foobar'", + pcall_err(api.nvim_set_option_value, 'foobar', 'baz', { win = api.nvim_get_current_win() }) + ) eq( "Invalid 'scope': expected 'local' or 'global'", pcall_err(api.nvim_get_option_value, 'scrolloff', { scope = 'bogus' }) @@ -1952,6 +1952,16 @@ describe('API', function() api.nvim_set_current_win(api.nvim_list_wins()[2]) eq(api.nvim_list_wins()[2], api.nvim_get_current_win()) end) + + it('failure modes', function() + n.command('split') + + eq('Invalid window id: 9999', pcall_err(api.nvim_set_current_win, 9999)) + + -- XXX: force nvim_set_current_win to fail somehow. + n.command("au WinLeave * throw 'foo'") + eq('WinLeave Autocommands for "*": foo', pcall_err(api.nvim_set_current_win, 1000)) + end) end) describe('nvim_{get,set}_current_tabpage, nvim_list_tabpages', function() @@ -1971,6 +1981,16 @@ describe('API', function() eq(api.nvim_list_tabpages()[2], api.nvim_get_current_tabpage()) eq(api.nvim_list_wins()[2], api.nvim_get_current_win()) end) + + it('failure modes', function() + n.command('tabnew') + + eq('Invalid tabpage id: 999', pcall_err(api.nvim_set_current_tabpage, 999)) + + -- XXX: force nvim_set_current_tabpage to fail somehow. + n.command("au TabLeave * throw 'foo'") + eq('TabLeave Autocommands for "*": foo', pcall_err(api.nvim_set_current_tabpage, 1)) + end) end) describe('nvim_get_mode', function() @@ -2679,7 +2699,8 @@ describe('API', function() -- :terminal with args + running process. command('enew') local progpath_esc = eval('shellescape(v:progpath)') - fn.termopen(('%s -u NONE -i NONE'):format(progpath_esc), { + fn.jobstart(('%s -u NONE -i NONE'):format(progpath_esc), { + term = true, env = { VIMRUNTIME = os.getenv('VIMRUNTIME') }, }) eq(-1, eval('jobwait([&channel], 0)[0]')) -- Running? @@ -3654,6 +3675,30 @@ describe('API', function() async_meths.nvim_echo({ { 'msg\nmsg' }, { 'msg' } }, false, {}) eq('', exec_capture('messages')) end) + + it('can print error message', function() + async_meths.nvim_echo({ { 'Error\nMessage' } }, false, { err = true }) + screen:expect([[ + | + {1:~ }|*3 + {3: }| + {9:Error} | + {9:Message} | + {6:Press ENTER or type command to continue}^ | + ]]) + feed(':messages<CR>') + screen:expect([[ + ^ | + {1:~ }|*6 + | + ]]) + async_meths.nvim_echo({ { 'Error' }, { 'Message', 'Special' } }, false, { err = true }) + screen:expect([[ + ^ | + {1:~ }|*6 + {9:Error}{16:Message} | + ]]) + end) end) describe('nvim_open_term', function() @@ -3760,7 +3805,7 @@ describe('API', function() screen:expect { grid = [[ | - {1:~}{102: }{4: }{1: }| + {1:~}{4:^ }{1: }| {1:~}{4: }{1: }|*4 {1:~ }|*3 {5:-- TERMINAL --} | @@ -3776,7 +3821,7 @@ describe('API', function() screen:expect { grid = [[ | - {1:~}{4:herrejösses!}{102: }{4: }{1: }| + {1:~}{4:herrejösses!^ }{1: }| {1:~}{4: }{1: }|*4 {1:~ }|*3 {5:-- TERMINAL --} | @@ -3955,8 +4000,8 @@ describe('API', function() str = 'TextWithWarningHighlightTextWithUserHighlight', width = 45, highlights = { - { start = 0, group = 'WarningMsg' }, - { start = 24, group = 'User1' }, + { start = 0, group = 'WarningMsg', groups = { 'StatusLine', 'WarningMsg' } }, + { start = 24, group = 'User1', groups = { 'StatusLine', 'User1' } }, }, }, api.nvim_eval_statusline( @@ -3971,7 +4016,7 @@ describe('API', function() str = 'TextWithNoHighlight', width = 19, highlights = { - { start = 0, group = 'StatusLine' }, + { start = 0, group = 'StatusLine', groups = { 'StatusLine' } }, }, }, api.nvim_eval_statusline('TextWithNoHighlight', { highlights = true })) end) @@ -3983,8 +4028,8 @@ describe('API', function() str = 'TextWithNoHighlightTextWithWarningHighlight', width = 43, highlights = { - { start = 0, group = 'StatusLineNC' }, - { start = 19, group = 'WarningMsg' }, + { start = 0, group = 'StatusLineNC', groups = { 'StatusLineNC' } }, + { start = 19, group = 'WarningMsg', groups = { 'StatusLineNC', 'WarningMsg' } }, }, }, api.nvim_eval_statusline( @@ -4000,8 +4045,8 @@ describe('API', function() str = 'TextWithNoHighlightTextWithWarningHighlight', width = 43, highlights = { - { start = 0, group = 'TabLineFill' }, - { start = 19, group = 'WarningMsg' }, + { start = 0, group = 'TabLineFill', groups = { 'TabLineFill' } }, + { start = 19, group = 'WarningMsg', groups = { 'TabLineFill', 'WarningMsg' } }, }, }, api.nvim_eval_statusline( @@ -4017,8 +4062,8 @@ describe('API', function() str = 'TextWithNoHighlightTextWithWarningHighlight', width = 43, highlights = { - { start = 0, group = 'WinBar' }, - { start = 19, group = 'WarningMsg' }, + { start = 0, group = 'WinBar', groups = { 'WinBar' } }, + { start = 19, group = 'WarningMsg', groups = { 'WinBar', 'WarningMsg' } }, }, }, api.nvim_eval_statusline( @@ -4045,11 +4090,11 @@ describe('API', function() str = '││bbaa 4 ', width = 9, highlights = { - { group = 'CursorLineFold', start = 0 }, - { group = 'Normal', start = 6 }, - { group = 'ErrorMsg', start = 6 }, - { group = 'IncSearch', start = 8 }, - { group = 'Normal', start = 10 }, + { group = 'CursorLineFold', start = 0, groups = { 'CursorLineFold' } }, + { group = 'Normal', start = 6, groups = { 'Normal' } }, + { group = 'ErrorMsg', start = 6, groups = { 'CursorLineSign', 'ErrorMsg' } }, + { group = 'IncSearch', start = 8, groups = { 'CursorLineSign', 'IncSearch' } }, + { group = 'Normal', start = 10, groups = { 'Normal' } }, }, }, api.nvim_eval_statusline( '%C%s%=%l ', @@ -4060,8 +4105,8 @@ describe('API', function() str = ' 3 ', width = 9, highlights = { - { group = 'LineNr', start = 0 }, - { group = 'ErrorMsg', start = 8 }, + { group = 'LineNr', start = 0, groups = { 'LineNr' } }, + { group = 'ErrorMsg', start = 8, groups = { 'LineNr', 'ErrorMsg' } }, }, }, api.nvim_eval_statusline('%l%#ErrorMsg# ', { use_statuscol_lnum = 3, highlights = true }) @@ -5359,8 +5404,53 @@ describe('API', function() 13 | ]], }) - -- takes buffer line count from correct buffer with "win" and {0, -1} "range" - api.nvim__redraw({ win = 0, range = { 0, -1 } }) + end) + + it('nvim__redraw range parameter', function() + Screen.new(10, 5) + fn.setline(1, fn.range(4)) + + exec_lua([[ + _G.lines_list = {} + ns = vim.api.nvim_create_namespace('') + vim.api.nvim_set_decoration_provider(ns, { + on_win = function() + end, + on_line = function(_, _, _, line) + table.insert(_G.lines_list, line) + end, + }) + function _G.get_lines() + local lines = _G.lines_list + _G.lines_list = {} + return lines + end + ]]) + + api.nvim__redraw({ flush = true, valid = false }) + exec_lua('_G.get_lines()') + + local actual_lines = {} + local function test(range) + api.nvim__redraw({ win = 0, range = range }) + table.insert(actual_lines, exec_lua('return _G.get_lines()')) + end + + test({ 0, -1 }) + test({ 2, 2 ^ 31 }) + test({ 2, 2 ^ 32 }) + test({ 2 ^ 31 - 1, 2 }) + test({ 2 ^ 32 - 1, 2 }) + + local expected_lines = { + { 0, 1, 2, 3 }, + { 2, 3 }, + { 2, 3 }, + {}, + {}, + } + eq(expected_lines, actual_lines) + n.assert_alive() end) end) diff --git a/test/functional/api/window_spec.lua b/test/functional/api/window_spec.lua index 92999f383a..028f0beb38 100644 --- a/test/functional/api/window_spec.lua +++ b/test/functional/api/window_spec.lua @@ -359,6 +359,15 @@ describe('API/win', function() eq(2, api.nvim_win_get_height(api.nvim_list_wins()[2])) end) + it('failure modes', function() + command('split') + eq('Invalid window id: 999999', pcall_err(api.nvim_win_set_height, 999999, 10)) + eq( + 'Wrong type for argument 2 when calling nvim_win_set_height, expecting Integer', + pcall_err(api.nvim_win_set_height, 0, 0.9) + ) + end) + it('correctly handles height=1', function() command('split') api.nvim_set_current_win(api.nvim_list_wins()[1]) @@ -409,6 +418,15 @@ describe('API/win', function() eq(2, api.nvim_win_get_width(api.nvim_list_wins()[2])) end) + it('failure modes', function() + command('vsplit') + eq('Invalid window id: 999999', pcall_err(api.nvim_win_set_width, 999999, 10)) + eq( + 'Wrong type for argument 2 when calling nvim_win_set_width, expecting Integer', + pcall_err(api.nvim_win_set_width, 0, 0.9) + ) + end) + it('do not cause ml_get errors with foldmethod=expr #19989', function() insert([[ aaaaa @@ -488,6 +506,48 @@ describe('API/win', function() assert_alive() end) + describe('after closing', function() + local buf, win0, win1, win2 + + before_each(function() + win0 = api.nvim_get_current_win() + command('new') + buf = api.nvim_get_current_buf() + win1 = api.nvim_get_current_win() + command('set numberwidth=10') + command('split') + win2 = api.nvim_get_current_win() + command('set numberwidth=15') + command('enew') + api.nvim_set_current_win(win1) + command('normal ix') + command('enew') + api.nvim_set_current_win(win0) + eq(4, api.nvim_get_option_value('numberwidth', {})) + end) + + -- at this point buffer `buf` is current in no windows. Closing shouldn't affect its defaults + it('0 windows', function() + api.nvim_set_current_buf(buf) + eq(10, api.nvim_get_option_value('numberwidth', {})) + end) + + it('1 window', function() + api.nvim_win_close(win1, false) + + api.nvim_set_current_buf(buf) + eq(10, api.nvim_get_option_value('numberwidth', {})) + end) + + it('2 windows', function() + api.nvim_win_close(win1, false) + api.nvim_win_close(win2, false) + + api.nvim_set_current_buf(buf) + eq(10, api.nvim_get_option_value('numberwidth', {})) + end) + end) + it('returns values for unset local options', function() eq(-1, api.nvim_get_option_value('scrolloff', { win = 0, scope = 'local' })) end) @@ -1664,7 +1724,7 @@ describe('API/win', function() autocmd BufWinEnter * ++once let fired = v:true ]]) eq( - 'Failed to set buffer 2', + 'Vim:E37: No write since last change (add ! to override)', pcall_err(api.nvim_open_win, api.nvim_create_buf(true, true), false, { split = 'left' }) ) eq(false, eval('fired')) diff --git a/test/functional/autocmd/autocmd_spec.lua b/test/functional/autocmd/autocmd_spec.lua index c62e4752e0..1b7275ebf6 100644 --- a/test/functional/autocmd/autocmd_spec.lua +++ b/test/functional/autocmd/autocmd_spec.lua @@ -160,7 +160,7 @@ describe('autocmd', function() it('++once', function() -- :help autocmd-once -- - -- ":autocmd ... ++once" executes its handler once, then removes the handler. + -- ":autocmd … ++once" executes its handler once, then removes the handler. -- local expected = { 'Many1', @@ -206,7 +206,7 @@ describe('autocmd', function() ) -- - -- ":autocmd ... ++once" handlers can be deleted. + -- ":autocmd … ++once" handlers can be deleted. -- expected = {} command('let g:foo = []') @@ -216,7 +216,7 @@ describe('autocmd', function() eq(expected, eval('g:foo')) -- - -- ":autocmd ... <buffer> ++once ++nested" + -- ":autocmd … <buffer> ++once ++nested" -- expected = { 'OptionSet-Once', @@ -250,6 +250,24 @@ describe('autocmd', function() --- Autocommands ---]]), fn.execute('autocmd Tabnew') ) + + -- + -- :autocmd does not recursively call ++once Lua handlers. + -- + exec_lua [[vim.g.count = 0]] + eq(0, eval('g:count')) + exec_lua [[ + vim.api.nvim_create_autocmd('User', { + once = true, + pattern = nil, + callback = function() + vim.g.count = vim.g.count + 1 + vim.api.nvim_exec_autocmds('User', { pattern = nil }) + end, + }) + vim.api.nvim_exec_autocmds('User', { pattern = nil }) + ]] + eq(1, eval('g:count')) end) it('internal `aucmd_win` window', function() diff --git a/test/functional/autocmd/completedone_spec.lua b/test/functional/autocmd/completedone_spec.lua index 33beb16db2..36dc73842d 100644 --- a/test/functional/autocmd/completedone_spec.lua +++ b/test/functional/autocmd/completedone_spec.lua @@ -32,7 +32,7 @@ describe('CompleteDone', function() feed('<Esc>') eq('cancel', eval('g:donereason')) end) - it('when overriden by another complete()', function() + it('when overridden by another complete()', function() call('complete', call('col', '.'), { 'bar', 'baz' }) eq('cancel', eval('g:donereason')) end) diff --git a/test/functional/autocmd/dirchanged_spec.lua b/test/functional/autocmd/dirchanged_spec.lua index 1cde0e0552..9b572df568 100644 --- a/test/functional/autocmd/dirchanged_spec.lua +++ b/test/functional/autocmd/dirchanged_spec.lua @@ -351,11 +351,10 @@ describe('autocmd DirChanged and DirChangedPre', function() eq(2, eval('g:cdprecount')) eq(2, eval('g:cdcount')) - local status, err = pcall(function() - request('nvim_set_current_dir', '/doesnotexist') - end) - eq(false, status) - eq('Failed to change directory', string.match(err, ': (.*)')) + eq( + 'Vim:E344: Can\'t find directory "/doesnotexist" in cdpath', + t.pcall_err(request, 'nvim_set_current_dir', '/doesnotexist') + ) eq({ directory = '/doesnotexist', scope = 'global', changed_window = false }, eval('g:evpre')) eq(3, eval('g:cdprecount')) eq(2, eval('g:cdcount')) diff --git a/test/functional/autocmd/focus_spec.lua b/test/functional/autocmd/focus_spec.lua index 7f6092bf48..177e8997cf 100644 --- a/test/functional/autocmd/focus_spec.lua +++ b/test/functional/autocmd/focus_spec.lua @@ -47,7 +47,7 @@ describe('autoread TUI FocusGained/FocusLost', function() screen:expect { grid = [[ - {1: } | + ^ | {4:~ }|*3 {5:[No Name] }| | @@ -57,7 +57,7 @@ describe('autoread TUI FocusGained/FocusLost', function() feed_command('edit ' .. path) screen:expect { grid = [[ - {1: } | + ^ | {4:~ }|*3 {5:xtest-foo }| :edit xtest-foo | @@ -68,7 +68,7 @@ describe('autoread TUI FocusGained/FocusLost', function() feed_data('\027[O') screen:expect { grid = [[ - {1: } | + ^ | {4:~ }|*3 {5:xtest-foo }| :edit xtest-foo | @@ -83,7 +83,7 @@ describe('autoread TUI FocusGained/FocusLost', function() screen:expect { grid = [[ - {1:l}ine 1 | + ^line 1 | line 2 | line 3 | line 4 | diff --git a/test/functional/autocmd/termxx_spec.lua b/test/functional/autocmd/termxx_spec.lua index baf2bb6071..950ef2f58a 100644 --- a/test/functional/autocmd/termxx_spec.lua +++ b/test/functional/autocmd/termxx_spec.lua @@ -213,9 +213,11 @@ describe('autocmd TextChangedT', function() end) it('cannot delete terminal buffer', function() - command([[autocmd TextChangedT * call nvim_input('<CR>') | bwipe!]]) + command('autocmd TextChangedT * bwipe!') tt.feed_data('a') screen:expect({ any = 'E937: ' }) + feed('<CR>') + command('autocmd! TextChangedT') matches( '^E937: Attempt to delete a buffer that is in use: term://', api.nvim_get_vvar('errmsg') diff --git a/test/functional/core/channels_spec.lua b/test/functional/core/channels_spec.lua index dee13d19ae..7b10eb05ef 100644 --- a/test/functional/core/channels_spec.lua +++ b/test/functional/core/channels_spec.lua @@ -5,7 +5,6 @@ local clear, eq, eval, next_msg, ok, source = n.clear, t.eq, n.eval, n.next_msg, local command, fn, api = n.command, n.fn, n.api local matches = t.matches local sleep = vim.uv.sleep -local spawn, nvim_argv = n.spawn, n.nvim_argv local get_session, set_session = n.get_session, n.set_session local nvim_prog = n.nvim_prog local is_os = t.is_os @@ -33,10 +32,10 @@ describe('channels', function() end) pending('can connect to socket', function() - local server = spawn(nvim_argv, nil, nil, true) + local server = n.new_session(true) set_session(server) local address = fn.serverlist()[1] - local client = spawn(nvim_argv, nil, nil, true) + local client = n.new_session(true) set_session(client) source(init) @@ -63,7 +62,7 @@ describe('channels', function() it('dont crash due to garbage in rpc #23781', function() local client = get_session() - local server = spawn(nvim_argv, nil, nil, true) + local server = n.new_session(true) set_session(server) local address = fn.serverlist()[1] set_session(client) diff --git a/test/functional/core/exit_spec.lua b/test/functional/core/exit_spec.lua index 34c3eedbd2..65f6bc28a6 100644 --- a/test/functional/core/exit_spec.lua +++ b/test/functional/core/exit_spec.lua @@ -8,8 +8,6 @@ local feed = n.feed local eval = n.eval local eq = t.eq local run = n.run -local fn = n.fn -local nvim_prog = n.nvim_prog local pcall_err = t.pcall_err local exec_capture = n.exec_capture local poke_eventloop = n.poke_eventloop @@ -69,8 +67,8 @@ describe(':cquit', function() poke_eventloop() assert_alive() else - fn.system({ nvim_prog, '-u', 'NONE', '-i', 'NONE', '--headless', '--cmd', cmdline }) - eq(exit_code, eval('v:shell_error')) + local p = n.spawn_wait('--cmd', cmdline) + eq(exit_code, p.status) end end diff --git a/test/functional/core/fileio_spec.lua b/test/functional/core/fileio_spec.lua index cf9715f848..b1a8e21762 100644 --- a/test/functional/core/fileio_spec.lua +++ b/test/functional/core/fileio_spec.lua @@ -31,7 +31,6 @@ local feed_command = n.feed_command local skip = t.skip local is_os = t.is_os local is_ci = t.is_ci -local spawn = n.spawn local set_session = n.set_session describe('fileio', function() @@ -51,12 +50,11 @@ describe('fileio', function() rmdir('Xtest_backupdir with spaces') end) - local args = { nvim_prog, '--clean', '--cmd', 'set nofsync directory=Xtest_startup_swapdir' } + local args = { '--clean', '--cmd', 'set nofsync directory=Xtest_startup_swapdir' } --- Starts a new nvim session and returns an attached screen. - local function startup(extra_args) - extra_args = extra_args or {} - local argv = vim.iter({ args, '--embed', extra_args }):flatten():totable() - local screen_nvim = spawn(argv) + local function startup() + local argv = vim.iter({ args, '--embed' }):flatten():totable() + local screen_nvim = n.new_session(false, { args = argv, merge = false }) set_session(screen_nvim) local screen = Screen.new(70, 10) screen:set_default_attr_ids({ @@ -100,7 +98,8 @@ describe('fileio', function() eq('foozubbaz', trim(read_file('Xtest_startup_file1'))) -- 4. Exit caused by deadly signal (+ 'swapfile'). - local j = fn.jobstart(vim.iter({ args, '--embed' }):flatten():totable(), { rpc = true }) + local j = + fn.jobstart(vim.iter({ nvim_prog, args, '--embed' }):flatten():totable(), { rpc = true }) fn.rpcrequest( j, 'nvim_exec2', diff --git a/test/functional/core/job_spec.lua b/test/functional/core/job_spec.lua index 618c294566..e833b5127d 100644 --- a/test/functional/core/job_spec.lua +++ b/test/functional/core/job_spec.lua @@ -65,6 +65,39 @@ describe('jobs', function() ]]) end) + it('validation', function() + matches( + "E475: Invalid argument: job cannot have both 'pty' and 'rpc' options set", + pcall_err(command, "call jobstart(['cat', '-'], { 'pty': v:true, 'rpc': v:true })") + ) + matches( + 'E475: Invalid argument: expected valid directory', + pcall_err(command, "call jobstart(['cat', '-'], { 'cwd': 9313843 })") + ) + matches( + 'E475: Invalid argument: expected valid directory', + pcall_err(command, "call jobstart(['cat', '-'], { 'cwd': 'bogusssssss/bogus' })") + ) + matches( + "E475: Invalid argument: 'term' must be Boolean", + pcall_err(command, "call jobstart(['cat', '-'], { 'term': 'bogus' })") + ) + matches( + "E475: Invalid argument: 'term' must be Boolean", + pcall_err(command, "call jobstart(['cat', '-'], { 'term': 1 })") + ) + command('set modified') + matches( + vim.pesc('jobstart(...,{term=true}) requires unmodified buffer'), + pcall_err(command, "call jobstart(['cat', '-'], { 'term': v:true })") + ) + + -- Non-failure cases: + command('set nomodified') + command("call jobstart(['cat', '-'], { 'term': v:true })") + command("call jobstart(['cat', '-'], { 'term': v:false })") + end) + it('must specify env option as a dict', function() command('let g:job_opts.env = v:true') local _, err = pcall(function() @@ -940,6 +973,39 @@ describe('jobs', function() feed('<CR>') fn.jobstop(api.nvim_get_var('id')) end) + + it('does not set UI busy with zero timeout #31712', function() + local screen = Screen.new(50, 6) + command([[let g:id = jobstart(['sleep', '0.3'])]]) + local busy = 0 + screen._handle_busy_start = (function(orig) + return function() + orig(screen) + busy = busy + 1 + end + end)(screen._handle_busy_start) + source([[ + func PrintAndPoll() + echon "aaa\nbbb" + call jobwait([g:id], 0) + echon "\nccc" + endfunc + ]]) + feed_command('call PrintAndPoll()') + screen:expect { + grid = [[ + | + {3: }| + aaa | + bbb | + ccc | + {6:Press ENTER or type command to continue}^ | + ]], + } + feed('<CR>') + fn.jobstop(api.nvim_get_var('id')) + eq(0, busy) + end) end) pending('exit event follows stdout, stderr', function() @@ -969,13 +1035,6 @@ describe('jobs', function() eq({ 'notification', 'exit', { 0, 143 } }, next_msg()) end) - it('cannot have both rpc and pty options', function() - command('let g:job_opts.pty = v:true') - command('let g:job_opts.rpc = v:true') - local _, err = pcall(command, "let j = jobstart(['cat', '-'], g:job_opts)") - matches("E475: Invalid argument: job cannot have both 'pty' and 'rpc' options set", err) - end) - it('does not crash when repeatedly failing to start shell', function() source([[ set shell=nosuchshell @@ -1198,7 +1257,7 @@ describe('jobs', function() }) -- Wait for startup to complete, so that all terminal responses are received. screen:expect([[ - {1: } | + ^ | ~ |*3 {1:[No Name] 0,0-1 All}| | @@ -1208,7 +1267,7 @@ describe('jobs', function() feed(':q<CR>') screen:expect([[ | - [Process exited 0]{1: } | + [Process exited 0]^ | |*4 {3:-- TERMINAL --} | ]]) @@ -1230,7 +1289,7 @@ describe('pty process teardown', function() it('does not prevent/delay exit. #4798 #4900', function() skip(fn.executable('sleep') == 0, 'missing "sleep" command') -- Use a nested nvim (in :term) to test without --headless. - fn.termopen({ + fn.jobstart({ n.nvim_prog, '-u', 'NONE', @@ -1243,7 +1302,10 @@ describe('pty process teardown', function() '+terminal', '+!(sleep 300 &)', '+qa', - }, { env = { VIMRUNTIME = os.getenv('VIMRUNTIME') } }) + }, { + term = true, + env = { VIMRUNTIME = os.getenv('VIMRUNTIME') }, + }) -- Exiting should terminate all descendants (PTY, its children, ...). screen:expect([[ diff --git a/test/functional/core/log_spec.lua b/test/functional/core/log_spec.lua index 57dfd6364c..571bece833 100644 --- a/test/functional/core/log_spec.lua +++ b/test/functional/core/log_spec.lua @@ -46,7 +46,7 @@ describe('log', function() env = env, }) screen:expect([[ - {1: } | + ^ | ~ |*4 | {3:-- TERMINAL --} | diff --git a/test/functional/core/main_spec.lua b/test/functional/core/main_spec.lua index a445423efc..65a7c556b8 100644 --- a/test/functional/core/main_spec.lua +++ b/test/functional/core/main_spec.lua @@ -9,7 +9,6 @@ local feed = n.feed local eval = n.eval local clear = n.clear local fn = n.fn -local nvim_prog_abs = n.nvim_prog_abs local write_file = t.write_file local is_os = t.is_os local skip = t.skip @@ -35,7 +34,7 @@ describe('command-line option', function() it('treats - as stdin', function() eq(nil, uv.fs_stat(fname)) fn.system({ - nvim_prog_abs(), + n.nvim_prog, '-u', 'NONE', '-i', @@ -56,41 +55,39 @@ describe('command-line option', function() eq(nil, uv.fs_stat(fname)) eq(true, not not dollar_fname:find('%$%w+')) write_file(dollar_fname, ':call setline(1, "100500")\n:wqall!\n') - fn.system({ - nvim_prog_abs(), - '-u', - 'NONE', - '-i', - 'NONE', - '--headless', + local p = n.spawn_wait( '--cmd', 'set noswapfile shortmess+=IFW fileformats=unix', '-s', dollar_fname, - fname, - }) - eq(0, eval('v:shell_error')) + fname + ) + eq(0, p.status) local attrs = uv.fs_stat(fname) eq(#'100500\n', attrs.size) end) - it('does not crash when run completion in ex mode', function() - fn.system({ - nvim_prog_abs(), - '--clean', - '-e', - '-s', - '--cmd', - 'exe "norm! i\\<C-X>\\<C-V>"', - }) - eq(0, eval('v:shell_error')) + it('does not crash when running completion in Ex mode', function() + local p = + n.spawn_wait('--clean', '-e', '-s', '--cmd', 'exe "norm! i\\<C-X>\\<C-V>"', '--cmd', 'qa!') + eq(0, p.status) + end) + + it('does not crash when running completion from -l script', function() + local lua_fname = 'Xinscompl.lua' + write_file(lua_fname, [=[vim.cmd([[exe "norm! i\<C-X>\<C-V>"]])]=]) + finally(function() + os.remove(lua_fname) + end) + local p = n.spawn_wait('--clean', '-l', lua_fname) + eq(0, p.status) end) it('does not crash after reading from stdin in non-headless mode', function() skip(is_os('win')) local screen = Screen.new(40, 8) local args = { - nvim_prog_abs(), + n.nvim_prog, '-u', 'NONE', '-i', @@ -103,7 +100,8 @@ describe('command-line option', function() -- Need to explicitly pipe to stdin so that the embedded Nvim instance doesn't try to read -- data from the terminal #18181 - fn.termopen(string.format([[echo "" | %s]], table.concat(args, ' ')), { + fn.jobstart(string.format([[echo "" | %s]], table.concat(args, ' ')), { + term = true, env = { VIMRUNTIME = os.getenv('VIMRUNTIME') }, }) screen:expect( @@ -120,7 +118,7 @@ describe('command-line option', function() feed('i:cq<CR>') screen:expect([[ | - [Process exited 1]{2: } | + [Process exited 1]^ | |*5 {5:-- TERMINAL --} | ]]) @@ -137,60 +135,49 @@ describe('command-line option', function() ]=] end) - it('errors out when trying to use nonexistent file with -s', function() + it('fails when trying to use nonexistent file with -s', function() + local p = n.spawn_wait( + '--cmd', + 'set noswapfile shortmess+=IFW fileformats=unix', + '--cmd', + 'language C', + '-s', + nonexistent_fname + ) eq( 'Cannot open for reading: "' .. nonexistent_fname .. '": no such file or directory\n', - fn.system({ - nvim_prog_abs(), - '-u', - 'NONE', - '-i', - 'NONE', - '--headless', - '--cmd', - 'set noswapfile shortmess+=IFW fileformats=unix', - '--cmd', - 'language C', - '-s', - nonexistent_fname, - }) + --- TODO(justinmk): using `p.output` because Nvim emits CRLF even on non-Win. Emit LF instead? + p:output() ) - eq(2, eval('v:shell_error')) + eq(2, p.status) end) it('errors out when trying to use -s twice', function() write_file(fname, ':call setline(1, "1")\n:wqall!\n') write_file(dollar_fname, ':call setline(1, "2")\n:wqall!\n') - eq( - 'Attempt to open script file again: "-s ' .. dollar_fname .. '"\n', - fn.system({ - nvim_prog_abs(), - '-u', - 'NONE', - '-i', - 'NONE', - '--headless', - '--cmd', - 'set noswapfile shortmess+=IFW fileformats=unix', - '--cmd', - 'language C', - '-s', - fname, - '-s', - dollar_fname, - fname_2, - }) + local p = n.spawn_wait( + '--cmd', + 'set noswapfile shortmess+=IFW fileformats=unix', + '--cmd', + 'language C', + '-s', + fname, + '-s', + dollar_fname, + fname_2 ) - eq(2, eval('v:shell_error')) + --- TODO(justinmk): using `p.output` because Nvim emits CRLF even on non-Win. Emit LF instead? + eq('Attempt to open script file again: "-s ' .. dollar_fname .. '"\n', p:output()) + eq(2, p.status) eq(nil, uv.fs_stat(fname_2)) end) end) it('nvim -v, :version', function() matches('Run ":verbose version"', fn.execute(':version')) - matches('Compilation: .*Run :checkhealth', fn.execute(':verbose version')) - matches('Run "nvim %-V1 %-v"', fn.system({ nvim_prog_abs(), '-v' })) - matches('Compilation: .*Run :checkhealth', fn.system({ nvim_prog_abs(), '-V1', '-v' })) + matches('fall%-back for %$VIM: .*Run :checkhealth', fn.execute(':verbose version')) + matches('Run "nvim %-V1 %-v"', n.spawn_wait('-v').stdout) + matches('fall%-back for %$VIM: .*Run :checkhealth', n.spawn_wait('-V1', '-v').stdout) end) if is_os('win') then @@ -204,7 +191,7 @@ describe('command-line option', function() eq( 'some text', fn.system({ - nvim_prog_abs(), + n.nvim_prog, '-es', '+%print', '+q', diff --git a/test/functional/core/remote_spec.lua b/test/functional/core/remote_spec.lua index 6cc28ddeef..1cfa0535f6 100644 --- a/test/functional/core/remote_spec.lua +++ b/test/functional/core/remote_spec.lua @@ -10,10 +10,8 @@ local expect = n.expect local fn = n.fn local insert = n.insert local nvim_prog = n.nvim_prog -local new_argv = n.new_argv local neq = t.neq local set_session = n.set_session -local spawn = n.spawn local tmpname = t.tmpname local write_file = t.write_file @@ -32,8 +30,7 @@ describe('Remote', function() describe('connect to server and', function() local server before_each(function() - server = spawn(new_argv(), true) - set_session(server) + server = n.clear() end) after_each(function() @@ -49,7 +46,7 @@ describe('Remote', function() -- to wait for the remote instance to exit and calling jobwait blocks -- the event loop. If the server event loop is blocked, it can't process -- our incoming --remote calls. - local client_starter = spawn(new_argv(), false, nil, true) + local client_starter = n.new_session(true) set_session(client_starter) -- Call jobstart() and jobwait() in the same RPC request to reduce flakiness. eq( @@ -144,15 +141,8 @@ describe('Remote', function() describe('exits with error on', function() local function run_and_check_exit_code(...) - local bogus_argv = new_argv(...) - - -- Create an nvim instance just to run the remote-invoking nvim. We want - -- to wait for the remote instance to exit and calling jobwait blocks - -- the event loop. If the server event loop is blocked, it can't process - -- our incoming --remote calls. - clear() - -- Call jobstart() and jobwait() in the same RPC request to reduce flakiness. - eq({ 2 }, exec_lua([[return vim.fn.jobwait({ vim.fn.jobstart(...) })]], bogus_argv)) + local p = n.spawn_wait { args = { ... } } + eq(2, p.status) end it('bogus subcommand', function() run_and_check_exit_code('--remote-bogus') diff --git a/test/functional/core/startup_spec.lua b/test/functional/core/startup_spec.lua index 7062211187..8ecd3dca97 100644 --- a/test/functional/core/startup_spec.lua +++ b/test/functional/core/startup_spec.lua @@ -55,7 +55,10 @@ describe('startup', function() clear() local screen screen = Screen.new(84, 3) - fn.termopen({ nvim_prog, '-u', 'NONE', '--server', eval('v:servername'), '--remote-ui' }) + fn.jobstart( + { nvim_prog, '-u', 'NONE', '--server', eval('v:servername'), '--remote-ui' }, + { term = true } + ) screen:expect([[ ^Cannot attach UI of :terminal child to its parent. (Unset $NVIM to skip this check) | |*2 @@ -74,22 +77,9 @@ describe('startup', function() end) it('--startuptime does not crash on error #31125', function() - eq( - "E484: Can't open file .", - fn.system({ - nvim_prog, - '-u', - 'NONE', - '-i', - 'NONE', - '--headless', - '--startuptime', - '.', - '-c', - '42cquit', - }) - ) - eq(42, api.nvim_get_vvar('shell_error')) + local p = n.spawn_wait('--startuptime', '.', '-c', '42cquit') + eq("E484: Can't open file .", p.stderr) + eq(42, p.status) end) it('-D does not hang #12647', function() @@ -98,7 +88,7 @@ describe('startup', function() screen = Screen.new(60, 7) -- not the same colors on windows for some reason screen._default_attr_ids = nil - local id = fn.termopen({ + local id = fn.jobstart({ nvim_prog, '-u', 'NONE', @@ -108,6 +98,7 @@ describe('startup', function() 'set noruler', '-D', }, { + term = true, env = { VIMRUNTIME = os.getenv('VIMRUNTIME'), }, @@ -145,13 +136,18 @@ describe('startup', function() vim.list_extend(args, { '-l', (script or 'test/functional/fixtures/startup.lua') }) vim.list_extend(args, lua_args or {}) local out = fn.system(args, input):gsub('\r\n', '\n') - return eq(dedent(expected), out) + if type(expected) == 'function' then + return expected(out) + else + return eq(dedent(expected), out) + end end it('failure modes', function() -- nvim -l <empty> - matches('nvim%.?e?x?e?: Argument missing after: "%-l"', fn.system({ nvim_prog, '-l' })) - eq(1, eval('v:shell_error')) + local proc = n.spawn_wait('-l') + matches('nvim%.?e?x?e?: Argument missing after: "%-l"', proc.stderr) + eq(1, proc.status) end) it('os.exit() sets Nvim exitcode', function() @@ -178,13 +174,13 @@ describe('startup', function() end) it('Lua-error sets Nvim exitcode', function() + local proc = n.spawn_wait('-l', 'test/functional/fixtures/startup-fail.lua') + matches('E5113: .* my pearls!!', proc:output()) + eq(0, proc.signal) + eq(1, proc.status) + eq(0, eval('v:shell_error')) matches( - 'E5113: .* my pearls!!', - fn.system({ nvim_prog, '-l', 'test/functional/fixtures/startup-fail.lua' }) - ) - eq(1, eval('v:shell_error')) - matches( 'E5113: .* %[string "error%("whoa"%)"%]:1: whoa', fn.system({ nvim_prog, '-l', '-' }, 'error("whoa")') ) @@ -291,14 +287,30 @@ describe('startup', function() eq(0, eval('v:shell_error')) end) - it('disables swapfile/shada/config/plugins', function() + it('disables swapfile/shada/config/plugins unless overridden', function() + local script = [[print(('updatecount=%d shadafile=%s loadplugins=%s scripts=%d'):format( + vim.o.updatecount, vim.o.shadafile, tostring(vim.o.loadplugins), math.max(1, #vim.fn.getscriptinfo())))]] + finally(function() + os.remove('Xtest_shada') + end) + assert_l_out( 'updatecount=0 shadafile=NONE loadplugins=false scripts=1\n', nil, nil, '-', - [[print(('updatecount=%d shadafile=%s loadplugins=%s scripts=%d'):format( - vim.o.updatecount, vim.o.shadafile, tostring(vim.o.loadplugins), math.max(1, #vim.fn.getscriptinfo())))]] + script + ) + + -- User can override. + assert_l_out( + function(out) + return matches('updatecount=99 shadafile=Xtest_shada loadplugins=true scripts=2%d\n', out) + end, + { '+set updatecount=99', '-i', 'Xtest_shada', '+set loadplugins', '-u', 'NORC' }, + nil, + '-', + script ) end) end) @@ -343,7 +355,7 @@ describe('startup', function() local screen = Screen.new(25, 3) -- Remote UI connected by --embed. -- TODO: a lot of tests in this file already use the new default color scheme. - -- once we do the batch update of tests to use it, remove this workarond + -- once we do the batch update of tests to use it, remove this workaround screen._default_attr_ids = nil command([[echo has('ttyin') has('ttyout')]]) screen:expect([[ @@ -360,7 +372,7 @@ describe('startup', function() command([[set shellcmdflag=/s\ /c shellxquote=\"]]) end -- Running in :terminal - fn.termopen({ + fn.jobstart({ nvim_prog, '-u', 'NONE', @@ -371,6 +383,7 @@ describe('startup', function() '-c', 'echo has("ttyin") has("ttyout")', }, { + term = true, env = { VIMRUNTIME = os.getenv('VIMRUNTIME'), }, @@ -393,13 +406,14 @@ describe('startup', function() os.remove('Xtest_startup_ttyout') end) -- Running in :terminal - fn.termopen( + fn.jobstart( ( [["%s" -u NONE -i NONE --cmd "%s"]] .. [[ -c "call writefile([has('ttyin'), has('ttyout')], 'Xtest_startup_ttyout')"]] .. [[ -c q | cat -v]] ):format(nvim_prog, nvim_set), { + term = true, env = { VIMRUNTIME = os.getenv('VIMRUNTIME'), }, @@ -424,7 +438,7 @@ describe('startup', function() os.remove('Xtest_startup_ttyout') end) -- Running in :terminal - fn.termopen( + fn.jobstart( ( [[echo foo | ]] -- Input from a pipe. .. [["%s" -u NONE -i NONE --cmd "%s"]] @@ -432,6 +446,7 @@ describe('startup', function() .. [[ -c q -- -]] ):format(nvim_prog, nvim_set), { + term = true, env = { VIMRUNTIME = os.getenv('VIMRUNTIME'), }, @@ -454,13 +469,14 @@ describe('startup', function() command([[set shellcmdflag=/s\ /c shellxquote=\"]]) end -- Running in :terminal - fn.termopen( + fn.jobstart( ( [[echo foo | ]] .. [["%s" -u NONE -i NONE --cmd "%s"]] .. [[ -c "echo has('ttyin') has('ttyout')"]] ):format(nvim_prog, nvim_set), { + term = true, env = { VIMRUNTIME = os.getenv('VIMRUNTIME'), }, @@ -577,19 +593,21 @@ describe('startup', function() eq(' encoding=utf-8\n', fn.system({ nvim_prog, '-n', '-es' }, { 'set encoding', '' })) end) - it('-es/-Es disables swapfile, user config #8540', function() + it('-es/-Es disables swapfile/shada/config #8540', function() for _, arg in ipairs({ '-es', '-Es' }) do local out = fn.system({ nvim_prog, arg, - '+set swapfile? updatecount? shadafile?', + '+set updatecount? shadafile? loadplugins?', '+put =map(getscriptinfo(), {-> v:val.name})', '+%print', }) local line1 = string.match(out, '^.-\n') -- updatecount=0 means swapfile was disabled. - eq(' swapfile updatecount=0 shadafile=\n', line1) - -- Standard plugins were loaded, but not user config. + eq(' updatecount=0 shadafile=NONE loadplugins\n', line1) + -- Standard plugins were loaded, but not user config. #31878 + local nrlines = #vim.split(out, '\n') + ok(nrlines > 20, '>20', nrlines) ok(string.find(out, 'man.lua') ~= nil) ok(string.find(out, 'init.vim') == nil) end @@ -598,15 +616,15 @@ describe('startup', function() it('fails on --embed with -es/-Es/-l', function() matches( 'nvim[.exe]*: %-%-embed conflicts with %-es/%-Es/%-l', - fn.system({ nvim_prog, '--embed', '-es' }) + n.spawn_wait('--embed', '-es').stderr ) matches( 'nvim[.exe]*: %-%-embed conflicts with %-es/%-Es/%-l', - fn.system({ nvim_prog, '--embed', '-Es' }) + n.spawn_wait('--embed', '-Es').stderr ) matches( 'nvim[.exe]*: %-%-embed conflicts with %-es/%-Es/%-l', - fn.system({ nvim_prog, '--embed', '-l', 'foo.lua' }) + n.spawn_wait('--embed', '-l', 'foo.lua').stderr ) end) @@ -614,7 +632,7 @@ describe('startup', function() local screen screen = Screen.new(60, 6) screen._default_attr_ids = nil - local id = fn.termopen({ + local id = fn.jobstart({ nvim_prog, '-u', 'NONE', @@ -625,6 +643,7 @@ describe('startup', function() '--cmd', 'let g:foo = g:bar', }, { + term = true, env = { VIMRUNTIME = os.getenv('VIMRUNTIME'), }, @@ -689,20 +708,8 @@ describe('startup', function() end) it('get command line arguments from v:argv', function() - local out = fn.system({ - nvim_prog, - '-u', - 'NONE', - '-i', - 'NONE', - '--headless', - '--cmd', - nvim_set, - '-c', - [[echo v:argv[-1:] len(v:argv) > 1]], - '+q', - }) - eq("['+q'] 1", out) + local p = n.spawn_wait('--cmd', nvim_set, '-c', [[echo v:argv[-1:] len(v:argv) > 1]], '+q') + eq("['+q'] 1", p.stderr) end) end) @@ -1144,7 +1151,8 @@ describe('user config init', function() local screen = Screen.new(50, 8) screen._default_attr_ids = nil - fn.termopen({ nvim_prog }, { + fn.jobstart({ nvim_prog }, { + term = true, env = { VIMRUNTIME = os.getenv('VIMRUNTIME'), }, @@ -1153,7 +1161,7 @@ describe('user config init', function() -- `i` to enter Terminal mode, `a` to allow feed('ia') screen:expect([[ - | + ^ | ~ |*4 [No Name] 0,0-1 All| | @@ -1162,7 +1170,7 @@ describe('user config init', function() feed(':echo g:exrc_file<CR>') screen:expect(string.format( [[ - | + ^ | ~ |*4 [No Name] 0,0-1 All| %s%s| @@ -1418,7 +1426,7 @@ describe('inccommand on ex mode', function() clear() local screen screen = Screen.new(60, 10) - local id = fn.termopen({ + local id = fn.jobstart({ nvim_prog, '-u', 'NONE', @@ -1429,6 +1437,7 @@ describe('inccommand on ex mode', function() '-E', 'test/README.md', }, { + term = true, env = { VIMRUNTIME = os.getenv('VIMRUNTIME') }, }) fn.chansend(id, '%s/N') diff --git a/test/functional/editor/completion_spec.lua b/test/functional/editor/completion_spec.lua index 030181764d..7c68de272b 100644 --- a/test/functional/editor/completion_spec.lua +++ b/test/functional/editor/completion_spec.lua @@ -10,6 +10,7 @@ local fn = n.fn local command = n.command local api = n.api local poke_eventloop = n.poke_eventloop +local exec_lua = n.exec_lua describe('completion', function() local screen @@ -1023,18 +1024,18 @@ describe('completion', function() it("'ignorecase' 'infercase' CTRL-X CTRL-N #6451", function() feed_command('set ignorecase infercase') - feed_command('edit runtime/doc/backers.txt') + feed_command('edit runtime/doc/credits.txt') feed('oX<C-X><C-N>') screen:expect { grid = [[ - *backers.txt* Nvim | - Xnull^ | - {12:Xnull }{101: } | - {4:Xoxomoon }{101: } | - {4:Xu }{101: } NVIM REFERENCE MANUAL | - {4:Xpayn }{12: } | - {4:Xinity }{12: } | - {5:-- Keyword Local completion (^N^P) }{6:match 1 of 7} | + *credits.txt* Nvim | + Xvi^ | + {12:Xvi }{101: } | + {4:Xvim }{101: } | + {4:X11 }{12: } NVIM REFERENCE MANUAL | + {4:Xnull }{12: } | + {4:Xoxomoon }{12: } | + {5:-- Keyword Local completion (^N^P) }{6:match 1 of 10} | ]], } end) @@ -1265,7 +1266,7 @@ describe('completion', function() command([[ call setline(1, ['aaaa']) let ns_id = nvim_create_namespace('extmark') - let mark_id = nvim_buf_set_extmark(0, ns_id, 0, 0, { 'end_col':2, 'hl_group':'Error'}) + let mark_id = nvim_buf_set_extmark(0, ns_id, 0, 0, { 'end_col':2, 'hl_group':'Error' }) let mark = nvim_buf_get_extmark_by_id(0, ns_id, mark_id, { 'details':1 }) inoremap <C-x> <C-r>=Complete()<CR> function Complete() abort @@ -1302,5 +1303,53 @@ describe('completion', function() aaaaa | {5:-- INSERT --} | ]]) + -- Also when completion leader is changed #31384 + feed('<Esc>hi<C-N><C-P>a') + screen:expect({ + grid = [[ + {9:aa}a^aa | + {4:aaaa } | + {4:aaaaa } | + {5:-- Keyword completion (^N^P) }{19:Back at original} | + ]], + }) + -- But still grows with end_right_gravity #31437 + command( + "call nvim_buf_set_extmark(0, ns_id, 1, 0, { 'end_col':2, 'hl_group':'Error', 'end_right_gravity': 1 })" + ) + feed('<Esc>ji<C-N>a') + screen:expect({ + grid = [[ + {9:aa}aaa | + {9:aaa}^aa | + aaaaa | + {5:-- INSERT --} | + ]], + }) + end) + + describe('nvim__complete_set', function() + it("fails when 'completeopt' does not include popup", function() + exec_lua([[ + function _G.omni_test(findstart, base) + if findstart == 1 then + return vim.fn.col('.') - 1 + end + return { { word = 'one' } } + end + vim.api.nvim_create_autocmd('CompleteChanged', { + callback = function() + local ok, err = pcall(vim.api.nvim__complete_set, 0, { info = '1info' }) + if not ok then + vim.g.err_msg = err + end + end, + }) + vim.opt.completeopt = 'menu,menuone' + vim.opt.omnifunc = 'v:lua.omni_test' + ]]) + feed('S<C-X><C-O>') + eq('completeopt option does not include popup', api.nvim_get_var('err_msg')) + end) end) end) diff --git a/test/functional/editor/defaults_spec.lua b/test/functional/editor/defaults_spec.lua index 82d285cc9a..ee6bfa1be9 100644 --- a/test/functional/editor/defaults_spec.lua +++ b/test/functional/editor/defaults_spec.lua @@ -32,7 +32,7 @@ describe('default', function() describe('popupmenu', function() it('can be disabled by user', function() n.clear { - args = { '+autocmd! nvim_popupmenu', '+aunmenu PopUp' }, + args = { '+autocmd! nvim.popupmenu', '+aunmenu PopUp' }, } local screen = Screen.new(40, 8) n.insert([[ @@ -94,8 +94,103 @@ describe('default', function() end) describe('key mappings', function() + describe('Visual mode search mappings', function() + it('handle various chars properly', function() + n.clear({ args_rm = { '--cmd' } }) + local screen = Screen.new(60, 8) + screen:set_default_attr_ids({ + [1] = { foreground = Screen.colors.NvimDarkGray4 }, + [2] = { + foreground = Screen.colors.NvimDarkGray3, + background = Screen.colors.NvimLightGray3, + }, + [3] = { + foreground = Screen.colors.NvimLightGrey1, + background = Screen.colors.NvimDarkYellow, + }, + [4] = { + foreground = Screen.colors.NvimDarkGrey1, + background = Screen.colors.NvimLightYellow, + }, + }) + n.api.nvim_buf_set_lines(0, 0, -1, true, { + [[testing <CR> /?\!1]], + [[testing <CR> /?\!2]], + [[testing <CR> /?\!3]], + [[testing <CR> /?\!4]], + }) + n.feed('gg0vf!o*') + screen:expect([[ + {3:testing <CR> /?\!}1 | + {4:^testing <CR> /?\!}2 | + {3:testing <CR> /?\!}3 | + {3:testing <CR> /?\!}4 | + {1:~ }|*2 + {2:[No Name] [+] 2,1 All}| + /\Vtesting <CR> \/?\\! [2/4] | + ]]) + n.feed('n') + screen:expect([[ + {3:testing <CR> /?\!}1 | + {3:testing <CR> /?\!}2 | + {4:^testing <CR> /?\!}3 | + {3:testing <CR> /?\!}4 | + {1:~ }|*2 + {2:[No Name] [+] 3,1 All}| + /\Vtesting <CR> \/?\\! [3/4] | + ]]) + n.feed('G0vf!o#') + screen:expect([[ + {3:testing <CR> /?\!}1 | + {3:testing <CR> /?\!}2 | + {4:^testing <CR> /?\!}3 | + {3:testing <CR> /?\!}4 | + {1:~ }|*2 + {2:[No Name] [+] 3,1 All}| + ?\Vtesting <CR> /?\\! [3/4] | + ]]) + n.feed('n') + screen:expect([[ + {3:testing <CR> /?\!}1 | + {4:^testing <CR> /?\!}2 | + {3:testing <CR> /?\!}3 | + {3:testing <CR> /?\!}4 | + {1:~ }|*2 + {2:[No Name] [+] 2,1 All}| + ?\Vtesting <CR> /?\\! [2/4] | + ]]) + end) + end) + describe('unimpaired-style mappings', function() - it('do not show a full stack trace #30625', function() + it('show the command ouptut when successful', function() + n.clear({ args_rm = { '--cmd' } }) + local screen = Screen.new(40, 8) + n.fn.setqflist({ + { filename = 'file1', text = 'item1' }, + { filename = 'file2', text = 'item2' }, + }) + + n.feed(']q') + + screen:set_default_attr_ids({ + [1] = { foreground = Screen.colors.NvimDarkGrey4 }, + [2] = { + background = Screen.colors.NvimLightGray3, + foreground = Screen.colors.NvimDarkGrey3, + }, + }) + screen:expect({ + grid = [[ + ^ | + {1:~ }|*5 + {2:file2 0,0-1 All}| + (2 of 2): item2 | + ]], + }) + end) + + it('do not show a full stack trace when unsuccessful #30625', function() n.clear({ args_rm = { '--cmd' } }) local screen = Screen.new(40, 8) screen:set_default_attr_ids({ diff --git a/test/functional/editor/mode_normal_spec.lua b/test/functional/editor/mode_normal_spec.lua index 5237f51237..353a261edb 100644 --- a/test/functional/editor/mode_normal_spec.lua +++ b/test/functional/editor/mode_normal_spec.lua @@ -26,10 +26,10 @@ describe('Normal mode', function() it('&showcmd does not crash with :startinsert #28419', function() local screen = Screen.new(60, 17) - fn.termopen( - { n.nvim_prog, '--clean', '--cmd', 'startinsert' }, - { env = { VIMRUNTIME = os.getenv('VIMRUNTIME') } } - ) + fn.jobstart({ n.nvim_prog, '--clean', '--cmd', 'startinsert' }, { + term = true, + env = { VIMRUNTIME = os.getenv('VIMRUNTIME') }, + }) screen:expect({ grid = [[ ^ | diff --git a/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua b/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua index 2820cc9663..f84fc140d2 100644 --- a/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua +++ b/test/functional/ex_cmds/swapfile_preserve_recover_spec.lua @@ -12,12 +12,10 @@ local fn = n.fn local nvim_prog = n.nvim_prog local ok = t.ok local rmdir = n.rmdir -local new_argv = n.new_argv local new_pipename = n.new_pipename local pesc = vim.pesc local os_kill = n.os_kill local set_session = n.set_session -local spawn = n.spawn local async_meths = n.async_meths local expect_msg_seq = n.expect_msg_seq local pcall_err = t.pcall_err @@ -56,7 +54,7 @@ describe("preserve and (R)ecover with custom 'directory'", function() local nvim0 before_each(function() - nvim0 = spawn(new_argv()) + nvim0 = n.new_session(false) set_session(nvim0) rmdir(swapdir) mkdir(swapdir) @@ -76,7 +74,8 @@ describe("preserve and (R)ecover with custom 'directory'", function() local function test_recover(swappath1) -- Start another Nvim instance. - local nvim2 = spawn({ nvim_prog, '-u', 'NONE', '-i', 'NONE', '--embed' }, true) + local nvim2 = + n.new_session(false, { args = { '-u', 'NONE', '-i', 'NONE', '--embed' }, merge = false }) set_session(nvim2) exec(init) @@ -115,7 +114,8 @@ describe("preserve and (R)ecover with custom 'directory'", function() t.skip(t.is_os('win')) local screen0 = Screen.new() local child_server = new_pipename() - fn.termopen({ nvim_prog, '-u', 'NONE', '-i', 'NONE', '--listen', child_server }, { + fn.jobstart({ nvim_prog, '-u', 'NONE', '-i', 'NONE', '--listen', child_server }, { + term = true, env = { VIMRUNTIME = os.getenv('VIMRUNTIME') }, }) screen0:expect({ any = pesc('[No Name]') }) -- Wait for the child process to start. @@ -140,7 +140,7 @@ describe('swapfile detection', function() set swapfile fileformat=unix nomodified undolevels=-1 nohidden ]] before_each(function() - nvim0 = spawn(new_argv()) + nvim0 = n.new_session(false) set_session(nvim0) rmdir(swapdir) mkdir(swapdir) @@ -167,12 +167,13 @@ describe('swapfile detection', function() command('preserve') -- Start another Nvim instance. - local nvim2 = spawn({ nvim_prog, '-u', 'NONE', '-i', 'NONE', '--embed' }, true, nil, true) + local nvim2 = + n.new_session(true, { args = { '-u', 'NONE', '-i', 'NONE', '--embed' }, merge = false }) set_session(nvim2) local screen2 = Screen.new(256, 40) screen2._default_attr_ids = nil exec(init) - command('autocmd! nvim_swapfile') -- Delete the default handler (which skips the dialog). + command('autocmd! nvim.swapfile') -- Delete the default handler (which skips the dialog). -- With shortmess+=F command('set shortmess+=F') @@ -219,6 +220,7 @@ describe('swapfile detection', function() .. [[%.swp"]], } feed('e') -- Chose "Edit" at the swap dialog. + screen2:expect({ any = pesc('E5555: API call: Vim(edit):E325: ATTENTION') }) feed('<c-c>') screen2:expect(expected_no_dialog) @@ -249,7 +251,7 @@ describe('swapfile detection', function() command('preserve') -- Make sure the swap file exists. local nvimpid = fn.getpid() - local nvim1 = spawn(new_argv(), true, nil, true) + local nvim1 = n.new_session(true) set_session(nvim1) local screen = Screen.new(75, 18) exec(init) @@ -271,11 +273,11 @@ describe('swapfile detection', function() [1] = { bold = true, foreground = Screen.colors.SeaGreen }, -- MoreMsg }) - local nvim1 = spawn(new_argv(), true, nil, true) + local nvim1 = n.new_session(true) set_session(nvim1) screen:attach() exec(init) - command('autocmd! nvim_swapfile') -- Delete the default handler (which skips the dialog). + command('autocmd! nvim.swapfile') -- Delete the default handler (which skips the dialog). feed(':split Xfile1\n') -- The default SwapExists handler does _not_ skip this prompt. screen:expect({ @@ -290,11 +292,11 @@ describe('swapfile detection', function() ]]) nvim1:close() - local nvim2 = spawn(new_argv(), true, nil, true) + local nvim2 = n.new_session(true) set_session(nvim2) screen:attach() exec(init) - command('autocmd! nvim_swapfile') -- Delete the default handler (which skips the dialog). + command('autocmd! nvim.swapfile') -- Delete the default handler (which skips the dialog). command('set more') command('au bufadd * let foo_w = wincol()') feed(':e Xfile1<CR>') @@ -325,7 +327,7 @@ describe('swapfile detection', function() exec(init) if not swapexists then - command('autocmd! nvim_swapfile') -- Delete the default handler (which skips the dialog). + command('autocmd! nvim.swapfile') -- Delete the default handler (which skips the dialog). end command('set nohidden') @@ -446,9 +448,10 @@ describe('quitting swapfile dialog on startup stops TUI properly', function() end) it('(Q)uit at first file argument', function() - local chan = fn.termopen( + local chan = fn.jobstart( { nvim_prog, '-u', 'NONE', '-i', 'NONE', '--cmd', init_dir, '--cmd', init_set, testfile }, { + term = true, env = { VIMRUNTIME = os.getenv('VIMRUNTIME') }, } ) @@ -468,7 +471,7 @@ describe('quitting swapfile dialog on startup stops TUI properly', function() end) it('(A)bort at second file argument with -p', function() - local chan = fn.termopen({ + local chan = fn.jobstart({ nvim_prog, '-u', 'NONE', @@ -482,6 +485,7 @@ describe('quitting swapfile dialog on startup stops TUI properly', function() otherfile, testfile, }, { + term = true, env = { VIMRUNTIME = os.getenv('VIMRUNTIME') }, }) retry(nil, nil, function() @@ -508,7 +512,7 @@ describe('quitting swapfile dialog on startup stops TUI properly', function() second %s /^ \zssecond$/ third %s /^ \zsthird$/]]):format(testfile, testfile, testfile) ) - local chan = fn.termopen({ + local chan = fn.jobstart({ nvim_prog, '-u', 'NONE', @@ -522,6 +526,7 @@ describe('quitting swapfile dialog on startup stops TUI properly', function() 'set tags=' .. otherfile, '-tsecond', }, { + term = true, env = { VIMRUNTIME = os.getenv('VIMRUNTIME') }, }) retry(nil, nil, function() @@ -532,10 +537,6 @@ describe('quitting swapfile dialog on startup stops TUI properly', function() end) api.nvim_chan_send(chan, 'q') retry(nil, nil, function() - eq('Press ENTER or type command to continue', eval("getline('$')->trim(' ', 2)")) - end) - api.nvim_chan_send(chan, '\r') - retry(nil, nil, function() eq( { '', '[Process exited 1]', '' }, eval("[1, 2, '$']->map({_, lnum -> getline(lnum)->trim(' ', 2)})") diff --git a/test/functional/ex_cmds/wundo_spec.lua b/test/functional/ex_cmds/wundo_spec.lua index 2299f33f06..9b81c6d06d 100644 --- a/test/functional/ex_cmds/wundo_spec.lua +++ b/test/functional/ex_cmds/wundo_spec.lua @@ -5,8 +5,6 @@ local n = require('test.functional.testnvim')() local command = n.command local clear = n.clear local eval = n.eval -local spawn = n.spawn -local nvim_prog = n.nvim_prog local set_session = n.set_session describe(':wundo', function() @@ -24,15 +22,11 @@ end) describe('u_* functions', function() it('safely fail on new, non-empty buffer', function() - local session = spawn({ - nvim_prog, - '-u', - 'NONE', - '-i', - 'NONE', - '--embed', - '-c', - 'set undodir=. undofile', + local session = n.new_session(false, { + args = { + '-c', + 'set undodir=. undofile', + }, }) set_session(session) command('echo "True"') -- Should not error out due to crashed Neovim diff --git a/test/functional/fixtures/CMakeLists.txt b/test/functional/fixtures/CMakeLists.txt index 150407fe46..a388f9cb33 100644 --- a/test/functional/fixtures/CMakeLists.txt +++ b/test/functional/fixtures/CMakeLists.txt @@ -1,7 +1,4 @@ add_library(test_lib INTERFACE) -if(MINGW) - target_link_libraries(test_lib INTERFACE -municode) -endif() if(WIN32) target_compile_definitions(test_lib INTERFACE MSWIN) endif() diff --git a/test/functional/fixtures/fake-lsp-server.lua b/test/functional/fixtures/fake-lsp-server.lua index f813927f77..5d7ab2ad12 100644 --- a/test/functional/fixtures/fake-lsp-server.lua +++ b/test/functional/fixtures/fake-lsp-server.lua @@ -386,6 +386,21 @@ function tests.check_forward_content_modified() } end +function tests.check_forward_server_cancelled() + skeleton { + on_init = function() + return { capabilities = {} } + end, + body = function() + expect_request('error_code_test', function() + return { code = -32802 }, nil, { method = 'error_code_test', client_id = 1 } + end) + expect_notification('finish') + notify('finish') + end, + } +end + function tests.check_pending_request_tracked() skeleton { on_init = function(_) diff --git a/test/functional/fixtures/printargs-test.c b/test/functional/fixtures/printargs-test.c index 2c25cf8447..a1d3fdf76e 100644 --- a/test/functional/fixtures/printargs-test.c +++ b/test/functional/fixtures/printargs-test.c @@ -2,7 +2,7 @@ int main(int argc, char **argv) { - for (int i=1; i<argc; i++) { + for (int i = 1; i < argc; i++) { printf("arg%d=%s;", i, argv[i]); } return 0; diff --git a/test/functional/fixtures/shell-test.c b/test/functional/fixtures/shell-test.c index bd71e7d11b..f3e94a28da 100644 --- a/test/functional/fixtures/shell-test.c +++ b/test/functional/fixtures/shell-test.c @@ -1,11 +1,12 @@ +#include <stdbool.h> +#include <stdint.h> #include <stdio.h> #include <string.h> -#include <stdint.h> #ifdef _MSC_VER -#include <Windows.h> -#define usleep(usecs) Sleep(usecs/1000) +# include <Windows.h> +# define usleep(usecs) Sleep(usecs/1000) #else -#include <unistd.h> +# include <unistd.h> #endif static void flush_wait(void) @@ -56,7 +57,7 @@ int main(int argc, char **argv) if (argc >= 2) { if (strcmp(argv[1], "-t") == 0) { if (argc < 3) { - fprintf(stderr,"Missing prompt text for -t option\n"); + fprintf(stderr, "Missing prompt text for -t option\n"); return 5; } else { fprintf(stderr, "%s $ ", argv[2]); @@ -107,18 +108,18 @@ int main(int argc, char **argv) char cmd[100]; int arg; - while (1) { + while (true) { fprintf(stderr, "interact $ "); if (fgets(input, sizeof(input), stdin) == NULL) { break; // EOF } - if(1 == sscanf(input, "%99s %d", cmd, &arg)) { + if (1 == sscanf(input, "%99s %d", cmd, &arg)) { arg = 0; } if (strcmp(cmd, "exit") == 0) { - return arg; + return arg; } else { fprintf(stderr, "command not found: %s\n", cmd); } diff --git a/test/functional/fixtures/streams-test.c b/test/functional/fixtures/streams-test.c index 5a59abb33b..68e668d5fa 100644 --- a/test/functional/fixtures/streams-test.c +++ b/test/functional/fixtures/streams-test.c @@ -1,6 +1,5 @@ /// Helper program to exit and keep stdout open (like "xclip -i -loops 1"). #include <stdio.h> - #include <uv.h> int main(int argc, char **argv) @@ -8,7 +7,7 @@ int main(int argc, char **argv) uv_loop_t *loop = uv_default_loop(); uv_process_t child_req; - char * args[3]; + char *args[3]; args[0] = "sleep"; args[1] = "10"; args[2] = NULL; diff --git a/test/functional/legacy/cmdline_spec.lua b/test/functional/legacy/cmdline_spec.lua index bf146e1322..819b40323a 100644 --- a/test/functional/legacy/cmdline_spec.lua +++ b/test/functional/legacy/cmdline_spec.lua @@ -159,6 +159,50 @@ describe('cmdline', function() endfunc ]]) + feed(':resize -3<CR>') + screen:expect([[ + ^ | + {1:~ }|*2 + {3:[No Name] }| + |*4 + ]]) + + -- :resize now also changes 'cmdheight' accordingly + feed(':set cmdheight+=1<CR>') + screen:expect([[ + ^ | + {1:~ }| + {3:[No Name] }| + |*5 + ]]) + + -- using more space moves the status line up + feed(':set cmdheight+=1<CR>') + screen:expect([[ + ^ | + {3:[No Name] }| + |*6 + ]]) + + -- reducing cmdheight moves status line down + feed(':set cmdheight-=3<CR>') + screen:expect([[ + ^ | + {1:~ }|*3 + {3:[No Name] }| + |*3 + ]]) + + -- reducing window size and then setting cmdheight + feed(':resize -1<CR>') + feed(':set cmdheight=1<CR>') + screen:expect([[ + ^ | + {1:~ }|*5 + {3:[No Name] }| + | + ]]) + -- setting 'cmdheight' works after outputting two messages feed(':call EchoTwo()') screen:expect([[ @@ -185,6 +229,16 @@ describe('cmdline', function() bar | {6:Press ENTER or type command to continue}^ | ]]) + + -- window commands do not reduce 'cmdheight' to value lower than :set by user + feed('<CR>:wincmd _<CR>') + screen:expect([[ + ^ | + {1:~ }|*4 + {3:[No Name] }| + :wincmd _ | + | + ]]) end) -- oldtest: Test_cmdheight_tabline() @@ -216,6 +270,22 @@ describe('cmdline', function() longish | ]] end) + + -- oldtest: Test_rulerformat_function() + it("'rulerformat' can use %!", function() + local screen = Screen.new(40, 2) + exec([[ + func TestRulerFn() + return '10,20%=30%%' + endfunc + ]]) + api.nvim_set_option_value('ruler', true, {}) + api.nvim_set_option_value('rulerformat', '%!TestRulerFn()', {}) + screen:expect([[ + ^ | + 10,20 30% | + ]]) + end) end) describe('cmdwin', function() diff --git a/test/functional/legacy/highlight_spec.lua b/test/functional/legacy/highlight_spec.lua index e663931bd7..24d6abcc6b 100644 --- a/test/functional/legacy/highlight_spec.lua +++ b/test/functional/legacy/highlight_spec.lua @@ -29,8 +29,8 @@ describe(':highlight', function() | TermCursor {2:xxx} {18:cterm=}reverse | {18:gui=}reverse | - TermCursorNC xxx cleared | NonText {1:xxx} {18:ctermfg=}12 | + {18:gui=}bold | {6:-- More --}^ | ]]) feed('q') diff --git a/test/functional/legacy/messages_spec.lua b/test/functional/legacy/messages_spec.lua index bc58551a34..db5e3f6857 100644 --- a/test/functional/legacy/messages_spec.lua +++ b/test/functional/legacy/messages_spec.lua @@ -82,7 +82,7 @@ describe('messages', function() NoSuchFil^e | three | {1:~ }|*5 - from DebugSilent visual | + | {9:E447: Can't find file "NoSuchFile" in path} | ]]) end) @@ -689,4 +689,43 @@ describe('messages', function() ]]) os.remove('b.txt') end) + + -- oldtest: Test_messagesopt_wait() + it('&messagesopt "wait"', function() + screen = Screen.new(45, 6) + command('set cmdheight=1') + + -- Check hit-enter prompt + command('set messagesopt=hit-enter,history:500') + feed(":echo 'foo' | echo 'bar' | echo 'baz'\n") + screen:expect([[ + | + {3: }| + foo | + bar | + baz | + {6:Press ENTER or type command to continue}^ | + ]]) + feed('<CR>') + + -- Check no hit-enter prompt when "wait:" is set + command('set messagesopt=wait:500,history:500') + feed(":echo 'foo' | echo 'bar' | echo 'baz'\n") + screen:expect({ + grid = [[ + | + {1:~ }| + {3: }| + foo | + bar | + baz | + ]], + timeout = 500, + }) + screen:expect([[ + ^ | + {1:~ }|*4 + | + ]]) + end) end) diff --git a/test/functional/legacy/signs_spec.lua b/test/functional/legacy/signs_spec.lua index ac7c8cd0bc..af355f27e6 100644 --- a/test/functional/legacy/signs_spec.lua +++ b/test/functional/legacy/signs_spec.lua @@ -26,6 +26,9 @@ describe('signs', function() -- oldtest: Test_sign_cursor_position() it('are drawn correctly', function() local screen = Screen.new(75, 6) + screen:add_extra_attr_ids({ + [100] = { foreground = Screen.colors.Blue4, background = Screen.colors.Yellow }, + }) exec([[ call setline(1, [repeat('x', 75), 'mmmm', 'yyyy']) call cursor(2,1) @@ -37,7 +40,7 @@ describe('signs', function() screen:expect([[ {7: }xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx| {7: }xx | - {10:=>}^mmmm | + {100:=>}^mmmm | {7: }yyyy | {1:~ }| | @@ -48,7 +51,7 @@ describe('signs', function() screen:expect([[ {7: }xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx| {7: }xx | - {10:-)}^mmmm | + {100:-)}^mmmm | {7: }yyyy | {1:~ }| | @@ -59,7 +62,7 @@ describe('signs', function() screen:expect([[ {7: }xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx| {7: }xx | - {10:-)}{4:^mmmm }| + {100:-)}{4:^mmmm }| {7: }yyyy | {1:~ }| | diff --git a/test/functional/legacy/substitute_spec.lua b/test/functional/legacy/substitute_spec.lua index 0081371f7f..30ff13140f 100644 --- a/test/functional/legacy/substitute_spec.lua +++ b/test/functional/legacy/substitute_spec.lua @@ -220,8 +220,10 @@ describe(':substitute', function() {2:o}ne | two | three | - {1:~ }|*4 - {6:replace with (y/n/a/q/l/^E/^Y)?}^ | + {1:~ }|*2 + {3: }| + {6:replace with ? (y)es/(n)o/(a)ll/(q)uit/(l)ast/scroll up(^}| + {6:E)/down(^Y)}^ | ]]) end) end) diff --git a/test/functional/legacy/window_cmd_spec.lua b/test/functional/legacy/window_cmd_spec.lua index d68c780f49..fac982354c 100644 --- a/test/functional/legacy/window_cmd_spec.lua +++ b/test/functional/legacy/window_cmd_spec.lua @@ -120,7 +120,7 @@ describe('splitkeep', function() row = 0, col = 0, })) - vim.cmd("call termopen([&sh, &shcf, 'true'], { 'on_exit': 'C2' })") + vim.cmd("call jobstart([&sh, &shcf, 'true'], { 'term': v:true, 'on_exit': 'C2' })") end })]]) feed('j') @@ -299,7 +299,7 @@ describe('splitkeep', function() c | {1:~ }| {3:[No Name] }| - | + :call win_move_statusline(win, 1) | ]]) end) diff --git a/test/functional/lua/diagnostic_spec.lua b/test/functional/lua/diagnostic_spec.lua index eb1ac3e6a1..a19f558ef2 100644 --- a/test/functional/lua/diagnostic_spec.lua +++ b/test/functional/lua/diagnostic_spec.lua @@ -5,6 +5,7 @@ local command = n.command local clear = n.clear local exec_lua = n.exec_lua local eq = t.eq +local neq = t.neq local matches = t.matches local api = n.api local pcall_err = t.pcall_err @@ -112,6 +113,18 @@ describe('vim.diagnostic', function() ) end + function _G.get_virt_lines_extmarks(ns) + ns = vim.diagnostic.get_namespace(ns) + local virt_lines_ns = ns.user_data.virt_lines_ns + return vim.api.nvim_buf_get_extmarks( + _G.diagnostic_bufnr, + virt_lines_ns, + 0, + -1, + { details = true } + ) + end + ---@param ns integer function _G.get_underline_extmarks(ns) ---@type integer @@ -160,6 +173,11 @@ describe('vim.diagnostic', function() 'DiagnosticUnderlineOk', 'DiagnosticUnderlineWarn', 'DiagnosticUnnecessary', + 'DiagnosticVirtualLinesError', + 'DiagnosticVirtualLinesHint', + 'DiagnosticVirtualLinesInfo', + 'DiagnosticVirtualLinesOk', + 'DiagnosticVirtualLinesWarn', 'DiagnosticVirtualTextError', 'DiagnosticVirtualTextHint', 'DiagnosticVirtualTextInfo', @@ -368,6 +386,9 @@ describe('vim.diagnostic', function() end) it('handles one namespace clearing highlights while the other still has highlights', function() + exec_lua(function() + vim.diagnostic.config({ virtual_text = true }) + end) -- 1 Error (1) -- 1 Warning (2) -- 1 Warning (2) + 1 Warning (1) @@ -442,6 +463,10 @@ describe('vim.diagnostic', function() end) it('does not display diagnostics when disabled', function() + exec_lua(function() + vim.diagnostic.config({ virtual_text = true }) + end) + eq( { 0, 2 }, exec_lua(function() @@ -574,7 +599,7 @@ describe('vim.diagnostic', function() vim.diagnostic.set( _G.diagnostic_ns, _G.diagnostic_bufnr, - { { lnum = 0, end_lnum = 0, col = 0, end_col = 0 } } + { { message = '', lnum = 0, end_lnum = 0, col = 0, end_col = 0 } } ) vim.cmd('bwipeout! ' .. _G.diagnostic_bufnr) @@ -915,6 +940,10 @@ describe('vim.diagnostic', function() describe('reset()', function() it('diagnostic count is 0 and displayed diagnostics are 0 after call', function() + exec_lua(function() + vim.diagnostic.config({ virtual_text = true }) + end) + -- 1 Error (1) -- 1 Warning (2) -- 1 Warning (2) + 1 Warning (1) @@ -1005,7 +1034,7 @@ describe('vim.diagnostic', function() vim.diagnostic.set( _G.diagnostic_ns, _G.diagnostic_bufnr, - { { lnum = 0, end_lnum = 0, col = 0, end_col = 0 } } + { { message = '', lnum = 0, end_lnum = 0, col = 0, end_col = 0 } } ) vim.cmd('bwipeout! ' .. _G.diagnostic_bufnr) @@ -2105,6 +2134,139 @@ describe('vim.diagnostic', function() end) ) end) + + it('can filter diagnostics by returning nil when formatting', function() + local result = exec_lua(function() + vim.diagnostic.config { + virtual_text = { + format = function(diagnostic) + if diagnostic.code == 'foo_err' then + return nil + end + return diagnostic.message + end, + }, + } + + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_error('An error here!', 0, 0, 0, 0, 'foo_server', 'foo_err'), + _G.make_error('An error there!', 1, 1, 1, 1, 'bar_server', 'bar_err'), + }) + + local extmarks = _G.get_virt_text_extmarks(_G.diagnostic_ns) + return extmarks + end) + + eq(1, #result) + eq(' An error there!', result[1][4].virt_text[3][1]) + end) + + it('can only show virtual_text for the current line', function() + local result = exec_lua(function() + vim.api.nvim_win_set_cursor(0, { 1, 0 }) + + vim.diagnostic.config({ virtual_text = { current_line = true } }) + + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_error('Error here!', 0, 0, 0, 0, 'foo_server'), + _G.make_error('Another error there!', 1, 0, 1, 0, 'foo_server'), + }) + + local extmarks = _G.get_virt_text_extmarks(_G.diagnostic_ns) + return extmarks + end) + + eq(1, #result) + eq(' Error here!', result[1][4].virt_text[3][1]) + end) + end) + + describe('handlers.virtual_lines', function() + it('includes diagnostic code and message', function() + local result = exec_lua(function() + vim.diagnostic.config({ virtual_lines = true }) + + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_error('Missed symbol `,`', 0, 0, 0, 0, 'lua_ls', 'miss-symbol'), + }) + + local extmarks = _G.get_virt_lines_extmarks(_G.diagnostic_ns) + return extmarks[1][4].virt_lines + end) + + eq('miss-symbol: Missed symbol `,`', result[1][3][1]) + end) + + it('adds space to the left of the diagnostic', function() + local error_offset = 5 + local result = exec_lua(function() + vim.diagnostic.config({ virtual_lines = true }) + + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_error('Error here!', 0, error_offset, 0, error_offset, 'foo_server'), + }) + + local extmarks = _G.get_virt_lines_extmarks(_G.diagnostic_ns) + return extmarks[1][4].virt_lines + end) + + eq(error_offset, result[1][1][1]:len()) + end) + + it('highlights diagnostics in multiple lines by default', function() + local result = exec_lua(function() + vim.diagnostic.config({ virtual_lines = true }) + + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_error('Error here!', 0, 0, 0, 0, 'foo_server'), + _G.make_error('Another error there!', 1, 0, 1, 0, 'foo_server'), + }) + + local extmarks = _G.get_virt_lines_extmarks(_G.diagnostic_ns) + return extmarks + end) + + eq(2, #result) + eq('Error here!', result[1][4].virt_lines[1][3][1]) + eq('Another error there!', result[2][4].virt_lines[1][3][1]) + end) + + it('can highlight diagnostics only in the current line', function() + local result = exec_lua(function() + vim.api.nvim_win_set_cursor(0, { 1, 0 }) + + vim.diagnostic.config({ virtual_lines = { current_line = true } }) + + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_error('Error here!', 0, 0, 0, 0, 'foo_server'), + _G.make_error('Another error there!', 1, 0, 1, 0, 'foo_server'), + }) + + local extmarks = _G.get_virt_lines_extmarks(_G.diagnostic_ns) + return extmarks + end) + + eq(1, #result) + eq('Error here!', result[1][4].virt_lines[1][3][1]) + end) + + it('supports a format function for diagnostic messages', function() + local result = exec_lua(function() + vim.diagnostic.config({ + virtual_lines = { + format = function() + return 'Error here!' + end, + }, + }) + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_error('Invalid syntax', 0, 0, 0, 0), + }) + local extmarks = _G.get_virt_lines_extmarks(_G.diagnostic_ns) + return extmarks[1][4].virt_lines + end) + eq('Error here!', result[1][3][1]) + end) end) describe('set()', function() @@ -2116,7 +2278,11 @@ describe('vim.diagnostic', function() end) it('can perform updates after insert_leave', function() - exec_lua [[vim.api.nvim_set_current_buf( _G.diagnostic_bufnr)]] + exec_lua(function() + vim.diagnostic.config({ virtual_text = true }) + vim.api.nvim_set_current_buf(_G.diagnostic_bufnr) + end) + api.nvim_input('o') eq({ mode = 'i', blocking = false }, api.nvim_get_mode()) @@ -2257,7 +2423,10 @@ describe('vim.diagnostic', function() end) it('can perform updates while in insert mode, if desired', function() - exec_lua [[vim.api.nvim_set_current_buf( _G.diagnostic_bufnr)]] + exec_lua(function() + vim.diagnostic.config({ virtual_text = true }) + vim.api.nvim_set_current_buf(_G.diagnostic_bufnr) + end) api.nvim_input('o') eq({ mode = 'i', blocking = false }, api.nvim_get_mode()) @@ -2291,6 +2460,10 @@ describe('vim.diagnostic', function() end) it('can set diagnostics without displaying them', function() + exec_lua(function() + vim.diagnostic.config({ virtual_text = true }) + end) + eq( 0, exec_lua(function() @@ -3212,6 +3385,74 @@ describe('vim.diagnostic', function() end) end) + describe('setqflist()', function() + it('updates existing diagnostics quickfix if one already exists', function() + local result = exec_lua(function() + vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr) + + vim.fn.setqflist({}, ' ', { title = 'Diagnostics' }) + local diagnostics_qf_id = vim.fn.getqflist({ id = 0 }).id + + vim.diagnostic.setqflist({ title = 'Diagnostics' }) + local qf_id = vim.fn.getqflist({ id = 0, nr = '$' }).id + + return { diagnostics_qf_id, qf_id } + end) + + eq(result[1], result[2]) + end) + + it('navigates to existing diagnostics quickfix if one already exists and open=true', function() + local result = exec_lua(function() + vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr) + + vim.fn.setqflist({}, ' ', { title = 'Diagnostics' }) + local diagnostics_qf_id = vim.fn.getqflist({ id = 0 }).id + + vim.fn.setqflist({}, ' ', { title = 'Other' }) + + vim.diagnostic.setqflist({ title = 'Diagnostics', open = true }) + local qf_id = vim.fn.getqflist({ id = 0 }).id + + return { diagnostics_qf_id, qf_id } + end) + + eq(result[1], result[2]) + end) + + it('sets new diagnostics quickfix as active when open=true', function() + local result = exec_lua(function() + vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr) + + vim.fn.setqflist({}, ' ', { title = 'Other' }) + local other_qf_id = vim.fn.getqflist({ id = 0 }).id + + vim.diagnostic.setqflist({ title = 'Diagnostics', open = true }) + local qf_id = vim.fn.getqflist({ id = 0 }).id + + return { other_qf_id, qf_id } + end) + + neq(result[1], result[2]) + end) + + it('opens quickfix window when open=true', function() + local qf_winid = exec_lua(function() + vim.api.nvim_win_set_buf(0, _G.diagnostic_bufnr) + + vim.diagnostic.set(_G.diagnostic_ns, _G.diagnostic_bufnr, { + _G.make_error('Error', 1, 1, 1, 1), + }) + + vim.diagnostic.setqflist({ open = true }) + + return vim.fn.getqflist({ winid = 0 }).winid + end) + + neq(0, qf_winid) + end) + end) + describe('match()', function() it('matches a string', function() local msg = 'ERROR: george.txt:19:84:Two plus two equals five' diff --git a/test/functional/lua/filetype_spec.lua b/test/functional/lua/filetype_spec.lua index b6011d5268..b75ff75b05 100644 --- a/test/functional/lua/filetype_spec.lua +++ b/test/functional/lua/filetype_spec.lua @@ -199,7 +199,19 @@ describe('filetype.lua', function() finally(function() uv.fs_unlink('Xfiletype/.config/git') end) - clear({ args = { '--clean', 'Xfiletype/.config/git/config' } }) + local args = { '--clean', 'Xfiletype/.config/git/config' } + clear({ args = args }) eq('gitconfig', api.nvim_get_option_value('filetype', {})) + table.insert(args, 2, '--cmd') + table.insert(args, 3, "autocmd BufRead * call expand('<afile>')") + clear({ args = args }) + eq('gitconfig', api.nvim_get_option_value('filetype', {})) + end) + + it('works with :doautocmd BufRead #31306', function() + clear({ args = { '--clean' } }) + eq('', api.nvim_get_option_value('filetype', {})) + command('doautocmd BufRead README.md') + eq('markdown', api.nvim_get_option_value('filetype', {})) end) end) diff --git a/test/functional/lua/fs_spec.lua b/test/functional/lua/fs_spec.lua index f0d49205e7..33af25f629 100644 --- a/test/functional/lua/fs_spec.lua +++ b/test/functional/lua/fs_spec.lua @@ -17,6 +17,8 @@ local mkdir = t.mkdir local nvim_prog_basename = is_os('win') and 'nvim.exe' or 'nvim' +local link_limit = is_os('win') and 64 or (is_os('mac') or is_os('bsd')) and 33 or 41 + local test_basename_dirname_eq = { '~/foo/', '~/foo', @@ -152,7 +154,7 @@ describe('vim.fs', function() ) end) - it('works with opts.depth and opts.skip', function() + it('works with opts.depth, opts.skip and opts.follow', function() io.open('testd/a1', 'w'):close() io.open('testd/b1', 'w'):close() io.open('testd/c1', 'w'):close() @@ -166,10 +168,10 @@ describe('vim.fs', function() io.open('testd/a/b/c/b4', 'w'):close() io.open('testd/a/b/c/c4', 'w'):close() - local function run(dir, depth, skip) - return exec_lua(function() - local r = {} - local skip_f + local function run(dir, depth, skip, follow) + return exec_lua(function(follow_) + local r = {} --- @type table<string, string> + local skip_f --- @type function if skip then skip_f = function(n0) if vim.tbl_contains(skip or {}, n0) then @@ -177,11 +179,11 @@ describe('vim.fs', function() end end end - for name, type_ in vim.fs.dir(dir, { depth = depth, skip = skip_f }) do + for name, type_ in vim.fs.dir(dir, { depth = depth, skip = skip_f, follow = follow_ }) do r[name] = type_ end return r - end) + end, follow) end local exp = {} @@ -197,6 +199,7 @@ describe('vim.fs', function() exp['a/b2'] = 'file' exp['a/c2'] = 'file' exp['a/b'] = 'directory' + local lexp = vim.deepcopy(exp) eq(exp, run('testd', 2)) @@ -213,6 +216,29 @@ describe('vim.fs', function() exp['a/b/c/c4'] = 'file' eq(exp, run('testd', 999)) + + vim.uv.fs_symlink(vim.uv.fs_realpath('testd/a'), 'testd/l', { junction = true, dir = true }) + lexp['l'] = 'link' + eq(lexp, run('testd', 2, nil, false)) + + lexp['l/a2'] = 'file' + lexp['l/b2'] = 'file' + lexp['l/c2'] = 'file' + lexp['l/b'] = 'directory' + eq(lexp, run('testd', 2, nil, true)) + end) + + it('follow=true handles symlink loop', function() + local cwd = 'testd/a/b/c' + local symlink = cwd .. '/link_loop' ---@type string + vim.uv.fs_symlink(vim.uv.fs_realpath(cwd), symlink, { junction = true, dir = true }) + + eq( + link_limit, + exec_lua(function() + return #vim.iter(vim.fs.dir(cwd, { depth = math.huge, follow = true })):totable() + end) + ) end) end) @@ -228,6 +254,53 @@ describe('vim.fs', function() eq({ nvim_dir }, vim.fs.find(name, { path = parent, upward = true, type = 'directory' })) end) + it('follows symlinks', function() + local build_dir = test_source_path .. '/build' ---@type string + local symlink = test_source_path .. '/build_link' ---@type string + vim.uv.fs_symlink(build_dir, symlink, { junction = true, dir = true }) + + finally(function() + vim.uv.fs_unlink(symlink) + end) + + eq( + { nvim_prog, symlink .. '/bin/' .. nvim_prog_basename }, + vim.fs.find(nvim_prog_basename, { + path = test_source_path, + type = 'file', + limit = 2, + follow = true, + }) + ) + + eq( + { nvim_prog }, + vim.fs.find(nvim_prog_basename, { + path = test_source_path, + type = 'file', + limit = 2, + follow = false, + }) + ) + end) + + it('follow=true handles symlink loop', function() + local cwd = test_source_path ---@type string + local symlink = test_source_path .. '/loop_link' ---@type string + vim.uv.fs_symlink(cwd, symlink, { junction = true, dir = true }) + + finally(function() + vim.uv.fs_unlink(symlink) + end) + + eq(link_limit, #vim.fs.find(nvim_prog_basename, { + path = test_source_path, + type = 'file', + limit = math.huge, + follow = true, + })) + end) + it('accepts predicate as names', function() local opts = { path = nvim_dir, upward = true, type = 'directory' } eq( @@ -273,14 +346,14 @@ describe('vim.fs', function() end) it('works with a single marker', function() - eq(test_source_path, exec_lua([[return vim.fs.root(0, '.git')]])) + eq(test_source_path, exec_lua([[return vim.fs.root(0, 'CMakePresets.json')]])) end) it('works with multiple markers', function() local bufnr = api.nvim_get_current_buf() eq( vim.fs.joinpath(test_source_path, 'test/functional/fixtures'), - exec_lua([[return vim.fs.root(..., {'CMakeLists.txt', '.git'})]], bufnr) + exec_lua([[return vim.fs.root(..., {'CMakeLists.txt', 'CMakePresets.json'})]], bufnr) ) end) @@ -295,26 +368,26 @@ describe('vim.fs', function() end) it('works with a filename argument', function() - eq(test_source_path, exec_lua([[return vim.fs.root(..., '.git')]], nvim_prog)) + eq(test_source_path, exec_lua([[return vim.fs.root(..., 'CMakePresets.json')]], nvim_prog)) end) it('works with a relative path', function() eq( test_source_path, - exec_lua([[return vim.fs.root(..., '.git')]], vim.fs.basename(nvim_prog)) + exec_lua([[return vim.fs.root(..., 'CMakePresets.json')]], vim.fs.basename(nvim_prog)) ) end) it('uses cwd for unnamed buffers', function() command('new') - eq(test_source_path, exec_lua([[return vim.fs.root(0, '.git')]])) + eq(test_source_path, exec_lua([[return vim.fs.root(0, 'CMakePresets.json')]])) end) it("uses cwd for buffers with non-empty 'buftype'", function() command('new') command('set buftype=nofile') command('file lua://') - eq(test_source_path, exec_lua([[return vim.fs.root(0, '.git')]])) + eq(test_source_path, exec_lua([[return vim.fs.root(0, 'CMakePresets.json')]])) end) end) @@ -323,6 +396,20 @@ describe('vim.fs', function() eq('foo/bar/baz', vim.fs.joinpath('foo', 'bar', 'baz')) eq('foo/bar/baz', vim.fs.joinpath('foo', '/bar/', '/baz')) end) + it('rewrites backslashes on Windows', function() + if is_os('win') then + eq('foo/bar/baz/zub/', vim.fs.joinpath([[foo]], [[\\bar\\\\baz]], [[zub\]])) + else + eq([[foo/\\bar\\\\baz/zub\]], vim.fs.joinpath([[foo]], [[\\bar\\\\baz]], [[zub\]])) + end + end) + it('strips redundant slashes', function() + if is_os('win') then + eq('foo/bar/baz/zub/', vim.fs.joinpath([[foo//]], [[\\bar\\\\baz]], [[zub\]])) + else + eq('foo/bar/baz/zub/', vim.fs.joinpath([[foo]], [[//bar////baz]], [[zub/]])) + end + end) end) describe('normalize()', function() @@ -347,8 +434,8 @@ describe('vim.fs', function() end) -- Opts required for testing posix paths and win paths - local posix_opts = is_os('win') and { win = false } or {} - local win_opts = is_os('win') and {} or { win = true } + local posix_opts = { win = false } + local win_opts = { win = true } it('preserves leading double slashes in POSIX paths', function() eq('//foo', vim.fs.normalize('//foo', posix_opts)) @@ -359,6 +446,29 @@ describe('vim.fs', function() eq('/foo/bar', vim.fs.normalize('/foo//bar////', posix_opts)) end) + it('normalizes drive letter', function() + eq('C:/', vim.fs.normalize('C:/', win_opts)) + eq('C:/', vim.fs.normalize('c:/', win_opts)) + eq('D:/', vim.fs.normalize('d:/', win_opts)) + eq('C:', vim.fs.normalize('C:', win_opts)) + eq('C:', vim.fs.normalize('c:', win_opts)) + eq('D:', vim.fs.normalize('d:', win_opts)) + eq('C:/foo/test', vim.fs.normalize('C:/foo/test/', win_opts)) + eq('C:/foo/test', vim.fs.normalize('c:/foo/test/', win_opts)) + eq('D:foo/test', vim.fs.normalize('D:foo/test/', win_opts)) + eq('D:foo/test', vim.fs.normalize('d:foo/test/', win_opts)) + end) + + it('always treats paths as case-sensitive #31833', function() + eq('TEST', vim.fs.normalize('TEST', win_opts)) + eq('test', vim.fs.normalize('test', win_opts)) + eq('C:/FOO/test', vim.fs.normalize('C:/FOO/test', win_opts)) + eq('C:/foo/test', vim.fs.normalize('C:/foo/test', win_opts)) + eq('//SERVER/SHARE/FOO/BAR', vim.fs.normalize('//SERVER/SHARE/FOO/BAR', win_opts)) + eq('//server/share/foo/bar', vim.fs.normalize('//server/share/foo/bar', win_opts)) + eq('C:/FOO/test', vim.fs.normalize('c:/FOO/test', win_opts)) + end) + it('allows backslashes on unix-based os', function() eq('/home/user/hello\\world', vim.fs.normalize('/home/user/hello\\world', posix_opts)) end) @@ -454,4 +564,92 @@ describe('vim.fs', function() end) end) end) + + describe('abspath()', function() + local cwd = assert(t.fix_slashes(assert(vim.uv.cwd()))) + local home = t.fix_slashes(assert(vim.uv.os_homedir())) + + it('works', function() + eq(cwd .. '/foo', vim.fs.abspath('foo')) + eq(cwd .. '/././foo', vim.fs.abspath('././foo')) + eq(cwd .. '/.././../foo', vim.fs.abspath('.././../foo')) + end) + + it('works with absolute paths', function() + if is_os('win') then + eq([[C:/foo]], vim.fs.abspath([[C:\foo]])) + eq([[C:/foo/../.]], vim.fs.abspath([[C:\foo\..\.]])) + eq('//foo/bar', vim.fs.abspath('\\\\foo\\bar')) + else + eq('/foo/../.', vim.fs.abspath('/foo/../.')) + eq('/foo/bar', vim.fs.abspath('/foo/bar')) + end + end) + + it('expands ~', function() + eq(home .. '/foo', vim.fs.abspath('~/foo')) + eq(home .. '/./.././foo', vim.fs.abspath('~/./.././foo')) + end) + + if is_os('win') then + it('works with drive-specific cwd on Windows', function() + local cwd_drive = cwd:match('^%w:') + + eq(cwd .. '/foo', vim.fs.abspath(cwd_drive .. 'foo')) + end) + end + end) + + describe('relpath()', function() + it('works', function() + local cwd = assert(t.fix_slashes(assert(vim.uv.cwd()))) + local my_dir = vim.fs.joinpath(cwd, 'foo') + + eq(nil, vim.fs.relpath('/var/lib', '/var')) + eq(nil, vim.fs.relpath('/var/lib', '/bin')) + eq(nil, vim.fs.relpath(my_dir, 'bin')) + eq(nil, vim.fs.relpath(my_dir, './bin')) + eq(nil, vim.fs.relpath(my_dir, '././')) + eq(nil, vim.fs.relpath(my_dir, '../')) + eq(nil, vim.fs.relpath('/var/lib', '/')) + eq(nil, vim.fs.relpath('/var/lib', '//')) + eq(nil, vim.fs.relpath(' ', '/var')) + eq(nil, vim.fs.relpath(' ', '/var')) + eq('.', vim.fs.relpath('/var/lib', '/var/lib')) + eq('lib', vim.fs.relpath('/var/', '/var/lib')) + eq('var/lib', vim.fs.relpath('/', '/var/lib')) + eq('bar/package.json', vim.fs.relpath('/foo/test', '/foo/test/bar/package.json')) + eq('foo/bar', vim.fs.relpath(cwd, 'foo/bar')) + eq('foo/bar', vim.fs.relpath('.', vim.fs.joinpath(cwd, 'foo/bar'))) + eq('bar', vim.fs.relpath('foo', 'foo/bar')) + eq(nil, vim.fs.relpath('/var/lib', '/var/library/foo')) + + if is_os('win') then + eq(nil, vim.fs.relpath('/', ' ')) + eq(nil, vim.fs.relpath('/', 'var')) + else + local cwd_rel_root = cwd:sub(2) + eq(cwd_rel_root .. '/ ', vim.fs.relpath('/', ' ')) + eq(cwd_rel_root .. '/var', vim.fs.relpath('/', 'var')) + end + + if is_os('win') then + eq(nil, vim.fs.relpath('c:/aaaa/', '/aaaa/cccc')) + eq(nil, vim.fs.relpath('c:/aaaa/', './aaaa/cccc')) + eq(nil, vim.fs.relpath('c:/aaaa/', 'aaaa/cccc')) + eq(nil, vim.fs.relpath('c:/blah\\blah', 'd:/games')) + eq(nil, vim.fs.relpath('c:/games', 'd:/games')) + eq(nil, vim.fs.relpath('c:/games', 'd:/games/foo')) + eq(nil, vim.fs.relpath('c:/aaaa/bbbb', 'c:/aaaa')) + eq('cccc', vim.fs.relpath('c:/aaaa/', 'c:/aaaa/cccc')) + eq('aaaa/bbbb', vim.fs.relpath('C:/', 'c:\\aaaa\\bbbb')) + eq('bar/package.json', vim.fs.relpath('C:\\foo\\test', 'C:\\foo\\test\\bar\\package.json')) + eq('baz', vim.fs.relpath('\\\\foo\\bar', '\\\\foo\\bar\\baz')) + eq(nil, vim.fs.relpath('a/b/c', 'a\\b')) + eq('d', vim.fs.relpath('a/b/c', 'a\\b\\c\\d')) + eq('.', vim.fs.relpath('\\\\foo\\bar\\baz', '\\\\foo\\bar\\baz')) + eq(nil, vim.fs.relpath('C:\\foo\\test', 'C:\\foo\\Test\\bar\\package.json')) + end + end) + end) end) diff --git a/test/functional/lua/func_memoize_spec.lua b/test/functional/lua/func_memoize_spec.lua new file mode 100644 index 0000000000..ca518ab88d --- /dev/null +++ b/test/functional/lua/func_memoize_spec.lua @@ -0,0 +1,142 @@ +local t = require('test.testutil') +local n = require('test.functional.testnvim')() +local clear = n.clear +local exec_lua = n.exec_lua +local eq = t.eq + +describe('vim.func._memoize', function() + before_each(clear) + + it('caches function results based on their parameters', function() + exec_lua([[ + _G.count = 0 + + local adder = vim.func._memoize('concat', function(arg1, arg2) + _G.count = _G.count + 1 + return arg1 + arg2 + end) + + collectgarbage('stop') + adder(3, -4) + adder(3, -4) + adder(3, -4) + adder(3, -4) + adder(3, -4) + collectgarbage('restart') + ]]) + + eq(1, exec_lua([[return _G.count]])) + end) + + it('caches function results using a weak table by default', function() + exec_lua([[ + _G.count = 0 + + local adder = vim.func._memoize('concat-2', function(arg1, arg2) + _G.count = _G.count + 1 + return arg1 + arg2 + end) + + adder(3, -4) + collectgarbage() + adder(3, -4) + collectgarbage() + adder(3, -4) + ]]) + + eq(3, exec_lua([[return _G.count]])) + end) + + it('can cache using a strong table', function() + exec_lua([[ + _G.count = 0 + + local adder = vim.func._memoize('concat-2', function(arg1, arg2) + _G.count = _G.count + 1 + return arg1 + arg2 + end, false) + + adder(3, -4) + collectgarbage() + adder(3, -4) + collectgarbage() + adder(3, -4) + ]]) + + eq(1, exec_lua([[return _G.count]])) + end) + + it('can clear a single cache entry', function() + exec_lua([[ + _G.count = 0 + + local adder = vim.func._memoize(function(arg1, arg2) + return tostring(arg1) .. '%%' .. tostring(arg2) + end, function(arg1, arg2) + _G.count = _G.count + 1 + return arg1 + arg2 + end) + + collectgarbage('stop') + adder(3, -4) + adder(3, -4) + adder(3, -4) + adder(3, -4) + adder(3, -4) + adder:clear(3, -4) + adder(3, -4) + collectgarbage('restart') + ]]) + + eq(2, exec_lua([[return _G.count]])) + end) + + it('can clear the entire cache', function() + exec_lua([[ + _G.count = 0 + + local adder = vim.func._memoize(function(arg1, arg2) + return tostring(arg1) .. '%%' .. tostring(arg2) + end, function(arg1, arg2) + _G.count = _G.count + 1 + return arg1 + arg2 + end) + + collectgarbage('stop') + adder(1, 2) + adder(3, -4) + adder(1, 2) + adder(3, -4) + adder(1, 2) + adder(3, -4) + adder:clear() + adder(1, 2) + adder(3, -4) + collectgarbage('restart') + ]]) + + eq(4, exec_lua([[return _G.count]])) + end) + + it('can cache functions that return nil', function() + exec_lua([[ + _G.count = 0 + + local adder = vim.func._memoize('concat', function(arg1, arg2) + _G.count = _G.count + 1 + return nil + end) + + collectgarbage('stop') + adder(1, 2) + adder(1, 2) + adder(1, 2) + adder(1, 2) + adder:clear() + adder(1, 2) + collectgarbage('restart') + ]]) + + eq(2, exec_lua([[return _G.count]])) + end) +end) diff --git a/test/functional/lua/hl_spec.lua b/test/functional/lua/hl_spec.lua index 89881973bf..512f6be48f 100644 --- a/test/functional/lua/hl_spec.lua +++ b/test/functional/lua/hl_spec.lua @@ -104,6 +104,33 @@ describe('vim.hl.range', function() | ]]) end) + + it('removes highlight after given `timeout`', function() + local timeout = 100 + exec_lua(function() + local ns = vim.api.nvim_create_namespace('') + vim.hl.range(0, ns, 'Search', { 0, 0 }, { 4, 0 }, { timeout = timeout }) + end) + screen:expect({ + grid = [[ + {10:^asdfghjkl}{100:$} | + {10:«口=口»}{100:$} | + {10:qwertyuiop}{100:$} | + {10:口口=口口}{1:$} | + zxcvbnm{1:$} | + | + ]], + timeout = timeout, + }) + screen:expect([[ + ^asdfghjkl{1:$} | + «口=口»{1:$} | + qwertyuiop{1:$} | + 口口=口口{1:$} | + zxcvbnm{1:$} | + | + ]]) + end) end) describe('vim.hl.on_yank', function() @@ -144,7 +171,7 @@ describe('vim.hl.on_yank', function() vim.api.nvim_buf_set_mark(0, ']', 1, 1, {}) vim.hl.on_yank({ timeout = math.huge, on_macro = true, event = { operator = 'y' } }) end) - local ns = api.nvim_create_namespace('hlyank') + local ns = api.nvim_create_namespace('nvim.hlyank') local win = api.nvim_get_current_win() eq({ win }, api.nvim__ns_get(ns).wins) command('wincmd w') @@ -158,7 +185,7 @@ describe('vim.hl.on_yank', function() vim.api.nvim_buf_set_mark(0, ']', 1, 1, {}) vim.hl.on_yank({ timeout = math.huge, on_macro = true, event = { operator = 'y' } }) end) - local ns = api.nvim_create_namespace('hlyank') + local ns = api.nvim_create_namespace('nvim.hlyank') eq(api.nvim_get_current_win(), api.nvim__ns_get(ns).wins[1]) command('wincmd w') exec_lua(function() diff --git a/test/functional/lua/json_spec.lua b/test/functional/lua/json_spec.lua index a6e814d739..e4a1df1d4c 100644 --- a/test/functional/lua/json_spec.lua +++ b/test/functional/lua/json_spec.lua @@ -152,6 +152,45 @@ describe('vim.json.encode()', function() clear() end) + it('escape_slash', function() + -- With slash + eq('"Test\\/"', exec_lua([[return vim.json.encode('Test/', { escape_slash = true })]])) + eq( + 'Test/', + exec_lua([[return vim.json.decode(vim.json.encode('Test/', { escape_slash = true }))]]) + ) + + -- Without slash + eq('"Test/"', exec_lua([[return vim.json.encode('Test/')]])) + eq('"Test/"', exec_lua([[return vim.json.encode('Test/', {})]])) + eq('"Test/"', exec_lua([[return vim.json.encode('Test/', { _invalid = true })]])) + eq('"Test/"', exec_lua([[return vim.json.encode('Test/', { escape_slash = false })]])) + eq( + '"Test/"', + exec_lua([[return vim.json.encode('Test/', { _invalid = true, escape_slash = false })]]) + ) + eq( + 'Test/', + exec_lua([[return vim.json.decode(vim.json.encode('Test/', { escape_slash = false }))]]) + ) + + -- Checks for for global side-effects + eq( + '"Test/"', + exec_lua([[ + vim.json.encode('Test/', { escape_slash = true }) + return vim.json.encode('Test/') + ]]) + ) + eq( + '"Test\\/"', + exec_lua([[ + vim.json.encode('Test/', { escape_slash = false }) + return vim.json.encode('Test/', { escape_slash = true }) + ]]) + ) + end) + it('dumps strings', function() eq('"Test"', exec_lua([[return vim.json.encode('Test')]])) eq('""', exec_lua([[return vim.json.encode('')]])) diff --git a/test/functional/lua/loader_spec.lua b/test/functional/lua/loader_spec.lua index 8508f2aa14..20d3a940b2 100644 --- a/test/functional/lua/loader_spec.lua +++ b/test/functional/lua/loader_spec.lua @@ -10,7 +10,17 @@ local eq = t.eq describe('vim.loader', function() before_each(clear) - it('can work in compatibility with --luamod-dev #27413', function() + it('can be disabled', function() + exec_lua(function() + local orig_loader = _G.loadfile + vim.loader.enable() + assert(orig_loader ~= _G.loadfile) + vim.loader.enable(false) + assert(orig_loader == _G.loadfile) + end) + end) + + it('works with --luamod-dev #27413', function() clear({ args = { '--luamod-dev' } }) exec_lua(function() vim.loader.enable() @@ -31,7 +41,7 @@ describe('vim.loader', function() end) end) - it('handles changing files (#23027)', function() + it('handles changing files #23027', function() exec_lua(function() vim.loader.enable() end) @@ -63,7 +73,7 @@ describe('vim.loader', function() ) end) - it('handles % signs in modpath (#24491)', function() + it('handles % signs in modpath #24491', function() exec_lua [[ vim.loader.enable() ]] @@ -82,7 +92,7 @@ describe('vim.loader', function() eq(2, exec_lua('return loadfile(...)()', tmp2)) end) - it('correct indent on error message (#29809)', function() + it('indents error message #29809', function() local errmsg = exec_lua [[ vim.loader.enable() local _, errmsg = pcall(require, 'non_existent_module') diff --git a/test/functional/lua/system_spec.lua b/test/functional/lua/system_spec.lua index afbada007d..6c320376c9 100644 --- a/test/functional/lua/system_spec.lua +++ b/test/functional/lua/system_spec.lua @@ -18,8 +18,7 @@ local function system_sync(cmd, opts) local res = obj:wait() -- Check the process is no longer running - local proc = vim.api.nvim_get_proc(obj.pid) - assert(not proc, 'process still exists') + assert(not vim.api.nvim_get_proc(obj.pid), 'process still exists') return res end) @@ -27,23 +26,23 @@ end local function system_async(cmd, opts) return exec_lua(function() - _G.done = false + local done = false + local res --- @type vim.SystemCompleted? local obj = vim.system(cmd, opts, function(obj) - _G.done = true - _G.ret = obj + done = true + res = obj end) local ok = vim.wait(10000, function() - return _G.done + return done end) assert(ok, 'process did not exit') -- Check the process is no longer running - local proc = vim.api.nvim_get_proc(obj.pid) - assert(not proc, 'process still exists') + assert(not vim.api.nvim_get_proc(obj.pid), 'process still exists') - return _G.ret + return res end) end @@ -114,10 +113,39 @@ describe('vim.system', function() end) if t.is_os('win') then - it('can resolve windows command extentions.', function() + it('can resolve windows command extensions', function() t.write_file('test.bat', 'echo hello world') system_sync({ 'chmod', '+x', 'test.bat' }) system_sync({ './test' }) end) end + + it('always captures all content of stdout/stderr #30846', function() + t.skip(n.fn.executable('git') == 0, 'missing "git" command') + t.skip(n.fn.isdirectory('.git') == 0, 'missing ".git" directory') + eq( + 0, + exec_lua(function() + local done = 0 + local fail = 0 + for _ = 1, 200 do + vim.system( + { 'git', 'show', ':0:test/functional/plugin/lsp_spec.lua' }, + { text = true }, + function(o) + if o.code ~= 0 or #o.stdout == 0 then + fail = fail + 1 + end + done = done + 1 + end + ) + end + + local ok = vim.wait(10000, function() + return done == 200 + end, 200) + return fail + (ok and 0 or 1) + end) + ) + end) end) diff --git a/test/functional/lua/text_spec.lua b/test/functional/lua/text_spec.lua index be471bfd62..dd08a6ec04 100644 --- a/test/functional/lua/text_spec.lua +++ b/test/functional/lua/text_spec.lua @@ -26,5 +26,21 @@ describe('vim.text', function() eq(output, vim.text.hexencode(input)) eq(input, vim.text.hexdecode(output)) end) + + it('errors on invalid input', function() + -- Odd number of hex characters + do + local res, err = vim.text.hexdecode('ABC') + eq(nil, res) + eq('string must have an even number of hex characters', err) + end + + -- Non-hexadecimal input + do + local res, err = vim.text.hexdecode('nothex') + eq(nil, res) + eq('string must contain only hex characters', err) + end + end) end) end) diff --git a/test/functional/lua/thread_spec.lua b/test/functional/lua/thread_spec.lua index 310705fd97..8ca4bdc4f5 100644 --- a/test/functional/lua/thread_spec.lua +++ b/test/functional/lua/thread_spec.lua @@ -19,6 +19,26 @@ describe('thread', function() screen = Screen.new(50, 10) end) + it('handle non-string error', function() + exec_lua [[ + local thread = vim.uv.new_thread(function() + error() + end) + vim.uv.thread_join(thread) + ]] + + screen:expect([[ + | + {1:~ }|*5 + {3: }| + {9:Error in luv thread:} | + {9:[NULL]} | + {6:Press ENTER or type command to continue}^ | + ]]) + feed('<cr>') + assert_alive() + end) + it('entry func is executed in protected mode', function() exec_lua [[ local thread = vim.uv.new_thread(function() diff --git a/test/functional/lua/ui_event_spec.lua b/test/functional/lua/ui_event_spec.lua index c8616e3e11..ddb10127e4 100644 --- a/test/functional/lua/ui_event_spec.lua +++ b/test/functional/lua/ui_event_spec.lua @@ -106,20 +106,15 @@ describe('vim.ui_attach', function() end) it('does not crash on exit', function() - fn.system({ - n.nvim_prog, - '-u', - 'NONE', - '-i', - 'NONE', + local p = n.spawn_wait( '--cmd', [[ lua ns = vim.api.nvim_create_namespace 'testspace' ]], '--cmd', [[ lua vim.ui_attach(ns, {ext_popupmenu=true}, function() end) ]], '--cmd', - 'quitall!', - }) - eq(0, n.eval('v:shell_error')) + 'quitall!' + ) + eq(0, p.status) end) it('can receive accurate message kinds even if they are history', function() @@ -173,18 +168,67 @@ describe('vim.ui_attach', function() vim.ui_attach(ns, { ext_messages = true }, function(ev) if ev == 'msg_show' then vim.schedule(function() vim.cmd.redraw() end) - else - vim.cmd.redraw() + elseif ev:find('cmdline') then + _G.cmdline = _G.cmdline + (ev == 'cmdline_show' and 1 or 0) + vim.api.nvim_buf_set_lines(0, 0, -1, false, { tostring(_G.cmdline) }) + vim.cmd('redraw') end - _G.cmdline = _G.cmdline + (ev == 'cmdline_show' and 1 or 0) end )]]) + screen:expect([[ + ^ | + {1:~ }|*4 + ]]) feed(':') - n.assert_alive() - eq(2, exec_lua('return _G.cmdline')) - n.assert_alive() + screen:expect({ + grid = [[ + ^1 | + {1:~ }|*4 + ]], + cmdline = { { + content = { { '' } }, + firstc = ':', + pos = 0, + } }, + }) feed('version<CR><CR>v<Esc>') - n.assert_alive() + screen:expect({ + grid = [[ + ^2 | + {1:~ }|*4 + ]], + cmdline = { { abort = false } }, + }) + feed([[:call confirm("Save changes?", "&Yes\n&No\n&Cancel")<CR>]]) + screen:expect({ + grid = [[ + ^4 | + {1:~ }|*4 + ]], + cmdline = { + { + content = { { '' } }, + hl_id = 10, + pos = 0, + prompt = '[Y]es, (N)o, (C)ancel: ', + }, + }, + messages = { + { + content = { { '\nSave changes?\n', 6, 10 } }, + history = false, + kind = 'confirm', + }, + }, + }) + feed('n') + screen:expect({ + grid = [[ + ^4 | + {1:~ }|*4 + ]], + cmdline = { { abort = false } }, + }) end) it("preserved 'incsearch/command' screen state after :redraw from ext_cmdline", function() @@ -261,30 +305,15 @@ describe('vim.ui_attach', function() lled in a fast event context | {1:~ }| ]], + cmdline = { { abort = false } }, messages = { { - content = { { 'E122: Function Foo already exists, add ! to replace it', 9, 7 } }, + content = { { 'E122: Function Foo already exists, add ! to replace it', 9, 6 } }, + history = true, kind = 'emsg', }, }, }) - -- No fast context for prompt message kinds - feed(':%s/Function/Replacement/c<cr>') - screen:expect({ - grid = [[ - ^E122: {10:Function} Foo already exists, add !| - to replace it | - replace with Replacement (y/n/a/q/l/^E/^| - Y)? | - {1:~ }| - ]], - messages = { - { - content = { { 'replace with Replacement (y/n/a/q/l/^E/^Y)?', 6, 19 } }, - kind = 'confirm_sub', - }, - }, - }) end) end) @@ -316,30 +345,36 @@ describe('vim.ui_attach', function() vim.api.nvim_buf_set_lines(0, -2, -1, false, { err[1] }) end) ]]) + local s1 = [[ + ^ | + {1:~ }|*4 + ]] + screen:expect(s1) + feed('QQQQQQ<CR>') screen:expect({ grid = [[ - ^ | - {1:~ }|*4 - ]], - }) - feed('ifoo') - screen:expect({ - grid = [[ - foo^ | - {1:~ }|*4 - ]], - showmode = { { '-- INSERT --', 5, 12 } }, - }) - feed('<esc>:1mes clear<cr>:mes<cr>') - screen:expect({ - grid = [[ - foo | - {3: }| - {9:Excessive errors in vim.ui_attach() call}| - {9:back from ns: 1.} | + {9:obal 'err' (a nil value)} | + {9:stack traceback:} | + {9: [string "<nvim>"]:2: in function}| + {9: <[string "<nvim>"]:1>} | {100:Press ENTER or type command to continue}^ | ]], + messages = { + { + content = { { 'Press ENTER or type command to continue', 100, 18 } }, + history = true, + kind = 'return_prompt', + }, + }, }) + feed(':1mes clear<CR>:mes<CR>') + screen:expect([[ + | + {3: }| + {9:Excessive errors in vim.ui_attach() call}| + {9:back from ns: 1.} | + {100:Press ENTER or type command to continue}^ | + ]]) feed('<cr>') -- Also when scheduled exec_lua([[ @@ -348,16 +383,17 @@ describe('vim.ui_attach', function() end) ]]) screen:expect({ - any = 'fo^o', + grid = s1, messages = { { content = { { 'Error executing vim.schedule lua callback: [string "<nvim>"]:2: attempt to index global \'err\' (a nil value)\nstack traceback:\n\t[string "<nvim>"]:2: in function <[string "<nvim>"]:2>', 9, - 7, + 6, }, }, + history = true, kind = 'lua_error', }, { @@ -365,26 +401,35 @@ describe('vim.ui_attach', function() { 'Error executing vim.schedule lua callback: [string "<nvim>"]:2: attempt to index global \'err\' (a nil value)\nstack traceback:\n\t[string "<nvim>"]:2: in function <[string "<nvim>"]:2>', 9, - 7, + 6, }, }, + history = true, kind = 'lua_error', }, { - content = { { 'Press ENTER or type command to continue', 100, 19 } }, + content = { { 'Press ENTER or type command to continue', 100, 18 } }, + history = false, kind = 'return_prompt', }, }, }) feed('<esc>:1mes clear<cr>:mes<cr>') - screen:expect({ - grid = [[ - foo | - {3: }| - {9:Excessive errors in vim.ui_attach() call}| - {9:back from ns: 2.} | - {100:Press ENTER or type command to continue}^ | - ]], - }) + screen:expect([[ + | + {3: }| + {9:Excessive errors in vim.ui_attach() call}| + {9:back from ns: 2.} | + {100:Press ENTER or type command to continue}^ | + ]]) + end) + + it('sourcing invalid file does not crash #32166', function() + exec_lua([[ + local ns = vim.api.nvim_create_namespace("") + vim.ui_attach(ns, { ext_messages = true }, function() end) + ]]) + feed((':luafile %s<CR>'):format(testlog)) + n.assert_alive() end) end) diff --git a/test/functional/lua/uri_spec.lua b/test/functional/lua/uri_spec.lua index 258b96bc43..d706f8b78b 100644 --- a/test/functional/lua/uri_spec.lua +++ b/test/functional/lua/uri_spec.lua @@ -252,4 +252,12 @@ describe('URI methods', function() end ) end) + + describe('encode to uri', function() + it('rfc2732 including brackets', function() + exec_lua("str = '[:]'") + exec_lua("rfc = 'rfc2732'") + eq('[%3a]', exec_lua('return vim.uri_encode(str, rfc)')) + end) + end) end) diff --git a/test/functional/lua/vim_spec.lua b/test/functional/lua/vim_spec.lua index 3cfbfe167a..55e5158596 100644 --- a/test/functional/lua/vim_spec.lua +++ b/test/functional/lua/vim_spec.lua @@ -3435,7 +3435,6 @@ stack traceback: end) it('can discard input', function() - clear() -- discard every other normal 'x' command exec_lua [[ n_key = 0 @@ -3461,7 +3460,6 @@ stack traceback: end) it('callback invalid return', function() - clear() -- second key produces an error which removes the callback exec_lua [[ n_call = 0 @@ -3955,6 +3953,17 @@ stack traceback: eq(win2, val) end) + it('failure modes', function() + matches( + 'nvim_exec2%(%): Vim:E492: Not an editor command: fooooo', + pcall_err(exec_lua, [[vim.api.nvim_win_call(0, function() vim.cmd 'fooooo' end)]]) + ) + eq( + 'Error executing lua: [string "<nvim>"]:0: fooooo', + pcall_err(exec_lua, [[vim.api.nvim_win_call(0, function() error('fooooo') end)]]) + ) + end) + it('does not cause ml_get errors with invalid visual selection', function() -- Add lines to the current buffer and make another window looking into an empty buffer. exec_lua [[ diff --git a/test/functional/lua/watch_spec.lua b/test/functional/lua/watch_spec.lua index ad16df8a7c..3b109b70d5 100644 --- a/test/functional/lua/watch_spec.lua +++ b/test/functional/lua/watch_spec.lua @@ -91,8 +91,7 @@ describe('vim._watch', function() skip(is_os('mac'), 'flaky test on mac') skip(is_os('bsd'), 'Stopped working on bsd after 3ca967387c49c754561c3b11a574797504d40f38') elseif watchfunc == 'watchdirs' and is_os('mac') then - -- Bump this (or fix the bug) if CI continues to fail in future versions of macos CI. - skip(is_ci() and vim.uv.os_uname().release == '24.0.0', 'weird failure for macOS arm 15 CI') + skip(true, 'weird failure since macOS 14 CI, see bbf208784ca279178ba0075b60d3e9c80f11da7a') else skip( is_os('bsd'), diff --git a/test/functional/lua/with_spec.lua b/test/functional/lua/with_spec.lua index 6127e83619..92e798e7f3 100644 --- a/test/functional/lua/with_spec.lua +++ b/test/functional/lua/with_spec.lua @@ -1621,4 +1621,21 @@ describe('vim._with', function() matches('Invalid buffer', get_error('{ buf = -1 }, function() end')) matches('Invalid window', get_error('{ win = -1 }, function() end')) end) + + it('no double-free when called from :filter browse oldfiles #31501', function() + exec_lua([=[ + vim.api.nvim_create_autocmd('BufEnter', { + callback = function() + vim._with({ lockmarks = true }, function() end) + end, + }) + vim.cmd([[ + let v:oldfiles = ['Xoldfile'] + call nvim_input('1<CR>') + noswapfile filter /Xoldfile/ browse oldfiles + ]]) + ]=]) + n.assert_alive() + eq('Xoldfile', fn.bufname('%')) + end) end) diff --git a/test/functional/options/defaults_spec.lua b/test/functional/options/defaults_spec.lua index e7f47ef4e9..a82279e775 100644 --- a/test/functional/options/defaults_spec.lua +++ b/test/functional/options/defaults_spec.lua @@ -14,7 +14,6 @@ local api = n.api local command = n.command local clear = n.clear local exc_exec = n.exc_exec -local exec_lua = n.exec_lua local eval = n.eval local eq = t.eq local ok = t.ok @@ -929,17 +928,12 @@ describe('stdpath()', function() assert_alive() -- Check for crash. #8393 -- Check that Nvim rejects invalid APPNAMEs - -- Call jobstart() and jobwait() in the same RPC request to reduce flakiness. local function test_appname(testAppname, expected_exitcode) - local lua_code = string.format( - [[ - local child = vim.fn.jobstart({ vim.v.progpath, '--clean', '--headless', '--listen', 'x', '+qall!' }, { env = { NVIM_APPNAME = %q } }) - return vim.fn.jobwait({ child }, %d)[1] - ]], - testAppname, - 3000 - ) - eq(expected_exitcode, exec_lua(lua_code)) + local p = n.spawn_wait({ + args = { '--listen', 'x', '+qall!' }, + env = { NVIM_APPNAME = testAppname }, + }) + eq(expected_exitcode, p.status) end -- Invalid appnames: test_appname('a/../b', 1) diff --git a/test/functional/options/winfixbuf_spec.lua b/test/functional/options/winfixbuf_spec.lua index 124f194b5a..a01650ea71 100644 --- a/test/functional/options/winfixbuf_spec.lua +++ b/test/functional/options/winfixbuf_spec.lua @@ -1,55 +1,51 @@ local n = require('test.functional.testnvim')() +local t = require('test.testutil') local clear = n.clear local exec_lua = n.exec_lua -describe("Nvim API calls with 'winfixbuf'", function() +describe("'winfixbuf'", function() before_each(function() clear() end) - it("Calling vim.api.nvim_win_set_buf with 'winfixbuf'", function() - local results = exec_lua([[ - local function _setup_two_buffers() - local buffer = vim.api.nvim_create_buf(true, true) - - vim.api.nvim_create_buf(true, true) -- Make another buffer - - local current_window = 0 - vim.api.nvim_set_option_value("winfixbuf", true, {win=current_window}) - - return buffer - end - - local other_buffer = _setup_two_buffers() - local current_window = 0 - local results, _ = pcall(vim.api.nvim_win_set_buf, current_window, other_buffer) - - return results + ---@return integer + local function setup_winfixbuf() + return exec_lua([[ + local buffer = vim.api.nvim_create_buf(true, true) + vim.api.nvim_create_buf(true, true) -- Make another buffer + vim.wo.winfixbuf = true + return buffer ]]) - - assert(results == false) + end + + it('nvim_win_set_buf on non-current buffer', function() + local other_buf = setup_winfixbuf() + t.eq( + "Vim:E1513: Cannot switch buffer. 'winfixbuf' is enabled", + t.pcall_err(n.api.nvim_win_set_buf, 0, other_buf) + ) end) - it("Calling vim.api.nvim_set_current_buf with 'winfixbuf'", function() - local results = exec_lua([[ - local function _setup_two_buffers() - local buffer = vim.api.nvim_create_buf(true, true) - - vim.api.nvim_create_buf(true, true) -- Make another buffer - - local current_window = 0 - vim.api.nvim_set_option_value("winfixbuf", true, {win=current_window}) - - return buffer - end - - local other_buffer = _setup_two_buffers() - local results, _ = pcall(vim.api.nvim_set_current_buf, other_buffer) + it('nvim_set_current_buf on non-current buffer', function() + local other_buf = setup_winfixbuf() + t.eq( + "Vim:E1513: Cannot switch buffer. 'winfixbuf' is enabled", + t.pcall_err(n.api.nvim_set_current_buf, other_buf) + ) + end) - return results - ]]) + it('nvim_win_set_buf on current buffer', function() + setup_winfixbuf() + local curbuf = n.api.nvim_get_current_buf() + n.api.nvim_win_set_buf(0, curbuf) + t.eq(curbuf, n.api.nvim_get_current_buf()) + end) - assert(results == false) + it('nvim_set_current_buf on current buffer', function() + setup_winfixbuf() + local curbuf = n.api.nvim_get_current_buf() + n.api.nvim_set_current_buf(curbuf) + t.eq(curbuf, n.api.nvim_get_current_buf()) end) end) diff --git a/test/functional/plugin/health_spec.lua b/test/functional/plugin/health_spec.lua index 753da64522..406b5c3c16 100644 --- a/test/functional/plugin/health_spec.lua +++ b/test/functional/plugin/health_spec.lua @@ -66,6 +66,18 @@ describe(':checkhealth', function() eq({}, getcompletion('', 'checkhealth')) assert_alive() end) + + it('vim.g.health', function() + clear() + command("let g:health = {'style':'float'}") + command('checkhealth lsp') + eq( + 'editor', + exec_lua([[ + return vim.api.nvim_win_get_config(0).relative + ]]) + ) + end) end) describe('vim.health', function() diff --git a/test/functional/plugin/lsp/completion_spec.lua b/test/functional/plugin/lsp/completion_spec.lua index 39b6ddc105..4e90c2fd1b 100644 --- a/test/functional/plugin/lsp/completion_spec.lua +++ b/test/functional/plugin/lsp/completion_spec.lua @@ -216,6 +216,43 @@ describe('vim.lsp.completion: item conversion', function() }) end) + it('uses filterText as word if label/newText would not match', function() + local items = { + { + filterText = '<module', + insertTextFormat = 2, + kind = 10, + label = 'module', + sortText = 'module', + textEdit = { + newText = '<module>$1</module>$0', + range = { + start = { + character = 0, + line = 0, + }, + ['end'] = { + character = 0, + line = 0, + }, + }, + }, + }, + } + assert_completion_matches('<mo', items, { + { + abbr = 'module', + word = '<module', + }, + }) + assert_completion_matches('', items, { + { + abbr = 'module', + word = 'module', + }, + }) + end) + it('fuzzy matches on label when filterText is missing', function() assert_completion_matches('fo', { { label = 'foo' }, @@ -731,9 +768,10 @@ describe('vim.lsp.completion: item conversion', function() ) end) +--- @param name string --- @param completion_result lsp.CompletionList --- @return integer -local function create_server(completion_result) +local function create_server(name, completion_result) return exec_lua(function() local server = _G._create_server({ capabilities = { @@ -751,7 +789,7 @@ local function create_server(completion_result) local bufnr = vim.api.nvim_get_current_buf() vim.api.nvim_win_set_buf(0, bufnr) return vim.lsp.start({ - name = 'dummy', + name = name, cmd = server.cmd, on_attach = function(client, bufnr0) vim.lsp.completion.enable(true, client.id, bufnr0, { @@ -800,7 +838,7 @@ describe('vim.lsp.completion: protocol', function() end it('fetches completions and shows them using complete on trigger', function() - create_server({ + create_server('dummy', { isIncomplete = false, items = { { @@ -892,7 +930,7 @@ describe('vim.lsp.completion: protocol', function() end) it('merges results from multiple clients', function() - create_server({ + create_server('dummy1', { isIncomplete = false, items = { { @@ -900,7 +938,7 @@ describe('vim.lsp.completion: protocol', function() }, }, }) - create_server({ + create_server('dummy2', { isIncomplete = false, items = { { @@ -933,7 +971,7 @@ describe('vim.lsp.completion: protocol', function() }, }, } - local client_id = create_server(completion_list) + local client_id = create_server('dummy', completion_list) exec_lua(function() _G.called = false @@ -970,7 +1008,7 @@ describe('vim.lsp.completion: protocol', function() end) it('enable(…,{convert=fn}) custom word/abbr format', function() - create_server({ + create_server('dummy', { isIncomplete = false, items = { { @@ -1012,7 +1050,7 @@ describe('vim.lsp.completion: integration', function() exec_lua(function() vim.o.completeopt = 'menuone,noselect' end) - create_server(completion_list) + create_server('dummy', completion_list) feed('i world<esc>0ih<c-x><c-o>') retry(nil, nil, function() eq( diff --git a/test/functional/plugin/lsp/diagnostic_spec.lua b/test/functional/plugin/lsp/diagnostic_spec.lua index 5afbe22793..4ecb056d01 100644 --- a/test/functional/plugin/lsp/diagnostic_spec.lua +++ b/test/functional/plugin/lsp/diagnostic_spec.lua @@ -89,7 +89,7 @@ describe('vim.lsp.diagnostic', function() return extmarks end - client_id = assert(vim.lsp.start_client { + client_id = assert(vim.lsp.start({ cmd_env = { NVIM_LUA_NOTRACK = '1', }, @@ -101,7 +101,7 @@ describe('vim.lsp.diagnostic', function() '--headless', }, offset_encoding = 'utf-16', - }) + }, { attach = false })) end) fake_uri = 'file:///fake/uri' @@ -209,10 +209,16 @@ describe('vim.lsp.diagnostic', function() before_each(function() exec_lua(create_server_definition) exec_lua(function() + _G.requests = 0 _G.server = _G._create_server({ capabilities = { diagnosticProvider = {}, }, + handlers = { + [vim.lsp.protocol.Methods.textDocument_diagnostic] = function() + _G.requests = _G.requests + 1 + end, + }, }) function _G.get_extmarks(bufnr, client_id0) @@ -373,5 +379,56 @@ describe('vim.lsp.diagnostic', function() end) ) end) + + it('handles server cancellation', function() + eq( + 1, + exec_lua(function() + vim.lsp.diagnostic.on_diagnostic({ + code = vim.lsp.protocol.ErrorCodes.ServerCancelled, + -- Empty data defaults to retriggering request + data = {}, + message = '', + }, {}, { + method = vim.lsp.protocol.Methods.textDocument_diagnostic, + client_id = client_id, + }) + + return _G.requests + end) + ) + + eq( + 2, + exec_lua(function() + vim.lsp.diagnostic.on_diagnostic({ + code = vim.lsp.protocol.ErrorCodes.ServerCancelled, + data = { retriggerRequest = true }, + message = '', + }, {}, { + method = vim.lsp.protocol.Methods.textDocument_diagnostic, + client_id = client_id, + }) + + return _G.requests + end) + ) + + eq( + 2, + exec_lua(function() + vim.lsp.diagnostic.on_diagnostic({ + code = vim.lsp.protocol.ErrorCodes.ServerCancelled, + data = { retriggerRequest = false }, + message = '', + }, {}, { + method = vim.lsp.protocol.Methods.textDocument_diagnostic, + client_id = client_id, + }) + + return _G.requests + end) + ) + end) end) end) diff --git a/test/functional/plugin/lsp/folding_range_spec.lua b/test/functional/plugin/lsp/folding_range_spec.lua new file mode 100644 index 0000000000..7e68a598d2 --- /dev/null +++ b/test/functional/plugin/lsp/folding_range_spec.lua @@ -0,0 +1,647 @@ +local t = require('test.testutil') +local n = require('test.functional.testnvim')() +local Screen = require('test.functional.ui.screen') +local t_lsp = require('test.functional.plugin.lsp.testutil') + +local eq = t.eq +local tempname = t.tmpname + +local clear_notrace = t_lsp.clear_notrace +local create_server_definition = t_lsp.create_server_definition + +local api = n.api +local exec_lua = n.exec_lua +local insert = n.insert +local command = n.command +local feed = n.feed + +describe('vim.lsp.folding_range', function() + local text = [[// foldLevel() {{{2 +/// @return fold level at line number "lnum" in the current window. +static int foldLevel(linenr_T lnum) +{ + // While updating the folds lines between invalid_top and invalid_bot have + // an undefined fold level. Otherwise update the folds first. + if (invalid_top == 0) { + checkupdate(curwin); + } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) { + return prev_lnum_lvl; + } else if (lnum >= invalid_top && lnum <= invalid_bot) { + return -1; + } + + // Return quickly when there is no folding at all in this window. + if (!hasAnyFolding(curwin)) { + return 0; + } + + return foldLevelWin(curwin, lnum); +}]] + + local result = { + { + endLine = 19, + kind = 'region', + startCharacter = 1, + startLine = 3, + }, + { + endCharacter = 2, + endLine = 7, + kind = 'region', + startCharacter = 25, + startLine = 6, + }, + { + endCharacter = 2, + endLine = 9, + kind = 'region', + startCharacter = 55, + startLine = 8, + }, + { + endCharacter = 2, + endLine = 11, + kind = 'region', + startCharacter = 58, + startLine = 10, + }, + { + endCharacter = 2, + endLine = 16, + kind = 'region', + startCharacter = 31, + startLine = 15, + }, + { + endCharacter = 68, + endLine = 1, + kind = 'comment', + startCharacter = 2, + startLine = 0, + }, + { + endCharacter = 64, + endLine = 5, + kind = 'comment', + startCharacter = 4, + startLine = 4, + }, + } + + local bufnr ---@type integer + local client_id ---@type integer + + clear_notrace() + before_each(function() + clear_notrace() + + exec_lua(create_server_definition) + bufnr = n.api.nvim_get_current_buf() + client_id = exec_lua(function() + _G.server = _G._create_server({ + capabilities = { + foldingRangeProvider = true, + }, + handlers = { + ['textDocument/foldingRange'] = function(_, _, callback) + callback(nil, result) + end, + }, + }) + + vim.api.nvim_win_set_buf(0, bufnr) + + return vim.lsp.start({ name = 'dummy', cmd = _G.server.cmd }) + end) + command('set foldmethod=expr foldcolumn=1 foldlevel=999') + insert(text) + end) + after_each(function() + api.nvim_exec_autocmds('VimLeavePre', { modeline = false }) + end) + + describe('setup()', function() + ---@type integer + local bufnr_set_expr + ---@type integer + local bufnr_never_set_expr + + local function buf_autocmd_num(bufnr_to_check) + return exec_lua(function() + return #vim.api.nvim_get_autocmds({ buffer = bufnr_to_check, event = 'LspNotify' }) + end) + end + + before_each(function() + command([[setlocal foldexpr=v:lua.vim.lsp.foldexpr()]]) + exec_lua(function() + bufnr_set_expr = vim.api.nvim_create_buf(true, false) + vim.api.nvim_set_current_buf(bufnr_set_expr) + end) + insert(text) + command('write ' .. tempname(false)) + command([[setlocal foldexpr=v:lua.vim.lsp.foldexpr()]]) + exec_lua(function() + bufnr_never_set_expr = vim.api.nvim_create_buf(true, false) + vim.api.nvim_set_current_buf(bufnr_never_set_expr) + end) + insert(text) + api.nvim_win_set_buf(0, bufnr_set_expr) + end) + + it('only create event hooks where foldexpr has been set', function() + eq(1, buf_autocmd_num(bufnr)) + eq(1, buf_autocmd_num(bufnr_set_expr)) + eq(0, buf_autocmd_num(bufnr_never_set_expr)) + end) + + it('does not create duplicate event hooks after reloaded', function() + command('edit') + eq(1, buf_autocmd_num(bufnr_set_expr)) + end) + + it('cleans up event hooks when buffer is unloaded', function() + command('bdelete') + eq(0, buf_autocmd_num(bufnr_set_expr)) + end) + end) + + describe('expr()', function() + --- @type test.functional.ui.screen + local screen + before_each(function() + screen = Screen.new(80, 45) + screen:set_default_attr_ids({ + [1] = { background = Screen.colors.Grey, foreground = Screen.colors.DarkBlue }, + [2] = { bold = true, foreground = Screen.colors.Blue1 }, + [3] = { bold = true, reverse = true }, + [4] = { reverse = true }, + }) + command([[set foldexpr=v:lua.vim.lsp.foldexpr()]]) + command([[split]]) + end) + + it('can compute fold levels', function() + ---@type table<integer, string> + local foldlevels = {} + for i = 1, 21 do + foldlevels[i] = exec_lua('return vim.lsp.foldexpr(' .. i .. ')') + end + eq({ + [1] = '>1', + [2] = '<1', + [3] = '0', + [4] = '>1', + [5] = '>2', + [6] = '<2', + [7] = '>2', + [8] = '<2', + [9] = '>2', + [10] = '<2', + [11] = '>2', + [12] = '<2', + [13] = '1', + [14] = '1', + [15] = '1', + [16] = '>2', + [17] = '<2', + [18] = '1', + [19] = '1', + [20] = '<1', + [21] = '0', + }, foldlevels) + end) + + it('updates folds in all windows', function() + screen:expect({ + grid = [[ +{1:-}// foldLevel() {{{2 | +{1:│}/// @return fold level at line number "lnum" in the current window. | +{1: }static int foldLevel(linenr_T lnum) | +{1:-}{ | +{1:-} // While updating the folds lines between invalid_top and invalid_bot have | +{1:2} // an undefined fold level. Otherwise update the folds first. | +{1:-} if (invalid_top == 0) { | +{1:2} checkupdate(curwin); | +{1:-} } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) { | +{1:2} return prev_lnum_lvl; | +{1:-} } else if (lnum >= invalid_top && lnum <= invalid_bot) { | +{1:2} return -1; | +{1:│} } | +{1:│} | +{1:│} // Return quickly when there is no folding at all in this window. | +{1:-} if (!hasAnyFolding(curwin)) { | +{1:2} return 0; | +{1:│} } | +{1:│} | +{1:│} return foldLevelWin(curwin, lnum); | +{1: }^} | +{3:[No Name] [+] }| +{1:-}// foldLevel() {{{2 | +{1:│}/// @return fold level at line number "lnum" in the current window. | +{1: }static int foldLevel(linenr_T lnum) | +{1:-}{ | +{1:-} // While updating the folds lines between invalid_top and invalid_bot have | +{1:2} // an undefined fold level. Otherwise update the folds first. | +{1:-} if (invalid_top == 0) { | +{1:2} checkupdate(curwin); | +{1:-} } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) { | +{1:2} return prev_lnum_lvl; | +{1:-} } else if (lnum >= invalid_top && lnum <= invalid_bot) { | +{1:2} return -1; | +{1:│} } | +{1:│} | +{1:│} // Return quickly when there is no folding at all in this window. | +{1:-} if (!hasAnyFolding(curwin)) { | +{1:2} return 0; | +{1:│} } | +{1:│} | +{1:│} return foldLevelWin(curwin, lnum); | +{1: }} | +{4:[No Name] [+] }| + | + ]], + }) + end) + + it('persists wherever foldexpr is set', function() + command([[setlocal foldexpr=]]) + feed('<C-w><C-w>zx') + screen:expect({ + grid = [[ +{1: }// foldLevel() {{{2 | +{1: }/// @return fold level at line number "lnum" in the current window. | +{1: }static int foldLevel(linenr_T lnum) | +{1: }{ | +{1: } // While updating the folds lines between invalid_top and invalid_bot have | +{1: } // an undefined fold level. Otherwise update the folds first. | +{1: } if (invalid_top == 0) { | +{1: } checkupdate(curwin); | +{1: } } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) { | +{1: } return prev_lnum_lvl; | +{1: } } else if (lnum >= invalid_top && lnum <= invalid_bot) { | +{1: } return -1; | +{1: } } | +{1: } | +{1: } // Return quickly when there is no folding at all in this window. | +{1: } if (!hasAnyFolding(curwin)) { | +{1: } return 0; | +{1: } } | +{1: } | +{1: } return foldLevelWin(curwin, lnum); | +{1: }} | +{4:[No Name] [+] }| +{1:-}// foldLevel() {{{2 | +{1:│}/// @return fold level at line number "lnum" in the current window. | +{1: }static int foldLevel(linenr_T lnum) | +{1:-}{ | +{1:-} // While updating the folds lines between invalid_top and invalid_bot have | +{1:2} // an undefined fold level. Otherwise update the folds first. | +{1:-} if (invalid_top == 0) { | +{1:2} checkupdate(curwin); | +{1:-} } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) { | +{1:2} return prev_lnum_lvl; | +{1:-} } else if (lnum >= invalid_top && lnum <= invalid_bot) { | +{1:2} return -1; | +{1:│} } | +{1:│} | +{1:│} // Return quickly when there is no folding at all in this window. | +{1:-} if (!hasAnyFolding(curwin)) { | +{1:2} return 0; | +{1:│} } | +{1:│} | +{1:│} return foldLevelWin(curwin, lnum); | +{1: }^} | +{3:[No Name] [+] }| + | + ]], + }) + end) + + it('synchronizes changed rows with their previous foldlevels', function() + command('1,2d') + screen:expect({ + grid = [[ +{1: }^static int foldLevel(linenr_T lnum) | +{1:-}{ | +{1:-} // While updating the folds lines between invalid_top and invalid_bot have | +{1:2} // an undefined fold level. Otherwise update the folds first. | +{1:-} if (invalid_top == 0) { | +{1:2} checkupdate(curwin); | +{1:-} } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) { | +{1:2} return prev_lnum_lvl; | +{1:-} } else if (lnum >= invalid_top && lnum <= invalid_bot) { | +{1:2} return -1; | +{1:│} } | +{1:│} | +{1:│} // Return quickly when there is no folding at all in this window. | +{1:-} if (!hasAnyFolding(curwin)) { | +{1:2} return 0; | +{1:│} } | +{1:│} | +{1:│} return foldLevelWin(curwin, lnum); | +{1: }} | +{2:~ }|*2 +{3:[No Name] [+] }| +{1: }static int foldLevel(linenr_T lnum) | +{1:-}{ | +{1:-} // While updating the folds lines between invalid_top and invalid_bot have | +{1:2} // an undefined fold level. Otherwise update the folds first. | +{1:-} if (invalid_top == 0) { | +{1:2} checkupdate(curwin); | +{1:-} } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) { | +{1:2} return prev_lnum_lvl; | +{1:-} } else if (lnum >= invalid_top && lnum <= invalid_bot) { | +{1:2} return -1; | +{1:│} } | +{1:│} | +{1:│} // Return quickly when there is no folding at all in this window. | +{1:-} if (!hasAnyFolding(curwin)) { | +{1:2} return 0; | +{1:│} } | +{1:│} | +{1:│} return foldLevelWin(curwin, lnum); | +{1: }} | +{2:~ }|*2 +{4:[No Name] [+] }| + | +]], + }) + end) + + it('clears folds when sole client detaches', function() + exec_lua(function() + vim.lsp.buf_detach_client(bufnr, client_id) + end) + screen:expect({ + grid = [[ +{1: }// foldLevel() {{{2 | +{1: }/// @return fold level at line number "lnum" in the current window. | +{1: }static int foldLevel(linenr_T lnum) | +{1: }{ | +{1: } // While updating the folds lines between invalid_top and invalid_bot have | +{1: } // an undefined fold level. Otherwise update the folds first. | +{1: } if (invalid_top == 0) { | +{1: } checkupdate(curwin); | +{1: } } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) { | +{1: } return prev_lnum_lvl; | +{1: } } else if (lnum >= invalid_top && lnum <= invalid_bot) { | +{1: } return -1; | +{1: } } | +{1: } | +{1: } // Return quickly when there is no folding at all in this window. | +{1: } if (!hasAnyFolding(curwin)) { | +{1: } return 0; | +{1: } } | +{1: } | +{1: } return foldLevelWin(curwin, lnum); | +{1: }^} | +{3:[No Name] [+] }| +{1: }// foldLevel() {{{2 | +{1: }/// @return fold level at line number "lnum" in the current window. | +{1: }static int foldLevel(linenr_T lnum) | +{1: }{ | +{1: } // While updating the folds lines between invalid_top and invalid_bot have | +{1: } // an undefined fold level. Otherwise update the folds first. | +{1: } if (invalid_top == 0) { | +{1: } checkupdate(curwin); | +{1: } } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) { | +{1: } return prev_lnum_lvl; | +{1: } } else if (lnum >= invalid_top && lnum <= invalid_bot) { | +{1: } return -1; | +{1: } } | +{1: } | +{1: } // Return quickly when there is no folding at all in this window. | +{1: } if (!hasAnyFolding(curwin)) { | +{1: } return 0; | +{1: } } | +{1: } | +{1: } return foldLevelWin(curwin, lnum); | +{1: }} | +{4:[No Name] [+] }| + | + ]], + }) + end) + + it('remains valid after the client re-attaches.', function() + exec_lua(function() + vim.lsp.buf_detach_client(bufnr, client_id) + vim.lsp.buf_attach_client(bufnr, client_id) + end) + screen:expect({ + grid = [[ +{1:-}// foldLevel() {{{2 | +{1:│}/// @return fold level at line number "lnum" in the current window. | +{1: }static int foldLevel(linenr_T lnum) | +{1:-}{ | +{1:-} // While updating the folds lines between invalid_top and invalid_bot have | +{1:2} // an undefined fold level. Otherwise update the folds first. | +{1:-} if (invalid_top == 0) { | +{1:2} checkupdate(curwin); | +{1:-} } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) { | +{1:2} return prev_lnum_lvl; | +{1:-} } else if (lnum >= invalid_top && lnum <= invalid_bot) { | +{1:2} return -1; | +{1:│} } | +{1:│} | +{1:│} // Return quickly when there is no folding at all in this window. | +{1:-} if (!hasAnyFolding(curwin)) { | +{1:2} return 0; | +{1:│} } | +{1:│} | +{1:│} return foldLevelWin(curwin, lnum); | +{1: }^} | +{3:[No Name] [+] }| +{1:-}// foldLevel() {{{2 | +{1:│}/// @return fold level at line number "lnum" in the current window. | +{1: }static int foldLevel(linenr_T lnum) | +{1:-}{ | +{1:-} // While updating the folds lines between invalid_top and invalid_bot have | +{1:2} // an undefined fold level. Otherwise update the folds first. | +{1:-} if (invalid_top == 0) { | +{1:2} checkupdate(curwin); | +{1:-} } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) { | +{1:2} return prev_lnum_lvl; | +{1:-} } else if (lnum >= invalid_top && lnum <= invalid_bot) { | +{1:2} return -1; | +{1:│} } | +{1:│} | +{1:│} // Return quickly when there is no folding at all in this window. | +{1:-} if (!hasAnyFolding(curwin)) { | +{1:2} return 0; | +{1:│} } | +{1:│} | +{1:│} return foldLevelWin(curwin, lnum); | +{1: }} | +{4:[No Name] [+] }| + | + ]], + }) + end) + end) + + describe('foldtext()', function() + --- @type test.functional.ui.screen + local screen + before_each(function() + screen = Screen.new(80, 23) + screen:set_default_attr_ids({ + [1] = { background = Screen.colors.Grey, foreground = Screen.colors.DarkBlue }, + [2] = { foreground = Screen.colors.DarkBlue, background = Screen.colors.LightGrey }, + [3] = { bold = true, foreground = Screen.colors.Blue1 }, + [4] = { bold = true, reverse = true }, + [5] = { reverse = true }, + }) + command( + [[set foldexpr=v:lua.vim.lsp.foldexpr() foldtext=v:lua.vim.lsp.foldtext() foldlevel=1]] + ) + end) + + it('shows the first folded line if `collapsedText` does not exist', function() + screen:expect({ + grid = [[ +{1:-}// foldLevel() {{{2 | +{1:│}/// @return fold level at line number "lnum" in the current window. | +{1: }static int foldLevel(linenr_T lnum) | +{1:-}{ | +{1:+}{2: // While updating the folds lines between invalid_top and invalid_bot have···}| +{1:+}{2: if (invalid_top == 0) {······················································}| +{1:+}{2: } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) {························}| +{1:+}{2: } else if (lnum >= invalid_top && lnum <= invalid_bot) {·····················}| +{1:│} } | +{1:│} | +{1:│} // Return quickly when there is no folding at all in this window. | +{1:+}{2: if (!hasAnyFolding(curwin)) {················································}| +{1:│} } | +{1:│} | +{1:│} return foldLevelWin(curwin, lnum); | +{1: }^} | +{3:~ }|*6 + | + ]], + }) + end) + end) + + describe('foldclose()', function() + --- @type test.functional.ui.screen + local screen + before_each(function() + screen = Screen.new(80, 23) + screen:set_default_attr_ids({ + [1] = { background = Screen.colors.Grey, foreground = Screen.colors.DarkBlue }, + [2] = { foreground = Screen.colors.DarkBlue, background = Screen.colors.LightGrey }, + [3] = { bold = true, foreground = Screen.colors.Blue1 }, + [4] = { bold = true, reverse = true }, + [5] = { reverse = true }, + }) + command([[set foldexpr=v:lua.vim.lsp.foldexpr()]]) + end) + + it('closes all folds of one kind immediately', function() + exec_lua(function() + vim.lsp.foldclose('comment') + end) + screen:expect({ + grid = [[ +{1:+}{2:+-- 2 lines: foldLevel()······················································}| +{1: }static int foldLevel(linenr_T lnum) | +{1:-}{ | +{1:+}{2:+--- 2 lines: While updating the folds lines between invalid_top and invalid_b}| +{1:-} if (invalid_top == 0) { | +{1:2} checkupdate(curwin); | +{1:-} } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) { | +{1:2} return prev_lnum_lvl; | +{1:-} } else if (lnum >= invalid_top && lnum <= invalid_bot) { | +{1:2} return -1; | +{1:│} } | +{1:│} | +{1:│} // Return quickly when there is no folding at all in this window. | +{1:-} if (!hasAnyFolding(curwin)) { | +{1:2} return 0; | +{1:│} } | +{1:│} | +{1:│} return foldLevelWin(curwin, lnum); | +{1: }^} | +{3:~ }|*3 + | + ]], + }) + end) + + it('closes the smallest fold first', function() + exec_lua(function() + vim.lsp.foldclose('region') + end) + screen:expect({ + grid = [[ +{1:-}// foldLevel() {{{2 | +{1:│}/// @return fold level at line number "lnum" in the current window. | +{1: }static int foldLevel(linenr_T lnum) | +{1:+}{2:+-- 17 lines: {································································}| +{1: }^} | +{3:~ }|*17 + | + ]], + }) + command('4foldopen') + screen:expect({ + grid = [[ +{1:-}// foldLevel() {{{2 | +{1:│}/// @return fold level at line number "lnum" in the current window. | +{1: }static int foldLevel(linenr_T lnum) | +{1:-}{ | +{1:-} // While updating the folds lines between invalid_top and invalid_bot have | +{1:2} // an undefined fold level. Otherwise update the folds first. | +{1:+}{2:+--- 2 lines: if (invalid_top == 0) {·········································}| +{1:+}{2:+--- 2 lines: } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) {···········}| +{1:+}{2:+--- 2 lines: } else if (lnum >= invalid_top && lnum <= invalid_bot) {········}| +{1:│} } | +{1:│} | +{1:│} // Return quickly when there is no folding at all in this window. | +{1:+}{2:+--- 2 lines: if (!hasAnyFolding(curwin)) {···································}| +{1:│} } | +{1:│} | +{1:│} return foldLevelWin(curwin, lnum); | +{1: }^} | +{3:~ }|*5 + | + ]], + }) + end) + + it('is defered when the buffer is not up-to-date', function() + exec_lua(function() + vim.lsp.foldclose('comment') + vim.lsp.util.buf_versions[bufnr] = 0 + end) + screen:expect({ + grid = [[ +{1:+}{2:+-- 2 lines: foldLevel()······················································}| +{1: }static int foldLevel(linenr_T lnum) | +{1:-}{ | +{1:+}{2:+--- 2 lines: While updating the folds lines between invalid_top and invalid_b}| +{1:-} if (invalid_top == 0) { | +{1:2} checkupdate(curwin); | +{1:-} } else if (lnum == prev_lnum && prev_lnum_lvl >= 0) { | +{1:2} return prev_lnum_lvl; | +{1:-} } else if (lnum >= invalid_top && lnum <= invalid_bot) { | +{1:2} return -1; | +{1:│} } | +{1:│} | +{1:│} // Return quickly when there is no folding at all in this window. | +{1:-} if (!hasAnyFolding(curwin)) { | +{1:2} return 0; | +{1:│} } | +{1:│} | +{1:│} return foldLevelWin(curwin, lnum); | +{1: }^} | +{3:~ }|*3 + | + ]], + }) + end) + end) +end) diff --git a/test/functional/plugin/lsp/handler_spec.lua b/test/functional/plugin/lsp/handler_spec.lua deleted file mode 100644 index 4b05b676a8..0000000000 --- a/test/functional/plugin/lsp/handler_spec.lua +++ /dev/null @@ -1,42 +0,0 @@ -local t = require('test.testutil') -local n = require('test.functional.testnvim')() - -local eq = t.eq -local exec_lua = n.exec_lua -local pcall_err = t.pcall_err -local matches = t.matches - -describe('lsp-handlers', function() - describe('vim.lsp._with_extend', function() - it('should return a table with the default keys', function() - eq( - { hello = 'world' }, - exec_lua(function() - return vim.lsp._with_extend('test', { hello = 'world' }) - end) - ) - end) - - it('should override with config keys', function() - eq( - { hello = 'universe', other = true }, - exec_lua(function() - return vim.lsp._with_extend( - 'test', - { other = true, hello = 'world' }, - { hello = 'universe' } - ) - end) - ) - end) - - it('should not allow invalid keys', function() - matches( - '.*Invalid option for `test`.*', - pcall_err(exec_lua, function() - return vim.lsp._with_extend('test', { hello = 'world' }, { invalid = true }) - end) - ) - end) - end) -end) diff --git a/test/functional/plugin/lsp/incremental_sync_spec.lua b/test/functional/plugin/lsp/incremental_sync_spec.lua index f60e159d64..0bca1fa4ca 100644 --- a/test/functional/plugin/lsp/incremental_sync_spec.lua +++ b/test/functional/plugin/lsp/incremental_sync_spec.lua @@ -23,7 +23,7 @@ before_each(function() -- local line_ending = format_line_ending[vim.api.nvim_get_option_value('fileformat', {})] --- @diagnostic disable-next-line:duplicate-set-field - function _G.test_register(bufnr, id, offset_encoding, line_ending) + function _G.test_register(bufnr, id, position_encoding, line_ending) local prev_lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, true) local function callback(_, bufnr0, _changedtick, firstline, lastline, new_lastline) @@ -38,7 +38,7 @@ before_each(function() firstline, lastline, new_lastline, - offset_encoding, + position_encoding, line_ending ) @@ -63,15 +63,15 @@ local function test_edit( prev_buffer, edit_operations, expected_text_changes, - offset_encoding, + position_encoding, line_ending ) - offset_encoding = offset_encoding or 'utf-16' + position_encoding = position_encoding or 'utf-16' line_ending = line_ending or '\n' api.nvim_buf_set_lines(0, 0, -1, true, prev_buffer) exec_lua(function() - return _G.test_register(0, 'test1', offset_encoding, line_ending) + return _G.test_register(0, 'test1', position_encoding, line_ending) end) for _, edit in ipairs(edit_operations) do diff --git a/test/functional/plugin/lsp/semantic_tokens_spec.lua b/test/functional/plugin/lsp/semantic_tokens_spec.lua index 280bd27207..9912bf2063 100644 --- a/test/functional/plugin/lsp/semantic_tokens_spec.lua +++ b/test/functional/plugin/lsp/semantic_tokens_spec.lua @@ -456,7 +456,7 @@ describe('semantic token highlighting', function() vim.notify = function(...) table.insert(_G.notifications, 1, { ... }) end - return vim.lsp.start_client({ name = 'dummy', cmd = _G.server.cmd }) + return vim.lsp.start({ name = 'dummy', cmd = _G.server.cmd }, { attach = false }) end) eq(false, exec_lua('return vim.lsp.buf_is_attached(0, ...)', client_id)) diff --git a/test/functional/plugin/lsp/testutil.lua b/test/functional/plugin/lsp/testutil.lua index a36cbac568..95fc22b96b 100644 --- a/test/functional/plugin/lsp/testutil.lua +++ b/test/functional/plugin/lsp/testutil.lua @@ -182,16 +182,17 @@ function M.test_rpc_server(config) ) end local client = setmetatable({}, { - __index = function(_, name) + __index = function(t, name) -- Workaround for not being able to yield() inside __index for Lua 5.1 :( -- Otherwise I would just return the value here. - return function(...) + return function(arg1, ...) + local ismethod = arg1 == t return exec_lua(function(...) - if type(_G.TEST_RPC_CLIENT[name]) == 'function' then - return _G.TEST_RPC_CLIENT[name](...) - else - return _G.TEST_RPC_CLIENT[name] + local client = _G.TEST_RPC_CLIENT + if type(client[name]) == 'function' then + return client[name](ismethod and client or arg1, ...) end + return client[name] end, ...) end end, diff --git a/test/functional/plugin/lsp/utils_spec.lua b/test/functional/plugin/lsp/utils_spec.lua index 813b8de812..1e3e759e0b 100644 --- a/test/functional/plugin/lsp/utils_spec.lua +++ b/test/functional/plugin/lsp/utils_spec.lua @@ -5,6 +5,8 @@ local Screen = require('test.functional.ui.screen') local feed = n.feed local eq = t.eq local exec_lua = n.exec_lua +local command, api = n.command, n.api +local pcall_err = t.pcall_err describe('vim.lsp.util', function() before_each(n.clear) @@ -265,6 +267,66 @@ describe('vim.lsp.util', function() eq(56, opts.height) end) + + describe('vim.lsp.util.open_floating_preview', function() + local var_name = 'lsp_floating_preview' + local curbuf = api.nvim_get_current_buf() + + it('clean bufvar after fclose', function() + exec_lua(function() + vim.lsp.util.open_floating_preview({ 'test' }, '', { height = 5, width = 2 }) + end) + eq(true, api.nvim_win_is_valid(api.nvim_buf_get_var(curbuf, var_name))) + command('fclose') + eq( + 'Key not found: lsp_floating_preview', + pcall_err(api.nvim_buf_get_var, curbuf, var_name) + ) + end) + + it('clean bufvar after CursorMoved', function() + local result = exec_lua(function() + vim.lsp.util.open_floating_preview({ 'test' }, '', { height = 5, width = 2 }) + local winnr = vim.b[vim.api.nvim_get_current_buf()].lsp_floating_preview + local result = vim.api.nvim_win_is_valid(winnr) + vim.api.nvim_feedkeys(vim.keycode('G'), 'txn', false) + return result + end) + eq(true, result) + eq( + 'Key not found: lsp_floating_preview', + pcall_err(api.nvim_buf_get_var, curbuf, var_name) + ) + end) + end) + end) + end) + + it('open_floating_preview zindex greater than current window', function() + local screen = Screen.new() + exec_lua(function() + vim.api.nvim_open_win(0, true, { + relative = 'editor', + border = 'single', + height = 11, + width = 51, + row = 2, + col = 2, + }) + vim.keymap.set('n', 'K', function() + vim.lsp.util.open_floating_preview({ 'foo' }, '', { border = 'single' }) + end, {}) end) + feed('K') + screen:expect([[ + ┌───────────────────────────────────────────────────┐| + │{4:^ }│| + │┌───┐{11: }│| + ││{4:foo}│{11: }│| + │└───┘{11: }│| + │{11:~ }│|*7 + └───────────────────────────────────────────────────┘| + | + ]]) end) end) diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 5222216faf..db3ab8ed98 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -95,7 +95,7 @@ describe('LSP', function() exec_lua(function() _G.lsp = require('vim.lsp') function _G.test__start_client() - return vim.lsp.start_client { + return vim.lsp.start({ cmd_env = { NVIM_LOG_FILE = fake_lsp_logfile, NVIM_APPNAME = 'nvim_lsp_test', @@ -112,7 +112,7 @@ describe('LSP', function() name = 'test_folder', }, }, - } + }, { attach = false }) end _G.TEST_CLIENT1 = _G.test__start_client() end) @@ -232,7 +232,7 @@ describe('LSP', function() -- client is a dummy object which will queue up commands to be run -- once the server initializes. It can't accept lua callbacks or -- other types that may be unserializable for now. - client.stop() + client:stop() end, -- If the program timed out, then code will be nil. on_exit = function(code, signal) @@ -254,8 +254,8 @@ describe('LSP', function() test_rpc_server { test_name = 'basic_init', on_init = function(client) - client.notify('test') - client.stop() + client:notify('test') + client:stop() end, on_exit = function(code, signal) eq(101, code, 'exit code') -- See fake-lsp-server.lua @@ -275,7 +275,7 @@ describe('LSP', function() test_rpc_server({ test_name = 'basic_init_did_change_configuration', on_init = function(client, _) - client.stop() + client:stop() end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -333,9 +333,9 @@ describe('LSP', function() test_name = 'basic_init', on_init = function(client) eq(0, client.server_capabilities().textDocumentSync.change) - client.request('shutdown') - client.notify('exit') - client.stop() + client:request('shutdown') + client:notify('exit') + client:stop() end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -377,7 +377,7 @@ describe('LSP', function() end, on_init = function(_client) client = _client - client.notify('finish') + client:notify('finish') end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -395,7 +395,7 @@ describe('LSP', function() return vim.lsp.buf_is_attached(_G.BUFFER, _G.TEST_RPC_CLIENT_ID) end) ) - client.stop() + client:stop() end end, } @@ -430,7 +430,7 @@ describe('LSP', function() return vim.lsp.buf_attach_client(_G.BUFFER, _G.TEST_RPC_CLIENT_ID) end) ) - client.notify('finish') + client:notify('finish') end, on_handler = function(_, _, ctx) if ctx.method == 'finish' then @@ -439,7 +439,7 @@ describe('LSP', function() return vim.lsp.buf_detach_client(_G.BUFFER, _G.TEST_RPC_CLIENT_ID) end) eq('basic_init', api.nvim_get_var('lsp_detached')) - client.stop() + client:stop() end end, } @@ -466,13 +466,20 @@ describe('LSP', function() true, exec_lua(function() local keymap --- @type table<string,any> + local called = false + local origin = vim.lsp.buf.hover + vim.lsp.buf.hover = function() + called = true + end vim._with({ buf = _G.BUFFER }, function() keymap = vim.fn.maparg('K', 'n', false, true) end) - return keymap.callback == vim.lsp.buf.hover + keymap.callback() + vim.lsp.buf.hover = origin + return called end) ) - client.stop() + client:stop() end end, on_exit = function(_, _) @@ -480,13 +487,13 @@ describe('LSP', function() eq('', get_buf_option('omnifunc')) eq('', get_buf_option('formatexpr')) eq( - '', + true, exec_lua(function() local keymap --- @type string vim._with({ buf = _G.BUFFER }, function() keymap = vim.fn.maparg('K', 'n', false, false) end) - return keymap + return keymap:match('<Lua %d+: .+/runtime/lua/vim/lsp%.lua:%d+>') ~= nil end) ) end, @@ -524,7 +531,7 @@ describe('LSP', function() eq('v:lua.vim.lsp.tagfunc', get_buf_option('tagfunc', BUFFER_1)) eq('v:lua.vim.lsp.omnifunc', get_buf_option('omnifunc', BUFFER_2)) eq('v:lua.vim.lsp.formatexpr()', get_buf_option('formatexpr', BUFFER_2)) - client.stop() + client:stop() end end, on_exit = function(_, _) @@ -554,7 +561,7 @@ describe('LSP', function() eq('tfu', get_buf_option('tagfunc')) eq('ofu', get_buf_option('omnifunc')) eq('fex', get_buf_option('formatexpr')) - client.stop() + client:stop() end end, on_exit = function(_, _) @@ -711,10 +718,10 @@ describe('LSP', function() ctx.method, result ) - client.notify('workspace/configuration', server_result) + client:notify('workspace/configuration', server_result) end if ctx.method == 'shutdown' then - client.stop() + client:stop() end end, } @@ -756,7 +763,7 @@ describe('LSP', function() test_rpc_server { test_name = 'basic_check_capabilities', on_init = function(client) - client.stop() + client:stop() local full_kind = exec_lua(function() return require 'vim.lsp.protocol'.TextDocumentSyncKind.Full end) @@ -798,7 +805,7 @@ describe('LSP', function() vim.api.nvim_exec_autocmds('BufWritePost', { buffer = _G.BUFFER, modeline = false }) end) else - client.stop() + client:stop() end end, } @@ -898,7 +905,7 @@ describe('LSP', function() end) end) else - client.stop() + client:stop() end end, }) @@ -929,20 +936,20 @@ describe('LSP', function() vim.api.nvim_exec_autocmds('BufWritePost', { buffer = _G.BUFFER, modeline = false }) end) else - client.stop() + client:stop() end end, } end) - it('client.supports_methods() should validate capabilities', function() + it('client:supports_methods() should validate capabilities', function() local expected_handlers = { { NIL, {}, { method = 'shutdown', client_id = 1 } }, } test_rpc_server { test_name = 'capabilities_for_client_supports_method', on_init = function(client) - client.stop() + client:stop() local expected_sync_capabilities = { change = 1, openClose = true, @@ -958,11 +965,11 @@ describe('LSP', function() eq(true, client.server_capabilities().codeLensProvider.resolveProvider) -- known methods for resolved capabilities - eq(true, client.supports_method('textDocument/hover')) - eq(false, client.supports_method('textDocument/definition')) + eq(true, client:supports_method('textDocument/hover')) + eq(false, client:supports_method('textDocument/definition')) -- unknown methods are assumed to be supported. - eq(true, client.supports_method('unknown-method')) + eq(true, client:supports_method('unknown-method')) end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -989,7 +996,7 @@ describe('LSP', function() end) end, on_init = function(client) - client.stop() + client:stop() exec_lua(function() vim.lsp.buf.type_definition() end) @@ -1018,7 +1025,7 @@ describe('LSP', function() end) end, on_init = function(client) - client.stop() + client:stop() exec_lua(function() vim.lsp.buf.type_definition() end) @@ -1042,7 +1049,7 @@ describe('LSP', function() test_rpc_server { test_name = 'check_forward_request_cancelled', on_init = function(_client) - _client.request('error_code_test') + _client:request('error_code_test') client = _client end, on_exit = function(code, signal) @@ -1053,7 +1060,40 @@ describe('LSP', function() on_handler = function(err, _, ctx) eq(table.remove(expected_handlers), { err, {}, ctx }, 'expected handler') if ctx.method == 'finish' then - client.stop() + client:stop() + end + end, + } + end) + + it('should forward ServerCancelled to callback', function() + local expected_handlers = { + { NIL, {}, { method = 'finish', client_id = 1 } }, + { + { code = -32802 }, + NIL, + { method = 'error_code_test', bufnr = 1, client_id = 1, version = 0 }, + }, + } + local client --- @type vim.lsp.Client + test_rpc_server { + test_name = 'check_forward_server_cancelled', + on_init = function(_client) + _client:request('error_code_test') + client = _client + end, + on_exit = function(code, signal) + eq(0, code, 'exit code') + eq(0, signal, 'exit signal') + eq(0, #expected_handlers, 'did not call expected handler') + end, + on_handler = function(err, _, ctx) + eq(table.remove(expected_handlers), { err, _, ctx }, 'expected handler') + if ctx.method ~= 'finish' then + client:notify('finish') + end + if ctx.method == 'finish' then + client:stop() end end, } @@ -1072,7 +1112,7 @@ describe('LSP', function() test_rpc_server { test_name = 'check_forward_content_modified', on_init = function(_client) - _client.request('error_code_test') + _client:request('error_code_test') client = _client end, on_exit = function(code, signal) @@ -1082,12 +1122,11 @@ describe('LSP', function() end, on_handler = function(err, _, ctx) eq(table.remove(expected_handlers), { err, _, ctx }, 'expected handler') - -- if ctx.method == 'error_code_test' then client.notify("finish") end if ctx.method ~= 'finish' then - client.notify('finish') + client:notify('finish') end if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1103,13 +1142,13 @@ describe('LSP', function() test_name = 'check_pending_request_tracked', on_init = function(_client) client = _client - client.request('slow_request') + client:request('slow_request') local request = exec_lua(function() return _G.TEST_RPC_CLIENT.requests[2] end) eq('slow_request', request.method) eq('pending', request.type) - client.notify('release') + client:notify('release') end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -1123,10 +1162,10 @@ describe('LSP', function() return _G.TEST_RPC_CLIENT.requests[2] end) eq(nil, request) - client.notify('finish') + client:notify('finish') end if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1141,14 +1180,14 @@ describe('LSP', function() test_name = 'check_cancel_request_tracked', on_init = function(_client) client = _client - client.request('slow_request') - client.cancel_request(2) + client:request('slow_request') + client:cancel_request(2) local request = exec_lua(function() return _G.TEST_RPC_CLIENT.requests[2] end) eq('slow_request', request.method) eq('cancel', request.type) - client.notify('release') + client:notify('release') end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -1162,7 +1201,7 @@ describe('LSP', function() end) eq(nil, request) if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1178,19 +1217,19 @@ describe('LSP', function() test_name = 'check_tracked_requests_cleared', on_init = function(_client) client = _client - client.request('slow_request') + client:request('slow_request') local request = exec_lua(function() return _G.TEST_RPC_CLIENT.requests[2] end) eq('slow_request', request.method) eq('pending', request.type) - client.cancel_request(2) + client:cancel_request(2) request = exec_lua(function() return _G.TEST_RPC_CLIENT.requests[2] end) eq('slow_request', request.method) eq('cancel', request.type) - client.notify('release') + client:notify('release') end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -1204,10 +1243,10 @@ describe('LSP', function() return _G.TEST_RPC_CLIENT.requests[2] end) eq(nil, request) - client.notify('finish') + client:notify('finish') end if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1225,11 +1264,11 @@ describe('LSP', function() command('let g:requests = 0') command('autocmd LspRequest * let g:requests+=1') client = _client - client.request('slow_request') + client:request('slow_request') eq(1, eval('g:requests')) - client.cancel_request(2) + client:cancel_request(2) eq(2, eval('g:requests')) - client.notify('release') + client:notify('release') end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -1240,10 +1279,10 @@ describe('LSP', function() on_handler = function(err, _, ctx) eq(table.remove(expected_handlers), { err, {}, ctx }, 'expected handler') if ctx.method == 'slow_request' then - client.notify('finish') + client:notify('finish') end if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1277,7 +1316,7 @@ describe('LSP', function() end) eq(full_kind, client.server_capabilities().textDocumentSync.change) eq(true, client.server_capabilities().textDocumentSync.openClose) - client.notify('finish') + client:notify('finish') end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -1286,7 +1325,7 @@ describe('LSP', function() on_handler = function(err, result, ctx) eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1333,11 +1372,11 @@ describe('LSP', function() end, on_handler = function(err, result, ctx) if ctx.method == 'start' then - client.notify('finish') + client:notify('finish') end eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1378,11 +1417,11 @@ describe('LSP', function() end, on_handler = function(err, result, ctx) if ctx.method == 'start' then - client.notify('finish') + client:notify('finish') end eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1428,11 +1467,11 @@ describe('LSP', function() 'boop', }) end) - client.notify('finish') + client:notify('finish') end eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1479,11 +1518,11 @@ describe('LSP', function() 'boop', }) end) - client.notify('finish') + client:notify('finish') end eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1529,7 +1568,7 @@ describe('LSP', function() end, on_init = function(_client) client = _client - eq(true, client.supports_method('textDocument/inlayHint')) + eq(true, client:supports_method('textDocument/inlayHint')) exec_lua(function() assert(vim.lsp.buf_attach_client(_G.BUFFER, _G.TEST_RPC_CLIENT_ID)) end) @@ -1545,11 +1584,11 @@ describe('LSP', function() end) end if ctx.method == 'textDocument/inlayHint' then - client.notify('finish') + client:notify('finish') end eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1598,11 +1637,11 @@ describe('LSP', function() '123boop', }) end) - client.notify('finish') + client:notify('finish') end eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1652,11 +1691,11 @@ describe('LSP', function() '123boop', }) end) - client.notify('finish') + client:notify('finish') end eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1699,11 +1738,11 @@ describe('LSP', function() on_handler = function(err, result, ctx) if ctx.method == 'start' then n.command('normal! 1Go') - client.notify('finish') + client:notify('finish') end eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1752,11 +1791,11 @@ describe('LSP', function() 'boop', }) end) - client.notify('finish') + client:notify('finish') end eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -1806,15 +1845,29 @@ describe('LSP', function() }) vim.api.nvim_command(_G.BUFFER .. 'bwipeout') end) - client.notify('finish') + client:notify('finish') end eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') if ctx.method == 'finish' then - client.stop() + client:stop() end end, } end) + + it('vim.lsp.start when existing client has no workspace_folders', function() + exec_lua(create_server_definition) + eq( + { 2, 'foo', 'foo' }, + exec_lua(function() + local server = _G._create_server() + vim.lsp.start { cmd = server.cmd, name = 'foo' } + vim.lsp.start { cmd = server.cmd, name = 'foo', root_dir = 'bar' } + local foos = vim.lsp.get_clients() + return { #foos, foos[1].name, foos[2].name } + end) + ) + end) end) describe('parsing tests', function() @@ -1830,7 +1883,7 @@ describe('LSP', function() on_setup = function() end, on_init = function(_client) client = _client - client.stop(true) + client:stop(true) end, on_exit = function(code, signal) eq(0, code, 'exit code') @@ -1882,7 +1935,7 @@ describe('LSP', function() on_handler = function(err, result, ctx) eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') if ctx.method == 'finish' then - client.stop() + client:stop() end end, } @@ -2348,7 +2401,7 @@ describe('LSP', function() test_rpc_server { test_name = 'basic_init', on_init = function(client, _) - client.stop() + client:stop() end, -- If the program timed out, then code will be nil. on_exit = function(code, signal) @@ -3448,6 +3501,19 @@ describe('LSP', function() end) ) end) + it('handles empty line', function() + exec_lua(function() + _G.contents = { + '', + } + end) + eq( + { 20, 1 }, + exec_lua(function() + return { vim.lsp.util._make_floating_popup_size(_G.contents, { width = 20 }) } + end) + ) + end) end) describe('lsp.util.trim.trim_empty_lines', function() @@ -3499,7 +3565,7 @@ describe('LSP', function() } return vim.lsp.util.convert_signature_help_to_markdown_lines(signature_help, 'zig', { '(' }) end) - -- Note that although the higlight positions below are 0-indexed, the 2nd parameter + -- Note that although the highlight positions below are 0-indexed, the 2nd parameter -- corresponds to the 3rd line because the first line is the ``` from the -- Markdown block. local expected = { 3, 4, 3, 11 } @@ -4284,7 +4350,7 @@ describe('LSP', function() end) ) end - client.stop() + client:stop() end end, } @@ -4331,7 +4397,7 @@ describe('LSP', function() return type(vim.lsp.commands['dummy2']) end) ) - client.stop() + client:stop() end end, } @@ -4372,7 +4438,7 @@ describe('LSP', function() vim.lsp.buf.code_action() end) elseif ctx.method == 'shutdown' then - client.stop() + client:stop() end end, }) @@ -4447,7 +4513,7 @@ describe('LSP', function() return type(vim.lsp.commands['executed_type_annotate']) end) ) - client.stop() + client:stop() end end, } @@ -4564,7 +4630,7 @@ describe('LSP', function() end) eq({ command = 'Dummy', title = 'Lens1' }, cmd) elseif ctx.method == 'shutdown' then - client.stop() + client:stop() end end, } @@ -4653,7 +4719,7 @@ describe('LSP', function() end) eq({ command = 'Dummy', title = 'Lens2' }, response) elseif ctx.method == 'shutdown' then - client.stop() + client:stop() end end, } @@ -4762,7 +4828,7 @@ describe('LSP', function() return notify_msg end) eq('[LSP] Format request failed, no matching language servers.', notify_msg) - client.stop() + client:stop() end, } end) @@ -4795,7 +4861,7 @@ describe('LSP', function() end) eq(nil, notify_msg) elseif ctx.method == 'shutdown' then - client.stop() + client:stop() end end, } @@ -4836,7 +4902,7 @@ describe('LSP', function() end) eq(nil, notify_msg) elseif ctx.method == 'shutdown' then - client.stop() + client:stop() end end, } @@ -4883,7 +4949,7 @@ describe('LSP', function() end) eq(nil, notify_msg) elseif ctx.method == 'shutdown' then - client.stop() + client:stop() end end, } @@ -4930,7 +4996,7 @@ describe('LSP', function() end) eq({ handler_called = true }, result) elseif ctx.method == 'shutdown' then - client.stop() + client:stop() end end, } @@ -5145,8 +5211,8 @@ describe('LSP', function() local win = vim.api.nvim_get_current_win() vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, { 'local x = 10', '', 'print(x)' }) vim.api.nvim_win_set_cursor(win, { 3, 6 }) - local client_id1 = assert(vim.lsp.start({ name = 'dummy', cmd = server1.cmd })) - local client_id2 = assert(vim.lsp.start({ name = 'dummy', cmd = server2.cmd })) + local client_id1 = assert(vim.lsp.start({ name = 'dummy1', cmd = server1.cmd })) + local client_id2 = assert(vim.lsp.start({ name = 'dummy2', cmd = server2.cmd })) local response vim.lsp.buf.definition({ on_list = function(r) @@ -5477,7 +5543,7 @@ describe('LSP', function() result[#result + 1] = { method = method, fname = fname, - supported = client.supports_method(method, { bufnr = bufnr }), + supported = client:supports_method(method, { bufnr = bufnr }), } end @@ -6059,15 +6125,6 @@ describe('LSP', function() end eq(is_os('mac') or is_os('win'), check_registered(nil)) -- start{_client}() defaults to make_client_capabilities(). - eq(false, check_registered(vim.empty_dict())) - eq( - false, - check_registered({ - workspace = { - ignoreMe = true, - }, - }) - ) eq( false, check_registered({ @@ -6090,4 +6147,154 @@ describe('LSP', function() ) end) end) + + describe('vim.lsp.config() and vim.lsp.enable()', function() + it('can merge settings from "*"', function() + eq( + { + name = 'foo', + cmd = { 'foo' }, + root_markers = { '.git' }, + }, + exec_lua(function() + vim.lsp.config('*', { root_markers = { '.git' } }) + vim.lsp.config('foo', { cmd = { 'foo' } }) + + return vim.lsp.config['foo'] + end) + ) + end) + + it('sets up an autocmd', function() + eq( + 1, + exec_lua(function() + vim.lsp.config('foo', { + cmd = { 'foo' }, + root_markers = { '.foorc' }, + }) + vim.lsp.enable('foo') + return #vim.api.nvim_get_autocmds({ + group = 'nvim.lsp.enable', + event = 'FileType', + }) + end) + ) + end) + + it('attaches to buffers', function() + exec_lua(create_server_definition) + + local tmp1 = t.tmpname(true) + local tmp2 = t.tmpname(true) + + exec_lua(function() + local server = _G._create_server({ + handlers = { + initialize = function(_, _, callback) + callback(nil, { capabilities = {} }) + end, + }, + }) + + vim.lsp.config('foo', { + cmd = server.cmd, + filetypes = { 'foo' }, + root_markers = { '.foorc' }, + }) + + vim.lsp.config('bar', { + cmd = server.cmd, + filetypes = { 'bar' }, + root_markers = { '.foorc' }, + }) + + vim.lsp.enable('foo') + vim.lsp.enable('bar') + + vim.cmd.edit(tmp1) + vim.bo.filetype = 'foo' + _G.foo_buf = vim.api.nvim_get_current_buf() + + vim.cmd.edit(tmp2) + vim.bo.filetype = 'bar' + _G.bar_buf = vim.api.nvim_get_current_buf() + end) + + eq( + { 1, 'foo', 1, 'bar' }, + exec_lua(function() + local foos = vim.lsp.get_clients({ bufnr = assert(_G.foo_buf) }) + local bars = vim.lsp.get_clients({ bufnr = assert(_G.bar_buf) }) + return { #foos, foos[1].name, #bars, bars[1].name } + end) + ) + end) + + it('does not attach to buffers more than once if no root_dir', function() + exec_lua(create_server_definition) + + local tmp1 = t.tmpname(true) + + eq( + 1, + exec_lua(function() + local server = _G._create_server({ + handlers = { + initialize = function(_, _, callback) + callback(nil, { capabilities = {} }) + end, + }, + }) + + vim.lsp.config('foo', { cmd = server.cmd, filetypes = { 'foo' } }) + vim.lsp.enable('foo') + + vim.cmd.edit(assert(tmp1)) + vim.bo.filetype = 'foo' + vim.bo.filetype = 'foo' + + return #vim.lsp.get_clients({ bufnr = vim.api.nvim_get_current_buf() }) + end) + ) + end) + + it('supports async function for root_dir', function() + exec_lua(create_server_definition) + + local tmp1 = t.tmpname(true) + exec_lua(function() + local server = _G._create_server({ + handlers = { + initialize = function(_, _, callback) + callback(nil, { capabilities = {} }) + end, + }, + }) + + vim.lsp.config('foo', { + cmd = server.cmd, + filetypes = { 'foo' }, + root_dir = function(cb) + vim.system({ 'sleep', '0' }, {}, function() + cb('some_dir') + end) + end, + }) + vim.lsp.enable('foo') + + vim.cmd.edit(assert(tmp1)) + vim.bo.filetype = 'foo' + end) + + retry(nil, 1000, function() + eq( + 'some_dir', + exec_lua(function() + return vim.lsp.get_clients({ bufnr = vim.api.nvim_get_current_buf() })[1].root_dir + end) + ) + end) + end) + end) end) diff --git a/test/functional/plugin/man_spec.lua b/test/functional/plugin/man_spec.lua index 8906e60dce..c1dbc6dac3 100644 --- a/test/functional/plugin/man_spec.lua +++ b/test/functional/plugin/man_spec.lua @@ -21,13 +21,12 @@ local function get_search_history(name) local man = require('man') local res = {} --- @diagnostic disable-next-line:duplicate-set-field - man.find_path = function(sect, name0) + man._find_path = function(name0, sect) table.insert(res, { sect, name0 }) return nil end - local ok, rv = pcall(man.open_page, -1, { tab = 0 }, args) - assert(not ok) - assert(rv and rv:match('no manual entry')) + local err = man.open_page(-1, { tab = 0 }, args) + assert(err and err:match('no manual entry')) return res end) end @@ -225,7 +224,7 @@ describe(':Man', function() matches('^/.+', actual_file) local args = { nvim_prog, '--headless', '+:Man ' .. actual_file, '+q' } matches( - ('Error detected while processing command line:\r\n' .. 'man.lua: "no manual entry for %s"'):format( + ('Error detected while processing command line:\r\n' .. 'man.lua: no manual entry for %s'):format( pesc(actual_file) ), fn.system(args, { '' }) @@ -235,8 +234,8 @@ describe(':Man', function() it('tries variants with spaces, underscores #22503', function() eq({ - { '', 'NAME WITH SPACES' }, - { '', 'NAME_WITH_SPACES' }, + { vim.NIL, 'NAME WITH SPACES' }, + { vim.NIL, 'NAME_WITH_SPACES' }, }, get_search_history('NAME WITH SPACES')) eq({ { '3', 'some other man' }, @@ -255,12 +254,21 @@ describe(':Man', function() { 'n', 'some_other_man' }, }, get_search_history('n some other man')) eq({ - { '', '123some other man' }, - { '', '123some_other_man' }, + { vim.NIL, '123some other man' }, + { vim.NIL, '123some_other_man' }, }, get_search_history('123some other man')) eq({ { '1', 'other_man' }, { '1', 'other_man' }, }, get_search_history('other_man(1)')) end) + + it('can complete', function() + eq( + true, + exec_lua(function() + return #require('man').man_complete('f', 'Man f') > 0 + end) + ) + end) end) diff --git a/test/functional/provider/clipboard_spec.lua b/test/functional/provider/clipboard_spec.lua index 722442acbd..2b54ea93e0 100644 --- a/test/functional/provider/clipboard_spec.lua +++ b/test/functional/provider/clipboard_spec.lua @@ -544,7 +544,7 @@ describe('clipboard (with fake clipboard.vim)', function() ]]) feed('gg^<C-v>') -- Goto start of top line enter visual block mode feed('3ljy^k') -- yank 4x2 block & goto initial location - feed('P') -- Paste it infront + feed('P') -- Paste it before cursor expect([[ aabbaabbcc ddeeddeeff diff --git a/test/functional/script/luacats_grammar_spec.lua b/test/functional/script/luacats_grammar_spec.lua index 8bc55879d4..6e73f6894b 100644 --- a/test/functional/script/luacats_grammar_spec.lua +++ b/test/functional/script/luacats_grammar_spec.lua @@ -118,9 +118,9 @@ describe('luacats grammar', function() type = '"rfc2396" | "rfc2732" | "rfc3986" | nil', }) - test('@param offset_encoding "utf-8" | "utf-16" | "utf-32" | nil', { + test('@param position_encoding "utf-8" | "utf-16" | "utf-32" | nil', { kind = 'param', - name = 'offset_encoding', + name = 'position_encoding', type = '"utf-8" | "utf-16" | "utf-32" | nil', }) diff --git a/test/functional/shada/marks_spec.lua b/test/functional/shada/marks_spec.lua index d680f0b83b..837978dee1 100644 --- a/test/functional/shada/marks_spec.lua +++ b/test/functional/shada/marks_spec.lua @@ -218,7 +218,7 @@ describe('ShaDa support code', function() -- -c temporary sets lnum to zero to make `+/pat` work, so calling setpcmark() -- during -c used to add item with zero lnum to jump list. it('does not create incorrect file for non-existent buffers when writing from -c', function() - local argv = n.new_argv { + local p = n.spawn_wait { args_rm = { '-i', '--embed', -- no --embed @@ -232,12 +232,13 @@ describe('ShaDa support code', function() 'qall', }, } - eq('', fn.system(argv)) + eq('', p:output()) + eq(0, p.status) eq(0, exc_exec('rshada')) end) it('does not create incorrect file for non-existent buffers opened from -c', function() - local argv = n.new_argv { + local p = n.spawn_wait { args_rm = { '-i', '--embed', -- no --embed @@ -251,7 +252,8 @@ describe('ShaDa support code', function() 'autocmd VimEnter * qall', }, } - eq('', fn.system(argv)) + eq('', p:output()) + eq(0, p.status) eq(0, exc_exec('rshada')) end) diff --git a/test/functional/shada/shada_spec.lua b/test/functional/shada/shada_spec.lua index 5debdc6c77..78fe19b984 100644 --- a/test/functional/shada/shada_spec.lua +++ b/test/functional/shada/shada_spec.lua @@ -6,8 +6,7 @@ local uv = vim.uv local paths = t.paths local api, nvim_command, fn, eq = n.api, n.command, n.fn, t.eq -local write_file, spawn, set_session, nvim_prog, exc_exec = - t.write_file, n.spawn, n.set_session, n.nvim_prog, n.exc_exec +local write_file, set_session, exc_exec = t.write_file, n.set_session, n.exc_exec local is_os = t.is_os local skip = t.skip @@ -254,7 +253,7 @@ describe('ShaDa support code', function() it('does not crash when ShaDa file directory is not writable', function() skip(is_os('win')) - fn.mkdir(dirname, '', 0) + fn.mkdir(dirname, '', '0') eq(0, fn.filewritable(dirname)) reset { shadafile = dirshada, args = { '--cmd', 'set shada=' } } api.nvim_set_option_value('shada', "'10", {}) @@ -270,10 +269,10 @@ end) describe('ShaDa support code', function() it('does not write NONE file', function() - local session = spawn( - { nvim_prog, '-u', 'NONE', '-i', 'NONE', '--embed', '--headless', '--cmd', 'qall' }, - true - ) + local session = n.new_session(false, { + merge = false, + args = { '-u', 'NONE', '-i', 'NONE', '--embed', '--headless', '--cmd', 'qall' }, + }) session:close() eq(nil, uv.fs_stat('NONE')) eq(nil, uv.fs_stat('NONE.tmp.a')) @@ -281,7 +280,10 @@ describe('ShaDa support code', function() it('does not read NONE file', function() write_file('NONE', '\005\001\015\131\161na\162rX\194\162rc\145\196\001-') - local session = spawn({ nvim_prog, '-u', 'NONE', '-i', 'NONE', '--embed', '--headless' }, true) + local session = n.new_session( + false, + { merge = false, args = { '-u', 'NONE', '-i', 'NONE', '--embed', '--headless' } } + ) set_session(session) eq('', fn.getreg('a')) session:close() diff --git a/test/functional/terminal/altscreen_spec.lua b/test/functional/terminal/altscreen_spec.lua index 4a61e0203d..839e37f541 100644 --- a/test/functional/terminal/altscreen_spec.lua +++ b/test/functional/terminal/altscreen_spec.lua @@ -35,13 +35,13 @@ describe(':terminal altscreen', function() line6 | line7 | line8 | - {1: } | + ^ | {3:-- TERMINAL --} | ]]) enter_altscreen() screen:expect([[ |*5 - {1: } | + ^ | {3:-- TERMINAL --} | ]]) eq(10, api.nvim_buf_line_count(0)) @@ -68,7 +68,7 @@ describe(':terminal altscreen', function() line6 | line7 | line8 | - {1: } | + ^ | {3:-- TERMINAL --} | ]]) feed('<c-\\><c-n>gg') @@ -103,7 +103,7 @@ describe(':terminal altscreen', function() line14 | line15 | line16 | - {1: } | + ^ | {3:-- TERMINAL --} | ]]) end) @@ -132,7 +132,7 @@ describe(':terminal altscreen', function() screen:expect([[ |*2 rows: 4, cols: 50 | - {1: } | + ^ | {3:-- TERMINAL --} | ]]) end @@ -160,7 +160,7 @@ describe(':terminal altscreen', function() line5 | line6 | line7 | - line8 | + ^line8 | {3:-- TERMINAL --} | ]]) end) diff --git a/test/functional/terminal/api_spec.lua b/test/functional/terminal/api_spec.lua index b550df80c3..a8e5367176 100644 --- a/test/functional/terminal/api_spec.lua +++ b/test/functional/terminal/api_spec.lua @@ -33,7 +33,7 @@ describe('api', function() it('qa! RPC request during insert-mode', function() screen:expect { grid = [[ - {1: } | + ^ | {4:~ }|*4 | {3:-- TERMINAL --} | @@ -45,7 +45,7 @@ describe('api', function() -- Wait for socket creation. screen:expect([[ - {1: } | + ^ | {4:~ }|*4 ]] .. socket_name .. [[ | {3:-- TERMINAL --} | @@ -57,7 +57,7 @@ describe('api', function() tt.feed_data('i[tui] insert-mode') -- Wait for stdin to be processed. screen:expect([[ - [tui] insert-mode{1: } | + [tui] insert-mode^ | {4:~ }|*4 {3:-- INSERT --} | {3:-- TERMINAL --} | @@ -73,7 +73,7 @@ describe('api', function() [tui] insert-mode | [socket 1] this is more t | han 25 columns | - [socket 2] input{1: } | + [socket 2] input^ | {4:~ } | {3:-- INSERT --} | {3:-- TERMINAL --} | diff --git a/test/functional/terminal/buffer_spec.lua b/test/functional/terminal/buffer_spec.lua index 05258a9e50..4635259e33 100644 --- a/test/functional/terminal/buffer_spec.lua +++ b/test/functional/terminal/buffer_spec.lua @@ -89,7 +89,7 @@ describe(':terminal buffer', function() feed('<c-\\><c-n>') screen:expect([[ tty ready | - {2:^ } | + ^ | |*5 ]]) end) @@ -109,7 +109,7 @@ describe(':terminal buffer', function() feed('<c-\\><c-n>dd') screen:expect([[ tty ready | - {2:^ } | + ^ | |*4 {8:E21: Cannot make changes, 'modifiable' is off} | ]]) @@ -122,7 +122,7 @@ describe(':terminal buffer', function() screen:expect([[ ^tty ready | appended tty ready |*2 - {2: } | + | |*2 :let @a = "appended " . @a | ]]) @@ -142,7 +142,7 @@ describe(':terminal buffer', function() screen:expect([[ ^tty ready | appended tty ready | - {2: } | + | |*3 :put a | ]]) @@ -151,7 +151,7 @@ describe(':terminal buffer', function() screen:expect([[ tty ready | appended tty ready |*2 - {2: } | + | | ^ | :6put a | @@ -198,7 +198,7 @@ describe(':terminal buffer', function() {4:~ }| {5:========== }| rows: 2, cols: 50 | - {2: } | + | {18:========== }| | ]]) @@ -234,7 +234,7 @@ describe(':terminal buffer', function() command('set rightleft') screen:expect([[ ydaer ytt| - {1:a}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + ^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| |*4 {3:-- TERMINAL --} | ]]) @@ -277,7 +277,7 @@ describe(':terminal buffer', function() screen:expect { grid = [[ tty ready | - {2:^ } | + ^ | |*4 {3:-- (terminal) --} | ]], @@ -288,7 +288,7 @@ describe(':terminal buffer', function() screen:expect { grid = [[ tty ready | - {2: } | + | |*4 :let g:x = 17^ | ]], @@ -298,7 +298,7 @@ describe(':terminal buffer', function() screen:expect { grid = [[ tty ready | - {1: } | + ^ | |*4 {3:-- TERMINAL --} | ]], @@ -351,7 +351,7 @@ describe(':terminal buffer', function() end) it('TermRequest synchronization #27572', function() - command('autocmd! nvim_terminal TermRequest') + command('autocmd! nvim.terminal TermRequest') local term = exec_lua([[ _G.input = {} local term = vim.api.nvim_open_term(0, { @@ -378,7 +378,7 @@ describe(':terminal buffer', function() }, exec_lua('return _G.input')) end) - it('no heap-buffer-overflow when using termopen(echo) #3161', function() + it('no heap-buffer-overflow when using jobstart("echo",{term=true}) #3161', function() local testfilename = 'Xtestfile-functional-terminal-buffers_spec' write_file(testfilename, 'aaaaaaaaaaaaaaaaaaaaaaaaaaaa') finally(function() @@ -387,8 +387,8 @@ describe(':terminal buffer', function() feed_command('edit ' .. testfilename) -- Move cursor away from the beginning of the line feed('$') - -- Let termopen() modify the buffer - feed_command('call termopen("echo")') + -- Let jobstart(…,{term=true}) modify the buffer + feed_command([[call jobstart("echo", {'term':v:true})]]) assert_alive() feed_command('bdelete!') end) @@ -400,18 +400,31 @@ describe(':terminal buffer', function() assert_alive() end) - it('truncates number of composing characters to 5', function() + it('truncates the size of grapheme clusters', function() local chan = api.nvim_open_term(0, {}) local composing = ('a̳'):sub(2) - api.nvim_chan_send(chan, 'a' .. composing:rep(8)) + api.nvim_chan_send(chan, 'a' .. composing:rep(20)) retry(nil, nil, function() - eq('a' .. composing:rep(5), api.nvim_get_current_line()) + eq('a' .. composing:rep(14), api.nvim_get_current_line()) end) end) + it('handles extended grapheme clusters', function() + local screen = Screen.new(50, 7) + feed 'i' + local chan = api.nvim_open_term(0, {}) + api.nvim_chan_send(chan, '🏴☠️ yarrr') + screen:expect([[ + 🏴☠️ yarrr^ | + |*5 + {5:-- TERMINAL --} | + ]]) + eq('🏴☠️ yarrr', api.nvim_get_current_line()) + end) + it('handles split UTF-8 sequences #16245', function() local screen = Screen.new(50, 7) - fn.termopen({ testprg('shell-test'), 'UTF-8' }) + fn.jobstart({ testprg('shell-test'), 'UTF-8' }, { term = true }) screen:expect([[ ^å | ref: å̲ | @@ -422,6 +435,19 @@ describe(':terminal buffer', function() ]]) end) + it('handles unprintable chars', function() + local screen = Screen.new(50, 7) + feed 'i' + local chan = api.nvim_open_term(0, {}) + api.nvim_chan_send(chan, '\239\187\191') -- '\xef\xbb\xbf' + screen:expect([[ + {18:<feff>}^ | + |*5 + {5:-- TERMINAL --} | + ]]) + eq('\239\187\191', api.nvim_get_current_line()) + end) + it("handles bell respecting 'belloff' and 'visualbell'", function() local screen = Screen.new(50, 7) local chan = api.nvim_open_term(0, {}) @@ -531,16 +557,19 @@ describe('terminal input', function() '--cmd', 'set notermguicolors', '-c', - 'while 1 | redraw | echo keytrans(getcharstr()) | endwhile', + 'while 1 | redraw | echo keytrans(getcharstr(-1, #{simplify: 0})) | endwhile', }) screen:expect([[ - {1: } | + ^ | {4:~ }|*3 {5:[No Name] 0,0-1 All}| | {3:-- TERMINAL --} | ]]) - for _, key in ipairs({ + local keys = { + '<Tab>', + '<CR>', + '<Esc>', '<M-Tab>', '<M-CR>', '<M-Esc>', @@ -568,18 +597,36 @@ describe('terminal input', function() '<S-End>', '<C-End>', '<End>', - '<C-LeftMouse>', - '<C-LeftRelease>', - '<2-LeftMouse>', - '<2-LeftRelease>', - '<S-RightMouse>', - '<S-RightRelease>', - '<2-RightMouse>', - '<2-RightRelease>', - '<M-MiddleMouse>', - '<M-MiddleRelease>', - '<2-MiddleMouse>', - '<2-MiddleRelease>', + '<C-LeftMouse><0,0>', + '<C-LeftDrag><0,1>', + '<C-LeftRelease><0,1>', + '<2-LeftMouse><0,1>', + '<2-LeftDrag><0,0>', + '<2-LeftRelease><0,0>', + '<M-MiddleMouse><0,0>', + '<M-MiddleDrag><0,1>', + '<M-MiddleRelease><0,1>', + '<2-MiddleMouse><0,1>', + '<2-MiddleDrag><0,0>', + '<2-MiddleRelease><0,0>', + '<S-RightMouse><0,0>', + '<S-RightDrag><0,1>', + '<S-RightRelease><0,1>', + '<2-RightMouse><0,1>', + '<2-RightDrag><0,0>', + '<2-RightRelease><0,0>', + '<S-X1Mouse><0,0>', + '<S-X1Drag><0,1>', + '<S-X1Release><0,1>', + '<2-X1Mouse><0,1>', + '<2-X1Drag><0,0>', + '<2-X1Release><0,0>', + '<S-X2Mouse><0,0>', + '<S-X2Drag><0,1>', + '<S-X2Release><0,1>', + '<2-X2Mouse><0,1>', + '<2-X2Drag><0,0>', + '<2-X2Release><0,0>', '<S-ScrollWheelUp>', '<S-ScrollWheelDown>', '<ScrollWheelUp>', @@ -588,15 +635,22 @@ describe('terminal input', function() '<S-ScrollWheelRight>', '<ScrollWheelLeft>', '<ScrollWheelRight>', - }) do + } + -- FIXME: The escape sequence to enable kitty keyboard mode doesn't work on Windows + if not is_os('win') then + table.insert(keys, '<C-I>') + table.insert(keys, '<C-M>') + table.insert(keys, '<C-[>') + end + for _, key in ipairs(keys) do feed(key) screen:expect(([[ | {4:~ }|*3 {5:[No Name] 0,0-1 All}| - %s{1: }{MATCH: *}| + %s^ {MATCH: *}| {3:-- TERMINAL --} | - ]]):format(key)) + ]]):format(key:gsub('<%d+,%d+>$', ''))) end end) end) @@ -624,7 +678,7 @@ if is_os('win') then > :: appended :: tty ready | > :: tty ready | > :: appended :: tty ready | - ^> {2: } | + ^> | :let @a = @a . "\n:: appended " . @a . "\n\n" | ]]) -- operator count is also taken into consideration @@ -635,7 +689,7 @@ if is_os('win') then > :: appended :: tty ready | > :: tty ready | > :: appended :: tty ready | - ^> {2: } | + ^> | :let @a = @a . "\n:: appended " . @a . "\n\n" | ]]) end) @@ -649,7 +703,7 @@ if is_os('win') then | > :: tty ready | > :: appended :: tty ready | - > {2: } | + > | | ^ | :put a | @@ -662,14 +716,14 @@ if is_os('win') then > :: appended :: tty ready | > :: tty ready | > :: appended :: tty ready | - ^> {2: } | + ^> | :6put a | ]]) end) end) end -describe('termopen()', function() +describe('termopen() (deprecated alias to `jobstart(…,{term=true})`)', function() before_each(clear) it('disallowed when textlocked and in cmdwin buffer', function() diff --git a/test/functional/terminal/channel_spec.lua b/test/functional/terminal/channel_spec.lua index 9912c1ff7b..bb97411f43 100644 --- a/test/functional/terminal/channel_spec.lua +++ b/test/functional/terminal/channel_spec.lua @@ -75,8 +75,8 @@ describe('terminal channel is closed and later released if', function() eq(chans - 1, eval('len(nvim_list_chans())')) end) - it('opened by termopen(), exited, and deleted by pressing a key', function() - command([[let id = termopen('echo')]]) + it('opened by jobstart(…,{term=true}), exited, and deleted by pressing a key', function() + command([[let id = jobstart('echo',{'term':v:true})]]) local chans = eval('len(nvim_list_chans())') -- wait for process to exit screen:expect({ any = '%[Process exited 0%]' }) @@ -96,8 +96,8 @@ describe('terminal channel is closed and later released if', function() end) -- This indirectly covers #16264 - it('opened by termopen(), exited, and deleted by :bdelete', function() - command([[let id = termopen('echo')]]) + it('opened by jobstart(…,{term=true}), exited, and deleted by :bdelete', function() + command([[let id = jobstart('echo', {'term':v:true})]]) local chans = eval('len(nvim_list_chans())') -- wait for process to exit screen:expect({ any = '%[Process exited 0%]' }) @@ -124,7 +124,7 @@ it('chansend sends lines to terminal channel in proper order', function() screen._default_attr_ids = nil local shells = is_os('win') and { 'cmd.exe', 'pwsh.exe -nop', 'powershell.exe -nop' } or { 'sh' } for _, sh in ipairs(shells) do - command([[let id = termopen(']] .. sh .. [[')]]) + command([[let id = jobstart(']] .. sh .. [[', {'term':v:true})]]) command([[call chansend(id, ['echo "hello"', 'echo "world"', ''])]]) screen:expect { any = [[echo "hello".*echo "world"]], @@ -149,7 +149,7 @@ describe('no crash when TermOpen autocommand', function() }) end) - it('processes job exit event when using termopen()', function() + it('processes job exit event when using jobstart(…,{term=true})', function() command([[autocmd TermOpen * call input('')]]) async_meths.nvim_command('terminal foobar') screen:expect { @@ -179,7 +179,7 @@ describe('no crash when TermOpen autocommand', function() assert_alive() end) - it('wipes buffer and processes events when using termopen()', function() + it('wipes buffer and processes events when using jobstart(…,{term=true})', function() command([[autocmd TermOpen * bwipe! | call input('')]]) async_meths.nvim_command('terminal foobar') screen:expect { diff --git a/test/functional/terminal/clipboard_spec.lua b/test/functional/terminal/clipboard_spec.lua index 4a1a0e29fd..f0ce407eaa 100644 --- a/test/functional/terminal/clipboard_spec.lua +++ b/test/functional/terminal/clipboard_spec.lua @@ -56,7 +56,7 @@ describe(':terminal', function() return string.format('\027]52;;%s\027\\', arg) end - fn.termopen({ testprg('shell-test'), '-t', osc52(encoded) }) + fn.jobstart({ testprg('shell-test'), '-t', osc52(encoded) }, { term = true }) retry(nil, 1000, function() eq(text, exec_lua([[ return vim.g.clipboard_data ]])) diff --git a/test/functional/terminal/cursor_spec.lua b/test/functional/terminal/cursor_spec.lua index f223cdd417..83408e41b3 100644 --- a/test/functional/terminal/cursor_spec.lua +++ b/test/functional/terminal/cursor_spec.lua @@ -1,13 +1,12 @@ local t = require('test.testutil') local n = require('test.functional.testnvim')() -local Screen = require('test.functional.ui.screen') local tt = require('test.functional.testterm') local feed, clear = n.feed, n.clear local testprg, command = n.testprg, n.command local eq, eval = t.eq, n.eval local matches = t.matches -local poke_eventloop = n.poke_eventloop +local call = n.call local hide_cursor = tt.hide_cursor local show_cursor = tt.show_cursor local is_os = t.is_os @@ -16,16 +15,27 @@ local skip = t.skip describe(':terminal cursor', function() local screen + local terminal_mode_idx ---@type number + before_each(function() clear() screen = tt.setup_screen() + + if terminal_mode_idx == nil then + for i, v in ipairs(screen._mode_info) do + if v.name == 'terminal' then + terminal_mode_idx = i + end + end + assert(terminal_mode_idx) + end end) it('moves the screen cursor when focused', function() tt.feed_data('testing cursor') screen:expect([[ tty ready | - testing cursor{1: } | + testing cursor^ | |*4 {3:-- TERMINAL --} | ]]) @@ -35,7 +45,7 @@ describe(':terminal cursor', function() feed('<c-\\><c-n>') screen:expect([[ tty ready | - {2:^ } | + ^ | |*5 ]]) end) @@ -49,7 +59,7 @@ describe(':terminal cursor', function() screen:expect([[ {7: 1 }tty ready | {7: 2 }^rows: 6, cols: 46 | - {7: 3 }{2: } | + {7: 3 } | {7: 4 } | {7: 5 } | {7: 6 } | @@ -61,7 +71,7 @@ describe(':terminal cursor', function() screen:expect([[ {7: 1 }tty ready | {7: 2 }^rows: 6, cols: 46 | - {7: 3 }{2: } | + {7: 3 } | {7: 4 } | {7: 5 } | {7: 6 } | @@ -72,7 +82,7 @@ describe(':terminal cursor', function() screen:expect([[ {7: 1 }tty ready | {7: 2 }rows: 6, cols: 46 | - {7: 3 }{1: } | + {7: 3 }^ | {7: 4 } | {7: 5 } | {7: 6 } | @@ -82,8 +92,8 @@ describe(':terminal cursor', function() end) describe('when invisible', function() - it('is not highlighted and is detached from screen cursor', function() - skip(is_os('win')) + it('is not highlighted', function() + skip(is_os('win'), '#31587') hide_cursor() screen:expect([[ tty ready | @@ -93,59 +103,259 @@ describe(':terminal cursor', function() show_cursor() screen:expect([[ tty ready | - {1: } | + ^ | |*4 {3:-- TERMINAL --} | ]]) -- same for when the terminal is unfocused feed('<c-\\><c-n>') hide_cursor() + screen:expect({ + grid = [[ + tty ready | + ^ | + |*5 + ]], + unchanged = true, + }) + show_cursor() + screen:expect({ + grid = [[ + tty ready | + ^ | + |*5 + ]], + unchanged = true, + }) + end) + + it('becomes visible when exiting Terminal mode', function() + skip(is_os('win'), '#31587') + hide_cursor() + screen:expect([[ + tty ready | + |*5 + {3:-- TERMINAL --} | + ]]) + feed('<c-\\><c-n>') screen:expect([[ tty ready | ^ | |*5 ]]) - show_cursor() + feed('i') screen:expect([[ tty ready | - {2:^ } | |*5 + {3:-- TERMINAL --} | ]]) end) end) -end) -describe('cursor with customized highlighting', function() - local screen + it('can be modified by application #3681 #31685', function() + skip(is_os('win'), '#31587') - before_each(function() - clear() - command('highlight TermCursor ctermfg=45 ctermbg=46 cterm=NONE') - command('highlight TermCursorNC ctermfg=55 ctermbg=56 cterm=NONE') - screen = Screen.new(50, 7, { rgb = false }) - screen:set_default_attr_ids({ - [1] = { foreground = 45, background = 46 }, - [2] = { foreground = 55, background = 56 }, - [3] = { bold = true }, + local states = { + [1] = { blink = true, shape = 'block' }, + [2] = { blink = false, shape = 'block' }, + [3] = { blink = true, shape = 'horizontal' }, + [4] = { blink = false, shape = 'horizontal' }, + [5] = { blink = true, shape = 'vertical' }, + [6] = { blink = false, shape = 'vertical' }, + } + + for k, v in pairs(states) do + tt.feed_csi(('%d q'):format(k)) + screen:expect({ + grid = [[ + tty ready | + ^ | + |*4 + {3:-- TERMINAL --} | + ]], + condition = function() + if v.blink then + eq(500, screen._mode_info[terminal_mode_idx].blinkon) + eq(500, screen._mode_info[terminal_mode_idx].blinkoff) + else + eq(0, screen._mode_info[terminal_mode_idx].blinkon) + eq(0, screen._mode_info[terminal_mode_idx].blinkoff) + end + + eq(v.shape, screen._mode_info[terminal_mode_idx].cursor_shape) + + -- Cell percentages are hard coded for each shape in terminal.c + if v.shape == 'horizontal' then + eq(20, screen._mode_info[terminal_mode_idx].cell_percentage) + elseif v.shape == 'vertical' then + eq(25, screen._mode_info[terminal_mode_idx].cell_percentage) + end + end, + }) + end + + feed([[<C-\><C-N>]]) + + screen:expect([[ + tty ready | + ^ | + |*5 + ]]) + + -- Cursor returns to default on TermLeave + eq(500, screen._mode_info[terminal_mode_idx].blinkon) + eq(500, screen._mode_info[terminal_mode_idx].blinkoff) + eq('block', screen._mode_info[terminal_mode_idx].cursor_shape) + end) + + it('can be modified per terminal', function() + skip(is_os('win'), '#31587') + + -- Set cursor to vertical bar with blink + tt.feed_csi('5 q') + screen:expect({ + grid = [[ + tty ready | + ^ | + |*4 + {3:-- TERMINAL --} | + ]], + condition = function() + eq(500, screen._mode_info[terminal_mode_idx].blinkon) + eq(500, screen._mode_info[terminal_mode_idx].blinkoff) + eq('vertical', screen._mode_info[terminal_mode_idx].cursor_shape) + end, + }) + + tt.hide_cursor() + screen:expect({ + grid = [[ + tty ready | + | + |*4 + {3:-- TERMINAL --} | + ]], + condition = function() + eq(500, screen._mode_info[terminal_mode_idx].blinkon) + eq(500, screen._mode_info[terminal_mode_idx].blinkoff) + eq('vertical', screen._mode_info[terminal_mode_idx].cursor_shape) + end, }) - command('call termopen(["' .. testprg('tty-test') .. '"])') + + -- Exit terminal mode to reset terminal cursor settings to default and + -- create a new terminal window + feed([[<C-\><C-N>]]) + command('set statusline=~~~') + command('new') + call('jobstart', { testprg('tty-test') }, { term = true }) feed('i') - poke_eventloop() + screen:expect({ + grid = [[ + tty ready | + ^ | + {17:~~~ }| + rows: 2, cols: 50 | + | + {18:~~~ }| + {3:-- TERMINAL --} | + ]], + condition = function() + -- New terminal, cursor resets to defaults + eq(500, screen._mode_info[terminal_mode_idx].blinkon) + eq(500, screen._mode_info[terminal_mode_idx].blinkoff) + eq('block', screen._mode_info[terminal_mode_idx].cursor_shape) + end, + }) + + -- Set cursor to underline, no blink + tt.feed_csi('4 q') + screen:expect({ + grid = [[ + tty ready | + ^ | + {17:~~~ }| + rows: 2, cols: 50 | + | + {18:~~~ }| + {3:-- TERMINAL --} | + ]], + condition = function() + eq(0, screen._mode_info[terminal_mode_idx].blinkon) + eq(0, screen._mode_info[terminal_mode_idx].blinkoff) + eq('horizontal', screen._mode_info[terminal_mode_idx].cursor_shape) + end, + }) + + -- Switch back to first terminal, cursor should still be hidden + command('wincmd p') + screen:expect({ + grid = [[ + tty ready | + | + {18:~~~ }| + rows: 2, cols: 50 | + | + {17:~~~ }| + {3:-- TERMINAL --} | + ]], + condition = function() + eq(500, screen._mode_info[terminal_mode_idx].blinkon) + eq(500, screen._mode_info[terminal_mode_idx].blinkoff) + eq('vertical', screen._mode_info[terminal_mode_idx].cursor_shape) + end, + }) end) - it('overrides the default highlighting', function() + it('can be positioned arbitrarily', function() + clear() + screen = tt.setup_child_nvim({ + '-u', + 'NONE', + '-i', + 'NONE', + '--cmd', + n.nvim_set .. ' noshowmode', + }) + screen:expect([[ + ^ | + ~ |*4 + | + {3:-- TERMINAL --} | + ]]) + + feed('i<Tab>') screen:expect([[ - tty ready | - {1: } | - |*4 + ^ | + ~ |*4 + | {3:-- TERMINAL --} | ]]) - feed('<c-\\><c-n>') + end) + + it('preserves guicursor value on TermLeave #31612', function() + eq(3, screen._mode_info[terminal_mode_idx].hl_id) + + -- Change 'guicursor' while terminal mode is active + command('set guicursor+=t:Error') + + local error_hl_id = call('hlID', 'Error') + + screen:expect({ + condition = function() + eq(error_hl_id, screen._mode_info[terminal_mode_idx].hl_id) + end, + }) + + -- Exit terminal mode + feed([[<C-\><C-N>]]) + screen:expect([[ tty ready | - {2:^ } | + ^ | |*5 ]]) + + eq(error_hl_id, screen._mode_info[terminal_mode_idx].hl_id) end) end) @@ -171,19 +381,10 @@ describe('buffer cursor position is correct in terminal without number column', }, { cols = 70, }) - screen:set_default_attr_ids({ - [1] = { foreground = 253, background = 11 }, - [2] = { reverse = true }, - [3] = { bold = true }, - [4] = { background = 11 }, - }) - -- Also check for real cursor position, as it is used for stuff like input methods - screen._handle_busy_start = function() end - screen._handle_busy_stop = function() end screen:expect([[ |*4 Entering Ex mode. Type "visual" to go to Normal mode. | - :{2:^ } | + :^ | {3:-- TERMINAL --} | ]]) end @@ -200,7 +401,7 @@ describe('buffer cursor position is correct in terminal without number column', screen:expect([[ |*4 Entering Ex mode. Type "visual" to go to Normal mode. | - :aaaaaaaa{2:^ } | + :aaaaaaaa^ | {3:-- TERMINAL --} | ]]) eq({ 6, 9 }, eval('nvim_win_get_cursor(0)')) @@ -208,7 +409,7 @@ describe('buffer cursor position is correct in terminal without number column', screen:expect([[ |*4 Entering Ex mode. Type "visual" to go to Normal mode. | - :aaaaaaa^a{4: } | + :aaaaaaa^a | | ]]) eq({ 6, 8 }, eval('nvim_win_get_cursor(0)')) @@ -219,7 +420,7 @@ describe('buffer cursor position is correct in terminal without number column', screen:expect([[ |*4 Entering Ex mode. Type "visual" to go to Normal mode. | - :aaaaaa{2:^a}a | + :aaaaaa^aa | {3:-- TERMINAL --} | ]]) eq({ 6, 7 }, eval('nvim_win_get_cursor(0)')) @@ -227,7 +428,7 @@ describe('buffer cursor position is correct in terminal without number column', screen:expect([[ |*4 Entering Ex mode. Type "visual" to go to Normal mode. | - :aaaaa^a{4:a}a | + :aaaaa^aaa | | ]]) eq({ 6, 6 }, eval('nvim_win_get_cursor(0)')) @@ -238,7 +439,7 @@ describe('buffer cursor position is correct in terminal without number column', screen:expect([[ |*4 Entering Ex mode. Type "visual" to go to Normal mode. | - :a{2:^a}aaaaaa | + :a^aaaaaaa | {3:-- TERMINAL --} | ]]) eq({ 6, 2 }, eval('nvim_win_get_cursor(0)')) @@ -246,7 +447,7 @@ describe('buffer cursor position is correct in terminal without number column', screen:expect([[ |*4 Entering Ex mode. Type "visual" to go to Normal mode. | - :^a{4:a}aaaaaa | + :^aaaaaaaa | | ]]) eq({ 6, 1 }, eval('nvim_win_get_cursor(0)')) @@ -263,7 +464,7 @@ describe('buffer cursor position is correct in terminal without number column', screen:expect([[ |*4 Entering Ex mode. Type "visual" to go to Normal mode. | - :µµµµµµµµ{2:^ } | + :µµµµµµµµ^ | {3:-- TERMINAL --} | ]]) eq({ 6, 17 }, eval('nvim_win_get_cursor(0)')) @@ -271,7 +472,7 @@ describe('buffer cursor position is correct in terminal without number column', screen:expect([[ |*4 Entering Ex mode. Type "visual" to go to Normal mode. | - :µµµµµµµ^µ{4: } | + :µµµµµµµ^µ | | ]]) eq({ 6, 15 }, eval('nvim_win_get_cursor(0)')) @@ -282,7 +483,7 @@ describe('buffer cursor position is correct in terminal without number column', screen:expect([[ |*4 Entering Ex mode. Type "visual" to go to Normal mode. | - :µµµµµµ{2:^µ}µ | + :µµµµµµ^µµ | {3:-- TERMINAL --} | ]]) eq({ 6, 13 }, eval('nvim_win_get_cursor(0)')) @@ -290,7 +491,7 @@ describe('buffer cursor position is correct in terminal without number column', screen:expect([[ |*4 Entering Ex mode. Type "visual" to go to Normal mode. | - :µµµµµ^µ{4:µ}µ | + :µµµµµ^µµµ | | ]]) eq({ 6, 11 }, eval('nvim_win_get_cursor(0)')) @@ -301,7 +502,7 @@ describe('buffer cursor position is correct in terminal without number column', screen:expect([[ |*4 Entering Ex mode. Type "visual" to go to Normal mode. | - :µ{2:^µ}µµµµµµ | + :µ^µµµµµµµ | {3:-- TERMINAL --} | ]]) eq({ 6, 3 }, eval('nvim_win_get_cursor(0)')) @@ -309,7 +510,7 @@ describe('buffer cursor position is correct in terminal without number column', screen:expect([[ |*4 Entering Ex mode. Type "visual" to go to Normal mode. | - :^µ{4:µ}µµµµµµ | + :^µµµµµµµµ | | ]]) eq({ 6, 1 }, eval('nvim_win_get_cursor(0)')) @@ -326,7 +527,7 @@ describe('buffer cursor position is correct in terminal without number column', screen:expect([[ |*4 Entering Ex mode. Type "visual" to go to Normal mode. | - :µ̳µ̳µ̳µ̳µ̳µ̳µ̳µ̳{2:^ } | + :µ̳µ̳µ̳µ̳µ̳µ̳µ̳µ̳^ | {3:-- TERMINAL --} | ]]) eq({ 6, 33 }, eval('nvim_win_get_cursor(0)')) @@ -334,7 +535,7 @@ describe('buffer cursor position is correct in terminal without number column', screen:expect([[ |*4 Entering Ex mode. Type "visual" to go to Normal mode. | - :µ̳µ̳µ̳µ̳µ̳µ̳µ̳^µ̳{4: } | + :µ̳µ̳µ̳µ̳µ̳µ̳µ̳^µ̳ | | ]]) eq({ 6, 29 }, eval('nvim_win_get_cursor(0)')) @@ -346,7 +547,7 @@ describe('buffer cursor position is correct in terminal without number column', screen:expect([[ |*4 Entering Ex mode. Type "visual" to go to Normal mode. | - :µ̳µ̳µ̳µ̳µ̳µ̳{2:^µ̳}µ̳ | + :µ̳µ̳µ̳µ̳µ̳µ̳^µ̳µ̳ | {3:-- TERMINAL --} | ]]) eq({ 6, 25 }, eval('nvim_win_get_cursor(0)')) @@ -354,7 +555,7 @@ describe('buffer cursor position is correct in terminal without number column', screen:expect([[ |*4 Entering Ex mode. Type "visual" to go to Normal mode. | - :µ̳µ̳µ̳µ̳µ̳^µ̳{4:µ̳}µ̳ | + :µ̳µ̳µ̳µ̳µ̳^µ̳µ̳µ̳ | | ]]) eq({ 6, 21 }, eval('nvim_win_get_cursor(0)')) @@ -366,7 +567,7 @@ describe('buffer cursor position is correct in terminal without number column', screen:expect([[ |*4 Entering Ex mode. Type "visual" to go to Normal mode. | - :µ̳{2:^µ̳}µ̳µ̳µ̳µ̳µ̳µ̳ | + :µ̳^µ̳µ̳µ̳µ̳µ̳µ̳µ̳ | {3:-- TERMINAL --} | ]]) eq({ 6, 5 }, eval('nvim_win_get_cursor(0)')) @@ -374,7 +575,7 @@ describe('buffer cursor position is correct in terminal without number column', screen:expect([[ |*4 Entering Ex mode. Type "visual" to go to Normal mode. | - :^µ̳{4:µ̳}µ̳µ̳µ̳µ̳µ̳µ̳ | + :^µ̳µ̳µ̳µ̳µ̳µ̳µ̳µ̳ | | ]]) eq({ 6, 1 }, eval('nvim_win_get_cursor(0)')) @@ -391,7 +592,7 @@ describe('buffer cursor position is correct in terminal without number column', screen:expect([[ |*4 Entering Ex mode. Type "visual" to go to Normal mode. | - :哦哦哦哦哦哦哦哦{2:^ } | + :哦哦哦哦哦哦哦哦^ | {3:-- TERMINAL --} | ]]) eq({ 6, 25 }, eval('nvim_win_get_cursor(0)')) @@ -399,7 +600,7 @@ describe('buffer cursor position is correct in terminal without number column', screen:expect([[ |*4 Entering Ex mode. Type "visual" to go to Normal mode. | - :哦哦哦哦哦哦哦^哦{4: } | + :哦哦哦哦哦哦哦^哦 | | ]]) eq({ 6, 22 }, eval('nvim_win_get_cursor(0)')) @@ -410,7 +611,7 @@ describe('buffer cursor position is correct in terminal without number column', screen:expect([[ |*4 Entering Ex mode. Type "visual" to go to Normal mode. | - :哦哦哦哦哦哦{2:^哦}哦 | + :哦哦哦哦哦哦^哦哦 | {3:-- TERMINAL --} | ]]) eq({ 6, 19 }, eval('nvim_win_get_cursor(0)')) @@ -418,7 +619,7 @@ describe('buffer cursor position is correct in terminal without number column', screen:expect([[ |*4 Entering Ex mode. Type "visual" to go to Normal mode. | - :哦哦哦哦哦^哦{4:哦}哦 | + :哦哦哦哦哦^哦哦哦 | | ]]) eq({ 6, 16 }, eval('nvim_win_get_cursor(0)')) @@ -429,7 +630,7 @@ describe('buffer cursor position is correct in terminal without number column', screen:expect([[ |*4 Entering Ex mode. Type "visual" to go to Normal mode. | - :哦{2:^哦}哦哦哦哦哦哦 | + :哦^哦哦哦哦哦哦哦 | {3:-- TERMINAL --} | ]]) eq({ 6, 4 }, eval('nvim_win_get_cursor(0)')) @@ -437,7 +638,7 @@ describe('buffer cursor position is correct in terminal without number column', screen:expect([[ |*4 Entering Ex mode. Type "visual" to go to Normal mode. | - :^哦{4:哦}哦哦哦哦哦哦 | + :^哦哦哦哦哦哦哦哦 | | ]]) eq({ 6, 1 }, eval('nvim_win_get_cursor(0)')) @@ -450,7 +651,7 @@ describe('buffer cursor position is correct in terminal without number column', screen:expect([[ |*4 Entering Ex mode. Type "visual" to go to Normal mode. | - :aaaaaaaa {2:^ } | + :aaaaaaaa ^ | {3:-- TERMINAL --} | ]]) matches('^:aaaaaaaa [ ]*$', eval('nvim_get_current_line()')) @@ -459,7 +660,7 @@ describe('buffer cursor position is correct in terminal without number column', screen:expect([[ |*4 Entering Ex mode. Type "visual" to go to Normal mode. | - :aaaaaaaa ^ {4: } | + :aaaaaaaa ^ | | ]]) eq({ 6, 12 }, eval('nvim_win_get_cursor(0)')) @@ -488,30 +689,20 @@ describe('buffer cursor position is correct in terminal with number column', fun }, { cols = 70, }) - screen:set_default_attr_ids({ - [1] = { foreground = 253, background = 11 }, - [2] = { reverse = true }, - [3] = { bold = true }, - [4] = { background = 11 }, - [7] = { foreground = 130 }, - }) - -- Also check for real cursor position, as it is used for stuff like input methods - screen._handle_busy_start = function() end - screen._handle_busy_stop = function() end screen:expect([[ {7: 1 } | {7: 2 } | {7: 3 } | {7: 4 } | {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | - {7: 6 }:{2:^ } | + {7: 6 }:^ | {3:-- TERMINAL --} | ]]) end before_each(function() clear() - command('set number') + command('au TermOpen * set number') end) describe('in a line with no multibyte chars or trailing spaces,', function() @@ -527,7 +718,7 @@ describe('buffer cursor position is correct in terminal with number column', fun {7: 3 } | {7: 4 } | {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | - {7: 6 }:aaaaaaaa{2:^ } | + {7: 6 }:aaaaaaaa^ | {3:-- TERMINAL --} | ]]) eq({ 6, 9 }, eval('nvim_win_get_cursor(0)')) @@ -538,7 +729,7 @@ describe('buffer cursor position is correct in terminal with number column', fun {7: 3 } | {7: 4 } | {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | - {7: 6 }:aaaaaaa^a{4: } | + {7: 6 }:aaaaaaa^a | | ]]) eq({ 6, 8 }, eval('nvim_win_get_cursor(0)')) @@ -552,7 +743,7 @@ describe('buffer cursor position is correct in terminal with number column', fun {7: 3 } | {7: 4 } | {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | - {7: 6 }:aaaaaa{2:^a}a | + {7: 6 }:aaaaaa^aa | {3:-- TERMINAL --} | ]]) eq({ 6, 7 }, eval('nvim_win_get_cursor(0)')) @@ -563,7 +754,7 @@ describe('buffer cursor position is correct in terminal with number column', fun {7: 3 } | {7: 4 } | {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | - {7: 6 }:aaaaa^a{4:a}a | + {7: 6 }:aaaaa^aaa | | ]]) eq({ 6, 6 }, eval('nvim_win_get_cursor(0)')) @@ -577,7 +768,7 @@ describe('buffer cursor position is correct in terminal with number column', fun {7: 3 } | {7: 4 } | {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | - {7: 6 }:a{2:^a}aaaaaa | + {7: 6 }:a^aaaaaaa | {3:-- TERMINAL --} | ]]) eq({ 6, 2 }, eval('nvim_win_get_cursor(0)')) @@ -588,7 +779,7 @@ describe('buffer cursor position is correct in terminal with number column', fun {7: 3 } | {7: 4 } | {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | - {7: 6 }:^a{4:a}aaaaaa | + {7: 6 }:^aaaaaaaa | | ]]) eq({ 6, 1 }, eval('nvim_win_get_cursor(0)')) @@ -608,7 +799,7 @@ describe('buffer cursor position is correct in terminal with number column', fun {7: 3 } | {7: 4 } | {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | - {7: 6 }:µµµµµµµµ{2:^ } | + {7: 6 }:µµµµµµµµ^ | {3:-- TERMINAL --} | ]]) eq({ 6, 17 }, eval('nvim_win_get_cursor(0)')) @@ -619,7 +810,7 @@ describe('buffer cursor position is correct in terminal with number column', fun {7: 3 } | {7: 4 } | {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | - {7: 6 }:µµµµµµµ^µ{4: } | + {7: 6 }:µµµµµµµ^µ | | ]]) eq({ 6, 15 }, eval('nvim_win_get_cursor(0)')) @@ -633,7 +824,7 @@ describe('buffer cursor position is correct in terminal with number column', fun {7: 3 } | {7: 4 } | {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | - {7: 6 }:µµµµµµ{2:^µ}µ | + {7: 6 }:µµµµµµ^µµ | {3:-- TERMINAL --} | ]]) eq({ 6, 13 }, eval('nvim_win_get_cursor(0)')) @@ -644,7 +835,7 @@ describe('buffer cursor position is correct in terminal with number column', fun {7: 3 } | {7: 4 } | {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | - {7: 6 }:µµµµµ^µ{4:µ}µ | + {7: 6 }:µµµµµ^µµµ | | ]]) eq({ 6, 11 }, eval('nvim_win_get_cursor(0)')) @@ -658,7 +849,7 @@ describe('buffer cursor position is correct in terminal with number column', fun {7: 3 } | {7: 4 } | {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | - {7: 6 }:µ{2:^µ}µµµµµµ | + {7: 6 }:µ^µµµµµµµ | {3:-- TERMINAL --} | ]]) eq({ 6, 3 }, eval('nvim_win_get_cursor(0)')) @@ -669,7 +860,7 @@ describe('buffer cursor position is correct in terminal with number column', fun {7: 3 } | {7: 4 } | {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | - {7: 6 }:^µ{4:µ}µµµµµµ | + {7: 6 }:^µµµµµµµµ | | ]]) eq({ 6, 1 }, eval('nvim_win_get_cursor(0)')) @@ -689,7 +880,7 @@ describe('buffer cursor position is correct in terminal with number column', fun {7: 3 } | {7: 4 } | {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | - {7: 6 }:µ̳µ̳µ̳µ̳µ̳µ̳µ̳µ̳{2:^ } | + {7: 6 }:µ̳µ̳µ̳µ̳µ̳µ̳µ̳µ̳^ | {3:-- TERMINAL --} | ]]) eq({ 6, 33 }, eval('nvim_win_get_cursor(0)')) @@ -700,7 +891,7 @@ describe('buffer cursor position is correct in terminal with number column', fun {7: 3 } | {7: 4 } | {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | - {7: 6 }:µ̳µ̳µ̳µ̳µ̳µ̳µ̳^µ̳{4: } | + {7: 6 }:µ̳µ̳µ̳µ̳µ̳µ̳µ̳^µ̳ | | ]]) eq({ 6, 29 }, eval('nvim_win_get_cursor(0)')) @@ -715,7 +906,7 @@ describe('buffer cursor position is correct in terminal with number column', fun {7: 3 } | {7: 4 } | {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | - {7: 6 }:µ̳µ̳µ̳µ̳µ̳µ̳{2:^µ̳}µ̳ | + {7: 6 }:µ̳µ̳µ̳µ̳µ̳µ̳^µ̳µ̳ | {3:-- TERMINAL --} | ]]) eq({ 6, 25 }, eval('nvim_win_get_cursor(0)')) @@ -726,7 +917,7 @@ describe('buffer cursor position is correct in terminal with number column', fun {7: 3 } | {7: 4 } | {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | - {7: 6 }:µ̳µ̳µ̳µ̳µ̳^µ̳{4:µ̳}µ̳ | + {7: 6 }:µ̳µ̳µ̳µ̳µ̳^µ̳µ̳µ̳ | | ]]) eq({ 6, 21 }, eval('nvim_win_get_cursor(0)')) @@ -741,7 +932,7 @@ describe('buffer cursor position is correct in terminal with number column', fun {7: 3 } | {7: 4 } | {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | - {7: 6 }:µ̳{2:^µ̳}µ̳µ̳µ̳µ̳µ̳µ̳ | + {7: 6 }:µ̳^µ̳µ̳µ̳µ̳µ̳µ̳µ̳ | {3:-- TERMINAL --} | ]]) eq({ 6, 5 }, eval('nvim_win_get_cursor(0)')) @@ -752,7 +943,7 @@ describe('buffer cursor position is correct in terminal with number column', fun {7: 3 } | {7: 4 } | {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | - {7: 6 }:^µ̳{4:µ̳}µ̳µ̳µ̳µ̳µ̳µ̳ | + {7: 6 }:^µ̳µ̳µ̳µ̳µ̳µ̳µ̳µ̳ | | ]]) eq({ 6, 1 }, eval('nvim_win_get_cursor(0)')) @@ -772,7 +963,7 @@ describe('buffer cursor position is correct in terminal with number column', fun {7: 3 } | {7: 4 } | {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | - {7: 6 }:哦哦哦哦哦哦哦哦{2:^ } | + {7: 6 }:哦哦哦哦哦哦哦哦^ | {3:-- TERMINAL --} | ]]) eq({ 6, 25 }, eval('nvim_win_get_cursor(0)')) @@ -783,7 +974,7 @@ describe('buffer cursor position is correct in terminal with number column', fun {7: 3 } | {7: 4 } | {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | - {7: 6 }:哦哦哦哦哦哦哦^哦{4: } | + {7: 6 }:哦哦哦哦哦哦哦^哦 | | ]]) eq({ 6, 22 }, eval('nvim_win_get_cursor(0)')) @@ -797,7 +988,7 @@ describe('buffer cursor position is correct in terminal with number column', fun {7: 3 } | {7: 4 } | {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | - {7: 6 }:哦哦哦哦哦哦{2:^哦}哦 | + {7: 6 }:哦哦哦哦哦哦^哦哦 | {3:-- TERMINAL --} | ]]) eq({ 6, 19 }, eval('nvim_win_get_cursor(0)')) @@ -808,7 +999,7 @@ describe('buffer cursor position is correct in terminal with number column', fun {7: 3 } | {7: 4 } | {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | - {7: 6 }:哦哦哦哦哦^哦{4:哦}哦 | + {7: 6 }:哦哦哦哦哦^哦哦哦 | | ]]) eq({ 6, 16 }, eval('nvim_win_get_cursor(0)')) @@ -822,7 +1013,7 @@ describe('buffer cursor position is correct in terminal with number column', fun {7: 3 } | {7: 4 } | {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | - {7: 6 }:哦{2:^哦}哦哦哦哦哦哦 | + {7: 6 }:哦^哦哦哦哦哦哦哦 | {3:-- TERMINAL --} | ]]) eq({ 6, 4 }, eval('nvim_win_get_cursor(0)')) @@ -833,7 +1024,7 @@ describe('buffer cursor position is correct in terminal with number column', fun {7: 3 } | {7: 4 } | {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | - {7: 6 }:^哦{4:哦}哦哦哦哦哦哦 | + {7: 6 }:^哦哦哦哦哦哦哦哦 | | ]]) eq({ 6, 1 }, eval('nvim_win_get_cursor(0)')) @@ -849,7 +1040,7 @@ describe('buffer cursor position is correct in terminal with number column', fun {7: 3 } | {7: 4 } | {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | - {7: 6 }:aaaaaaaa {2:^ } | + {7: 6 }:aaaaaaaa ^ | {3:-- TERMINAL --} | ]]) matches('^:aaaaaaaa [ ]*$', eval('nvim_get_current_line()')) @@ -861,7 +1052,7 @@ describe('buffer cursor position is correct in terminal with number column', fun {7: 3 } | {7: 4 } | {7: 5 }Entering Ex mode. Type "visual" to go to Normal mode. | - {7: 6 }:aaaaaaaa ^ {4: } | + {7: 6 }:aaaaaaaa ^ | | ]]) eq({ 6, 12 }, eval('nvim_win_get_cursor(0)')) diff --git a/test/functional/terminal/ex_terminal_spec.lua b/test/functional/terminal/ex_terminal_spec.lua index 5ebe7bd4fc..c29a1e9cd4 100644 --- a/test/functional/terminal/ex_terminal_spec.lua +++ b/test/functional/terminal/ex_terminal_spec.lua @@ -175,8 +175,8 @@ local function test_terminal_with_fake_shell(backslash) api.nvim_set_option_value('shellxquote', '', {}) -- win: avoid extra quotes end) - it('with no argument, acts like termopen()', function() - command('autocmd! nvim_terminal TermClose') + it('with no argument, acts like jobstart(…,{term=true})', function() + command('autocmd! nvim.terminal TermClose') feed_command('terminal') screen:expect([[ ^ready $ | @@ -196,7 +196,7 @@ local function test_terminal_with_fake_shell(backslash) ]]) end) - it("with no argument, but 'shell' has arguments, acts like termopen()", function() + it("with no argument, but 'shell' has arguments, acts like jobstart(…,{term=true})", function() api.nvim_set_option_value('shell', shell_path .. ' INTERACT', {}) feed_command('terminal') screen:expect([[ @@ -246,7 +246,7 @@ local function test_terminal_with_fake_shell(backslash) end) it('ignores writes if the backing stream closes', function() - command('autocmd! nvim_terminal TermClose') + command('autocmd! nvim.terminal TermClose') feed_command('terminal') feed('iiXXXXXXX') poke_eventloop() @@ -258,14 +258,14 @@ local function test_terminal_with_fake_shell(backslash) end) it('works with findfile()', function() - command('autocmd! nvim_terminal TermClose') + command('autocmd! nvim.terminal TermClose') feed_command('terminal') eq('term://', string.match(eval('bufname("%")'), '^term://')) eq('scripts/shadacat.py', eval('findfile("scripts/shadacat.py", ".")')) end) it('works with :find', function() - command('autocmd! nvim_terminal TermClose') + command('autocmd! nvim.terminal TermClose') feed_command('terminal') screen:expect([[ ^ready $ | diff --git a/test/functional/terminal/highlight_spec.lua b/test/functional/terminal/highlight_spec.lua index 7822a27b93..0afbd010f7 100644 --- a/test/functional/terminal/highlight_spec.lua +++ b/test/functional/terminal/highlight_spec.lua @@ -6,7 +6,6 @@ local tt = require('test.functional.testterm') local feed, clear = n.feed, n.clear local api = n.api local testprg, command = n.testprg, n.command -local nvim_prog_abs = n.nvim_prog_abs local fn = n.fn local nvim_set = n.nvim_set local is_os = t.is_os @@ -33,11 +32,11 @@ describe(':terminal highlight', function() [12] = { bold = true, underdouble = true }, [13] = { italic = true, undercurl = true }, }) - command(("enew | call termopen(['%s'])"):format(testprg('tty-test'))) + command(("enew | call jobstart(['%s'], {'term':v:true})"):format(testprg('tty-test'))) feed('i') screen:expect([[ tty ready | - {10: } | + ^ | |*4 {5:-- TERMINAL --} | ]]) @@ -61,7 +60,7 @@ describe(':terminal highlight', function() skip(is_os('win')) screen:expect(sub([[ tty ready | - {NUM:text}text{10: } | + {NUM:text}text^ | |*4 {5:-- TERMINAL --} | ]])) @@ -84,7 +83,7 @@ describe(':terminal highlight', function() line6 | line7 | line8 | - {10: } | + ^ | {5:-- TERMINAL --} | ]]) feed('<c-\\><c-n>gg') @@ -150,8 +149,8 @@ it(':terminal highlight has lower precedence than editor #9964', function() }, }) -- Child nvim process in :terminal (with cterm colors). - fn.termopen({ - nvim_prog_abs(), + fn.jobstart({ + n.nvim_prog, '-n', '-u', 'NORC', @@ -163,6 +162,7 @@ it(':terminal highlight has lower precedence than editor #9964', function() '+norm! ichild nvim', '+norm! oline 2', }, { + term = true, env = { VIMRUNTIME = os.getenv('VIMRUNTIME'), }, @@ -195,12 +195,12 @@ it('CursorLine and CursorColumn work in :terminal buffer in Normal mode', functi local screen = Screen.new(50, 7) screen:set_default_attr_ids({ [1] = { background = Screen.colors.Grey90 }, -- CursorLine, CursorColumn - [2] = { reverse = true }, -- TermCursor + [2] = { reverse = true }, [3] = { bold = true }, -- ModeMsg [4] = { background = Screen.colors.Grey90, reverse = true }, [5] = { background = Screen.colors.Red }, }) - command(("enew | call termopen(['%s'])"):format(testprg('tty-test'))) + command(("enew | call jobstart(['%s'], {'term':v:true})"):format(testprg('tty-test'))) screen:expect([[ ^tty ready | |*6 @@ -234,7 +234,7 @@ it('CursorLine and CursorColumn work in :terminal buffer in Normal mode', functi foobar foobar foobar foobar foobar foobar foobar f| oobar foobar foobar foobar foobar foobar foobar fo| obar foobar foobar foobar foobar foobar foobar foo| - bar foobar{2: } | + bar foobar^ | {3:-- TERMINAL --} | ]]) -- Leaving terminal mode restores old values. @@ -248,46 +248,60 @@ it('CursorLine and CursorColumn work in :terminal buffer in Normal mode', functi {1:bar fooba^r }| | ]]) - -- CursorLine and CursorColumn are combined with TermCursorNC. - command('highlight TermCursorNC gui=reverse') + + -- Skip the rest of these tests on Windows #31587 + if is_os('win') then + return + end + + -- CursorLine and CursorColumn are combined with terminal colors. + tt.set_reverse() + tt.feed_data(' foobar') + tt.clear_attrs() screen:expect([[ tty ready{1: } | foobar f{1:o}obar foobar foobar foobar foobar foobar | foobar fo{1:o}bar foobar foobar foobar foobar foobar f| oobar foo{1:b}ar foobar foobar foobar foobar foobar fo| obar foob{1:a}r foobar foobar foobar foobar foobar foo| - {1:bar fooba^r}{4: }{1: }| + {1:bar fooba^r}{4: foobar}{1: }| | ]]) - feed('2gg11|') + feed('2gg15|') screen:expect([[ - tty ready {1: } | - {1: foobar fo^obar foobar foobar foobar foobar foobar }| - foobar foo{1:b}ar foobar foobar foobar foobar foobar f| - oobar foob{1:a}r foobar foobar foobar foobar foobar fo| - obar fooba{1:r} foobar foobar foobar foobar foobar foo| - bar foobar{4: } | + tty ready {1: } | + {1: foobar foobar^ foobar foobar foobar foobar foobar }| + foobar foobar {1:f}oobar foobar foobar foobar foobar f| + oobar foobar f{1:o}obar foobar foobar foobar foobar fo| + obar foobar fo{1:o}bar foobar foobar foobar foobar foo| + bar foobar{2: foo}{4:b}{2:ar} | | ]]) - -- TermCursorNC has higher precedence. - command('highlight TermCursorNC gui=NONE guibg=Red') + + -- Set bg color to red + tt.feed_csi('48;2;255:0:0m') + tt.feed_data(' foobar') + tt.clear_attrs() + feed('2gg20|') + + -- Terminal color has higher precedence screen:expect([[ - tty ready {1: } | - {1: foobar fo^obar foobar foobar foobar foobar foobar }| - foobar foo{1:b}ar foobar foobar foobar foobar foobar f| - oobar foob{1:a}r foobar foobar foobar foobar foobar fo| - obar fooba{1:r} foobar foobar foobar foobar foobar foo| - bar foobar{5: } | + tty ready {1: } | + {1: foobar foobar foob^ar foobar foobar foobar foobar }| + foobar foobar fooba{1:r} foobar foobar foobar foobar f| + oobar foobar foobar{1: }foobar foobar foobar foobar fo| + obar foobar foobar {1:f}oobar foobar foobar foobar foo| + bar foobar{2: foobar}{5: foobar} | | ]]) feed('G$') screen:expect([[ - tty ready{1: } | - foobar f{1:o}obar foobar foobar foobar foobar foobar | - foobar fo{1:o}bar foobar foobar foobar foobar foobar f| - oobar foo{1:b}ar foobar foobar foobar foobar foobar fo| - obar foob{1:a}r foobar foobar foobar foobar foobar foo| - {1:bar fooba^r}{5: }{1: }| + tty ready {1: } | + foobar foobar foobar f{1:o}obar foobar foobar foobar | + foobar foobar foobar fo{1:o}bar foobar foobar foobar f| + oobar foobar foobar foo{1:b}ar foobar foobar foobar fo| + obar foobar foobar foob{1:a}r foobar foobar foobar foo| + {1:bar foobar}{4: foobar}{5: fooba^r}{1: }| | ]]) end) @@ -300,18 +314,17 @@ describe(':terminal highlight forwarding', function() screen = Screen.new(50, 7) screen:set_rgb_cterm(true) screen:set_default_attr_ids({ - [1] = { { reverse = true }, { reverse = true } }, - [2] = { { bold = true }, { bold = true } }, - [3] = { { fg_indexed = true, foreground = tonumber('0xe0e000') }, { foreground = 3 } }, - [4] = { { foreground = tonumber('0xff8000') }, {} }, + [1] = { { bold = true }, { bold = true } }, + [2] = { { fg_indexed = true, foreground = tonumber('0xe0e000') }, { foreground = 3 } }, + [3] = { { foreground = tonumber('0xff8000') }, {} }, }) - command(("enew | call termopen(['%s'])"):format(testprg('tty-test'))) + command(("enew | call jobstart(['%s'], {'term':v:true})"):format(testprg('tty-test'))) feed('i') screen:expect([[ tty ready | - {1: } | + ^ | |*4 - {2:-- TERMINAL --} | + {1:-- TERMINAL --} | ]]) end) @@ -326,9 +339,9 @@ describe(':terminal highlight forwarding', function() screen:expect { grid = [[ tty ready | - {3:text}{4:color}text{1: } | + {2:text}{3:color}text^ | |*4 - {2:-- TERMINAL --} | + {1:-- TERMINAL --} | ]], } end) @@ -351,11 +364,11 @@ describe(':terminal highlight with custom palette', function() [9] = { bold = true }, }) api.nvim_set_var('terminal_color_3', '#123456') - command(("enew | call termopen(['%s'])"):format(testprg('tty-test'))) + command(("enew | call jobstart(['%s'], {'term':v:true})"):format(testprg('tty-test'))) feed('i') screen:expect([[ tty ready | - {7: } | + ^ | |*4 {9:-- TERMINAL --} | ]]) @@ -369,7 +382,7 @@ describe(':terminal highlight with custom palette', function() tt.feed_data('text') screen:expect([[ tty ready | - {1:text}text{7: } | + {1:text}text^ | |*4 {9:-- TERMINAL --} | ]]) diff --git a/test/functional/terminal/mouse_spec.lua b/test/functional/terminal/mouse_spec.lua index 38d6b83417..5898484449 100644 --- a/test/functional/terminal/mouse_spec.lua +++ b/test/functional/terminal/mouse_spec.lua @@ -32,7 +32,7 @@ describe(':terminal mouse', function() line28 | line29 | line30 | - {1: } | + ^ | {3:-- TERMINAL --} | ]]) end) @@ -107,7 +107,7 @@ describe(':terminal mouse', function() line29 | line30 | mouse enabled | - {1: } | + ^ | {3:-- TERMINAL --} | ]]) end) @@ -121,7 +121,7 @@ describe(':terminal mouse', function() line29 | line30 | mouse enabled | - "#{1: } | + "#^ | {3:-- TERMINAL --} | ]]) feed('<LeftDrag><2,2>') @@ -131,7 +131,7 @@ describe(':terminal mouse', function() line29 | line30 | mouse enabled | - @##{1: } | + @##^ | {3:-- TERMINAL --} | ]]) feed('<LeftDrag><3,2>') @@ -141,7 +141,7 @@ describe(':terminal mouse', function() line29 | line30 | mouse enabled | - @$#{1: } | + @$#^ | {3:-- TERMINAL --} | ]]) feed('<LeftRelease><3,2>') @@ -151,7 +151,7 @@ describe(':terminal mouse', function() line29 | line30 | mouse enabled | - #$#{1: } | + #$#^ | {3:-- TERMINAL --} | ]]) end) @@ -165,7 +165,7 @@ describe(':terminal mouse', function() line29 | line30 | mouse enabled | - `!!{1: } | + `!!^ | {3:-- TERMINAL --} | ]]) end) @@ -179,7 +179,7 @@ describe(':terminal mouse', function() line29 | line30 | mouse enabled | - "#{1: } | + "#^ | {3:-- TERMINAL --} | ]]) feed('<ScrollWheelUp><1,2>') @@ -189,7 +189,7 @@ describe(':terminal mouse', function() line29 | line30 | mouse enabled | - `"#{1: } | + `"#^ | {3:-- TERMINAL --} | ]]) feed('<LeftDrag><2,2>') @@ -199,7 +199,7 @@ describe(':terminal mouse', function() line29 | line30 | mouse enabled | - @##{1: } | + @##^ | {3:-- TERMINAL --} | ]]) feed('<ScrollWheelUp><2,2>') @@ -209,7 +209,7 @@ describe(':terminal mouse', function() line29 | line30 | mouse enabled | - `##{1: } | + `##^ | {3:-- TERMINAL --} | ]]) feed('<LeftRelease><2,2>') @@ -219,7 +219,7 @@ describe(':terminal mouse', function() line29 | line30 | mouse enabled | - ###{1: } | + ###^ | {3:-- TERMINAL --} | ]]) end) @@ -237,7 +237,7 @@ describe(':terminal mouse', function() {7: 13 }line30 | {7: 14 }mouse enabled | {7: 15 }rows: 6, cols: 46 | - {7: 16 }{2: } | + {7: 16 } | | ]]) -- If click on the coordinate (0,1) of the region of the terminal @@ -249,7 +249,7 @@ describe(':terminal mouse', function() {7: 13 }line30 | {7: 14 }mouse enabled | {7: 15 }rows: 6, cols: 46 | - {7: 16 } !"{1: } | + {7: 16 } !"^ | {3:-- TERMINAL --} | ]]) end) @@ -261,7 +261,7 @@ describe(':terminal mouse', function() line30 | mouse enabled | rows: 5, cols: 50 | - {1: } | + ^ | ========== | {3:-- TERMINAL --} | ]]) @@ -271,7 +271,7 @@ describe(':terminal mouse', function() line30 | mouse enabled | rows: 5, cols: 50 | - {2:^ } | + ^ | ========== | | ]]) @@ -280,7 +280,7 @@ describe(':terminal mouse', function() mouse enabled | rows: 5, cols: 50 | rows: 4, cols: 50 | - {2:^ } | + ^ | ========== | |*2 ]]) @@ -293,7 +293,7 @@ describe(':terminal mouse', function() line30 │{4:~ }| mouse enabled │{4:~ }| rows: 5, cols: 24 │{4:~ }| - {1: } │{4:~ }| + ^ │{4:~ }| ========== ========== | {3:-- TERMINAL --} | ]]) @@ -303,7 +303,7 @@ describe(':terminal mouse', function() line30 │{4:~ }| mouse enabled │{4:~ }| rows: 5, cols: 24 │{4:~ }| - {2:^ } │{4:~ }| + ^ │{4:~ }| ========== ========== | | ]]) @@ -313,7 +313,7 @@ describe(':terminal mouse', function() mouse enabled │{4:~ }| rows: 5, cols: 24 │{4:~ }| rows: 5, cols: 23 │{4:~ }| - {2:^ } │{4:~ }| + ^ │{4:~ }| ========== ========== | | ]]) @@ -327,7 +327,7 @@ describe(':terminal mouse', function() line30 | mouse enabled | rows: 5, cols: 50 | - {1: } | + ^ | {3:-- TERMINAL --} | ]]) feed('<LeftMouse><0,0>') @@ -337,7 +337,7 @@ describe(':terminal mouse', function() line30 | mouse enabled | rows: 5, cols: 50 | - {2:^ } | + ^ | | ]]) command('set showtabline=2 tabline=TABLINE | startinsert') @@ -347,7 +347,7 @@ describe(':terminal mouse', function() mouse enabled | rows: 5, cols: 50 | rows: 4, cols: 50 | - {1: } | + ^ | {3:-- TERMINAL --} | ]]) feed('<LeftMouse><0,0>') @@ -357,7 +357,7 @@ describe(':terminal mouse', function() mouse enabled | rows: 5, cols: 50 | rows: 4, cols: 50 | - {2:^ } | + ^ | | ]]) command('setlocal winbar= | startinsert') @@ -367,7 +367,7 @@ describe(':terminal mouse', function() rows: 5, cols: 50 | rows: 4, cols: 50 | rows: 5, cols: 50 | - {1: } | + ^ | {3:-- TERMINAL --} | ]]) feed('<LeftMouse><0,0>') @@ -377,7 +377,7 @@ describe(':terminal mouse', function() rows: 5, cols: 50 | rows: 4, cols: 50 | rows: 5, cols: 50 | - {2:^ } | + ^ | | ]]) end) @@ -391,7 +391,7 @@ describe(':terminal mouse', function() line29 │line29 | line30 │line30 | rows: 5, cols: 25 │rows: 5, cols: 25 | - {2:^ } │{2: } | + ^ │ | ========== ========== | :vsp | ]]) @@ -401,7 +401,7 @@ describe(':terminal mouse', function() {4:~ }│line30 | {4:~ }│rows: 5, cols: 25 | {4:~ }│rows: 5, cols: 24 | - {4:~ }│{2: } | + {4:~ }│ | ========== ========== | :enew | set number | ]]) @@ -411,7 +411,7 @@ describe(':terminal mouse', function() {7: 28 }line │line30 | {7: 29 }line │rows: 5, cols: 25 | {7: 30 }line │rows: 5, cols: 24 | - {7: 31 }^ │{2: } | + {7: 31 }^ │ | ========== ========== | | ]]) @@ -421,7 +421,7 @@ describe(':terminal mouse', function() {7: 28 }line │line30 | {7: 29 }line │rows: 5, cols: 25 | {7: 30 }line │rows: 5, cols: 24 | - {7: 31 } │{1: } | + {7: 31 } │^ | ========== ========== | {3:-- TERMINAL --} | ]]) @@ -434,7 +434,7 @@ describe(':terminal mouse', function() {7: 28 }line │rows: 5, cols: 25 | {7: 29 }line │rows: 5, cols: 24 | {7: 30 }line │mouse enabled | - {7: 31 } │{1: } | + {7: 31 } │^ | ========== ========== | {3:-- TERMINAL --} | ]]) @@ -447,7 +447,7 @@ describe(':terminal mouse', function() {7: 22 }line │rows: 5, cols: 25 | {7: 23 }line │rows: 5, cols: 24 | {7: 24 }line │mouse enabled | - {7: 25 }line │{1: } | + {7: 25 }line │^ | ========== ========== | {3:-- TERMINAL --} | ]]) @@ -457,7 +457,7 @@ describe(':terminal mouse', function() {7: 27 }line │rows: 5, cols: 25 | {7: 28 }line │rows: 5, cols: 24 | {7: 29 }line │mouse enabled | - {7: 30 }line │{1: } | + {7: 30 }line │^ | ========== ========== | {3:-- TERMINAL --} | ]]) @@ -468,7 +468,7 @@ describe(':terminal mouse', function() {7: 17 }line │rows: 5, cols: 25 | {7: 18 }line │rows: 5, cols: 24 | {7: 19 }line │mouse enabled | - {7: 20 }line │{1: } | + {7: 20 }line │^ | ========== ========== | {3:-- TERMINAL --} | ]]) @@ -483,7 +483,7 @@ describe(':terminal mouse', function() {7: 2 }linelinelinelineline │rows: 5, cols: 25 | {7: 3 }linelinelinelineline │rows: 5, cols: 24 | {7: 4 }linelinelinelineline │mouse enabled | - {7: 5 }linelinelinelineline │{1: } | + {7: 5 }linelinelinelineline │^ | ========== ========== | {3:-- TERMINAL --} | ]]) @@ -493,7 +493,7 @@ describe(':terminal mouse', function() {7: 2 }nelinelineline │rows: 5, cols: 25 | {7: 3 }nelinelineline │rows: 5, cols: 24 | {7: 4 }nelinelineline │mouse enabled | - {7: 5 }nelinelineline │{1: } | + {7: 5 }nelinelineline │^ | ========== ========== | {3:-- TERMINAL --} | ]]) @@ -504,7 +504,7 @@ describe(':terminal mouse', function() {7: 2 }nelinelinelineline │rows: 5, cols: 25 | {7: 3 }nelinelinelineline │rows: 5, cols: 24 | {7: 4 }nelinelinelineline │mouse enabled | - {7: 5 }nelinelinelineline │{1: } | + {7: 5 }nelinelinelineline │^ | ========== ========== | {3:-- TERMINAL --} | ]]) @@ -517,7 +517,7 @@ describe(':terminal mouse', function() {7: 28 }l^ine │rows: 5, cols: 25 | {7: 29 }line │rows: 5, cols: 24 | {7: 30 }line │mouse enabled | - {7: 31 } │{2: } | + {7: 31 } │ | ========== ========== | | ]]) @@ -531,7 +531,7 @@ describe(':terminal mouse', function() {7: 28 }line │rows: 5, cols: 25 | {7: 29 }line │rows: 5, cols: 24 | {7: 30 }line │mouse enabled | - {7: 31 }^ │{2: } | + {7: 31 }^ │ | ========== ========== | | ]]) @@ -541,7 +541,7 @@ describe(':terminal mouse', function() rows: 5, cols: 24 │rows: 5, cols: 24 | mouse enabled │mouse enabled | rows: 5, cols: 25 │rows: 5, cols: 25 | - {2:^ } │{2: } | + ^ │ | ========== ========== | :bn | ]]) @@ -551,7 +551,7 @@ describe(':terminal mouse', function() {7: 28 }line │mouse enabled | {7: 29 }line │rows: 5, cols: 25 | {7: 30 }line │rows: 5, cols: 24 | - {7: 31 }^ │{2: } | + {7: 31 }^ │ | ========== ========== | :bn | ]]) diff --git a/test/functional/terminal/scrollback_spec.lua b/test/functional/terminal/scrollback_spec.lua index 1751db1aa9..804c5367eb 100644 --- a/test/functional/terminal/scrollback_spec.lua +++ b/test/functional/terminal/scrollback_spec.lua @@ -39,7 +39,7 @@ describe(':terminal scrollback', function() line28 | line29 | line30 | - {1: } | + ^ | {3:-- TERMINAL --} | ]]) end) @@ -67,7 +67,7 @@ describe(':terminal scrollback', function() line2 | line3 | line4 | - {1: } | + ^ | {3:-- TERMINAL --} | ]]) end) @@ -84,7 +84,7 @@ describe(':terminal scrollback', function() line3 | line4 | line5 | - {1: } | + ^ | {3:-- TERMINAL --} | ]]) eq(7, api.nvim_buf_line_count(0)) @@ -102,7 +102,7 @@ describe(':terminal scrollback', function() line5 | line6 | line7 | - line8{1: } | + line8^ | {3:-- TERMINAL --} | ]]) @@ -135,7 +135,7 @@ describe(':terminal scrollback', function() line5 | line6 | line7 | - ^line8{2: } | + ^line8 | | ]]) end) @@ -151,7 +151,7 @@ describe(':terminal scrollback', function() line3 | line4 | rows: 5, cols: 28 | - {2:^ } | + ^ | | ]]) end @@ -168,7 +168,7 @@ describe(':terminal scrollback', function() screen:expect([[ rows: 5, cols: 28 | rows: 3, cols: 26 | - {2:^ } | + ^ | | ]]) eq(8, api.nvim_buf_line_count(0)) @@ -201,7 +201,7 @@ describe(':terminal scrollback', function() screen:expect([[ tty ready | rows: 4, cols: 30 | - {1: } | + ^ | | {3:-- TERMINAL --} | ]]) @@ -220,7 +220,7 @@ describe(':terminal scrollback', function() screen:expect([[ rows: 4, cols: 30 | rows: 3, cols: 30 | - {1: } | + ^ | {3:-- TERMINAL --} | ]]) eq(4, api.nvim_buf_line_count(0)) @@ -235,7 +235,7 @@ describe(':terminal scrollback', function() screen:expect([[ rows: 4, cols: 30 | rows: 3, cols: 30 | - {1: } | + ^ | {3:-- TERMINAL --} | ]]) end) @@ -252,14 +252,14 @@ describe(':terminal scrollback', function() line2 | line3 | line4 | - {1: } | + ^ | {3:-- TERMINAL --} | ]]) screen:try_resize(screen._width, screen._height - 3) screen:expect([[ line4 | rows: 3, cols: 30 | - {1: } | + ^ | {3:-- TERMINAL --} | ]]) eq(7, api.nvim_buf_line_count(0)) @@ -278,7 +278,7 @@ describe(':terminal scrollback', function() line4 | rows: 3, cols: 30 | rows: 4, cols: 30 | - {1: } | + ^ | {3:-- TERMINAL --} | ]]) end @@ -300,7 +300,7 @@ describe(':terminal scrollback', function() rows: 3, cols: 30 | rows: 4, cols: 30 | rows: 7, cols: 30 | - {1: } | + ^ | {3:-- TERMINAL --} | ]]) eq(9, api.nvim_buf_line_count(0)) @@ -337,7 +337,7 @@ describe(':terminal scrollback', function() rows: 4, cols: 30 | rows: 7, cols: 30 | rows: 11, cols: 30 | - {1: } | + ^ | | {3:-- TERMINAL --} | ]]) @@ -355,14 +355,16 @@ describe(':terminal prints more lines than the screen height and exits', functio it('will push extra lines to scrollback', function() clear() local screen = Screen.new(30, 7, { rgb = false }) - command(("call termopen(['%s', '10']) | startinsert"):format(testprg('tty-test'))) + command( + ("call jobstart(['%s', '10'], {'term':v:true}) | startinsert"):format(testprg('tty-test')) + ) screen:expect([[ line6 | line7 | line8 | line9 | | - [Process exited 0]{2: } | + [Process exited 0]^ | {5:-- TERMINAL --} | ]]) feed('<cr>') @@ -454,7 +456,7 @@ describe("'scrollback' option", function() 39: line | 40: line | | - ${1: } | + $^ | {3:-- TERMINAL --} | ]], } @@ -493,7 +495,7 @@ describe("'scrollback' option", function() line28 | line29 | line30 | - {1: } | + ^ | {3:-- TERMINAL --} | ]]) local term_height = 6 -- Actual terminal screen height, not the scrollback @@ -623,7 +625,7 @@ describe('pending scrollback line handling', function() local bufnr = vim.api.nvim_create_buf(false, true) local args = ... vim.api.nvim_buf_call(bufnr, function() - vim.fn.termopen(args) + vim.fn.jobstart(args, { term = true }) end) vim.api.nvim_win_set_buf(0, bufnr) vim.cmd('startinsert') @@ -634,7 +636,7 @@ describe('pending scrollback line handling', function() screen:expect [[ hi |*4 | - [Process exited 0]{2: } | + [Process exited 0]^ | {3:-- TERMINAL --} | ]] assert_alive() diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua index ded0cd99d3..a2dc3c500f 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -26,7 +26,6 @@ local api = n.api local is_ci = t.is_ci local is_os = t.is_os local new_pipename = n.new_pipename -local spawn_argv = n.spawn_argv local set_session = n.set_session local write_file = t.write_file local eval = n.eval @@ -59,7 +58,7 @@ describe('TUI', function() 'colorscheme vim', }) screen:expect([[ - {1: } | + ^ | {4:~ }|*3 {5:[No Name] }| | @@ -105,7 +104,7 @@ describe('TUI', function() -- Need buffer rows to provoke the behavior. feed_data(':edit test/functional/fixtures/bigfile.txt\n') screen:expect([[ - {1:0}000;<control>;Cc;0;BN;;;;;N;NULL;;;; | + ^0000;<control>;Cc;0;BN;;;;;N;NULL;;;; | 0001;<control>;Cc;0;BN;;;;;N;START OF HEADING;;;; | 0002;<control>;Cc;0;BN;;;;;N;START OF TEXT;;;; | 0003;<control>;Cc;0;BN;;;;;N;END OF TEXT;;;; | @@ -155,7 +154,7 @@ describe('TUI', function() {8:FAIL 0} | {8:FAIL 1} | {8:FAIL 2} | - {10:-- More --}{1: } | + {10:-- More --}^ | {3:-- TERMINAL --} | ]], } @@ -170,7 +169,7 @@ describe('TUI', function() {8:FAIL 1} | {8:FAIL 2} | |*2 - {10:-- More --}{1: } | + {10:-- More --}^ | {3:-- TERMINAL --} | ]], } @@ -186,7 +185,7 @@ describe('TUI', function() {8:FAIL 3} | {8:FAIL 4} | {8:FAIL 5} | - {10:-- More --}{1: } | + {10:-- More --}^ | {3:-- TERMINAL --} | ]], } @@ -199,7 +198,7 @@ describe('TUI', function() {8:FAIL 3} | {8:FAIL 4} | {8:FAIL 5} | - {10:-- More --}{1: } | + {10:-- More --}^ | {3:-- TERMINAL --} | ]], } @@ -210,7 +209,7 @@ describe('TUI', function() {8:FAIL 3} | {8:FAIL 4} | {8:FAIL 5} | - {10:-- More --}{1: } | + {10:-- More --}^ | {3:-- TERMINAL --} | ]], } @@ -221,7 +220,7 @@ describe('TUI', function() :call ManyErr() | {8:Error detected while processing function ManyErr:} | {11:line 2:} | - {10:-- More --}{1: } | + {10:-- More --}^ | {3:-- TERMINAL --} | ]], } @@ -237,7 +236,7 @@ describe('TUI', function() {8:FAIL 2} | {8:FAIL 3} | {8:FAIL 4} | - {10:-- More --}{1: } | + {10:-- More --}^ | {3:-- TERMINAL --} | ]], } @@ -245,7 +244,7 @@ describe('TUI', function() feed_data('\003') screen:expect { grid = [[ - {1: } | + ^ | {4:~ }|*6 {5:[No Name] }| | @@ -259,7 +258,7 @@ describe('TUI', function() screen:expect([[ abc | test1 | - test2{1: } | + test2^ | {4:~ }| {5:[No Name] [+] }| {3:-- INSERT --} | @@ -269,7 +268,7 @@ describe('TUI', function() screen:expect([[ abc | test1 | - test{1:2} | + test^2 | {4:~ }| {5:[No Name] [+] }| | @@ -287,14 +286,14 @@ describe('TUI', function() alt-j | alt-k | alt-l | - {1: } | + ^ | {5:[No Name] [+] }| | {3:-- TERMINAL --} | ]]) feed_data('gg') screen:expect([[ - {1:a}lt-d | + ^alt-d | alt-f | alt-g | alt-h | @@ -309,7 +308,7 @@ describe('TUI', function() -- ALT+j inserts "ê". Nvim does not (#3982). feed_data('i\022\027j') screen:expect([[ - <M-j>{1: } | + <M-j>^ | {4:~ }|*3 {5:[No Name] [+] }| {3:-- INSERT --} | @@ -329,7 +328,7 @@ describe('TUI', function() ) feed_data('\027[27u;') screen:expect([[ - ESCsemicolo{1:n} | + ESCsemicolo^n | {4:~ }|*3 {5:[No Name] [+] }| | @@ -343,7 +342,7 @@ describe('TUI', function() it('interprets <Esc><Nul> as <M-C-Space> #17198', function() feed_data('i\022\027\000') screen:expect([[ - <M-C-Space>{1: } | + <M-C-Space>^ | {4:~ }|*3 {5:[No Name] [+] }| {3:-- INSERT --} | @@ -357,7 +356,7 @@ describe('TUI', function() feed_data('\022\022') -- ctrl+v feed_data('\022\013') -- ctrl+m screen:expect([[ - {6:^G^V^M}{1: } | + {6:^G^V^M}^ | {4:~ }|*3 {5:[No Name] [+] }| {3:-- INSERT --} | @@ -376,7 +375,7 @@ describe('TUI', function() {} ) screen:expect([[ - {11: 1 }{1:0}----1----2----3----4│{11: 1 }0----1----2----3----| + {11: 1 }^0----1----2----3----4│{11: 1 }0----1----2----3----| {11: 2 }0----1----2----3----4│{11: 2 }0----1----2----3----| {11: 3 }0----1----2----3----4│{11: 3 }0----1----2----3----| {11: 4 }0----1----2----3----4│{11: 4 }0----1----2----3----| @@ -391,7 +390,7 @@ describe('TUI', function() api.nvim_input_mouse('wheel', 'down', '', 0, 0, 7) end screen:expect([[ - {11: 2 }{1:0}----1----2----3----4│{11: 1 }0----1----2----3----| + {11: 2 }^0----1----2----3----4│{11: 1 }0----1----2----3----| {11: 3 }0----1----2----3----4│{11: 2 }0----1----2----3----| {11: 4 }0----1----2----3----4│{11: 3 }0----1----2----3----| {11: 5 }0----1----2----3----4│{11: 4 }0----1----2----3----| @@ -406,7 +405,7 @@ describe('TUI', function() api.nvim_input_mouse('wheel', 'down', '', 0, 0, 47) end screen:expect([[ - {11: 2 }{1:0}----1----2----3----4│{11: 2 }0----1----2----3----| + {11: 2 }^0----1----2----3----4│{11: 2 }0----1----2----3----| {11: 3 }0----1----2----3----4│{11: 3 }0----1----2----3----| {11: 4 }0----1----2----3----4│{11: 4 }0----1----2----3----| {11: 5 }0----1----2----3----4│{11: 5 }0----1----2----3----| @@ -421,7 +420,7 @@ describe('TUI', function() api.nvim_input_mouse('wheel', 'right', '', 0, 0, 7) end screen:expect([[ - {11: 2 }{1:-}---1----2----3----4-│{11: 2 }0----1----2----3----| + {11: 2 }^----1----2----3----4-│{11: 2 }0----1----2----3----| {11: 3 }----1----2----3----4-│{11: 3 }0----1----2----3----| {11: 4 }----1----2----3----4-│{11: 4 }0----1----2----3----| {11: 5 }----1----2----3----4-│{11: 5 }0----1----2----3----| @@ -436,7 +435,7 @@ describe('TUI', function() api.nvim_input_mouse('wheel', 'right', '', 0, 0, 47) end screen:expect([[ - {11: 2 }{1:-}---1----2----3----4-│{11: 2 }----1----2----3----4| + {11: 2 }^----1----2----3----4-│{11: 2 }----1----2----3----4| {11: 3 }----1----2----3----4-│{11: 3 }----1----2----3----4| {11: 4 }----1----2----3----4-│{11: 4 }----1----2----3----4| {11: 5 }----1----2----3----4-│{11: 5 }----1----2----3----4| @@ -451,7 +450,7 @@ describe('TUI', function() api.nvim_input_mouse('wheel', 'down', 'S', 0, 0, 7) end screen:expect([[ - {11: 5 }{1:-}---1----2----3----4-│{11: 2 }----1----2----3----4| + {11: 5 }^----1----2----3----4-│{11: 2 }----1----2----3----4| {11: 6 }----1----2----3----4-│{11: 3 }----1----2----3----4| {11: 7 }----1----2----3----4-│{11: 4 }----1----2----3----4| {11: 8 }----1----2----3----4-│{11: 5 }----1----2----3----4| @@ -466,7 +465,7 @@ describe('TUI', function() api.nvim_input_mouse('wheel', 'down', 'S', 0, 0, 47) end screen:expect([[ - {11: 5 }{1:-}---1----2----3----4-│{11: 5 }----1----2----3----4| + {11: 5 }^----1----2----3----4-│{11: 5 }----1----2----3----4| {11: 6 }----1----2----3----4-│{11: 6 }----1----2----3----4| {11: 7 }----1----2----3----4-│{11: 7 }----1----2----3----4| {11: 8 }----1----2----3----4-│{11: 8 }----1----2----3----4| @@ -481,7 +480,7 @@ describe('TUI', function() api.nvim_input_mouse('wheel', 'right', 'S', 0, 0, 7) end screen:expect([[ - {11: 5 }{1:-}---6----7----8----9 │{11: 5 }----1----2----3----4| + {11: 5 }^----6----7----8----9 │{11: 5 }----1----2----3----4| {11: 6 }----6----7----8----9 │{11: 6 }----1----2----3----4| {11: 7 }----6----7----8----9 │{11: 7 }----1----2----3----4| {11: 8 }----6----7----8----9 │{11: 8 }----1----2----3----4| @@ -496,7 +495,7 @@ describe('TUI', function() api.nvim_input_mouse('wheel', 'right', 'S', 0, 0, 47) end screen:expect([[ - {11: 5 }{1:-}---6----7----8----9 │{11: 5 }5----6----7----8----| + {11: 5 }^----6----7----8----9 │{11: 5 }5----6----7----8----| {11: 6 }----6----7----8----9 │{11: 6 }5----6----7----8----| {11: 7 }----6----7----8----9 │{11: 7 }5----6----7----8----| {11: 8 }----6----7----8----9 │{11: 8 }5----6----7----8----| @@ -512,7 +511,7 @@ describe('TUI', function() end screen:expect([[ {11: 4 }----6----7----8----9 │{11: 5 }5----6----7----8----| - {11: 5 }{1:-}---6----7----8----9 │{11: 6 }5----6----7----8----| + {11: 5 }^----6----7----8----9 │{11: 6 }5----6----7----8----| {11: 6 }----6----7----8----9 │{11: 7 }5----6----7----8----| {11: 7 }----6----7----8----9 │{11: 8 }5----6----7----8----| {5:[No Name] [+] }{1:[No Name] [+] }| @@ -527,7 +526,7 @@ describe('TUI', function() end screen:expect([[ {11: 4 }----6----7----8----9 │{11: 4 }5----6----7----8----| - {11: 5 }{1:-}---6----7----8----9 │{11: 5 }5----6----7----8----| + {11: 5 }^----6----7----8----9 │{11: 5 }5----6----7----8----| {11: 6 }----6----7----8----9 │{11: 6 }5----6----7----8----| {11: 7 }----6----7----8----9 │{11: 7 }5----6----7----8----| {5:[No Name] [+] }{1:[No Name] [+] }| @@ -542,7 +541,7 @@ describe('TUI', function() end screen:expect([[ {11: 4 }5----6----7----8----9│{11: 4 }5----6----7----8----| - {11: 5 }5{1:-}---6----7----8----9│{11: 5 }5----6----7----8----| + {11: 5 }5^----6----7----8----9│{11: 5 }5----6----7----8----| {11: 6 }5----6----7----8----9│{11: 6 }5----6----7----8----| {11: 7 }5----6----7----8----9│{11: 7 }5----6----7----8----| {5:[No Name] [+] }{1:[No Name] [+] }| @@ -557,7 +556,7 @@ describe('TUI', function() end screen:expect([[ {11: 4 }5----6----7----8----9│{11: 4 }-5----6----7----8---| - {11: 5 }5{1:-}---6----7----8----9│{11: 5 }-5----6----7----8---| + {11: 5 }5^----6----7----8----9│{11: 5 }-5----6----7----8---| {11: 6 }5----6----7----8----9│{11: 6 }-5----6----7----8---| {11: 7 }5----6----7----8----9│{11: 7 }-5----6----7----8---| {5:[No Name] [+] }{1:[No Name] [+] }| @@ -574,7 +573,7 @@ describe('TUI', function() {11: 1 }5----6----7----8----9│{11: 4 }-5----6----7----8---| {11: 2 }5----6----7----8----9│{11: 5 }-5----6----7----8---| {11: 3 }5----6----7----8----9│{11: 6 }-5----6----7----8---| - {11: 4 }5{1:-}---6----7----8----9│{11: 7 }-5----6----7----8---| + {11: 4 }5^----6----7----8----9│{11: 7 }-5----6----7----8---| {5:[No Name] [+] }{1:[No Name] [+] }| | {3:-- TERMINAL --} | @@ -589,7 +588,7 @@ describe('TUI', function() {11: 1 }5----6----7----8----9│{11: 1 }-5----6----7----8---| {11: 2 }5----6----7----8----9│{11: 2 }-5----6----7----8---| {11: 3 }5----6----7----8----9│{11: 3 }-5----6----7----8---| - {11: 4 }5{1:-}---6----7----8----9│{11: 4 }-5----6----7----8---| + {11: 4 }5^----6----7----8----9│{11: 4 }-5----6----7----8---| {5:[No Name] [+] }{1:[No Name] [+] }| | {3:-- TERMINAL --} | @@ -604,7 +603,7 @@ describe('TUI', function() {11: 1 }0----1----2----3----4│{11: 1 }-5----6----7----8---| {11: 2 }0----1----2----3----4│{11: 2 }-5----6----7----8---| {11: 3 }0----1----2----3----4│{11: 3 }-5----6----7----8---| - {11: 4 }0----1----2----3----{1:4}│{11: 4 }-5----6----7----8---| + {11: 4 }0----1----2----3----^4│{11: 4 }-5----6----7----8---| {5:[No Name] [+] }{1:[No Name] [+] }| | {3:-- TERMINAL --} | @@ -619,7 +618,7 @@ describe('TUI', function() {11: 1 }0----1----2----3----4│{11: 1 }0----1----2----3----| {11: 2 }0----1----2----3----4│{11: 2 }0----1----2----3----| {11: 3 }0----1----2----3----4│{11: 3 }0----1----2----3----| - {11: 4 }0----1----2----3----{1:4}│{11: 4 }0----1----2----3----| + {11: 4 }0----1----2----3----^4│{11: 4 }0----1----2----3----| {5:[No Name] [+] }{1:[No Name] [+] }| | {3:-- TERMINAL --} | @@ -645,7 +644,7 @@ describe('TUI', function() aunmenu PopUp " Delete the default MenuPopup event handler. - autocmd! nvim_popupmenu + autocmd! nvim.popupmenu menu PopUp.foo :let g:menustr = 'foo'<CR> menu PopUp.bar :let g:menustr = 'bar'<CR> menu PopUp.baz :let g:menustr = 'baz'<CR> @@ -660,7 +659,7 @@ describe('TUI', function() api.nvim_input_mouse('right', 'press', '', 0, 0, 4) end screen:expect([[ - {1:p}opup menu test | + ^popup menu test | {4:~ }{13: foo }{4: }| {4:~ }{13: bar }{4: }| {4:~ }{13: baz }{4: }| @@ -680,7 +679,7 @@ describe('TUI', function() api.nvim_input_mouse('wheel', 'up', '', 0, 0, 4) end screen:expect([[ - {1:p}opup menu test | + ^popup menu test | {4:~ }{14: foo }{4: }| {4:~ }{13: bar }{4: }| {4:~ }{13: baz }{4: }| @@ -694,7 +693,7 @@ describe('TUI', function() api.nvim_input_mouse('move', '', '', 0, 3, 6) end screen:expect([[ - {1:p}opup menu test | + ^popup menu test | {4:~ }{13: foo }{4: }| {4:~ }{13: bar }{4: }| {4:~ }{14: baz }{4: }| @@ -708,7 +707,7 @@ describe('TUI', function() api.nvim_input_mouse('wheel', 'down', '', 0, 3, 6) end screen:expect([[ - {1:p}opup menu test | + ^popup menu test | {4:~ }{13: foo }{4: }| {4:~ }{14: bar }{4: }| {4:~ }{13: baz }{4: }| @@ -722,7 +721,7 @@ describe('TUI', function() api.nvim_input_mouse('left', 'press', '', 0, 2, 6) end screen:expect([[ - {1:p}opup menu test | + ^popup menu test | {4:~ }|*3 {5:[No Name] [+] }| :let g:menustr = 'bar' | @@ -740,7 +739,7 @@ describe('TUI', function() api.nvim_input_mouse('right', 'press', '', 0, 2, 44) end screen:expect([[ - {1:p}opup menu test | + ^popup menu test | {4:~ }|*2 {4:~ }{13: foo }{4: }| {5:[No Name] [+] }{13: bar }{5: }| @@ -753,7 +752,7 @@ describe('TUI', function() api.nvim_input_mouse('right', 'drag', '', 0, 5, 47) end screen:expect([[ - {1:p}opup menu test | + ^popup menu test | {4:~ }|*2 {4:~ }{13: foo }{4: }| {5:[No Name] [+] }{13: bar }{5: }| @@ -766,7 +765,7 @@ describe('TUI', function() api.nvim_input_mouse('right', 'release', '', 0, 5, 47) end screen:expect([[ - {1:p}opup menu test | + ^popup menu test | {4:~ }|*3 {5:[No Name] [+] }| :let g:menustr = 'baz' | @@ -805,7 +804,7 @@ describe('TUI', function() feed_data(fn.nr2char(57415)) -- KP_EQUAL screen:expect([[ 0123456789./*-+ | - ={1: } | + =^ | {4:~ }|*2 {5:[No Name] [+] }| {3:-- INSERT --} | @@ -814,7 +813,7 @@ describe('TUI', function() feed_data(fn.nr2char(57417)) -- KP_LEFT screen:expect([[ 0123456789./*-+ | - {1:=} | + ^= | {4:~ }|*2 {5:[No Name] [+] }| {3:-- INSERT --} | @@ -823,7 +822,7 @@ describe('TUI', function() feed_data(fn.nr2char(57418)) -- KP_RIGHT screen:expect([[ 0123456789./*-+ | - ={1: } | + =^ | {4:~ }|*2 {5:[No Name] [+] }| {3:-- INSERT --} | @@ -831,7 +830,7 @@ describe('TUI', function() ]]) feed_data(fn.nr2char(57419)) -- KP_UP screen:expect([[ - 0{1:1}23456789./*-+ | + 0^123456789./*-+ | = | {4:~ }|*2 {5:[No Name] [+] }| @@ -841,7 +840,7 @@ describe('TUI', function() feed_data(fn.nr2char(57420)) -- KP_DOWN screen:expect([[ 0123456789./*-+ | - ={1: } | + =^ | {4:~ }|*2 {5:[No Name] [+] }| {3:-- INSERT --} | @@ -850,7 +849,7 @@ describe('TUI', function() feed_data(fn.nr2char(57425)) -- KP_INSERT screen:expect([[ 0123456789./*-+ | - ={1: } | + =^ | {4:~ }|*2 {5:[No Name] [+] }| {3:-- REPLACE --} | @@ -859,7 +858,7 @@ describe('TUI', function() feed_data('\027[27u') -- ESC screen:expect([[ 0123456789./*-+ | - {1:=} | + ^= | {4:~ }|*2 {5:[No Name] [+] }| | @@ -867,7 +866,7 @@ describe('TUI', function() ]]) feed_data('\027[57417;5u') -- CTRL + KP_LEFT screen:expect([[ - {1:0}123456789./*-+ | + ^0123456789./*-+ | = | {4:~ }|*2 {5:[No Name] [+] }| @@ -876,7 +875,7 @@ describe('TUI', function() ]]) feed_data('\027[57418;2u') -- SHIFT + KP_RIGHT screen:expect([[ - 0123456789{1:.}/*-+ | + 0123456789^./*-+ | = | {4:~ }|*2 {5:[No Name] [+] }| @@ -885,7 +884,7 @@ describe('TUI', function() ]]) feed_data(fn.nr2char(57426)) -- KP_DELETE screen:expect([[ - 0123456789{1:/}*-+ | + 0123456789^/*-+ | = | {4:~ }|*2 {5:[No Name] [+] }| @@ -894,7 +893,7 @@ describe('TUI', function() ]]) feed_data(fn.nr2char(57423)) -- KP_HOME screen:expect([[ - {1:0}123456789/*-+ | + ^0123456789/*-+ | = | {4:~ }|*2 {5:[No Name] [+] }| @@ -903,7 +902,7 @@ describe('TUI', function() ]]) feed_data(fn.nr2char(57424)) -- KP_END screen:expect([[ - 0123456789/*-{1:+} | + 0123456789/*-^+ | = | {4:~ }|*2 {5:[No Name] [+] }| @@ -921,7 +920,7 @@ describe('TUI', function() ) screen:expect([[ {12: + [No Name] + [No Name] }{3: [No Name] }{1: }{12:X}| - {1: } | + ^ | {4:~ }|*2 {5:[No Name] }| | @@ -930,7 +929,7 @@ describe('TUI', function() feed_data('\027[57421;5u') -- CTRL + KP_PAGE_UP screen:expect([[ {12: + [No Name] }{3: + [No Name] }{12: [No Name] }{1: }{12:X}| - 0123456789/*-{1:+} | + 0123456789/*-^+ | = | {4:~ }| {5:[No Name] [+] }| @@ -940,7 +939,7 @@ describe('TUI', function() feed_data('\027[57422;5u') -- CTRL + KP_PAGE_DOWN screen:expect([[ {12: + [No Name] + [No Name] }{3: [No Name] }{1: }{12:X}| - {1: } | + ^ | {4:~ }|*2 {5:[No Name] }| | @@ -961,7 +960,7 @@ describe('TUI', function() feed_data('\022\027[57379;48u') -- Shift + Alt + Ctrl + Super + Meta + F16 screen:expect([[ <D-j><T-k><T-D-CR><M-T-C-S-D-BS> | - <D-F13><T-F14><T-D-F15><M-T-C-S-D-F16>{1: } | + <D-F13><T-F14><T-D-F15><M-T-C-S-D-F16>^ | {4:~ }|*2 {5:[No Name] [+] }| {3:-- INSERT --} | @@ -973,7 +972,7 @@ describe('TUI', function() -- "bracketed paste" feed_data('i""\027i\027[200~') screen:expect([[ - "{1:"} | + "^" | {4:~ }|*3 {5:[No Name] [+] }| {3:-- INSERT --} | @@ -982,7 +981,7 @@ describe('TUI', function() feed_data('pasted from terminal') expect_child_buf_lines({ '"pasted from terminal"' }) screen:expect([[ - "pasted from terminal{1:"} | + "pasted from terminal^" | {4:~ }|*3 {5:[No Name] [+] }| {3:-- INSERT --} | @@ -994,7 +993,7 @@ describe('TUI', function() feed_data('\027[27u') -- ESC: go to Normal mode. wait_for_mode('n') screen:expect([[ - "pasted from termina{1:l}" | + "pasted from termina^l" | {4:~ }|*3 {5:[No Name] [+] }| | @@ -1005,7 +1004,7 @@ describe('TUI', function() expect_child_buf_lines({ '"pasted from terminapasted from terminalpasted from terminall"' }) screen:expect([[ "pasted from terminapasted from terminalpasted fro| - m termina{1:l}l" | + m termina^ll" | {4:~ }|*2 {5:[No Name] [+] }| | @@ -1027,7 +1026,7 @@ describe('TUI', function() this is line 1 | this is line 2 | line 3 is here | - {1: } | + ^ | {5:[No Name] [+] }| | {3:-- TERMINAL --} | @@ -1037,7 +1036,7 @@ describe('TUI', function() screen:expect([[ this{16: is line 1} | {16:this is line 2} | - {16:line}{1: }3 is here | + {16:line}^ 3 is here | | {5:[No Name] [+] }| {3:-- SELECT --} | @@ -1047,7 +1046,7 @@ describe('TUI', function() feed_data('just paste it™') feed_data('\027[201~') screen:expect([[ - thisjust paste it{1:™}3 is here | + thisjust paste it^™3 is here | | {4:~ }|*2 {5:[No Name] [+] }| @@ -1084,7 +1083,7 @@ describe('TUI', function() feed_data('i') screen:expect([[ tty ready | - {1: } | + ^ | |*2 {19:^^^^^^^ }| {3:-- TERMINAL --} |*2 @@ -1094,7 +1093,7 @@ describe('TUI', function() feed_data('\027[201~') screen:expect([[ tty ready | - hallo{1: } | + hallo^ | |*2 {19:^^^^^^^ }| {3:-- TERMINAL --} |*2 @@ -1111,7 +1110,7 @@ describe('TUI', function() local expected_grid1 = [[ line 1 | ESC:{6:^[} / CR: | - {1:x} | + ^x | {4:~ }| {5:[No Name] [+] 3,1 All}| | @@ -1126,7 +1125,7 @@ describe('TUI', function() ESC:{6:^[} / CR: | xline 1 | ESC:{6:^[} / CR: | - {1:x} | + ^x | {5:[No Name] [+] 5,1 Bot}| | {3:-- TERMINAL --} | @@ -1165,7 +1164,7 @@ describe('TUI', function() | {4:~ }|*2 {5:[No Name] [+] }| - :"{1:"} | + :"^" | {3:-- TERMINAL --} | ]]) -- "bracketed paste" @@ -1179,7 +1178,7 @@ describe('TUI', function() | {4:~ }|*2 {5:[No Name] [+] }| - :"line 1{1:"} | + :"line 1^" | {3:-- TERMINAL --} | ]]) -- Dot-repeat/redo. @@ -1188,7 +1187,7 @@ describe('TUI', function() feed_data('.') screen:expect([[ foo |*2 - {1: } | + ^ | {4:~ }| {5:[No Name] [+] }| | @@ -1235,7 +1234,7 @@ describe('TUI', function() wait_for_mode('n') screen:expect([[ foo | - {1: } | + ^ | {4:~ }|*2 {5:[No Name] [+] }| | @@ -1249,7 +1248,7 @@ describe('TUI', function() {5: }| {8:paste: Error executing lua: [string "<nvim>"]:4: f}| {8:ake fail} | - {10:Press ENTER or type command to continue}{1: } | + {10:Press ENTER or type command to continue}^ | {3:-- TERMINAL --} | ]]) -- Remaining chunks are discarded after vim.paste() failure. @@ -1265,7 +1264,7 @@ describe('TUI', function() feed_data('.') screen:expect([[ foo |*2 - {1: } | + ^ | {4:~ }| {5:[No Name] [+] }| | @@ -1275,7 +1274,7 @@ describe('TUI', function() feed_data('ityped input...\027[27u') screen:expect([[ foo |*2 - typed input..{1:.} | + typed input..^. | {4:~ }| {5:[No Name] [+] }| | @@ -1288,7 +1287,7 @@ describe('TUI', function() foo | typed input...line A | line B | - {1: } | + ^ | {5:[No Name] [+] }| | {3:-- TERMINAL --} | @@ -1352,7 +1351,7 @@ describe('TUI', function() {5: }| {8:paste: Error executing lua: Vim:E21: Cannot make c}| {8:hanges, 'modifiable' is off} | - {10:Press ENTER or type command to continue}{1: } | + {10:Press ENTER or type command to continue}^ | {3:-- TERMINAL --} | ]]) feed_data('\n') -- <Enter> to dismiss hit-enter prompt @@ -1361,7 +1360,7 @@ describe('TUI', function() screen:expect([[ success 1 | success 2 | - {1: } | + ^ | {4:~ }| {5:[No Name] [+] }| | @@ -1380,7 +1379,7 @@ describe('TUI', function() expected = expected .. ' end' screen:expect([[ zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz| - zzzzzzzzzzzzzz end{1: } | + zzzzzzzzzzzzzz end^ | {4:~ }|*2 {5:[No Name] [+] }| {3:-- INSERT --} | @@ -1399,7 +1398,7 @@ describe('TUI', function() | {4:~ }|*3 {5:[No Name] }| - :<{1: } | + :<^ | {3:-- TERMINAL --} | ]]) end) @@ -1420,7 +1419,7 @@ describe('TUI', function() item 2997 | item 2998 | item 2999 | - item 3000 end{1: } | + item 3000 end^ | {5:[No Name] [+] 3000,14 Bot}| {3:-- INSERT --} | {3:-- TERMINAL --} | @@ -1433,7 +1432,7 @@ describe('TUI', function() item 2997 | item 2998 | item 2999 | - item 3000 en{1:d}d | + item 3000 en^dd | {5:[No Name] [+] 5999,13 Bot}| | {3:-- TERMINAL --} | @@ -1457,7 +1456,7 @@ describe('TUI', function() | pasted from terminal (1) | {6:^[}[200~ | - {1: } | + ^ | {5:[No Name] [+] }| {3:-- INSERT --} | {3:-- TERMINAL --} | @@ -1472,7 +1471,7 @@ describe('TUI', function() -- Send "stop paste" sequence. feed_data('\027[201~') screen:expect([[ - {1: } | + ^ | {4:~ }|*3 {5:[No Name] }| {3:-- INSERT --} | @@ -1487,7 +1486,7 @@ describe('TUI', function() feed_data('\027[2') feed_data('00~pasted from terminal\027[201~') screen:expect([[ - pasted from terminal{1: } | + pasted from terminal^ | {4:~ }|*3 {5:[No Name] [+] }| {3:-- INSERT --} | @@ -1502,7 +1501,7 @@ describe('TUI', function() feed_data('\027[200~pasted from terminal\027[20') feed_data('1~') screen:expect([[ - pasted from terminal{1: } | + pasted from terminal^ | {4:~ }|*3 {5:[No Name] [+] }| {3:-- INSERT --} | @@ -1524,7 +1523,7 @@ describe('TUI', function() wait_for_mode('i') feed_data('\027[200~pasted') -- phase 1 screen:expect([[ - pasted{1: } | + pasted^ | {4:~ }|*3 {5:[No Name] [+] }| {3:-- INSERT --} | @@ -1532,7 +1531,7 @@ describe('TUI', function() ]]) feed_data(' from terminal') -- phase 2 screen:expect([[ - pasted from terminal{1: } | + pasted from terminal^ | {4:~ }|*3 {5:[No Name] [+] }| {3:-- INSERT --} | @@ -1568,7 +1567,7 @@ describe('TUI', function() feed_data('\028\014') -- crtl+\ ctrl+N feed_data(':set termguicolors?\n') screen:expect([[ - {5:^}{6:G} | + {6:^^G} | {2:~ }|*3 {3:[No Name] [+] }| notermguicolors | @@ -1577,7 +1576,7 @@ describe('TUI', function() feed_data(':set termguicolors\n') screen:expect([[ - {7:^}{8:G} | + {8:^^G} | {9:~}{10: }|*3 {3:[No Name] [+] }| :set termguicolors | @@ -1586,7 +1585,7 @@ describe('TUI', function() feed_data(':set notermguicolors\n') screen:expect([[ - {5:^}{6:G} | + {6:^^G} | {2:~ }|*3 {3:[No Name] [+] }| :set notermguicolors | @@ -1634,7 +1633,7 @@ describe('TUI', function() child_exec_lua('vim.cmd.terminal(...)', testprg('tty-test')) screen:expect { grid = [[ - {1:t}ty ready | + ^tty ready | |*3 {2:^^^^^^^ }| | @@ -1646,7 +1645,7 @@ describe('TUI', function() ) screen:expect { grid = [[ - {1:t}ty ready | + ^tty ready | {4:text}{5:color}text | |*2 {2:^^^^^^^ }| @@ -1658,7 +1657,7 @@ describe('TUI', function() feed_data(':set notermguicolors\n') screen:expect { grid = [[ - {1:t}ty ready | + ^tty ready | {4:text}colortext | |*2 {6:^^^^^^^}{7: }| @@ -1681,7 +1680,7 @@ describe('TUI', function() child_session:request('nvim_set_hl', 0, 'Visual', { undercurl = true }) feed_data('ifoobar\027V') screen:expect([[ - {5:fooba}{1:r} | + {5:fooba}^r | {4:~ }|*3 {2:[No Name] [+] }| {3:-- VISUAL LINE --} | @@ -1689,7 +1688,7 @@ describe('TUI', function() ]]) child_session:request('nvim_set_hl', 0, 'Visual', { underdouble = true }) screen:expect([[ - {6:fooba}{1:r} | + {6:fooba}^r | {4:~ }|*3 {2:[No Name] [+] }| {3:-- VISUAL LINE --} | @@ -1777,7 +1776,7 @@ describe('TUI', function() child_session:request('nvim_set_option_value', 'listchars', 'eol:$', { win = 0 }) feed_data('gg') local singlewidth_screen = [[ - {13:℃}{12:℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃}| + {12:^℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃}| {12:℃℃℃℃℃℃℃℃℃℃}{15:$}{12: }| ℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃℃| ℃℃℃℃℃℃℃℃℃℃{4:$} | @@ -1788,7 +1787,7 @@ describe('TUI', function() -- When grid assumes "℃" to be double-width but host terminal assumes it to be single-width, -- the second cell of "℃" is a space and the attributes of the "℃" are applied to it. local doublewidth_screen = [[ - {13:℃}{12: ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ }| + {12:^℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ }| {12:℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ }| {12:℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ }{15:$}{12: }| ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ ℃ {4:@@@@}| @@ -1821,7 +1820,7 @@ describe('TUI', function() child_session:request('nvim_set_option_value', 'listchars', 'eol:$', { win = 0 }) feed_data('gg') local singlewidth_screen = [[ - {13:✓}{12:✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓}| + {12:^✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓}| {12:✓✓✓✓✓✓✓✓✓✓}{15:$}{12: }| ✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓✓| ✓✓✓✓✓✓✓✓✓✓{4:$} | @@ -1832,7 +1831,7 @@ describe('TUI', function() -- When grid assumes "✓" to be double-width but host terminal assumes it to be single-width, -- the second cell of "✓" is a space and the attributes of the "✓" are applied to it. local doublewidth_screen = [[ - {13:✓}{12: ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ }| + {12:^✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ }| {12:✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ }| {12:✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ }{15:$}{12: }| ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ {4:@@@@}| @@ -1870,7 +1869,7 @@ describe('TUI', function() -- Close the :intro message and redraw the lines. feed_data('\n') screen:expect([[ - {13:Ꝩ}{12:ꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨ}| + {12:^ꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨ}| {12:ꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨ}|*310 {12:ꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨ℃ }| b | @@ -1912,7 +1911,7 @@ describe('TUI', function() -- Close the :intro message and redraw the lines. feed_data('\n') screen:expect([[ - {1:Ꝩ}ꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨ| + ^ꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨ| ꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨꝨ|*325 {3:-- TERMINAL --} | ]]) @@ -1925,7 +1924,7 @@ describe('TUI', function() feed_data ':set visualbell\n' screen:expect { grid = [[ - {1: } | + ^ | {4:~ }|*3 {5:[No Name] }| :set visualbell | @@ -1939,7 +1938,7 @@ describe('TUI', function() feed_data 'i' screen:expect { grid = [[ - {1: } | + ^ | {4:~ }|*3 {5:[No Name] }| {3:-- INSERT --} | @@ -1954,7 +1953,7 @@ describe('TUI', function() grid = [[ Vim: Caught deadly signal 'SIGTERM' | |*2 - [Process exited 1]{1: } | + [Process exited 1]^ | |*2 {3:-- TERMINAL --} | ]], @@ -1981,7 +1980,7 @@ describe('TUI', function() [5] = { bold = true }, }) screen:expect([[ - {1: } | + ^ | {2:~}{3: }|*3 {4:[No Name] }| | @@ -1989,7 +1988,7 @@ describe('TUI', function() ]]) feed_data('i') screen:expect([[ - {1: } | + ^ | {2:~}{3: }|*3 {4:[No Name] }| {5:-- INSERT --} | @@ -2000,7 +1999,7 @@ describe('TUI', function() it('redraws on SIGWINCH even if terminal size is unchanged #23411', function() child_session:request('nvim_echo', { { 'foo' } }, false, {}) screen:expect([[ - {1: } | + ^ | {4:~ }|*3 {5:[No Name] }| foo | @@ -2008,7 +2007,7 @@ describe('TUI', function() ]]) exec_lua([[vim.uv.kill(vim.fn.jobpid(vim.bo.channel), 'sigwinch')]]) screen:expect([[ - {1: } | + ^ | {4:~ }|*3 {5:[No Name] }| | @@ -2031,7 +2030,7 @@ describe('TUI', function() ]]) feed_data('\003') screen:expect([[ - {1: } | + ^ | {4:~ }|*3 {5:[No Name] }| Type :qa and press <Enter> to exit Nvim | @@ -2046,7 +2045,7 @@ describe('TUI', function() {1:foo} | {4:~ }|*3 {5:[No Name] [+] }| - /foo{1: } | + /foo^ | {3:-- TERMINAL --} | ]]) screen:sleep(10) @@ -2055,7 +2054,7 @@ describe('TUI', function() foo | {4:~ }|*3 {5:[No Name] [+] }| - /foob{1: } | + /foob^ | {3:-- TERMINAL --} | ]]) screen:sleep(10) @@ -2064,7 +2063,7 @@ describe('TUI', function() foo | {4:~ }|*3 {5:[No Name] [+] }| - /fooba{1: } | + /fooba^ | {3:-- TERMINAL --} | ]]) end) @@ -2114,7 +2113,7 @@ describe('TUI', function() [5] = { bold = true, reverse = true }, [6] = { foreground = Screen.colors.White, background = Screen.colors.DarkGreen }, }) - fn.termopen({ + fn.jobstart({ nvim_prog, '--clean', '--cmd', @@ -2124,6 +2123,7 @@ describe('TUI', function() '--cmd', 'let start = reltime() | while v:true | if reltimefloat(reltime(start)) > 2 | break | endif | endwhile', }, { + term = true, env = { VIMRUNTIME = os.getenv('VIMRUNTIME'), }, @@ -2146,7 +2146,7 @@ describe('TUI', function() for _, guicolors in ipairs({ 'notermguicolors', 'termguicolors' }) do it('has no black flicker when clearing regions during startup with ' .. guicolors, function() local screen = Screen.new(50, 10) - fn.termopen({ + fn.jobstart({ nvim_prog, '--clean', '--cmd', @@ -2154,6 +2154,7 @@ describe('TUI', function() '--cmd', 'sleep 10', }, { + term = true, env = { VIMRUNTIME = os.getenv('VIMRUNTIME'), }, @@ -2194,7 +2195,7 @@ describe('TUI', function() local screen = tt.setup_child_nvim({ '--clean', '-l', script_file }) screen:expect { grid = [[ - {1: } | + ^ | ~ |*3 [No Name] 0,0-1 All| | @@ -2207,7 +2208,7 @@ describe('TUI', function() Xargv0nvim | --embed | --clean | - {1:X}argv0nvim | + ^Xargv0nvim | [No Name] [+] 5,1 Bot| 4 more lines | {3:-- TERMINAL --} | @@ -2233,7 +2234,7 @@ describe('TUI', function() :q | abc | | - [Process exited 0]{1: } | + [Process exited 0]^ | | {3:-- TERMINAL --} | ]]) @@ -2254,7 +2255,7 @@ describe('TUI', function() }) screen:expect { grid = [[ - {1: } | + ^ | {4:~ }|*3 {5:[No Name] }| | @@ -2264,7 +2265,7 @@ describe('TUI', function() command([[call chansend(b:terminal_job_id, "\<C-h>")]]) screen:expect([[ - {1: } | + ^ | {4:~ }|*3 {5:[No Name] }| <C-h> | @@ -2287,7 +2288,7 @@ describe('TUI', function() }, { cols = 80 }) screen:expect { grid = [[ - {1:1}st line | + ^1st line | |*2 2nd line | {5:[No Name] [+] 1,1 All}| @@ -2300,7 +2301,7 @@ describe('TUI', function() grid = [[ 1st line | | - {1: } | + ^ | 2nd line | {5:[No Name] [+] 1,161 All}| | @@ -2320,7 +2321,7 @@ describe('TUI', function() }, { extra_rows = 10, cols = 66 }) screen:expect { grid = [[ - | + ^ | aabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabb|*12 aabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabba@@@| [No Name] [+] 1,0-1 Top| @@ -2339,7 +2340,7 @@ describe('TUI', function() -- 500-cell limit, so the buffer is flushed after these spaces. screen:expect { grid = [[ - | + ^ | aabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabb|*12 aabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabbaabba@@@| [No Name] [+] 1,0-1 Top| @@ -2366,7 +2367,7 @@ describe('TUI', function() screen:expect { grid = [[ - aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + ^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| ~ |*3 [No Name] [+] 1,1 All| | @@ -2378,7 +2379,8 @@ describe('TUI', function() feed_data(':set columns=12\n') screen:expect { grid = [[ - aaaaaaaaaaaa |*4 + ^aaaaaaaaaaaa | + aaaaaaaaaaaa |*3 < [+] 1,1 | | -- TERMINAL -- | @@ -2416,7 +2418,7 @@ describe('TUI UIEnter/UILeave', function() }) screen:expect { grid = [[ - {1: } | + ^ | {4:~ }|*3 {5:[No Name] }| | @@ -2426,7 +2428,7 @@ describe('TUI UIEnter/UILeave', function() feed_data(':echo g:evs\n') screen:expect { grid = [[ - {1: } | + ^ | {4:~ }|*3 {5:[No Name] }| ['VimEnter', 'UIEnter'] | @@ -2457,7 +2459,7 @@ describe('TUI FocusGained/FocusLost', function() }) screen:expect([[ - {1: } | + ^ | {4:~ }|*3 {5:[No Name] }| | @@ -2479,7 +2481,7 @@ describe('TUI FocusGained/FocusLost', function() retry(2, 3 * screen.timeout, function() feed_data('\027[I') screen:expect([[ - {1: } | + ^ | {4:~ }|*3 {5:[No Name] }| gained | @@ -2488,7 +2490,7 @@ describe('TUI FocusGained/FocusLost', function() feed_data('\027[O') screen:expect([[ - {1: } | + ^ | {4:~ }|*3 {5:[No Name] }| lost | @@ -2502,7 +2504,7 @@ describe('TUI FocusGained/FocusLost', function() feed_data('i') screen:expect { grid = [[ - {1: } | + ^ | {4:~ }|*3 {5:[No Name] }| :set noshowmode | @@ -2512,7 +2514,7 @@ describe('TUI FocusGained/FocusLost', function() retry(2, 3 * screen.timeout, function() feed_data('\027[I') screen:expect([[ - {1: } | + ^ | {4:~ }|*3 {5:[No Name] }| gained | @@ -2520,7 +2522,7 @@ describe('TUI FocusGained/FocusLost', function() ]]) feed_data('\027[O') screen:expect([[ - {1: } | + ^ | {4:~ }|*3 {5:[No Name] }| lost | @@ -2538,7 +2540,7 @@ describe('TUI FocusGained/FocusLost', function() | {4:~ }|*3 {5:[No Name] }| - :{1: } | + :^ | {3:-- TERMINAL --} | ]]) feed_data('\027[O') @@ -2547,7 +2549,7 @@ describe('TUI FocusGained/FocusLost', function() | {4:~ }|*3 {5:[No Name] }| - :{1: } | + :^ | {3:-- TERMINAL --} | ]], unchanged = true, @@ -2590,7 +2592,7 @@ describe('TUI FocusGained/FocusLost', function() -- Wait for terminal to be ready. screen:expect { grid = [[ - {1:r}eady $ zia | + ^ready $ zia | | [Process exited 0] | |*2 @@ -2602,7 +2604,7 @@ describe('TUI FocusGained/FocusLost', function() feed_data('\027[I') screen:expect { grid = [[ - {1:r}eady $ zia | + ^ready $ zia | | [Process exited 0] | |*2 @@ -2614,7 +2616,7 @@ describe('TUI FocusGained/FocusLost', function() feed_data('\027[O') screen:expect([[ - {1:r}eady $ zia | + ^ready $ zia | | [Process exited 0] | |*2 @@ -2634,7 +2636,7 @@ describe('TUI FocusGained/FocusLost', function() msg3 | msg4 | msg5 | - {10:Press ENTER or type command to continue}{1: } | + {10:Press ENTER or type command to continue}^ | {3:-- TERMINAL --} | ]], } @@ -2647,7 +2649,7 @@ describe('TUI FocusGained/FocusLost', function() msg3 | msg4 | msg5 | - {10:Press ENTER or type command to continue}{1: } | + {10:Press ENTER or type command to continue}^ | {3:-- TERMINAL --} | ]], unchanged = true, @@ -2690,7 +2692,7 @@ describe("TUI 't_Co' (terminal colors)", function() screen:expect(string.format( [[ - {1: } | + ^ | %s|*4 | {3:-- TERMINAL --} | @@ -2701,7 +2703,7 @@ describe("TUI 't_Co' (terminal colors)", function() feed_data(':echo &t_Co\n') screen:expect(string.format( [[ - {1: } | + ^ | %s|*4 %-3s | {3:-- TERMINAL --} | @@ -3028,7 +3030,7 @@ describe('TUI', function() -- Wait for TUI to start. feed_data('Gitext') screen:expect([[ - text{1: } | + text^ | {4:~ }|*4 {3:-- INSERT --} | {3:-- TERMINAL --} | @@ -3045,7 +3047,7 @@ describe('TUI', function() nvim_tui() screen:expect([[ - {1: } | + ^ | {4:~ }|*4 | {3:-- TERMINAL --} | @@ -3055,7 +3057,7 @@ describe('TUI', function() screen:expect { grid = [[ - {1: } | + ^ | {4:~ }|*4 | {3:-- TERMINAL --} | @@ -3181,7 +3183,6 @@ describe('TUI', function() local req = args.data local payload = req:match('^\027P%+q([%x;]+)$') if payload and vim.text.hexdecode(payload) == 'Ms' then - vim.g.xtgettcap = 'Ms' local resp = string.format('\027P1+r%s=%s\027\\', payload, vim.text.hexencode('\027]52;;\027\\')) vim.api.nvim_chan_send(vim.bo[args.buf].channel, resp) return true @@ -3199,9 +3200,6 @@ describe('TUI', function() }, { env = { VIMRUNTIME = os.getenv('VIMRUNTIME'), - - -- Only queries when SSH_TTY is set - SSH_TTY = '/dev/pts/1', }, }) @@ -3209,8 +3207,7 @@ describe('TUI', function() local child_session = n.connect(child_server) retry(nil, 1000, function() - eq('Ms', eval("get(g:, 'xtgettcap', '')")) - eq({ true, 'OSC 52' }, { child_session:request('nvim_eval', 'g:clipboard.name') }) + eq({ true, { osc52 = true } }, { child_session:request('nvim_eval', 'g:termfeatures') }) end) end) end) @@ -3305,13 +3302,39 @@ describe('TUI bg color', function() 'autocmd OptionSet background echo "did OptionSet, yay!"', }) screen:expect([[ - {1: } | + ^ | {3:~} |*3 {5:[No Name] 0,0-1 All}| did OptionSet, yay! | {3:-- TERMINAL --} | ]]) end) + + it('sends theme update notifications when background changes #31652', function() + command('set background=dark') -- set outer Nvim background + local child_server = new_pipename() + local screen = tt.setup_child_nvim({ + '--listen', + child_server, + '-u', + 'NONE', + '-i', + 'NONE', + '--cmd', + 'colorscheme vim', + '--cmd', + 'set noswapfile', + }) + screen:expect({ any = '%[No Name%]' }) + local child_session = n.connect(child_server) + retry(nil, nil, function() + eq({ true, 'dark' }, { child_session:request('nvim_eval', '&background') }) + end) + command('set background=light') -- set outer Nvim background + retry(nil, nil, function() + eq({ true, 'light' }, { child_session:request('nvim_eval', '&background') }) + end) + end) end) -- These tests require `tt` because --headless/--embed @@ -3322,8 +3345,8 @@ describe('TUI as a client', function() end) it('connects to remote instance (with its own TUI)', function() - local server_super = spawn_argv(false) -- equivalent to clear() - local client_super = spawn_argv(true) + local server_super = n.new_session(false) + local client_super = n.new_session(true) set_session(server_super) local server_pipe = new_pipename() @@ -3343,7 +3366,7 @@ describe('TUI as a client', function() feed_data('iHello, World') screen_server:expect { grid = [[ - Hello, World{1: } | + Hello, World^ | {4:~ }|*3 {5:[No Name] [+] }| {3:-- INSERT --} | @@ -3353,7 +3376,7 @@ describe('TUI as a client', function() feed_data('\027') screen_server:expect { grid = [[ - Hello, Worl{1:d} | + Hello, Worl^d | {4:~ }|*3 {5:[No Name] [+] }| | @@ -3370,7 +3393,7 @@ describe('TUI as a client', function() screen_client:expect { grid = [[ - Hello, Worl{1:d} | + Hello, Worl^d | {4:~ }|*3 {5:[No Name] [+] }| | @@ -3383,7 +3406,7 @@ describe('TUI as a client', function() feed_data('0:set lines=3\n') screen_server:expect { grid = [[ - {1:a}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + ^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {5:[No Name] [+] }| |*4 {3:-- TERMINAL --} | @@ -3397,8 +3420,8 @@ describe('TUI as a client', function() end) it('connects to remote instance (--headless)', function() - local server = spawn_argv(false) -- equivalent to clear() - local client_super = spawn_argv(true, { env = { NVIM_LOG_FILE = testlog } }) + local server = n.new_session(false) + local client_super = n.new_session(true, { env = { NVIM_LOG_FILE = testlog } }) set_session(server) local server_pipe = api.nvim_get_vvar('servername') @@ -3414,7 +3437,7 @@ describe('TUI as a client', function() screen_client:expect { grid = [[ - Halloj{1:!} | + Halloj^! | {4:~ }|*4 | {3:-- TERMINAL --} | @@ -3428,7 +3451,7 @@ describe('TUI as a client', function() grid = [[ Vim: Caught deadly signal 'SIGTERM' | |*2 - [Process exited 1]{1: } | + [Process exited 1]^ | |*2 {3:-- TERMINAL --} | ]], @@ -3457,15 +3480,15 @@ describe('TUI as a client', function() screen:expect([[ Remote ui failed to start: {MATCH:.*}| | - [Process exited 1]{1: } | + [Process exited 1]^ | |*3 {3:-- TERMINAL --} | ]]) end) local function test_remote_tui_quit(status) - local server_super = spawn_argv(false) -- equivalent to clear() - local client_super = spawn_argv(true) + local server_super = n.new_session(false) + local client_super = n.new_session(true) set_session(server_super) local server_pipe = new_pipename() @@ -3483,7 +3506,7 @@ describe('TUI as a client', function() }) screen_server:expect { grid = [[ - {1: } | + ^ | {4:~ }|*3 {5:[No Name] }| | @@ -3494,7 +3517,7 @@ describe('TUI as a client', function() feed_data('iHello, World') screen_server:expect { grid = [[ - Hello, World{1: } | + Hello, World^ | {4:~ }|*3 {5:[No Name] [+] }| {3:-- INSERT --} | @@ -3504,7 +3527,7 @@ describe('TUI as a client', function() feed_data('\027') screen_server:expect { grid = [[ - Hello, Worl{1:d} | + Hello, Worl^d | {4:~ }|*3 {5:[No Name] [+] }| | @@ -3521,7 +3544,7 @@ describe('TUI as a client', function() screen_client:expect { grid = [[ - Hello, Worl{1:d} | + Hello, Worl^d | {4:~ }|*3 {5:[No Name] [+] }| | @@ -3536,7 +3559,7 @@ describe('TUI as a client', function() screen_server:expect { grid = [[ | - [Process exited ]] .. status .. [[]{1: }{MATCH:%s+}| + [Process exited ]] .. status .. [[]^ {MATCH:%s+}| |*4 {3:-- TERMINAL --} | ]], @@ -3545,7 +3568,7 @@ describe('TUI as a client', function() screen_client:expect { grid = [[ | - [Process exited ]] .. status .. [[]{1: }{MATCH:%s+}| + [Process exited ]] .. status .. [[]^ {MATCH:%s+}| |*4 {3:-- TERMINAL --} | ]], diff --git a/test/functional/terminal/window_spec.lua b/test/functional/terminal/window_spec.lua index fdb606e959..a65d18de70 100644 --- a/test/functional/terminal/window_spec.lua +++ b/test/functional/terminal/window_spec.lua @@ -62,7 +62,7 @@ describe(':terminal window', function() screen:expect([[ {7:1 }tty ready | {7:2 }rows: 6, cols: 48 | - {7:3 }{1: } | + {7:3 }^ | {7:4 } | {7:5 } | {7:6 } | @@ -73,7 +73,7 @@ describe(':terminal window', function() {7:1 }tty ready | {7:2 }rows: 6, cols: 48 | {7:3 }abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUV| - {7:4 }WXYZ{1: } | + {7:4 }WXYZ^ | {7:5 } | {7:6 } | {3:-- TERMINAL --} | @@ -87,7 +87,7 @@ describe(':terminal window', function() {7: 2 }rows: 6, cols: 48 | {7: 3 }abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNO| {7: 4 }PQRSTUVWXYZrows: 6, cols: 41 | - {7: 5 }{1: } | + {7: 5 }^ | {7: 6 } | {3:-- TERMINAL --} | ]]) @@ -98,7 +98,7 @@ describe(':terminal window', function() {7: 3 }abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNO| {7: 4 }PQRSTUVWXYZrows: 6, cols: 41 | {7: 5 } abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMN| - {7: 6 }OPQRSTUVWXYZ{1: } | + {7: 6 }OPQRSTUVWXYZ^ | {3:-- TERMINAL --} | ]]) end) @@ -110,7 +110,7 @@ describe(':terminal window', function() screen:expect([[ {7:++1 }tty ready | {7:++2 }rows: 6, cols: 45 | - {7:++3 }{1: } | + {7:++3 }^ | {7:++4 } | {7:++5 } | {7:++6 } | @@ -123,7 +123,7 @@ describe(':terminal window', function() {7:++6 } | {7:++7 } | {7:++8 }abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRS| - {7:++9 }TUVWXYZ{1: } | + {7:++9 }TUVWXYZ^ | {3:-- TERMINAL --} | ]]) feed_data('\nabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ') @@ -133,7 +133,7 @@ describe(':terminal window', function() {7:++ 9 }STUVWXYZ | {7:++10 }abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQR| {7:++11 }STUVWXYZrows: 6, cols: 44 | - {7:++12 }{1: } | + {7:++12 }^ | {3:-- TERMINAL --} | ]]) end) @@ -144,7 +144,7 @@ describe(':terminal window', function() feed([[<C-\><C-N>]]) screen:expect([[ tty ready | - {2:^ } | + ^ | |*5 ]]) feed(':set colorcolumn=20<CR>i') @@ -153,7 +153,7 @@ describe(':terminal window', function() it('wont show the color column', function() screen:expect([[ tty ready | - {1: } | + ^ | |*4 {3:-- TERMINAL --} | ]]) @@ -170,7 +170,7 @@ describe(':terminal window', function() line2 | line3 | line4 | - {1: } | + ^ | {3:-- TERMINAL --} | ]]) end) @@ -184,7 +184,7 @@ describe(':terminal window', function() line2 | line3 | line4 | - {2: } | + | | ]]) end) @@ -206,7 +206,7 @@ describe(':terminal with multigrid', function() [3:--------------------------------------------------]| ## grid 2 tty ready | - {1: } | + ^ | |*4 ## grid 3 {3:-- TERMINAL --} | @@ -223,7 +223,7 @@ describe(':terminal with multigrid', function() ## grid 2 tty ready | rows: 10, cols: 20 | - {1: } | + ^ | |*7 ## grid 3 {3:-- TERMINAL --} | @@ -241,7 +241,7 @@ describe(':terminal with multigrid', function() ## grid 2 rows: 10, cols: 20 | rows: 3, cols: 70 | - {1: } | + ^ | ## grid 3 {3:-- TERMINAL --} | ]]) @@ -260,7 +260,7 @@ describe(':terminal with multigrid', function() rows: 10, cols: 20 | rows: 3, cols: 70 | rows: 6, cols: 50 | - {1: } | + ^ | | ## grid 3 {3:-- TERMINAL --} | diff --git a/test/functional/terminal/window_split_tab_spec.lua b/test/functional/terminal/window_split_tab_spec.lua index 272fc513af..dc22c87ca0 100644 --- a/test/functional/terminal/window_split_tab_spec.lua +++ b/test/functional/terminal/window_split_tab_spec.lua @@ -49,7 +49,7 @@ describe(':terminal', function() ========== | tty ready | rows: 5, cols: 50 | - {2: } | + | |*2 ========== | :2split | @@ -61,7 +61,7 @@ describe(':terminal', function() ========== | ^tty ready | rows: 5, cols: 50 | - {2: } | + | |*2 ========== | :wincmd p | @@ -77,7 +77,7 @@ describe(':terminal', function() command('bprevious') screen:expect([[ tty ready | - ^foo{2: } | + ^foo | |*8 ]]) end) @@ -102,7 +102,7 @@ describe(':terminal', function() screen:expect([[ tty ready | rows: 7, cols: 47 | - {2: } | + | |*3 ^ | | @@ -112,7 +112,7 @@ describe(':terminal', function() tty ready | rows: 7, cols: 47 | rows: 4, cols: 41 | - {2:^ } | + ^ | | ]]) end) diff --git a/test/functional/testnvim.lua b/test/functional/testnvim.lua index 60b2f872fc..59cb593cf7 100644 --- a/test/functional/testnvim.lua +++ b/test/functional/testnvim.lua @@ -4,7 +4,7 @@ local t = require('test.testutil') local Session = require('test.client.session') local uv_stream = require('test.client.uv_stream') local SocketStream = uv_stream.SocketStream -local ChildProcessStream = uv_stream.ChildProcessStream +local ProcStream = uv_stream.ProcStream local check_cores = t.check_cores local check_logs = t.check_logs @@ -48,6 +48,16 @@ M.nvim_argv = { 'unlet g:colors_name', '--embed', } +if os.getenv('OSV_PORT') then + table.insert(M.nvim_argv, '--cmd') + table.insert( + M.nvim_argv, + string.format( + "lua require('osv').launch({ port = %s, blocking = true })", + os.getenv('OSV_PORT') + ) + ) +end -- Directory containing nvim. M.nvim_dir = M.nvim_prog:gsub('[/\\][^/\\]+$', '') @@ -308,24 +318,14 @@ function M.stop() assert(session):stop() end -function M.nvim_prog_abs() - -- system(['build/bin/nvim']) does not work for whatever reason. It must - -- be executable searched in $PATH or something starting with / or ./. - if M.nvim_prog:match('[/\\]') then - return M.request('nvim_call_function', 'fnamemodify', { M.nvim_prog, ':p' }) - else - return M.nvim_prog - end -end - -- Use for commands which expect nvim to quit. -- The first argument can also be a timeout. function M.expect_exit(fn_or_timeout, ...) local eof_err_msg = 'EOF was received from Nvim. Likely the Nvim process crashed.' if type(fn_or_timeout) == 'function' then - eq(eof_err_msg, t.pcall_err(fn_or_timeout, ...)) + t.matches(eof_err_msg, t.pcall_err(fn_or_timeout, ...)) else - eq( + t.matches( eof_err_msg, t.pcall_err(function(timeout, fn, ...) fn(...) @@ -455,22 +455,6 @@ function M.check_close() session = nil end ---- @param argv string[] ---- @param merge boolean? ---- @param env string[]? ---- @param keep boolean? ---- @param io_extra uv.uv_pipe_t? used for stdin_fd, see :help ui-option ---- @return test.Session -function M.spawn(argv, merge, env, keep, io_extra) - if not keep then - M.check_close() - end - - local child_stream = - ChildProcessStream.spawn(merge and M.merge_args(prepend_argv, argv) or argv, env, io_extra) - return Session.new(child_stream) -end - -- Creates a new Session connected by domain socket (named pipe) or TCP. function M.connect(file_or_address) local addr, port = string.match(file_or_address, '(.*):(%d+)') @@ -479,61 +463,112 @@ function M.connect(file_or_address) return Session.new(stream) end --- Starts (and returns) a new global Nvim session. --- --- Parameters are interpreted as startup args, OR a map with these keys: --- args: List: Args appended to the default `nvim_argv` set. --- args_rm: List: Args removed from the default set. All cases are --- removed, e.g. args_rm={'--cmd'} removes all cases of "--cmd" --- (and its value) from the default set. --- env: Map: Defines the environment of the new session. --- --- Example: --- clear('-e') --- clear{args={'-e'}, args_rm={'-i'}, env={TERM=term}} +--- Starts a new, global Nvim session and clears the current one. +--- +--- Note: Use `new_session()` to start a session without replacing the current one. +--- +--- Parameters are interpreted as startup args, OR a map with these keys: +--- - args: List: Args appended to the default `nvim_argv` set. +--- - args_rm: List: Args removed from the default set. All cases are +--- removed, e.g. args_rm={'--cmd'} removes all cases of "--cmd" +--- (and its value) from the default set. +--- - env: Map: Defines the environment of the new session. +--- +--- Example: +--- ``` +--- clear('-e') +--- clear{args={'-e'}, args_rm={'-i'}, env={TERM=term}} +--- ``` +--- +--- @param ... string Nvim CLI args +--- @return test.Session +--- @overload fun(opts: test.session.Opts): test.Session function M.clear(...) - M.set_session(M.spawn_argv(false, ...)) + M.set_session(M.new_session(false, ...)) return M.get_session() end ---- same params as clear, but does returns the session instead ---- of replacing the default session +--- Starts a new Nvim process with the given args and returns a msgpack-RPC session. +--- +--- Does not replace the current global session, unlike `clear()`. +--- +--- @param keep boolean (default: false) Don't close the current global session. +--- @param ... string Nvim CLI args (or see overload) --- @return test.Session -function M.spawn_argv(keep, ...) - local argv, env, io_extra = M.new_argv(...) - return M.spawn(argv, nil, env, keep, io_extra) +--- @overload fun(keep: boolean, opts: test.session.Opts): test.Session +function M.new_session(keep, ...) + if not keep then + M.check_close() + end + + local argv, env, io_extra = M._new_argv(...) + + local proc = ProcStream.spawn(argv, env, io_extra) + return Session.new(proc) end ---- @class test.new_argv.Opts +--- Starts a (non-RPC, `--headless --listen "Tx"`) Nvim process, waits for exit, and returns result. +--- +--- @param ... string Nvim CLI args, or `test.session.Opts` table. +--- @return test.ProcStream +--- @overload fun(opts: test.session.Opts): test.ProcStream +function M.spawn_wait(...) + local opts = type(...) == 'string' and { args = { ... } } or ... + opts.args_rm = opts.args_rm and opts.args_rm or {} + table.insert(opts.args_rm, '--embed') + local argv, env, io_extra = M._new_argv(opts) + local proc = ProcStream.spawn(argv, env, io_extra) + proc.collect_text = true + proc:read_start() + proc:wait() + proc:close() + return proc +end + +--- @class test.session.Opts +--- Nvim CLI args --- @field args? string[] +--- Remove these args from the default `nvim_argv` args set. Ignored if `merge=false`. --- @field args_rm? string[] +--- (default: true) Merge `args` with the default set. Else use only the provided `args`. +--- @field merge? boolean +--- Environment variables --- @field env? table<string,string> +--- Used for stdin_fd, see `:help ui-option` --- @field io_extra? uv.uv_pipe_t ---- Builds an argument list for use in clear(). +--- @private --- ---- @see clear() for parameters. ---- @param ... string +--- Builds an argument list for use in `new_session()`, `clear()`, and `spawn_wait()`. +--- +--- @param ... string Nvim CLI args, or `test.session.Opts` table. --- @return string[] --- @return string[]? --- @return uv.uv_pipe_t? -function M.new_argv(...) - local args = { unpack(M.nvim_argv) } - table.insert(args, '--headless') - if _G._nvim_test_id then - -- Set the server name to the test-id for logging. #8519 - table.insert(args, '--listen') - table.insert(args, _G._nvim_test_id) +--- @overload fun(opts: test.session.Opts): string[], string[]?, uv.uv_pipe_t? +function M._new_argv(...) + --- @type test.session.Opts|string + local opts = select(1, ...) + local merge = type(opts) ~= 'table' and true or opts.merge ~= false + + local args = merge and { unpack(M.nvim_argv) } or { M.nvim_prog } + if merge then + table.insert(args, '--headless') + if _G._nvim_test_id then + -- Set the server name to the test-id for logging. #8519 + table.insert(args, '--listen') + table.insert(args, _G._nvim_test_id) + end end + local new_args --- @type string[] local io_extra --- @type uv.uv_pipe_t? - local env --- @type string[]? - --- @type test.new_argv.Opts|string - local opts = select(1, ...) + local env --- @type string[]? List of "key=value" env vars. + if type(opts) ~= 'table' then new_args = { ... } else - args = remove_args(args, opts.args_rm) + args = merge and remove_args(args, opts.args_rm) or args if opts.env then local env_opt = {} --- @type table<string,string> for k, v in pairs(opts.env) do @@ -800,81 +835,6 @@ function M.exec_capture(code) return M.api.nvim_exec2(code, { output = true }).output end ---- @param f function ---- @return table<string,any> -local function get_upvalues(f) - local i = 1 - local upvalues = {} --- @type table<string,any> - while true do - local n, v = debug.getupvalue(f, i) - if not n then - break - end - upvalues[n] = v - i = i + 1 - end - return upvalues -end - ---- @param f function ---- @param upvalues table<string,any> -local function set_upvalues(f, upvalues) - local i = 1 - while true do - local n = debug.getupvalue(f, i) - if not n then - break - end - if upvalues[n] then - debug.setupvalue(f, i, upvalues[n]) - end - i = i + 1 - end -end - ---- @type fun(f: function): table<string,any> -_G.__get_upvalues = nil - ---- @type fun(f: function, upvalues: table<string,any>) -_G.__set_upvalues = nil - ---- @param self table<string,function> ---- @param bytecode string ---- @param upvalues table<string,any> ---- @param ... any[] ---- @return any[] result ---- @return table<string,any> upvalues -local function exec_lua_handler(self, bytecode, upvalues, ...) - local f = assert(loadstring(bytecode)) - self.set_upvalues(f, upvalues) - local ret = { f(...) } --- @type any[] - --- @type table<string,any> - local new_upvalues = self.get_upvalues(f) - - do -- Check return value types for better error messages - local invalid_types = { - ['thread'] = true, - ['function'] = true, - ['userdata'] = true, - } - - for k, v in pairs(ret) do - if invalid_types[type(v)] then - error( - string.format( - "Return index %d with value '%s' of type '%s' cannot be serialized over RPC", - k, - tostring(v), - type(v) - ) - ) - end - end - end - - return ret, new_upvalues -end - --- Execute Lua code in the wrapped Nvim session. --- --- When `code` is passed as a function, it is converted into Lua byte code. @@ -921,52 +881,7 @@ function M.exec_lua(code, ...) end assert(session, 'no Nvim session') - - if not session.exec_lua_setup then - assert( - session:request( - 'nvim_exec_lua', - [[ - _G.__test_exec_lua = { - get_upvalues = loadstring((select(1,...))), - set_upvalues = loadstring((select(2,...))), - handler = loadstring((select(3,...))) - } - setmetatable(_G.__test_exec_lua, { __index = _G.__test_exec_lua }) - ]], - { string.dump(get_upvalues), string.dump(set_upvalues), string.dump(exec_lua_handler) } - ) - ) - session.exec_lua_setup = true - end - - local stat, rv = session:request( - 'nvim_exec_lua', - 'return { _G.__test_exec_lua:handler(...) }', - { string.dump(code), get_upvalues(code), ... } - ) - - if not stat then - error(rv[2]) - end - - --- @type any[], table<string,any> - local ret, upvalues = unpack(rv) - - -- Update upvalues - if next(upvalues) then - local caller = debug.getinfo(2) - local f = caller.func - -- On PUC-Lua, if the function is a tail call, then func will be nil. - -- In this case we need to use the current function. - if not f then - assert(caller.source == '=(tail call)') - f = debug.getinfo(1).func - end - set_upvalues(f, upvalues) - end - - return unpack(ret, 1, table.maxn(ret)) + return require('test.functional.testnvim.exec_lua')(session, 2, code, ...) end function M.get_pathsep() diff --git a/test/functional/testnvim/exec_lua.lua b/test/functional/testnvim/exec_lua.lua new file mode 100644 index 0000000000..ddd9905ce7 --- /dev/null +++ b/test/functional/testnvim/exec_lua.lua @@ -0,0 +1,148 @@ +--- @param f function +--- @return table<string,any> +local function get_upvalues(f) + local i = 1 + local upvalues = {} --- @type table<string,any> + while true do + local n, v = debug.getupvalue(f, i) + if not n then + break + end + upvalues[n] = v + i = i + 1 + end + return upvalues +end + +--- @param f function +--- @param upvalues table<string,any> +local function set_upvalues(f, upvalues) + local i = 1 + while true do + local n = debug.getupvalue(f, i) + if not n then + break + end + if upvalues[n] then + debug.setupvalue(f, i, upvalues[n]) + end + i = i + 1 + end +end + +--- @param messages string[] +--- @param ... ... +local function add_print(messages, ...) + local msg = {} --- @type string[] + for i = 1, select('#', ...) do + msg[#msg + 1] = tostring(select(i, ...)) + end + table.insert(messages, table.concat(msg, '\t')) +end + +local invalid_types = { + ['thread'] = true, + ['function'] = true, + ['userdata'] = true, +} + +--- @param r any[] +local function check_returns(r) + for k, v in pairs(r) do + if invalid_types[type(v)] then + error( + string.format( + "Return index %d with value '%s' of type '%s' cannot be serialized over RPC", + k, + tostring(v), + type(v) + ), + 2 + ) + end + end +end + +local M = {} + +--- This is run in the context of the remote Nvim instance. +--- @param bytecode string +--- @param upvalues table<string,any> +--- @param ... any[] +--- @return any[] result +--- @return table<string,any> upvalues +--- @return string[] messages +function M.handler(bytecode, upvalues, ...) + local messages = {} --- @type string[] + local orig_print = _G.print + + function _G.print(...) + add_print(messages, ...) + return orig_print(...) + end + + local f = assert(loadstring(bytecode)) + + set_upvalues(f, upvalues) + + -- Run in pcall so we can return any print messages + local ret = { pcall(f, ...) } --- @type any[] + + _G.print = orig_print + + local new_upvalues = get_upvalues(f) + + -- Check return value types for better error messages + check_returns(ret) + + return ret, new_upvalues, messages +end + +--- @param session test.Session +--- @param lvl integer +--- @param code function +--- @param ... ... +local function run(session, lvl, code, ...) + local stat, rv = session:request( + 'nvim_exec_lua', + [[return { require('test.functional.testnvim.exec_lua').handler(...) }]], + { string.dump(code), get_upvalues(code), ... } + ) + + if not stat then + error(rv[2], 2) + end + + --- @type any[], table<string,any>, string[] + local ret, upvalues, messages = unpack(rv) + + for _, m in ipairs(messages) do + print(m) + end + + if not ret[1] then + error(ret[2], 2) + end + + -- Update upvalues + if next(upvalues) then + local caller = debug.getinfo(lvl) + local i = 0 + + -- On PUC-Lua, if the function is a tail call, then func will be nil. + -- In this case we need to use the caller. + while not caller.func do + i = i + 1 + caller = debug.getinfo(lvl + i) + end + set_upvalues(caller.func, upvalues) + end + + return unpack(ret, 2, table.maxn(ret)) +end + +return setmetatable(M, { + __call = function(_, ...) + return run(...) + end, +}) diff --git a/test/functional/testterm.lua b/test/functional/testterm.lua index 3aadcc59a7..17209d947e 100644 --- a/test/functional/testterm.lua +++ b/test/functional/testterm.lua @@ -29,6 +29,10 @@ function M.feed_termcode(data) M.feed_data('\027' .. data) end +function M.feed_csi(data) + M.feed_termcode('[' .. data) +end + function M.make_lua_executor(session) return function(code, ...) local status, rv = session:request('nvim_exec_lua', code, { ... }) @@ -78,6 +82,9 @@ end function M.set_undercurl() M.feed_termcode('[4:3m') end +function M.set_reverse() + M.feed_termcode('[7m') +end function M.set_strikethrough() M.feed_termcode('[9m') end @@ -108,7 +115,6 @@ function M.setup_screen(extra_rows, cmd, cols, env, screen_opts) cols = cols and cols or 50 api.nvim_command('highlight TermCursor cterm=reverse') - api.nvim_command('highlight TermCursorNC ctermbg=11') api.nvim_command('highlight StatusLineTerm ctermbg=2 ctermfg=0') api.nvim_command('highlight StatusLineTermNC ctermbg=2 ctermfg=8') @@ -135,7 +141,7 @@ function M.setup_screen(extra_rows, cmd, cols, env, screen_opts) }) api.nvim_command('enew') - api.nvim_call_function('termopen', { cmd, env and { env = env } or nil }) + api.nvim_call_function('jobstart', { cmd, { term = true, env = (env and env or nil) } }) api.nvim_input('<CR>') local vim_errmsg = api.nvim_eval('v:errmsg') if vim_errmsg and '' ~= vim_errmsg then @@ -154,7 +160,7 @@ function M.setup_screen(extra_rows, cmd, cols, env, screen_opts) local empty_line = (' '):rep(cols) local expected = { 'tty ready' .. (' '):rep(cols - 9), - '{1: }' .. (' '):rep(cols - 1), + '^' .. (' '):rep(cols), empty_line, empty_line, empty_line, diff --git a/test/functional/treesitter/fold_spec.lua b/test/functional/treesitter/fold_spec.lua index e38e58ff92..ac58df4bba 100644 --- a/test/functional/treesitter/fold_spec.lua +++ b/test/functional/treesitter/fold_spec.lua @@ -5,6 +5,7 @@ local Screen = require('test.functional.ui.screen') local clear = n.clear local eq = t.eq local insert = n.insert +local write_file = t.write_file local exec_lua = n.exec_lua local command = n.command local feed = n.feed @@ -767,4 +768,79 @@ t2]]) ]], } end) + + it("doesn't call get_parser too often when parser is not available", function() + -- spy on vim.treesitter.get_parser() to keep track of how many times it is called + exec_lua(function() + _G.count = 0 + vim.treesitter.get_parser = (function(wrapped) + return function(...) + _G.count = _G.count + 1 + return wrapped(...) + end + end)(vim.treesitter.get_parser) + end) + + insert(test_text) + command [[ + set filetype=some_filetype_without_treesitter_parser + set foldmethod=expr foldexpr=v:lua.vim.treesitter.foldexpr() foldcolumn=1 foldlevel=0 + ]] + + -- foldexpr will return '0' for all lines + local levels = get_fold_levels() ---@type integer[] + eq(19, #levels) + for lnum, level in ipairs(levels) do + eq('0', level, string.format("foldlevel[%d] == %s; expected '0'", lnum, level)) + end + + eq( + 1, + exec_lua [[ return _G.count ]], + 'count should not be as high as the # of lines; actually only once for the buffer.' + ) + end) + + it('can detect a new parser and refresh folds accordingly', function() + local name = t.tmpname() + write_file(name, test_text) + command('edit ' .. name) + command [[ + set filetype=some_filetype_without_treesitter_parser + set foldmethod=expr foldexpr=v:lua.vim.treesitter.foldexpr() foldcolumn=1 foldlevel=0 + ]] + + -- foldexpr will return '0' for all lines + local levels = get_fold_levels() ---@type integer[] + eq(19, #levels) + for lnum, level in ipairs(levels) do + eq('0', level, string.format("foldlevel[%d] == %s; expected '0'", lnum, level)) + end + + -- reload buffer as c filetype to simulate new parser being found + feed('GA// vim: ft=c<Esc>') + command([[write | edit]]) + + eq({ + [1] = '>1', + [2] = '1', + [3] = '1', + [4] = '1', + [5] = '>2', + [6] = '2', + [7] = '2', + [8] = '1', + [9] = '1', + [10] = '>2', + [11] = '2', + [12] = '2', + [13] = '2', + [14] = '2', + [15] = '>3', + [16] = '3', + [17] = '3', + [18] = '2', + [19] = '1', + }, get_fold_levels()) + end) end) diff --git a/test/functional/treesitter/highlight_spec.lua b/test/functional/treesitter/highlight_spec.lua index 5c6be869c6..7f0a3cb342 100644 --- a/test/functional/treesitter/highlight_spec.lua +++ b/test/functional/treesitter/highlight_spec.lua @@ -12,6 +12,7 @@ local fn = n.fn local eq = t.eq local hl_query_c = [[ + ; query (ERROR) @error "if" @keyword @@ -65,40 +66,40 @@ static int nlua_schedule(lua_State *const lstate) }]] local hl_grid_legacy_c = [[ - {2:^/// Schedule Lua callback on main loop's event queue} | - {3:static} {3:int} nlua_schedule(lua_State *{3:const} lstate) | + {18:^/// Schedule Lua callback on main loop's event queue} | + {6:static} {6:int} nlua_schedule(lua_State *{6:const} lstate) | { | - {4:if} (lua_type(lstate, {5:1}) != LUA_TFUNCTION | + {15:if} (lua_type(lstate, {26:1}) != LUA_TFUNCTION | || lstate != lstate) { | - lua_pushliteral(lstate, {5:"vim.schedule: expected function"}); | - {4:return} lua_error(lstate); | + lua_pushliteral(lstate, {26:"vim.schedule: expected function"}); | + {15:return} lua_error(lstate); | } | | - LuaRef cb = nlua_ref(lstate, {5:1}); | + LuaRef cb = nlua_ref(lstate, {26:1}); | | multiqueue_put(main_loop.events, nlua_schedule_event, | - {5:1}, ({3:void} *)({3:ptrdiff_t})cb); | - {4:return} {5:0}; | + {26:1}, ({6:void} *)({6:ptrdiff_t})cb); | + {15:return} {26:0}; | } | {1:~ }|*2 | ]] local hl_grid_ts_c = [[ - {2:^/// Schedule Lua callback on main loop's event queue} | - {3:static} {3:int} {11:nlua_schedule}({3:lua_State} *{3:const} lstate) | + {18:^/// Schedule Lua callback on main loop's event queue} | + {6:static} {6:int} {25:nlua_schedule}({6:lua_State} *{6:const} lstate) | { | - {4:if} ({11:lua_type}(lstate, {5:1}) != {5:LUA_TFUNCTION} | - || {6:lstate} != {6:lstate}) { | - {11:lua_pushliteral}(lstate, {5:"vim.schedule: expected function"}); | - {4:return} {11:lua_error}(lstate); | + {15:if} ({25:lua_type}(lstate, {26:1}) != {26:LUA_TFUNCTION} | + || {19:lstate} != {19:lstate}) { | + {25:lua_pushliteral}(lstate, {26:"vim.schedule: expected function"}); | + {15:return} {25:lua_error}(lstate); | } | | - {7:LuaRef} cb = {11:nlua_ref}(lstate, {5:1}); | + {29:LuaRef} cb = {25:nlua_ref}(lstate, {26:1}); | | - multiqueue_put(main_loop.events, {11:nlua_schedule_event}, | - {5:1}, ({3:void} *)({3:ptrdiff_t})cb); | - {4:return} {5:0}; | + multiqueue_put(main_loop.events, {25:nlua_schedule_event}, | + {26:1}, ({6:void} *)({6:ptrdiff_t})cb); | + {15:return} {26:0}; | } | {1:~ }|*2 | @@ -145,10 +146,10 @@ local injection_grid_c = [[ ]] local injection_grid_expected_c = [[ - {3:int} x = {5:INT_MAX}; | - #define {5:READ_STRING}(x, y) ({3:char} *)read_string((x), ({3:size_t})(y)) | - #define foo {3:void} main() { \ | - {4:return} {5:42}; \ | + {6:int} x = {26:INT_MAX}; | + #define {26:READ_STRING}(x, y) ({6:char} *)read_string((x), ({6:size_t})(y)) | + #define foo {6:void} main() { \ | + {15:return} {26:42}; \ | } | ^ | {1:~ }|*11 @@ -161,20 +162,6 @@ describe('treesitter highlighting (C)', function() before_each(function() clear() screen = Screen.new(65, 18) - screen:set_default_attr_ids { - [1] = { bold = true, foreground = Screen.colors.Blue1 }, - [2] = { foreground = Screen.colors.Blue1 }, - [3] = { bold = true, foreground = Screen.colors.SeaGreen4 }, - [4] = { bold = true, foreground = Screen.colors.Brown }, - [5] = { foreground = Screen.colors.Magenta }, - [6] = { foreground = Screen.colors.Red }, - [7] = { bold = true, foreground = Screen.colors.SlateBlue }, - [8] = { foreground = Screen.colors.Grey100, background = Screen.colors.Red }, - [9] = { foreground = Screen.colors.Magenta, background = Screen.colors.Red }, - [10] = { foreground = Screen.colors.Red, background = Screen.colors.Red }, - [11] = { foreground = Screen.colors.Cyan4 }, - } - command [[ hi link @error ErrorMsg ]] command [[ hi link @warning WarningMsg ]] end) @@ -246,124 +233,124 @@ describe('treesitter highlighting (C)', function() feed('5Goc<esc>dd') - screen:expect { + screen:expect({ grid = [[ - {2:/// Schedule Lua callback on main loop's event queue} | - {3:static} {3:int} {11:nlua_schedule}({3:lua_State} *{3:const} lstate) | - { | - {4:if} ({11:lua_type}(lstate, {5:1}) != {5:LUA_TFUNCTION} | - || {6:lstate} != {6:lstate}) { | - {11:^lua_pushliteral}(lstate, {5:"vim.schedule: expected function"}); | - {4:return} {11:lua_error}(lstate); | - } | - | - {7:LuaRef} cb = {11:nlua_ref}(lstate, {5:1}); | - | - multiqueue_put(main_loop.events, {11:nlua_schedule_event}, | - {5:1}, ({3:void} *)({3:ptrdiff_t})cb); | - {4:return} {5:0}; | - } | - {1:~ }|*2 - | - ]], - } + {18:/// Schedule Lua callback on main loop's event queue} | + {6:static} {6:int} {25:nlua_schedule}({6:lua_State} *{6:const} lstate) | + { | + {15:if} ({25:lua_type}(lstate, {26:1}) != {26:LUA_TFUNCTION} | + || {19:lstate} != {19:lstate}) { | + {25:^lua_pushliteral}(lstate, {26:"vim.schedule: expected function"}); | + {15:return} {25:lua_error}(lstate); | + } | + | + {29:LuaRef} cb = {25:nlua_ref}(lstate, {26:1}); | + | + multiqueue_put(main_loop.events, {25:nlua_schedule_event}, | + {26:1}, ({6:void} *)({6:ptrdiff_t})cb); | + {15:return} {26:0}; | + } | + {1:~ }|*2 + | + ]], + }) feed('7Go*/<esc>') - screen:expect { + screen:expect({ grid = [[ - {2:/// Schedule Lua callback on main loop's event queue} | - {3:static} {3:int} {11:nlua_schedule}({3:lua_State} *{3:const} lstate) | - { | - {4:if} ({11:lua_type}(lstate, {5:1}) != {5:LUA_TFUNCTION} | - || {6:lstate} != {6:lstate}) { | - {11:lua_pushliteral}(lstate, {5:"vim.schedule: expected function"}); | - {4:return} {11:lua_error}(lstate); | - {8:*^/} | - } | - | - {7:LuaRef} cb = {11:nlua_ref}(lstate, {5:1}); | - | - multiqueue_put(main_loop.events, {11:nlua_schedule_event}, | - {5:1}, ({3:void} *)({3:ptrdiff_t})cb); | - {4:return} {5:0}; | - } | - {1:~ }| - | - ]], - } + {18:/// Schedule Lua callback on main loop's event queue} | + {6:static} {6:int} {25:nlua_schedule}({6:lua_State} *{6:const} lstate) | + { | + {15:if} ({25:lua_type}(lstate, {26:1}) != {26:LUA_TFUNCTION} | + || {19:lstate} != {19:lstate}) { | + {25:lua_pushliteral}(lstate, {26:"vim.schedule: expected function"}); | + {15:return} {25:lua_error}(lstate); | + {9:*^/} | + } | + | + {29:LuaRef} cb = {25:nlua_ref}(lstate, {26:1}); | + | + multiqueue_put(main_loop.events, {25:nlua_schedule_event}, | + {26:1}, ({6:void} *)({6:ptrdiff_t})cb); | + {15:return} {26:0}; | + } | + {1:~ }| + | + ]], + }) feed('3Go/*<esc>') - screen:expect { + screen:expect({ grid = [[ - {2:/// Schedule Lua callback on main loop's event queue} | - {3:static} {3:int} {11:nlua_schedule}({3:lua_State} *{3:const} lstate) | - { | - {2:/^*} | - {2: if (lua_type(lstate, 1) != LUA_TFUNCTION} | - {2: || lstate != lstate) {} | - {2: lua_pushliteral(lstate, "vim.schedule: expected function");} | - {2: return lua_error(lstate);} | - {2:*/} | - } | - | - {7:LuaRef} cb = {11:nlua_ref}(lstate, {5:1}); | - | - multiqueue_put(main_loop.events, {11:nlua_schedule_event}, | - {5:1}, ({3:void} *)({3:ptrdiff_t})cb); | - {4:return} {5:0}; | - {8:}} | - | - ]], - } + {18:/// Schedule Lua callback on main loop's event queue} | + {6:static} {6:int} {25:nlua_schedule}({6:lua_State} *{6:const} lstate) | + { | + {18:/^*} | + {18: if (lua_type(lstate, 1) != LUA_TFUNCTION} | + {18: || lstate != lstate) {} | + {18: lua_pushliteral(lstate, "vim.schedule: expected function");} | + {18: return lua_error(lstate);} | + {18:*/} | + } | + | + {29:LuaRef} cb = {25:nlua_ref}(lstate, {26:1}); | + | + multiqueue_put(main_loop.events, {25:nlua_schedule_event}, | + {26:1}, ({6:void} *)({6:ptrdiff_t})cb); | + {15:return} {26:0}; | + {9:}} | + | + ]], + }) feed('gg$') feed('~') - screen:expect { + screen:expect({ grid = [[ - {2:/// Schedule Lua callback on main loop's event queu^E} | - {3:static} {3:int} {11:nlua_schedule}({3:lua_State} *{3:const} lstate) | - { | - {2:/*} | - {2: if (lua_type(lstate, 1) != LUA_TFUNCTION} | - {2: || lstate != lstate) {} | - {2: lua_pushliteral(lstate, "vim.schedule: expected function");} | - {2: return lua_error(lstate);} | - {2:*/} | - } | - | - {7:LuaRef} cb = {11:nlua_ref}(lstate, {5:1}); | - | - multiqueue_put(main_loop.events, {11:nlua_schedule_event}, | - {5:1}, ({3:void} *)({3:ptrdiff_t})cb); | - {4:return} {5:0}; | - {8:}} | - | - ]], - } + {18:/// Schedule Lua callback on main loop's event queu^E} | + {6:static} {6:int} {25:nlua_schedule}({6:lua_State} *{6:const} lstate) | + { | + {18:/*} | + {18: if (lua_type(lstate, 1) != LUA_TFUNCTION} | + {18: || lstate != lstate) {} | + {18: lua_pushliteral(lstate, "vim.schedule: expected function");} | + {18: return lua_error(lstate);} | + {18:*/} | + } | + | + {29:LuaRef} cb = {25:nlua_ref}(lstate, {26:1}); | + | + multiqueue_put(main_loop.events, {25:nlua_schedule_event}, | + {26:1}, ({6:void} *)({6:ptrdiff_t})cb); | + {15:return} {26:0}; | + {9:}} | + | + ]], + }) feed('re') - screen:expect { + screen:expect({ grid = [[ - {2:/// Schedule Lua callback on main loop's event queu^e} | - {3:static} {3:int} {11:nlua_schedule}({3:lua_State} *{3:const} lstate) | - { | - {2:/*} | - {2: if (lua_type(lstate, 1) != LUA_TFUNCTION} | - {2: || lstate != lstate) {} | - {2: lua_pushliteral(lstate, "vim.schedule: expected function");} | - {2: return lua_error(lstate);} | - {2:*/} | - } | - | - {7:LuaRef} cb = {11:nlua_ref}(lstate, {5:1}); | - | - multiqueue_put(main_loop.events, {11:nlua_schedule_event}, | - {5:1}, ({3:void} *)({3:ptrdiff_t})cb); | - {4:return} {5:0}; | - {8:}} | - | - ]], - } + {18:/// Schedule Lua callback on main loop's event queu^e} | + {6:static} {6:int} {25:nlua_schedule}({6:lua_State} *{6:const} lstate) | + { | + {18:/*} | + {18: if (lua_type(lstate, 1) != LUA_TFUNCTION} | + {18: || lstate != lstate) {} | + {18: lua_pushliteral(lstate, "vim.schedule: expected function");} | + {18: return lua_error(lstate);} | + {18:*/} | + } | + | + {29:LuaRef} cb = {25:nlua_ref}(lstate, {26:1}); | + | + multiqueue_put(main_loop.events, {25:nlua_schedule_event}, | + {26:1}, ({6:void} *)({6:ptrdiff_t})cb); | + {15:return} {26:0}; | + {9:}} | + | + ]], + }) end) it('is updated with :sort', function() @@ -372,83 +359,79 @@ describe('treesitter highlighting (C)', function() local parser = vim.treesitter.get_parser(0, 'c') vim.treesitter.highlighter.new(parser, { queries = { c = hl_query_c } }) end) - screen:expect { + screen:expect({ grid = [[ - {3:int} width = {5:INT_MAX}, height = {5:INT_MAX}; | - {3:bool} ext_widgets[kUIExtCount]; | - {4:for} ({3:UIExtension} i = {5:0}; ({3:int})i < kUIExtCount; i++) { | - ext_widgets[i] = true; | - } | - | - {3:bool} inclusive = ui_override(); | - {4:for} ({3:size_t} i = {5:0}; i < ui_count; i++) { | - {3:UI} *ui = uis[i]; | - width = {5:MIN}(ui->width, width); | - height = {5:MIN}(ui->height, height); | - foo = {5:BAR}(ui->bazaar, bazaar); | - {4:for} ({3:UIExtension} j = {5:0}; ({3:int})j < kUIExtCount; j++) { | - ext_widgets[j] &= (ui->ui_ext[j] || inclusive); | - } | - } | - ^} | - | - ]], - } + {6:int} width = {26:INT_MAX}, height = {26:INT_MAX}; | + {6:bool} ext_widgets[kUIExtCount]; | + {15:for} ({6:UIExtension} i = {26:0}; ({6:int})i < kUIExtCount; i++) { | + ext_widgets[i] = true; | + } | + | + {6:bool} inclusive = ui_override(); | + {15:for} ({6:size_t} i = {26:0}; i < ui_count; i++) { | + {6:UI} *ui = uis[i]; | + width = {26:MIN}(ui->width, width); | + height = {26:MIN}(ui->height, height); | + foo = {26:BAR}(ui->bazaar, bazaar); | + {15:for} ({6:UIExtension} j = {26:0}; ({6:int})j < kUIExtCount; j++) { | + ext_widgets[j] &= (ui->ui_ext[j] || inclusive); | + } | + } | + ^} | + | + ]], + }) feed ':sort<cr>' - screen:expect { + screen:expect({ grid = [[ - ^ | - ext_widgets[j] &= (ui->ui_ext[j] || inclusive); | - {3:UI} *ui = uis[i]; | - ext_widgets[i] = true; | - foo = {5:BAR}(ui->bazaar, bazaar); | - {4:for} ({3:UIExtension} j = {5:0}; ({3:int})j < kUIExtCount; j++) { | - height = {5:MIN}(ui->height, height); | - width = {5:MIN}(ui->width, width); | - } | - {3:bool} ext_widgets[kUIExtCount]; | - {3:bool} inclusive = ui_override(); | - {4:for} ({3:UIExtension} i = {5:0}; ({3:int})i < kUIExtCount; i++) { | - {4:for} ({3:size_t} i = {5:0}; i < ui_count; i++) { | - {3:int} width = {5:INT_MAX}, height = {5:INT_MAX}; | - } |*2 - {3:void} ui_refresh({3:void}) | - :sort | - ]], - } + ^ | + ext_widgets[j] &= (ui->ui_ext[j] || inclusive); | + {6:UI} *ui = uis[i]; | + ext_widgets[i] = true; | + foo = {26:BAR}(ui->bazaar, bazaar); | + {15:for} ({6:UIExtension} j = {26:0}; ({6:int})j < kUIExtCount; j++) { | + height = {26:MIN}(ui->height, height); | + width = {26:MIN}(ui->width, width); | + } | + {6:bool} ext_widgets[kUIExtCount]; | + {6:bool} inclusive = ui_override(); | + {15:for} ({6:UIExtension} i = {26:0}; ({6:int})i < kUIExtCount; i++) { | + {15:for} ({6:size_t} i = {26:0}; i < ui_count; i++) { | + {6:int} width = {26:INT_MAX}, height = {26:INT_MAX}; | + } |*2 + {6:void} ui_refresh({6:void}) | + :sort | + ]], + }) - feed 'u' + feed 'u:<esc>' - screen:expect { + screen:expect({ grid = [[ - {3:int} width = {5:INT_MAX}, height = {5:INT_MAX}; | - {3:bool} ext_widgets[kUIExtCount]; | - {4:for} ({3:UIExtension} i = {5:0}; ({3:int})i < kUIExtCount; i++) { | - ext_widgets[i] = true; | - } | - | - {3:bool} inclusive = ui_override(); | - {4:for} ({3:size_t} i = {5:0}; i < ui_count; i++) { | - {3:UI} *ui = uis[i]; | - width = {5:MIN}(ui->width, width); | - height = {5:MIN}(ui->height, height); | - foo = {5:BAR}(ui->bazaar, bazaar); | - {4:for} ({3:UIExtension} j = {5:0}; ({3:int})j < kUIExtCount; j++) { | - ext_widgets[j] &= (ui->ui_ext[j] || inclusive); | - } | - } | - ^} | - 19 changes; before #2 {MATCH:.*}| - ]], - } + {6:int} width = {26:INT_MAX}, height = {26:INT_MAX}; | + {6:bool} ext_widgets[kUIExtCount]; | + {15:for} ({6:UIExtension} i = {26:0}; ({6:int})i < kUIExtCount; i++) { | + ext_widgets[i] = true; | + } | + | + {6:bool} inclusive = ui_override(); | + {15:for} ({6:size_t} i = {26:0}; i < ui_count; i++) { | + {6:UI} *ui = uis[i]; | + width = {26:MIN}(ui->width, width); | + height = {26:MIN}(ui->height, height); | + foo = {26:BAR}(ui->bazaar, bazaar); | + {15:for} ({6:UIExtension} j = {26:0}; ({6:int})j < kUIExtCount; j++) { | + ext_widgets[j] &= (ui->ui_ext[j] || inclusive); | + } | + } | + ^} | + | + ]], + }) end) it('supports with custom parser', function() - screen:set_default_attr_ids { - [1] = { bold = true, foreground = Screen.colors.SeaGreen4 }, - } - insert(test_text_c) screen:expect { @@ -488,28 +471,28 @@ describe('treesitter highlighting (C)', function() vim.treesitter.highlighter.new(parser, { queries = { c = '(identifier) @type' } }) end) - screen:expect { + screen:expect({ grid = [[ - int {1:width} = {1:INT_MAX}, {1:height} = {1:INT_MAX}; | - bool {1:ext_widgets}[{1:kUIExtCount}]; | - for (UIExtension {1:i} = 0; (int)i < kUIExtCount; i++) { | - ext_widgets[i] = true; | - } | - | - bool {1:inclusive} = {1:ui_override}(); | - for (size_t {1:i} = 0; i < ui_count; i++) { | - UI *{1:ui} = {1:uis}[{1:i}]; | - width = MIN(ui->width, width); | - height = MIN(ui->height, height); | - foo = BAR(ui->bazaar, bazaar); | - for (UIExtension {1:j} = 0; (int)j < kUIExtCount; j++) { | - ext_widgets[j] &= (ui->ui_ext[j] || inclusive); | - } | - } | - ^} | - | - ]], - } + int {6:width} = {6:INT_MAX}, {6:height} = {6:INT_MAX}; | + bool {6:ext_widgets}[{6:kUIExtCount}]; | + for (UIExtension {6:i} = 0; (int)i < kUIExtCount; i++) { | + ext_widgets[i] = true; | + } | + | + bool {6:inclusive} = {6:ui_override}(); | + for (size_t {6:i} = 0; i < ui_count; i++) { | + UI *{6:ui} = {6:uis}[{6:i}]; | + width = MIN(ui->width, width); | + height = MIN(ui->height, height); | + foo = BAR(ui->bazaar, bazaar); | + for (UIExtension {6:j} = 0; (int)j < kUIExtCount; j++) { | + ext_widgets[j] &= (ui->ui_ext[j] || inclusive); | + } | + } | + ^} | + | + ]], + }) end) it('supports injected languages', function() @@ -567,18 +550,18 @@ describe('treesitter highlighting (C)', function() vim.treesitter.highlighter.new(vim.treesitter.get_parser(0, 'c')) end) - screen:expect { + screen:expect({ grid = [[ - {3:int} x = {5:INT_MAX}; | - #define {5:READ_STRING}(x, y) ({3:char} *)read_string((x), ({3:size_t})(y)) | - #define foo {3:void} main() { \ | - {4:return} {5:42}; \ | - } | - ^ | - {1:~ }|*11 - | - ]], - } + {6:int} x = {26:INT_MAX}; | + #define {26:READ_STRING}(x, y) ({6:char} *)read_string((x), ({6:size_t})(y)) | + #define foo {6:void} main() { \ | + {15:return} {26:42}; \ | + } | + ^ | + {1:~ }|*11 + | + ]], + }) end) it('supports highlighting with custom highlight groups', function() @@ -595,27 +578,27 @@ describe('treesitter highlighting (C)', function() -- This will change ONLY the literal strings to look like comments -- The only literal string is the "vim.schedule: expected function" in this test. exec_lua [[vim.cmd("highlight link @string.nonexistent_specializer comment")]] - screen:expect { + screen:expect({ grid = [[ - {2:^/// Schedule Lua callback on main loop's event queue} | - {3:static} {3:int} {11:nlua_schedule}({3:lua_State} *{3:const} lstate) | - { | - {4:if} ({11:lua_type}(lstate, {5:1}) != {5:LUA_TFUNCTION} | - || {6:lstate} != {6:lstate}) { | - {11:lua_pushliteral}(lstate, {2:"vim.schedule: expected function"}); | - {4:return} {11:lua_error}(lstate); | - } | - | - {7:LuaRef} cb = {11:nlua_ref}(lstate, {5:1}); | - | - multiqueue_put(main_loop.events, {11:nlua_schedule_event}, | - {5:1}, ({3:void} *)({3:ptrdiff_t})cb); | - {4:return} {5:0}; | - } | - {1:~ }|*2 - | - ]], - } + {18:^/// Schedule Lua callback on main loop's event queue} | + {6:static} {6:int} {25:nlua_schedule}({6:lua_State} *{6:const} lstate) | + { | + {15:if} ({25:lua_type}(lstate, {26:1}) != {26:LUA_TFUNCTION} | + || {19:lstate} != {19:lstate}) { | + {25:lua_pushliteral}(lstate, {18:"vim.schedule: expected function"}); | + {15:return} {25:lua_error}(lstate); | + } | + | + {29:LuaRef} cb = {25:nlua_ref}(lstate, {26:1}); | + | + multiqueue_put(main_loop.events, {25:nlua_schedule_event}, | + {26:1}, ({6:void} *)({6:ptrdiff_t})cb); | + {15:return} {26:0}; | + } | + {1:~ }|*2 + | + ]], + }) screen:expect { unchanged = true } end) @@ -657,8 +640,8 @@ describe('treesitter highlighting (C)', function() } eq({ - { capture = 'constant', metadata = { priority = '101' }, lang = 'c' }, - { capture = 'type', metadata = {}, lang = 'c' }, + { capture = 'constant', metadata = { priority = '101' }, lang = 'c', id = 14 }, + { capture = 'type', metadata = {}, lang = 'c', id = 3 }, }, exec_lua [[ return vim.treesitter.get_captures_at_pos(0, 0, 2) ]]) end) @@ -691,25 +674,25 @@ describe('treesitter highlighting (C)', function() ) end) - screen:expect { + screen:expect({ grid = [[ - {3:char}* x = {5:"Will somebody ever read this?"}; | - ^ | - {1:~ }|*15 - | - ]], - } + {6:char}* x = {26:"Will somebody ever read this?"}; | + ^ | + {1:~ }|*15 + | + ]], + }) -- clearing specialization reactivates fallback command [[ hi clear @foo.bar ]] - screen:expect { + screen:expect({ grid = [[ - {5:char}* x = {5:"Will somebody ever read this?"}; | - ^ | - {1:~ }|*15 - | - ]], - } + {26:char}* x = {26:"Will somebody ever read this?"}; | + ^ | + {1:~ }|*15 + | + ]], + }) end ) @@ -740,27 +723,27 @@ describe('treesitter highlighting (C)', function() }) end) - screen:expect { + screen:expect({ grid = [[ - /// Schedule Lua callback on main loop's event queue | - {4:R} int nlua_schedule(lua_State *const ) | - { | - if (lua_type(, 1) != LUA_TFUNCTION | - || != ) { | - lua_pushliteral(, "vim.schedule: expected function"); | - return lua_error(); | - } | - | - LuaRef cb = nlua_ref(, 1); | - | - {11:V}(main_loop.events, nlua_schedule_event, | - 1, (void *)(ptrdiff_t)cb); | - return 0; | - ^} | - {1:~ }|*2 - | - ]], - } + /// Schedule Lua callback on main loop's event queue | + {15:R} int nlua_schedule(lua_State *const ) | + { | + if (lua_type(, 1) != LUA_TFUNCTION | + || != ) { | + lua_pushliteral(, "vim.schedule: expected function"); | + return lua_error(); | + } | + | + LuaRef cb = nlua_ref(, 1); | + | + {25:V}(main_loop.events, nlua_schedule_event, | + 1, (void *)(ptrdiff_t)cb); | + return 0; | + ^} | + {1:~ }|*2 + | + ]], + }) end) it('@foo.bar groups has the correct fallback behavior', function() @@ -801,16 +784,16 @@ describe('treesitter highlighting (C)', function() vim.treesitter.highlighter.new(vim.treesitter.get_parser(0, 'c')) end) - screen:expect { + screen:expect({ grid = [[ - {5:int x = 4;} | - {5:int y = 5;} | - {5:int z = 6;} | - ^ | - {1:~ }|*13 - | - ]], - } + {26:int x = 4;} | + {26:int y = 5;} | + {26:int z = 6;} | + ^ | + {1:~ }|*13 + | + ]], + }) end) it('gives higher priority to more specific captures #27895', function() @@ -830,14 +813,52 @@ describe('treesitter highlighting (C)', function() vim.treesitter.highlighter.new(vim.treesitter.get_parser(0, 'c')) end) - screen:expect { + screen:expect({ grid = [[ - void foo(int {4:*}{11:bar}); | - ^ | - {1:~ }|*15 - | - ]], - } + void foo(int {15:*}{25:bar}); | + ^ | + {1:~ }|*15 + | + ]], + }) + end) + + it('highlights applied to first line of closed fold', function() + insert(hl_text_c) + exec_lua(function() + vim.treesitter.query.set('c', 'highlights', hl_query_c) + vim.treesitter.highlighter.new(vim.treesitter.get_parser(0, 'c')) + end) + feed('ggjzfj') + command('set foldtext=') + screen:add_extra_attr_ids({ + [100] = { + bold = true, + background = Screen.colors.LightGray, + foreground = Screen.colors.SeaGreen4, + }, + [101] = { background = Screen.colors.LightGray, foreground = Screen.colors.DarkCyan }, + }) + screen:expect({ + grid = [[ + {18:/// Schedule Lua callback on main loop's event queue} | + {100:^static}{13: }{100:int}{13: }{101:nlua_schedule}{13:(}{100:lua_State}{13: *}{100:const}{13: lstate)················}| + {15:if} ({25:lua_type}(lstate, {26:1}) != {26:LUA_TFUNCTION} | + || {19:lstate} != {19:lstate}) { | + {25:lua_pushliteral}(lstate, {26:"vim.schedule: expected function"}); | + {15:return} {25:lua_error}(lstate); | + } | + | + {29:LuaRef} cb = {25:nlua_ref}(lstate, {26:1}); | + | + multiqueue_put(main_loop.events, {25:nlua_schedule_event}, | + {26:1}, ({6:void} *)({6:ptrdiff_t})cb); | + {15:return} {26:0}; | + } | + {1:~ }|*3 + | + ]], + }) end) end) @@ -847,13 +868,6 @@ describe('treesitter highlighting (lua)', function() before_each(function() clear() screen = Screen.new(65, 18) - screen:set_default_attr_ids { - [1] = { bold = true, foreground = Screen.colors.Blue }, - [2] = { foreground = Screen.colors.DarkCyan }, - [3] = { foreground = Screen.colors.Magenta }, - [4] = { foreground = Screen.colors.SlateBlue }, - [5] = { bold = true, foreground = Screen.colors.Brown }, - } end) it('supports language injections', function() @@ -867,15 +881,15 @@ describe('treesitter highlighting (lua)', function() vim.treesitter.start() end) - screen:expect { + screen:expect({ grid = [[ - {5:local} {2:ffi} {5:=} {4:require(}{3:'ffi'}{4:)} | - {2:ffi}{4:.}{2:cdef}{4:(}{3:"}{4:int}{3: }{4:(}{5:*}{3:fun}{4:)(int,}{3: }{4:char}{3: }{5:*}{4:);}{3:"}{4:)} | - ^ | - {1:~ }|*14 - | - ]], - } + {15:local} {25:ffi} {15:=} {16:require(}{26:'ffi'}{16:)} | + {25:ffi}{16:.}{25:cdef}{16:(}{26:"}{16:int}{26: }{16:(}{15:*}{26:fun}{16:)(int,}{26: }{16:char}{26: }{15:*}{16:);}{26:"}{16:)} | + ^ | + {1:~ }|*14 + | + ]], + }) end) end) @@ -885,16 +899,6 @@ describe('treesitter highlighting (help)', function() before_each(function() clear() screen = Screen.new(40, 6) - screen:set_default_attr_ids { - [1] = { foreground = Screen.colors.Blue1 }, - [2] = { bold = true, foreground = Screen.colors.Blue1 }, - [3] = { bold = true, foreground = Screen.colors.Brown }, - [4] = { foreground = Screen.colors.Cyan4 }, - [5] = { foreground = Screen.colors.Magenta1 }, - title = { bold = true, foreground = Screen.colors.Magenta1 }, - h1_delim = { nocombine = true, underdouble = true }, - h2_delim = { nocombine = true, underline = true }, - } end) it('defaults in vimdoc/highlights.scm', function() @@ -918,13 +922,18 @@ describe('treesitter highlighting (help)', function() vim.treesitter.start() end) + screen:add_extra_attr_ids({ + [100] = { nocombine = true, underdouble = true }, + [101] = { foreground = Screen.colors.Fuchsia, bold = true }, + [102] = { underline = true, nocombine = true }, + }) screen:expect({ grid = [[ - {h1_delim:^========================================}| - {title:NVIM DOCUMENTATION} | + {100:^========================================}| + {101:NVIM DOCUMENTATION} | | - {h2_delim:----------------------------------------}| - {title:ABOUT NVIM} | + {102:----------------------------------------}| + {101:ABOUT NVIM} | | ]], }) @@ -943,42 +952,42 @@ describe('treesitter highlighting (help)', function() vim.treesitter.start() end) - screen:expect { + screen:expect({ grid = [[ - {1:>}{3:ruby} | - {1: -- comment} | - {1: local this_is = 'actually_lua'} | - {1:<} | - ^ | - | - ]], - } + {18:>}{15:ruby} | + {18: -- comment} | + {18: local this_is = 'actually_lua'} | + {18:<} | + ^ | + | + ]], + }) n.api.nvim_buf_set_text(0, 0, 1, 0, 5, { 'lua' }) - screen:expect { + screen:expect({ grid = [[ - {1:>}{3:lua} | - {1: -- comment} | - {1: }{3:local}{1: }{4:this_is}{1: }{3:=}{1: }{5:'actually_lua'} | - {1:<} | - ^ | - | - ]], - } + {18:>}{15:lua} | + {18: -- comment} | + {18: }{15:local}{18: }{25:this_is}{18: }{15:=}{18: }{26:'actually_lua'} | + {18:<} | + ^ | + | + ]], + }) n.api.nvim_buf_set_text(0, 0, 1, 0, 4, { 'ruby' }) - screen:expect { + screen:expect({ grid = [[ - {1:>}{3:ruby} | - {1: -- comment} | - {1: local this_is = 'actually_lua'} | - {1:<} | - ^ | - | - ]], - } + {18:>}{15:ruby} | + {18: -- comment} | + {18: local this_is = 'actually_lua'} | + {18:<} | + ^ | + | + ]], + }) end) it('correctly redraws injections subpriorities', function() @@ -1003,16 +1012,16 @@ describe('treesitter highlighting (help)', function() vim.treesitter.highlighter.new(parser) end) - screen:expect { + screen:expect({ grid = [=[ - {3:local} {4:s} {3:=} {5:[[} | - {5: }{3:local}{5: }{4:also}{5: }{3:=}{5: }{4:lua} | - {5:]]} | - ^ | - {2:~ }| - | - ]=], - } + {15:local} {25:s} {15:=} {26:[[} | + {26: }{15:local}{26: }{25:also}{26: }{15:=}{26: }{25:lua} | + {26:]]} | + ^ | + {1:~ }| + | + ]=], + }) end) end) @@ -1022,12 +1031,6 @@ describe('treesitter highlighting (nested injections)', function() before_each(function() clear() screen = Screen.new(80, 7) - screen:set_default_attr_ids { - [1] = { foreground = Screen.colors.SlateBlue }, - [2] = { bold = true, foreground = Screen.colors.Brown }, - [3] = { foreground = Screen.colors.Cyan4 }, - [4] = { foreground = Screen.colors.Fuchsia }, - } end) it('correctly redraws nested injections (GitHub #25252)', function() @@ -1054,32 +1057,32 @@ vim.cmd([[ -- invalidate the language tree feed('ggi--[[<ESC>04x') - screen:expect { + screen:expect({ grid = [[ - {2:^function} {3:foo}{1:()} {1:print(}{4:"Lua!"}{1:)} {2:end} | - | - {2:local} {3:lorem} {2:=} {1:{} | - {3:ipsum} {2:=} {1:{},} | - {3:bar} {2:=} {1:{},} | - {1:}} | - | - ]], - } + {15:^function} {25:foo}{16:()} {16:print(}{26:"Lua!"}{16:)} {15:end} | + | + {15:local} {25:lorem} {15:=} {16:{} | + {25:ipsum} {15:=} {16:{},} | + {25:bar} {15:=} {16:{},} | + {16:}} | + | + ]], + }) -- spam newline insert/delete to invalidate Lua > Vim > Lua region feed('3jo<ESC>ddko<ESC>ddko<ESC>ddko<ESC>ddk0') - screen:expect { + screen:expect({ grid = [[ - {2:function} {3:foo}{1:()} {1:print(}{4:"Lua!"}{1:)} {2:end} | - | - {2:local} {3:lorem} {2:=} {1:{} | - ^ {3:ipsum} {2:=} {1:{},} | - {3:bar} {2:=} {1:{},} | - {1:}} | - | - ]], - } + {15:function} {25:foo}{16:()} {16:print(}{26:"Lua!"}{16:)} {15:end} | + | + {15:local} {25:lorem} {15:=} {16:{} | + ^ {25:ipsum} {15:=} {16:{},} | + {25:bar} {15:=} {16:{},} | + {16:}} | + | + ]], + }) end) end) @@ -1108,7 +1111,7 @@ describe('treesitter highlighting (markdown)', function() }) screen:expect({ grid = [[ - {25:[}{100:This link text}{25:](}{101:https://example.com}{25:)} is| + {100:[This link text](}{101:https://example.com}{100:)} is| a hyperlink^. | {1:~ }|*3 | @@ -1156,20 +1159,6 @@ it('starting and stopping treesitter highlight in init.lua works #29541', functi eq('', api.nvim_get_vvar('errmsg')) local screen = Screen.new(65, 18) - screen:set_default_attr_ids { - [1] = { bold = true, foreground = Screen.colors.Blue1 }, - [2] = { foreground = Screen.colors.Blue1 }, - [3] = { bold = true, foreground = Screen.colors.SeaGreen4 }, - [4] = { bold = true, foreground = Screen.colors.Brown }, - [5] = { foreground = Screen.colors.Magenta }, - [6] = { foreground = Screen.colors.Red }, - [7] = { bold = true, foreground = Screen.colors.SlateBlue }, - [8] = { foreground = Screen.colors.Grey100, background = Screen.colors.Red }, - [9] = { foreground = Screen.colors.Magenta, background = Screen.colors.Red }, - [10] = { foreground = Screen.colors.Red, background = Screen.colors.Red }, - [11] = { foreground = Screen.colors.Cyan4 }, - } - fn.setreg('r', hl_text_c) feed('i<C-R><C-O>r<Esc>gg') -- legacy syntax highlighting is used diff --git a/test/functional/treesitter/inspect_tree_spec.lua b/test/functional/treesitter/inspect_tree_spec.lua index 1f7d15cc96..68622140e4 100644 --- a/test/functional/treesitter/inspect_tree_spec.lua +++ b/test/functional/treesitter/inspect_tree_spec.lua @@ -120,14 +120,17 @@ describe('vim.treesitter.inspect_tree', function() end) it('updates source and tree buffer windows and closes them correctly', function() + local name = t.tmpname() + n.command('edit ' .. name) insert([[ print() ]]) + n.command('set filetype=lua | write') -- setup two windows for the source buffer exec_lua(function() _G.source_win = vim.api.nvim_get_current_win() - vim.api.nvim_open_win(0, false, { + _G.source_win2 = vim.api.nvim_open_win(0, false, { win = 0, split = 'left', }) @@ -135,40 +138,103 @@ describe('vim.treesitter.inspect_tree', function() -- setup three windows for the tree buffer exec_lua(function() - vim.treesitter.start(0, 'lua') vim.treesitter.inspect_tree() _G.tree_win = vim.api.nvim_get_current_win() - _G.tree_win_copy_1 = vim.api.nvim_open_win(0, false, { + _G.tree_win2 = vim.api.nvim_open_win(0, false, { win = 0, split = 'left', }) - _G.tree_win_copy_2 = vim.api.nvim_open_win(0, false, { + _G.tree_win3 = vim.api.nvim_open_win(0, false, { win = 0, split = 'left', }) end) - -- close original source window - exec_lua('vim.api.nvim_win_close(source_win, false)') + -- close original source window without closing tree views + exec_lua('vim.api.nvim_set_current_win(source_win)') + feed(':quit<CR>') + eq('', n.api.nvim_get_vvar('errmsg')) + eq(true, exec_lua('return vim.api.nvim_win_is_valid(tree_win)')) + eq(true, exec_lua('return vim.api.nvim_win_is_valid(tree_win2)')) + eq(true, exec_lua('return vim.api.nvim_win_is_valid(tree_win3)')) -- navigates correctly to the remaining source buffer window + exec_lua('vim.api.nvim_set_current_win(tree_win)') feed('<CR>') eq('', n.api.nvim_get_vvar('errmsg')) + eq(true, exec_lua('return vim.api.nvim_get_current_win() == source_win2')) -- close original tree window exec_lua(function() - vim.api.nvim_set_current_win(_G.tree_win_copy_1) + vim.api.nvim_set_current_win(_G.tree_win2) vim.api.nvim_win_close(_G.tree_win, false) end) -- navigates correctly to the remaining source buffer window feed('<CR>') eq('', n.api.nvim_get_vvar('errmsg')) + eq(true, exec_lua('return vim.api.nvim_get_current_win() == source_win2')) -- close source buffer window and all remaining tree windows - t.pcall_err(exec_lua, 'vim.api.nvim_win_close(0, false)') + n.expect_exit(n.command, 'quit') + end) - eq(false, exec_lua('return vim.api.nvim_win_is_valid(tree_win_copy_1)')) - eq(false, exec_lua('return vim.api.nvim_win_is_valid(tree_win_copy_2)')) + it('shows which nodes are missing', function() + insert([[ + int main() { + if (a.) { + // ^ MISSING field_identifier here + if (1) d() + // ^ MISSING ";" here + } + } + ]]) + + exec_lua(function() + vim.treesitter.start(0, 'c') + vim.treesitter.inspect_tree() + end) + feed('a') + + expect_tree [[ + (translation_unit ; [0, 0] - [8, 0] + (function_definition ; [0, 0] - [6, 1] + type: (primitive_type) ; [0, 0] - [0, 3] + declarator: (function_declarator ; [0, 4] - [0, 10] + declarator: (identifier) ; [0, 4] - [0, 8] + parameters: (parameter_list ; [0, 8] - [0, 10] + "(" ; [0, 8] - [0, 9] + ")")) ; [0, 9] - [0, 10] + body: (compound_statement ; [0, 11] - [6, 1] + "{" ; [0, 11] - [0, 12] + (if_statement ; [1, 4] - [5, 5] + "if" ; [1, 4] - [1, 6] + condition: (parenthesized_expression ; [1, 7] - [1, 11] + "(" ; [1, 7] - [1, 8] + (field_expression ; [1, 8] - [1, 10] + argument: (identifier) ; [1, 8] - [1, 9] + operator: "." ; [1, 9] - [1, 10] + field: (MISSING field_identifier)) ; [1, 10] - [1, 10] + ")") ; [1, 10] - [1, 11] + consequence: (compound_statement ; [1, 12] - [5, 5] + "{" ; [1, 12] - [1, 13] + (comment) ; [2, 4] - [2, 41] + (if_statement ; [3, 8] - [4, 36] + "if" ; [3, 8] - [3, 10] + condition: (parenthesized_expression ; [3, 11] - [3, 14] + "(" ; [3, 11] - [3, 12] + (number_literal) ; [3, 12] - [3, 13] + ")") ; [3, 13] - [3, 14] + consequence: (expression_statement ; [3, 15] - [4, 36] + (call_expression ; [3, 15] - [3, 18] + function: (identifier) ; [3, 15] - [3, 16] + arguments: (argument_list ; [3, 16] - [3, 18] + "(" ; [3, 16] - [3, 17] + ")")) ; [3, 17] - [3, 18] + (comment) ; [4, 8] - [4, 36] + (MISSING ";"))) ; [4, 36] - [4, 36] + "}")) ; [5, 4] - [5, 5] + "}"))) ; [6, 0] - [6, 1] + ]] end) end) diff --git a/test/functional/treesitter/language_spec.lua b/test/functional/treesitter/language_spec.lua index 120a15d7f9..a93b1063a1 100644 --- a/test/functional/treesitter/language_spec.lua +++ b/test/functional/treesitter/language_spec.lua @@ -117,6 +117,7 @@ describe('treesitter language API', function() '<node translation_unit>', exec_lua(function() local langtree = vim.treesitter.get_parser(0, 'c') + langtree:parse() local tree = langtree:tree_for_range({ 1, 3, 1, 3 }) return tostring(tree:root()) end) @@ -133,6 +134,7 @@ describe('treesitter language API', function() '<node translation_unit>', exec_lua(function() local langtree = vim.treesitter.get_parser(0, 'c') + langtree:parse() local tree = langtree:tree_for_range({ 10, 10, 10, 10 }) return tostring(tree:root()) end) @@ -149,6 +151,7 @@ describe('treesitter language API', function() '<node primitive_type>', exec_lua(function() local langtree = vim.treesitter.get_parser(0, 'c') + langtree:parse() local node = langtree:named_node_for_range({ 1, 3, 1, 3 }) return tostring(node) end) @@ -160,6 +163,7 @@ describe('treesitter language API', function() exec_lua(function() _G.langtree = vim.treesitter.get_parser(0, 'lua') + _G.langtree:parse() _G.node = _G.langtree:node_for_range({ 0, 3, 0, 3 }) end) diff --git a/test/functional/treesitter/node_spec.lua b/test/functional/treesitter/node_spec.lua index c87a56b160..235bf7861c 100644 --- a/test/functional/treesitter/node_spec.lua +++ b/test/functional/treesitter/node_spec.lua @@ -20,6 +20,7 @@ describe('treesitter node API', function() insert('F') exec_lua(function() vim.treesitter.start(0, 'lua') + vim.treesitter.get_parser(0):parse() vim.treesitter.get_node():tree() vim.treesitter.get_node():tree() collectgarbage() @@ -45,6 +46,7 @@ describe('treesitter node API', function() -- this buffer doesn't have filetype set! insert('local foo = function() end') exec_lua(function() + vim.treesitter.get_parser(0, 'lua'):parse() _G.node = vim.treesitter.get_node({ bufnr = 0, pos = { 0, 6 }, -- on "foo" @@ -161,32 +163,6 @@ describe('treesitter node API', function() eq(3, lua_eval('child:byte_length()')) end) - it('child_containing_descendant() works', function() - insert([[ - int main() { - int x = 3; - }]]) - - exec_lua(function() - local tree = vim.treesitter.get_parser(0, 'c'):parse()[1] - _G.root = tree:root() - _G.main = _G.root:child(0) - _G.body = _G.main:child(2) - _G.statement = _G.body:child(1) - _G.declarator = _G.statement:child(1) - _G.value = _G.declarator:child(1) - end) - - eq(lua_eval('main:type()'), lua_eval('root:child_containing_descendant(value):type()')) - eq(lua_eval('body:type()'), lua_eval('main:child_containing_descendant(value):type()')) - eq(lua_eval('statement:type()'), lua_eval('body:child_containing_descendant(value):type()')) - eq( - lua_eval('declarator:type()'), - lua_eval('statement:child_containing_descendant(value):type()') - ) - eq(vim.NIL, lua_eval('declarator:child_containing_descendant(value)')) - end) - it('child_with_descendant() works', function() insert([[ int main() { diff --git a/test/functional/treesitter/parser_spec.lua b/test/functional/treesitter/parser_spec.lua index 2f8d204d36..eb4651a81d 100644 --- a/test/functional/treesitter/parser_spec.lua +++ b/test/functional/treesitter/parser_spec.lua @@ -1,5 +1,6 @@ local t = require('test.testutil') local n = require('test.functional.testnvim')() +local ts_t = require('test.functional.treesitter.testutil') local clear = n.clear local dedent = t.dedent @@ -8,6 +9,8 @@ local insert = n.insert local exec_lua = n.exec_lua local pcall_err = t.pcall_err local feed = n.feed +local run_query = ts_t.run_query +local assert_alive = n.assert_alive describe('treesitter parser API', function() before_each(function() @@ -88,6 +91,197 @@ describe('treesitter parser API', function() eq(true, exec_lua('return parser:parse()[1] == tree2')) end) + it('parses buffer asynchronously', function() + insert([[ + int main() { + int x = 3; + }]]) + + exec_lua(function() + _G.parser = vim.treesitter.get_parser(0, 'c') + _G.lang = vim.treesitter.language.inspect('c') + _G.parser:parse(nil, function(_, trees) + _G.tree = trees[1] + _G.root = _G.tree:root() + end) + vim.wait(100, function() end) + end) + + eq('<tree>', exec_lua('return tostring(tree)')) + eq('<node translation_unit>', exec_lua('return tostring(root)')) + eq({ 0, 0, 3, 0 }, exec_lua('return {root:range()}')) + + eq(1, exec_lua('return root:child_count()')) + exec_lua('child = root:child(0)') + eq('<node function_definition>', exec_lua('return tostring(child)')) + eq({ 0, 0, 2, 1 }, exec_lua('return {child:range()}')) + + eq('function_definition', exec_lua('return child:type()')) + eq(true, exec_lua('return child:named()')) + eq('number', type(exec_lua('return child:symbol()'))) + eq(true, exec_lua('return lang.symbols[child:type()]')) + + exec_lua('anon = root:descendant_for_range(0,8,0,9)') + eq('(', exec_lua('return anon:type()')) + eq(false, exec_lua('return anon:named()')) + eq('number', type(exec_lua('return anon:symbol()'))) + eq(false, exec_lua([=[return lang.symbols[string.format('"%s"', anon:type())]]=])) + + exec_lua('descendant = root:descendant_for_range(1,2,1,12)') + eq('<node declaration>', exec_lua('return tostring(descendant)')) + eq({ 1, 2, 1, 12 }, exec_lua('return {descendant:range()}')) + eq( + '(declaration type: (primitive_type) declarator: (init_declarator declarator: (identifier) value: (number_literal)))', + exec_lua('return descendant:sexpr()') + ) + + feed('2G7|ay') + exec_lua(function() + _G.parser:parse(nil, function(_, trees) + _G.tree2 = trees[1] + _G.root2 = _G.tree2:root() + _G.descendant2 = _G.root2:descendant_for_range(1, 2, 1, 13) + end) + vim.wait(100, function() end) + end) + eq(false, exec_lua('return tree2 == tree1')) + eq(false, exec_lua('return root2 == root')) + eq('<node declaration>', exec_lua('return tostring(descendant2)')) + eq({ 1, 2, 1, 13 }, exec_lua('return {descendant2:range()}')) + + eq(true, exec_lua('return child == child')) + -- separate lua object, but represents same node + eq(true, exec_lua('return child == root:child(0)')) + eq(false, exec_lua('return child == descendant2')) + eq(false, exec_lua('return child == nil')) + eq(false, exec_lua('return child == tree')) + + eq('string', exec_lua('return type(child:id())')) + eq(true, exec_lua('return child:id() == child:id()')) + -- separate lua object, but represents same node + eq(true, exec_lua('return child:id() == root:child(0):id()')) + eq(false, exec_lua('return child:id() == descendant2:id()')) + eq(false, exec_lua('return child:id() == nil')) + eq(false, exec_lua('return child:id() == tree')) + + -- unchanged buffer: return the same tree + eq(true, exec_lua('return parser:parse()[1] == tree2')) + end) + + it('does not crash when editing large files', function() + insert([[printf("%s", "some text");]]) + feed('yy49999p') + + exec_lua(function() + _G.parser = vim.treesitter.get_parser(0, 'c') + _G.done = false + vim.treesitter.start(0, 'c') + _G.parser:parse(nil, function() + _G.done = true + end) + while not _G.done do + -- Busy wait until async parsing has completed + vim.wait(100, function() end) + end + end) + + eq(true, exec_lua([[return done]])) + exec_lua(function() + vim.api.nvim_input('Lxj') + end) + exec_lua(function() + vim.api.nvim_input('xj') + end) + exec_lua(function() + vim.api.nvim_input('xj') + end) + assert_alive() + end) + + it('resets parsing state on tree changes', function() + insert([[vim.api.nvim_set_hl(0, 'test2', { bg = 'green' })]]) + feed('yy1000p') + + exec_lua(function() + vim.cmd('set ft=lua') + + vim.treesitter.start(0) + local parser = assert(vim.treesitter.get_parser(0)) + + parser:parse(true, function() end) + vim.api.nvim_buf_set_lines(0, 1, -1, false, {}) + parser:parse(true) + end) + end) + + it('resets when buffer was editing during an async parse', function() + insert([[printf("%s", "some text");]]) + feed('yy49999p') + feed('gg4jO// Comment<Esc>') + + exec_lua(function() + _G.parser = vim.treesitter.get_parser(0, 'c') + _G.done = false + vim.treesitter.start(0, 'c') + _G.parser:parse(nil, function() + _G.done = true + end) + end) + + exec_lua(function() + vim.api.nvim_input('ggdj') + end) + + eq(false, exec_lua([[return done]])) + exec_lua(function() + while not _G.done do + -- Busy wait until async parsing finishes + vim.wait(100, function() end) + end + end) + eq(true, exec_lua([[return done]])) + eq('comment', exec_lua([[return parser:parse()[1]:root():named_child(2):type()]])) + eq({ 2, 0, 2, 10 }, exec_lua([[return {parser:parse()[1]:root():named_child(2):range()}]])) + end) + + it('handles multiple async parse calls', function() + insert([[printf("%s", "some text");]]) + feed('yy49999p') + + exec_lua(function() + -- Spy on vim.schedule + local schedule = vim.schedule + vim.schedule = function(fn) + _G.schedules = _G.schedules + 1 + schedule(fn) + end + _G.schedules = 0 + _G.parser = vim.treesitter.get_parser(0, 'c') + for i = 1, 5 do + _G['done' .. i] = false + _G.parser:parse(nil, function() + _G['done' .. i] = true + end) + end + schedule(function() + _G.schedules_snapshot = _G.schedules + end) + end) + + eq(2, exec_lua([[return schedules_snapshot]])) + eq( + { false, false, false, false, false }, + exec_lua([[return { done1, done2, done3, done4, done5 }]]) + ) + exec_lua(function() + while not _G.done1 do + -- Busy wait until async parsing finishes + vim.wait(100, function() end) + end + end) + eq({ true, true, true, true, true }, exec_lua([[return { done1, done2, done3, done4, done5 }]])) + end) + local test_text = [[ void ui_refresh(void) { @@ -310,6 +504,15 @@ end]] eq({ 0, 0, 0, 13 }, ret) end) + it('can run async parses with string parsers', function() + local ret = exec_lua(function() + local parser = vim.treesitter.get_string_parser('int foo = 42;', 'c') + return { parser:parse(nil, function() end)[1]:root():range() } + end) + + eq({ 0, 0, 0, 13 }, ret) + end) + it('allows to run queries with string parsers', function() local txt = [[ int foo = 42; @@ -430,7 +633,7 @@ int x = INT_MAX; }, get_ranges()) n.feed('7ggI//<esc>') - exec_lua([[parser:parse({6, 7})]]) + exec_lua([[parser:parse({5, 6})]]) eq('table', exec_lua('return type(parser:children().c)')) eq(2, exec_lua('return #parser:children().c:trees()')) eq({ @@ -644,6 +847,109 @@ print() end) end) + describe('trim! directive', function() + it('can trim all whitespace', function() + -- luacheck: push ignore 611 613 + insert([=[ + print([[ + + f + helllo + there + asdf + asdfassd + + + + ]]) + print([[ + + + + ]]) + + print([[]]) + + print([[ + ]]) + + print([[ hello 😃 ]]) + ]=]) + -- luacheck: pop + + local query_text = [[ + ; query + ((string_content) @str + (#trim! @str 1 1 1 1)) + ]] + + exec_lua(function() + vim.treesitter.start(0, 'lua') + end) + + eq({ + { 'str', { 2, 12, 6, 10 } }, + { 'str', { 11, 10, 11, 10 } }, + { 'str', { 17, 10, 17, 10 } }, + { 'str', { 19, 10, 19, 10 } }, + { 'str', { 22, 15, 22, 25 } }, + }, run_query('lua', query_text)) + end) + + it('trims only empty lines by default (backwards compatible)', function() + insert(dedent [[ + ## Heading + + With some text + + ## And another + + With some more here]]) + + local query_text = [[ + ; query + ((section) @fold + (#trim! @fold)) + ]] + + exec_lua(function() + vim.treesitter.start(0, 'markdown') + end) + + eq({ + { 'fold', { 0, 0, 2, 14 } }, + { 'fold', { 4, 0, 6, 19 } }, + }, run_query('markdown', query_text)) + end) + + it('can trim lines', function() + insert(dedent [[ + - Fold list + - Fold list + - Fold list + - Fold list + - Fold list + - Fold list + ]]) + + local query_text = [[ + ; query + ((list_item + (list)) @fold + (#trim! @fold 1 1 1 1)) + ]] + + exec_lua(function() + vim.treesitter.start(0, 'markdown') + end) + + eq({ + { 'fold', { 0, 0, 4, 13 } }, + { 'fold', { 1, 2, 3, 15 } }, + }, run_query('markdown', query_text)) + end) + end) + it('tracks the root range properly (#22911)', function() insert([[ int main() { @@ -659,32 +965,19 @@ print() vim.treesitter.start(0, 'c') end) - local function run_query() - return exec_lua(function() - local query = vim.treesitter.query.parse('c', query0) - local parser = vim.treesitter.get_parser() - local tree = parser:parse()[1] - local res = {} - for id, node in query:iter_captures(tree:root()) do - table.insert(res, { query.captures[id], node:range() }) - end - return res - end) - end - eq({ - { 'function', 0, 0, 2, 1 }, - { 'declaration', 1, 2, 1, 12 }, - }, run_query()) + { 'function', { 0, 0, 2, 1 } }, + { 'declaration', { 1, 2, 1, 12 } }, + }, run_query('c', query0)) n.command 'normal ggO' insert('int a;') eq({ - { 'declaration', 0, 0, 0, 6 }, - { 'function', 1, 0, 3, 1 }, - { 'declaration', 2, 2, 2, 12 }, - }, run_query()) + { 'declaration', { 0, 0, 0, 6 } }, + { 'function', { 1, 0, 3, 1 } }, + { 'declaration', { 2, 2, 2, 12 } }, + }, run_query('c', query0)) end) it('handles ranges when source is a multiline string (#20419)', function() @@ -858,11 +1151,13 @@ print() feed(':set ft=help<cr>') exec_lua(function() - vim.treesitter.get_parser(0, 'vimdoc', { - injections = { - vimdoc = '((codeblock (language) @injection.language (code) @injection.content) (#set! injection.include-children))', - }, - }) + vim.treesitter + .get_parser(0, 'vimdoc', { + injections = { + vimdoc = '((codeblock (language) @injection.language (code) @injection.content) (#set! injection.include-children))', + }, + }) + :parse() end) end) diff --git a/test/functional/treesitter/query_spec.lua b/test/functional/treesitter/query_spec.lua index 634f8af83d..6db0ffe5a0 100644 --- a/test/functional/treesitter/query_spec.lua +++ b/test/functional/treesitter/query_spec.lua @@ -86,7 +86,7 @@ void ui_refresh(void) local before = vim.api.nvim__stats().ts_query_parse_count collectgarbage('stop') for _ = 1, _n, 1 do - vim.treesitter.query.parse('c', long_query, _n) + vim.treesitter.query.parse('c', long_query) end collectgarbage('restart') collectgarbage('collect') @@ -96,8 +96,39 @@ void ui_refresh(void) end eq(1, q(1)) - -- cache is cleared by garbage collection even if valid "cquery" reference is kept around - eq(1, q(100)) + -- cache is retained even after garbage collection + eq(0, q(100)) + end) + + it('cache is cleared upon runtimepath changes, or setting query manually', function() + ---@return number + exec_lua(function() + _G.query_parse_count = _G.query_parse_count or 0 + local parse = vim.treesitter.query.parse + vim.treesitter.query.parse = function(...) + _G.query_parse_count = _G.query_parse_count + 1 + return parse(...) + end + end) + + local function q(_n) + return exec_lua(function() + for _ = 1, _n, 1 do + vim.treesitter.query.get('c', 'highlights') + end + return _G.query_parse_count + end) + end + + eq(1, q(10)) + exec_lua(function() + vim.opt.rtp:prepend('/another/dir') + end) + eq(2, q(100)) + exec_lua(function() + vim.treesitter.query.set('c', 'highlights', [[; test]]) + end) + eq(3, q(100)) end) it('supports query and iter by capture (iter_captures)', function() @@ -781,6 +812,34 @@ void ui_refresh(void) ) end) + it('supports "; extends" modeline in custom queries', function() + insert('int zeero = 0;') + local result = exec_lua(function() + vim.treesitter.query.set( + 'c', + 'highlights', + [[; extends + (identifier) @spell]] + ) + local query = vim.treesitter.query.get('c', 'highlights') + local parser = vim.treesitter.get_parser(0, 'c') + local root = parser:parse()[1]:root() + local res = {} + for id, node in query:iter_captures(root, 0) do + table.insert(res, { query.captures[id], vim.treesitter.get_node_text(node, 0) }) + end + return res + end) + eq({ + { 'type.builtin', 'int' }, + { 'variable', 'zeero' }, + { 'spell', 'zeero' }, + { 'operator', '=' }, + { 'number', '0' }, + { 'punctuation.delimiter', ';' }, + }, result) + end) + describe('Query:iter_captures', function() it('includes metadata for all captured nodes #23664', function() insert([[ @@ -835,9 +894,9 @@ void ui_refresh(void) local result = exec_lua(function() local query0 = vim.treesitter.query.parse('c', query) - local match_preds = query0.match_preds + local match_preds = query0._match_predicates local called = 0 - function query0:match_preds(...) + function query0:_match_predicates(...) called = called + 1 return match_preds(self, ...) end diff --git a/test/functional/treesitter/testutil.lua b/test/functional/treesitter/testutil.lua new file mode 100644 index 0000000000..f8934f06c3 --- /dev/null +++ b/test/functional/treesitter/testutil.lua @@ -0,0 +1,25 @@ +local n = require('test.functional.testnvim')() + +local exec_lua = n.exec_lua + +local M = {} + +---@param language string +---@param query_string string +function M.run_query(language, query_string) + return exec_lua(function(lang, query_str) + local query = vim.treesitter.query.parse(lang, query_str) + local parser = vim.treesitter.get_parser() + local tree = parser:parse()[1] + local res = {} + for id, node, metadata in query:iter_captures(tree:root(), 0) do + table.insert( + res, + { query.captures[id], metadata[id] and metadata[id].range or { node:range() } } + ) + end + return res + end, language, query_string) +end + +return M diff --git a/test/functional/ui/cmdline_spec.lua b/test/functional/ui/cmdline_spec.lua index 0221c1e0b0..ce7c9596bb 100644 --- a/test/functional/ui/cmdline_spec.lua +++ b/test/functional/ui/cmdline_spec.lua @@ -91,25 +91,27 @@ local function test_cmdline(linegrid) {1:~ }|*3 | ]], + cmdline = { { abort = true } }, } end) it('works with input()', function() feed(':call input("input", "default")<cr>') - screen:expect { + screen:expect({ grid = [[ - ^ | - {1:~ }|*3 - | - ]], + ^ | + {1:~ }|*3 + | + ]], cmdline = { { - prompt = 'input', content = { { 'default' } }, + hl_id = 0, pos = 7, + prompt = 'input', }, }, - } + }) feed('<cr>') screen:expect { @@ -118,6 +120,7 @@ local function test_cmdline(linegrid) {1:~ }|*3 | ]], + cmdline = { { abort = false } }, } end) @@ -210,6 +213,7 @@ local function test_cmdline(linegrid) content = { { 'xx3' } }, pos = 3, }, + { abort = false }, }, } @@ -220,6 +224,7 @@ local function test_cmdline(linegrid) {1:~ }|*3 | ]], + cmdline = { { abort = true } }, } end) @@ -294,6 +299,7 @@ local function test_cmdline(linegrid) {1:~ }|*3 | ]], + cmdline = { { abort = false } }, } -- Try once more, to check buffer is reinitialized. #8007 @@ -324,6 +330,7 @@ local function test_cmdline(linegrid) {1:~ }|*3 | ]], + cmdline = { { abort = false } }, } end) @@ -353,6 +360,7 @@ local function test_cmdline(linegrid) {3:[Command Line] }| | ]], + cmdline = { { abort = false } }, } -- nested cmdline @@ -404,6 +412,7 @@ local function test_cmdline(linegrid) {3:[Command Line] }| | ]], + cmdline = { [2] = { abort = true } }, } feed('<c-c>') @@ -452,6 +461,7 @@ local function test_cmdline(linegrid) cmdline = { { prompt = 'secret:', + hl_id = 0, content = { { '******' } }, pos = 6, }, @@ -495,6 +505,7 @@ local function test_cmdline(linegrid) cmdline = { { prompt = '>', + hl_id = 0, content = { { '(', 30 }, { 'a' }, @@ -797,11 +808,14 @@ local function test_cmdline(linegrid) -- This used to send an invalid event where pos where larger than the total -- length of content. Checked in _handle_cmdline_show. feed('<esc>') - screen:expect([[ - ^ | - {1:~ }|*3 - | - ]]) + screen:expect({ + grid = [[ + ^ | + {1:~ }|*3 + | + ]], + cmdline = { { abort = true } }, + }) end) it('does not move cursor to curwin #20309', function() @@ -827,6 +841,30 @@ local function test_cmdline(linegrid) } }, } end) + + it('show prompt hl_id', function() + screen:expect([[ + ^ | + {1:~ }|*3 + | + ]]) + feed(':echohl Error | call input("Prompt:")<CR>') + screen:expect({ + grid = [[ + ^ | + {1:~ }|*3 + | + ]], + cmdline = { + { + content = { { '' } }, + hl_id = 242, + pos = 0, + prompt = 'Prompt:', + }, + }, + }) + end) end -- the representation of cmdline and cmdline_block contents changed with ext_linegrid @@ -1000,6 +1038,36 @@ describe('cmdline redraw', function() ]], } end) + + it('silent prompt', function() + command([[nmap <silent> T :call confirm("Save changes?", "&Yes\n&No\n&Cancel")<CR>]]) + feed('T') + screen:expect([[ + | + {3: }| + | + {6:Save changes?} | + {6:[Y]es, (N)o, (C)ancel: }^ | + ]]) + end) + + it('substitute confirm prompt does not scroll', function() + screen:try_resize(75, screen._height) + command('call setline(1, "foo")') + command('set report=0') + feed(':%s/foo/bar/c<CR>') + screen:expect([[ + {2:foo} | + {1:~ }|*3 + {6:replace with bar? (y)es/(n)o/(a)ll/(q)uit/(l)ast/scroll up(^E)/down(^Y)}^ | + ]]) + feed('y') + screen:expect([[ + ^bar | + {1:~ }|*3 + 1 substitution on 1 line | + ]]) + end) end) describe('statusline is redrawn on entering cmdline', function() @@ -1447,31 +1515,29 @@ describe('cmdheight=0', function() it('when substitute text', function() command('set cmdheight=0 noruler laststatus=3') feed('ifoo<ESC>') - screen:expect { - grid = [[ + screen:try_resize(screen._width, 7) + screen:expect([[ fo^o | - {1:~ }|*3 + {1:~ }|*5 {3:[No Name] [+] }| - ]], - } + ]]) feed(':%s/foo/bar/gc<CR>') - screen:expect { - grid = [[ + screen:expect([[ {2:foo} | - {1:~ }|*3 - {6:replace wi...q/l/^E/^Y)?}^ | - ]], - } + {3: }| + |*2 + {6:replace with bar? (y)es/(}| + {6:n)o/(a)ll/(q)uit/(l)ast/s}| + {6:croll up(^E)/down(^Y)}^ | + ]]) feed('y') - screen:expect { - grid = [[ + screen:expect([[ ^bar | - {1:~ }|*3 + {1:~ }|*5 {3:[No Name] [+] }| - ]], - } + ]]) assert_alive() end) diff --git a/test/functional/ui/cursor_spec.lua b/test/functional/ui/cursor_spec.lua index d7c0657820..825a90fbc8 100644 --- a/test/functional/ui/cursor_spec.lua +++ b/test/functional/ui/cursor_spec.lua @@ -190,6 +190,19 @@ describe('ui/cursor', function() attr_lm = {}, short_name = 'sm', }, + [18] = { + blinkoff = 500, + blinkon = 500, + blinkwait = 0, + cell_percentage = 0, + cursor_shape = 'block', + name = 'terminal', + hl_id = 3, + id_lm = 3, + attr = { reverse = true }, + attr_lm = { reverse = true }, + short_name = 't', + }, } screen:expect(function() @@ -245,17 +258,20 @@ describe('ui/cursor', function() end end if m.hl_id then - m.hl_id = 66 + m.hl_id = 65 m.attr = { background = Screen.colors.DarkGray } end if m.id_lm then - m.id_lm = 73 + m.id_lm = 72 + m.attr_lm = {} end end -- Assert the new expectation. screen:expect(function() - eq(expected_mode_info, screen._mode_info) + for i, v in ipairs(expected_mode_info) do + eq(v, screen._mode_info[i]) + end eq(true, screen._cursor_style_enabled) eq('normal', screen.mode) end) @@ -361,4 +377,38 @@ describe('ui/cursor', function() end end) end) + + it(':sleep does not hide cursor when sleeping', function() + n.feed(':sleep 100m | echo 42\n') + screen:expect({ + grid = [[ + ^ | + {1:~ }|*3 + :sleep 100m | echo 42 | + ]], + timeout = 100, + }) + screen:expect([[ + ^ | + {1:~ }|*3 + 42 | + ]]) + end) + + it(':sleep! hides cursor when sleeping', function() + n.feed(':sleep! 100m | echo 42\n') + screen:expect({ + grid = [[ + | + {1:~ }|*3 + :sleep! 100m | echo 42 | + ]], + timeout = 100, + }) + screen:expect([[ + ^ | + {1:~ }|*3 + 42 | + ]]) + end) end) diff --git a/test/functional/ui/decorations_spec.lua b/test/functional/ui/decorations_spec.lua index fbf16f3afe..7969dd5d3b 100644 --- a/test/functional/ui/decorations_spec.lua +++ b/test/functional/ui/decorations_spec.lua @@ -509,6 +509,69 @@ describe('decorations providers', function() ]]} end) + it('can have virtual text of the style: eol_right_align', function() + insert(mulholland) + setup_provider [[ + local hl = api.nvim_get_hl_id_by_name "ErrorMsg" + local test_ns = api.nvim_create_namespace "mulholland" + function on_do(event, ...) + if event == "line" then + local win, buf, line = ... + api.nvim_buf_set_extmark(buf, test_ns, line, 0, { + virt_text = {{'+'}, {'1234567890', 'ErrorMsg'}}; + virt_text_pos='eol_right_align'; + ephemeral = true; + }) + end + end + ]] + + screen:expect{grid=[[ + // just to see if there was an accident | + // on Mulholland Drive +{2:1234567890}| + try_start(); +{2:1234567890}| + bufref_T save_buf; +{2:1234567890}| + switch_buffer(&save_buf, buf); +{2:12345678}| + posp = getmark(mark, false); +{2:1234567890}| + restore_buffer(&save_buf);^ +{2:1234567890}| + | + ]]} + end) + + it('multiple eol_right_align', function() + insert(mulholland) + setup_provider [[ + local hl = api.nvim_get_hl_id_by_name "ErrorMsg" + local test_ns = api.nvim_create_namespace "mulholland" + function on_do(event, ...) + if event == "line" then + local win, buf, line = ... + api.nvim_buf_set_extmark(buf, test_ns, line, 0, { + virt_text = {{'11111'}}; + virt_text_pos='eol_right_align'; + ephemeral = true; + }) + api.nvim_buf_set_extmark(0, test_ns, line, 0, { + virt_text = {{'22222'}}; + virt_text_pos='eol_right_align'; + ephemeral = true; + }) + end + end + ]] + + screen:expect{grid=[[ + // just to see if there was an accident | + // on Mulholland Drive 11111 22222| + try_start(); 11111 22222| + bufref_T save_buf; 11111 22222| + switch_buffer(&save_buf, buf); 11111 222| + posp = getmark(mark, false); 11111 22222| + restore_buffer(&save_buf);^ 11111 22222| + | + ]]} + end) + it('virtual text works with wrapped lines', function() insert(mulholland) feed('ggJj3JjJ') @@ -631,7 +694,7 @@ describe('decorations providers', function() {14: }hello97 | {14: }hello98 | {14: }hello99 | - X ^hello100 | + {14:X }^hello100 | {14: }hello101 | {14: }hello102 | {14: }hello103 | @@ -744,6 +807,30 @@ describe('decorations providers', function() ]]) eq(2, exec_lua([[return _G.cnt]])) end) + + it('can do large changes to the marktree', function() + insert("line1 with a lot of text\nline2 with a lot of text") + setup_provider([[ + function on_do(event, _, _, row) + if event == 'win' or (event == 'line' and row == 1) then + vim.api.nvim_buf_clear_namespace(0, ns1, 0, -1) + for i = 0,1 do + for j = 0,23 do + vim.api.nvim_buf_set_extmark(0, ns1, i, j, {hl_group='ErrorMsg', end_col = j+1}) + end + end + end + end + ]]) + + -- Doesn't crash when modifying the marktree between line1 and line2 + screen:expect([[ + {2:line1 with a lot of text} | + {2:line2 with a lot of tex^t} | + {1:~ }|*5 + | + ]]) + end) end) local example_text = [[ @@ -810,6 +897,9 @@ describe('extmark decorations', function() [42] = {undercurl = true, special = Screen.colors.Red}; [43] = {background = Screen.colors.Yellow, undercurl = true, special = Screen.colors.Red}; [44] = {background = Screen.colors.LightMagenta}; + [45] = { background = Screen.colors.Red, special = Screen.colors.Red, foreground = Screen.colors.Red }; + [46] = { background = Screen.colors.Blue, foreground = Screen.colors.Blue, special = Screen.colors.Red }; + [47] = { background = Screen.colors.Green, foreground = Screen.colors.Blue, special = Screen.colors.Red }; } ns = api.nvim_create_namespace 'test' @@ -1900,6 +1990,46 @@ describe('extmark decorations', function() ]]} end) + it('highlight can combine multiple groups', function() + screen:try_resize(50, 3) + command('hi Group1 guibg=Red guifg=Red guisp=Red') + command('hi Group2 guibg=Blue guifg=Blue') + command('hi Group3 guibg=Green') + insert([[example text]]) + api.nvim_buf_set_extmark(0, ns, 0, 0, { end_row=1, hl_group = {} }) + screen:expect([[ + example tex^t | + {1:~ }| + | + ]]) + + api.nvim_buf_clear_namespace(0, ns, 0, -1) + api.nvim_buf_set_extmark(0, ns, 0, 0, { end_row=1, hl_group = {'Group1'} }) + screen:expect([[ + {45:example tex^t} | + {1:~ }| + | + ]]) + api.nvim_buf_clear_namespace(0, ns, 0, -1) + api.nvim_buf_set_extmark(0, ns, 0, 0, { end_row = 1, hl_group = {'Group1', 'Group2'} }) + screen:expect([[ + {46:example tex^t} | + {1:~ }| + | + ]]) + api.nvim_buf_clear_namespace(0, ns, 0, -1) + api.nvim_buf_set_extmark(0, ns, 0, 0, { end_row = 1, hl_group = {'Group1', 'Group2', 'Group3'}, hl_eol=true }) + screen:expect([[ + {47:example tex^t }| + {1:~ }| + | + ]]) + + eq('Invalid hl_group: hl_group item', + pcall_err(api.nvim_buf_set_extmark, 0, ns, 0, 0, { end_row = 1, hl_group = {'Group1', 'Group2', {'fail'}}, hl_eol=true })) + end) + + it('highlight works after TAB with sidescroll #14201', function() screen:try_resize(50, 3) command('set nowrap') @@ -2301,13 +2431,16 @@ describe('extmark decorations', function() it('works with both hl_group and sign_hl_group', function() screen:try_resize(50, 3) + screen:add_extra_attr_ids({ + [100] = { background = Screen.colors.WebGray, foreground = Screen.colors.Blue, bold = true }, + }) insert('abcdefghijklmn') api.nvim_buf_set_extmark(0, ns, 0, 0, {sign_text='S', sign_hl_group='NonText', hl_group='Error', end_col=14}) - screen:expect{grid=[[ - {1:S }{4:abcdefghijklm^n} | + screen:expect([[ + {100:S }{9:abcdefghijklm^n} | {1:~ }| | - ]]} + ]]) end) it('virt_text_repeat_linebreak repeats virtual text on wrapped lines', function() @@ -5064,16 +5197,16 @@ l5 api.nvim_buf_set_extmark(0, ns, 1, -1, {sign_text='S'}) - screen:expect{grid=[[ + screen:expect([[ {7: }^l1 | - S l2 | + {7:S }l2 | {7: }l3 | {7: }l4 | {7: }l5 | {7: } | {1:~ }|*3 | - ]]} + ]]) end) it('can add a single sign (with end row)', function() @@ -5082,16 +5215,16 @@ l5 api.nvim_buf_set_extmark(0, ns, 1, -1, {sign_text='S', end_row=1}) - screen:expect{grid=[[ + screen:expect([[ {7: }^l1 | - S l2 | + {7:S }l2 | {7: }l3 | {7: }l4 | {7: }l5 | {7: } | {1:~ }|*3 | - ]]} + ]]) end) it('can add a single sign and text highlight', function() @@ -5099,16 +5232,16 @@ l5 feed 'gg' api.nvim_buf_set_extmark(0, ns, 1, 0, {sign_text='S', hl_group='Todo', end_col=1}) - screen:expect{grid=[[ + screen:expect([[ {7: }^l1 | - S {100:l}2 | + {7:S }{100:l}2 | {7: }l3 | {7: }l4 | {7: }l5 | {7: } | {1:~ }|*3 | - ]]} + ]]) api.nvim_buf_clear_namespace(0, ns, 0, -1) end) @@ -5119,16 +5252,16 @@ l5 api.nvim_buf_set_extmark(0, ns, 1, -1, {sign_text='S', end_row = 2}) - screen:expect{grid=[[ + screen:expect([[ {7: }^l1 | - S l2 | - S l3 | + {7:S }l2 | + {7:S }l3 | {7: }l4 | {7: }l5 | {7: } | {1:~ }|*3 | - ]]} + ]]) end) it('can add multiple signs (multiple extmarks)', function() @@ -5138,16 +5271,16 @@ l5 api.nvim_buf_set_extmark(0, ns, 1, -1, {sign_text='S1'}) api.nvim_buf_set_extmark(0, ns, 3, -1, {sign_text='S2', end_row = 4}) - screen:expect{grid=[[ + screen:expect([[ {7: }^l1 | - S1l2 | + {7:S1}l2 | {7: }l3 | - S2l4 | - S2l5 | + {7:S2}l4 | + {7:S2}l5 | {7: } | {1:~ }|*3 | - ]]} + ]]) end) it('can add multiple signs (multiple extmarks) 2', function() @@ -5156,16 +5289,16 @@ l5 api.nvim_buf_set_extmark(0, ns, 3, -1, {sign_text='S1'}) api.nvim_buf_set_extmark(0, ns, 1, -1, {sign_text='S2', end_row = 3}) - screen:expect{grid=[[ + screen:expect([[ {7: }^l1 | - S2{7: }l2 | - S2{7: }l3 | - S2S1l4 | + {7:S2 }l2 | + {7:S2 }l3 | + {7:S2S1}l4 | {7: }l5 | {7: } | {1:~ }|*3 | - ]]} + ]]) end) it('can add multiple signs (multiple extmarks) 3', function() @@ -5176,16 +5309,16 @@ l5 api.nvim_buf_set_extmark(0, ns, 1, -1, {sign_text='S1', end_row=2}) api.nvim_buf_set_extmark(0, ns, 2, -1, {sign_text='S2', end_row=3}) - screen:expect{grid=[[ + screen:expect([[ {7: }^l1 | - S1{7: }l2 | - S2S1l3 | - S2{7: }l4 | + {7:S1 }l2 | + {7:S2S1}l3 | + {7:S2 }l4 | {7: }l5 | {7: } | {1:~ }|*3 | - ]]} + ]]) end) it('can add multiple signs (multiple extmarks) 4', function() @@ -5195,16 +5328,16 @@ l5 api.nvim_buf_set_extmark(0, ns, 0, -1, {sign_text='S1', end_row=0}) api.nvim_buf_set_extmark(0, ns, 1, -1, {sign_text='S2', end_row=1}) - screen:expect{grid=[[ - S1^l1 | - S2l2 | + screen:expect([[ + {7:S1}^l1 | + {7:S2}l2 | {7: }l3 | {7: }l4 | {7: }l5 | {7: } | {1:~ }|*3 | - ]]} + ]]) end) it('works with old signs', function() @@ -5219,16 +5352,16 @@ l5 api.nvim_buf_set_extmark(0, ns, 0, -1, {sign_text='S4'}) api.nvim_buf_set_extmark(0, ns, 2, -1, {sign_text='S5'}) - screen:expect{grid=[[ - S4S1^l1 | - S2x l2 | - S5{7: }l3 | + screen:expect([[ + {7:S4S1}^l1 | + {7:S2x }l2 | + {7:S5 }l3 | {7: }l4 | {7: }l5 | {7: } | {1:~ }|*3 | - ]]} + ]]) end) it('works with old signs (with range)', function() @@ -5244,16 +5377,16 @@ l5 api.nvim_buf_set_extmark(0, ns, 0, -1, {sign_text='S4'}) api.nvim_buf_set_extmark(0, ns, 2, -1, {sign_text='S5'}) - screen:expect{grid=[[ - S4S3S1^l1 | - S3S2x l2 | - S5S3{7: }l3 | - S3{7: }l4 | - S3{7: }l5 | + screen:expect([[ + {7:S4S3S1}^l1 | + {7:S3S2x }l2 | + {7:S5S3 }l3 | + {7:S3 }l4 | + {7:S3 }l5 | {7: } | {1:~ }|*3 | - ]]} + ]]) end) it('can add a ranged sign (with start out of view)', function() @@ -5264,14 +5397,14 @@ l5 api.nvim_buf_set_extmark(0, ns, 1, -1, {sign_text='X', end_row=3}) - screen:expect{grid=[[ - X {7: }^l3 | - X {7: }l4 | + screen:expect([[ + {7:X }^l3 | + {7:X }l4 | {7: }l5 | {7: } | {1:~ }|*5 | - ]]} + ]]) end) it('can add lots of signs', function() @@ -5293,11 +5426,11 @@ l5 api.nvim_buf_set_extmark(0, ns, i, -1, { sign_text='Z' }) end - screen:expect{grid=[[ - Z Y X W {100:a} {100:b} {100:c} {100:d} {100:e} {100:f} {100:g} {100:h} |*8 - Z Y X W {100:a} {100:b} {100:c} {100:d} {100:e} {100:f} {100:g} {100:^h} | + screen:expect([[ + {7:Z Y X W }{100:a} {100:b} {100:c} {100:d} {100:e} {100:f} {100:g} {100:h} |*8 + {7:Z Y X W }{100:a} {100:b} {100:c} {100:d} {100:e} {100:f} {100:g} {100:^h} | | - ]]} + ]]) end) it('works with priority #19716', function() @@ -5313,20 +5446,20 @@ l5 api.nvim_buf_set_extmark(0, ns, 0, -1, {sign_text='S5', priority=200}) api.nvim_buf_set_extmark(0, ns, 0, -1, {sign_text='S1', priority=1}) - screen:expect{grid=[[ - S5S4O3S2S1^l1 | + screen:expect([[ + {7:S5S4O3S2S1}^l1 | {7: }l2 | | - ]]} + ]]) -- Check truncation works too api.nvim_set_option_value('signcolumn', 'auto', {}) - screen:expect{grid=[[ - S5^l1 | + screen:expect([[ + {7:S5}^l1 | {7: }l2 | | - ]]} + ]]) end) it('does not overflow with many old signs #23852', function() @@ -5343,21 +5476,21 @@ l5 command([[exe 'sign place 07 line=1 name=Oldsign priority=10 buffer=' . bufnr('')]]) command([[exe 'sign place 08 line=1 name=Oldsign priority=10 buffer=' . bufnr('')]]) command([[exe 'sign place 09 line=1 name=Oldsign priority=10 buffer=' . bufnr('')]]) - screen:expect{grid=[[ - O3O3O3O3O3O3O3O3O3^ | + screen:expect([[ + {7:O3O3O3O3O3O3O3O3O3}^ | {1:~ }| | - ]]} + ]]) api.nvim_buf_set_extmark(0, ns, 0, -1, {sign_text='S1', priority=1}) screen:expect_unchanged() api.nvim_buf_set_extmark(0, ns, 0, -1, {sign_text='S5', priority=200}) - screen:expect{grid=[[ - S5O3O3O3O3O3O3O3O3^ | + screen:expect([[ + {7:S5O3O3O3O3O3O3O3O3}^ | {1:~ }| | - ]]} + ]]) assert_alive() end) @@ -5383,12 +5516,12 @@ l5 api.nvim_buf_set_extmark(0, ns, 1, -1, {invalidate = true, sign_text='S3'}) feed('2Gdd') - screen:expect{grid=[[ - S1l1 | - S1^l3 | - S1l4 | + screen:expect([[ + {7:S1}l1 | + {7:S1}^l3 | + {7:S1}l4 | | - ]]} + ]]) end) it('correct width with multiple overlapping signs', function() @@ -5400,36 +5533,36 @@ l5 feed('gg') local s1 = [[ - S2S1^l1 | - S3S2l2 | - S3S2l3 | + {7:S2S1}^l1 | + {7:S3S2}l2 | + {7:S3S2}l3 | | ]] - screen:expect{grid=s1} + screen:expect(s1) -- Correct width when :move'ing a line with signs command('move2') - screen:expect{grid=[[ - S3{7: }l2 | - S3S2S1^l1 | + screen:expect([[ + {7:S3 }l2 | + {7:S3S2S1}^l1 | {7: }l3 | | - ]]} + ]]) command('silent undo') screen:expect{grid=s1} command('d') - screen:expect{grid=[[ - S3S2S1^l2 | - S3S2{7: }l3 | + screen:expect([[ + {7:S3S2S1}^l2 | + {7:S3S2 }l3 | {7: }l4 | | - ]]} + ]]) command('d') - screen:expect{grid=[[ - S3S2S1^l3 | + screen:expect([[ + {7:S3S2S1}^l3 | {7: }l4 | {7: }l5 | | - ]]} + ]]) end) it('correct width when adding and removing multiple signs', function() @@ -5452,12 +5585,12 @@ l5 redraw! call nvim_buf_del_extmark(0, ns, s1) ]]) - screen:expect{grid=[[ - S1^l1 | - S1l2 | - S1l3 | + screen:expect([[ + {7:S1}^l1 | + {7:S1}l2 | + {7:S1}l3 | | - ]]} + ]]) end) it('correct width when deleting lines', function() @@ -5472,12 +5605,12 @@ l5 call nvim_buf_del_extmark(0, ns, s3) norm 4Gdd ]]) - screen:expect{grid=[[ + screen:expect([[ {7: }l3 | - S2S1l5 | + {7:S2S1}l5 | {7: }^ | | - ]]} + ]]) end) it('correct width when splitting lines with signs on different columns', function() @@ -5487,12 +5620,12 @@ l5 api.nvim_buf_set_extmark(0, ns, 0, 0, {sign_text='S1'}) api.nvim_buf_set_extmark(0, ns, 0, 1, {sign_text='S2'}) feed('a<cr><esc>') - screen:expect{grid=[[ - S1l | - S2^1 | + screen:expect([[ + {7:S1}l | + {7:S2}^1 | {7: }l2 | | - ]]} + ]]) end) it('correct width after wiping a buffer', function() @@ -5501,12 +5634,12 @@ l5 feed('gg') local buf = api.nvim_get_current_buf() api.nvim_buf_set_extmark(buf, ns, 0, 0, { sign_text = 'h' }) - screen:expect{grid=[[ - h ^l1 | + screen:expect([[ + {7:h }^l1 | {7: }l2 | {7: }l3 | | - ]]} + ]]) api.nvim_win_set_buf(0, api.nvim_create_buf(false, true)) api.nvim_buf_delete(buf, {unload=true, force=true}) api.nvim_buf_set_lines(buf, 0, -1, false, {''}) @@ -5537,12 +5670,12 @@ l5 end) ]]) - screen:expect{grid=[[ - S1^l1 | - S2l2 | - S4l3 | + screen:expect([[ + {7:S1}^l1 | + {7:S2}l2 | + {7:S4}l3 | | - ]]} + ]]) end) it('no crash with sign after many marks #27137', function() @@ -5553,11 +5686,11 @@ l5 end api.nvim_buf_set_extmark(0, ns, 0, 0, {sign_text = 'S1'}) - screen:expect{grid=[[ - S1{9:^a} | + screen:expect([[ + {7:S1}{9:^a} | {1:~ }|*2 | - ]]} + ]]) end) it('correct sort order with multiple namespaces and same id', function() @@ -5565,11 +5698,11 @@ l5 api.nvim_buf_set_extmark(0, ns, 0, 0, {sign_text = 'S1', id = 1}) api.nvim_buf_set_extmark(0, ns2, 0, 0, {sign_text = 'S2', id = 1}) - screen:expect{grid=[[ - S2S1^ | + screen:expect([[ + {7:S2S1}^ | {1:~ }|*8 | - ]]} + ]]) end) it('correct number of signs after deleting text (#27046)', function() @@ -5586,12 +5719,12 @@ l5 api.nvim_buf_set_extmark(0, ns, 30, 0, {end_row = 30, end_col = 3, hl_group = 'Error'}) command('0d29') - screen:expect{grid=[[ - S4S3S2S1{9:^foo} | - S5{7: }{9:foo} | + screen:expect([[ + {7:S4S3S2S1}{9:^foo} | + {7:S5 }{9:foo} | {1:~ }|*7 29 fewer lines | - ]]} + ]]) api.nvim_buf_clear_namespace(0, ns, 0, -1) end) @@ -5599,21 +5732,17 @@ l5 it([[correct numberwidth with 'signcolumn' set to "number" #28984]], function() command('set number numberwidth=1 signcolumn=number') api.nvim_buf_set_extmark(0, ns, 0, 0, { sign_text = 'S1' }) - screen:expect({ - grid = [[ - S1 ^ | - {1:~ }|*8 - | - ]] - }) + screen:expect([[ + {7:S1 }^ | + {1:~ }|*8 + | + ]]) api.nvim_buf_del_extmark(0, ns, 1) - screen:expect({ - grid = [[ - {8:1 }^ | - {1:~ }|*8 - | - ]] - }) + screen:expect([[ + {8:1 }^ | + {1:~ }|*8 + | + ]]) end) it('supports emoji as signs', function() @@ -5626,10 +5755,10 @@ l5 api.nvim_buf_set_extmark(0, ns, 4, 0, {sign_text='❤x'}) screen:expect([[ {7: }^l1 | - 🧑🌾l2 | - ❤️l3 | - ❤ l4 | - ❤xl5 | + {7:🧑🌾}l2 | + {7:❤️}l3 | + {7:❤ }l4 | + {7:❤x}l5 | {7: } | {1:~ }|*3 | diff --git a/test/functional/ui/diff_spec.lua b/test/functional/ui/diff_spec.lua index 95159011f1..dae373297a 100644 --- a/test/functional/ui/diff_spec.lua +++ b/test/functional/ui/diff_spec.lua @@ -2044,6 +2044,26 @@ it('diff mode overlapped diff blocks will be merged', function() {2:Xdifile1 Xdifile2 }{3:Xdifile3 }| | ]]) + + WriteDiffFiles3('a\nb\nc', 'd\ne', 'b\nf') + screen:expect([[ + {7: }{27:a}{4: }│{7: }{27:d}{4: }│{7: }{27:^b}{4: }| + {7: }{27:b}{4: }│{7: }{27:e}{4: }│{7: }{27:f}{4: }| + {7: }{22:c }│{7: }{23:---------}│{7: }{23:---------}| + {1:~ }│{1:~ }│{1:~ }|*15 + {2:Xdifile1 Xdifile2 }{3:Xdifile3 }| + | + ]]) + + WriteDiffFiles3('a\nb\nc', 'd\ne', 'b') + screen:expect([[ + {7: }{27:a}{4: }│{7: }{27:d}{4: }│{7: }{27:^b}{4: }| + {7: }{27:b}{4: }│{7: }{27:e}{4: }│{7: }{23:---------}| + {7: }{22:c }│{7: }{23:---------}│{7: }{23:---------}| + {1:~ }│{1:~ }│{1:~ }|*15 + {2:Xdifile1 Xdifile2 }{3:Xdifile3 }| + | + ]]) end) -- oldtest: Test_diff_topline_noscroll() diff --git a/test/functional/ui/float_spec.lua b/test/functional/ui/float_spec.lua index 57ef9bcff6..15231e0f8c 100644 --- a/test/functional/ui/float_spec.lua +++ b/test/functional/ui/float_spec.lua @@ -1012,6 +1012,97 @@ describe('float window', function() end) end) + it('placed relative to tabline and laststatus', function() + local screen = Screen.new(20, 10) + screen:add_extra_attr_ids({ [100] = { bold = true, foreground = Screen.colors.Magenta } }) + command('set showtabline=1 laststatus=1') + api.nvim_open_win(0, false, { + relative = 'laststatus', + border = 'single', + anchor = 'SE', + width = 5, + height = 1, + row = 0, + col = 1000, + }) + local tabwin = api.nvim_open_win(0, false, { + relative = 'tabline', + border = 'single', + width = 5, + height = 1, + row = 0, + col = 1000, + }) + screen:expect([[ + ^ {2:┌─────┐}| + {1:~ }{2:│}{4: }{2:│}| + {1:~ }{2:└─────┘}| + {1:~ }|*3 + {1:~ }{2:┌─────┐}| + {1:~ }{2:│}{4: }{2:│}| + {1:~ }{2:└─────┘}| + | + ]]) + command('tabnew | tabnext') + screen:expect([[ + {5: }{100:3}{5: Name] }{24: No Name]X}| + ^ {2:┌─────┐}| + {1:~ }{2:│}{4: }{2:│}| + {1:~ }{2:└─────┘}| + {1:~ }|*2 + {1:~ }{2:┌─────┐}| + {1:~ }{2:│}{4: }{2:│}| + {1:~ }{2:└─────┘}| + | + ]]) + command('vsplit') + screen:expect([[ + {5: }{100:4}{5: Name] }{24: No Name]X}| + ^ {2:┌─────┐}| + {1:~ }{2:│}{4: }{2:│}| + {1:~ }{2:└─────┘}| + {1:~ }{2:│}{1:~}| + {1:~ }{2:┌─────┐}| + {1:~ }{2:│}{4: }{2:│}| + {1:~ }{2:└─────┘}| + {3:[No Name] }{2:<}| + | + ]]) + command('quit') + api.nvim_win_set_config(tabwin, { + relative = 'tabline', + border = 'single', + width = 5, + height = 1, + row = 1, + col = 0, + }) + screen:expect([[ + {5: }{100:3}{5: Name] }{24: No Name]X}| + ^ | + {2:┌─────┐}{1: }| + {2:│}{4: }{2:│}{1: }| + {2:└─────┘}{1: }| + {1:~ }| + {1:~ }{2:┌─────┐}| + {1:~ }{2:│}{4: }{2:│}| + {1:~ }{2:└─────┘}| + | + ]]) + command('tabonly') + screen:expect([[ + ^ | + {2:┌─────┐}{1: }| + {2:│}{4: }{2:│}{1: }| + {2:└─────┘}{1: }| + {1:~ }|*2 + {1:~ }{2:┌─────┐}| + {1:~ }{2:│}{4: }{2:│}| + {1:~ }{2:└─────┘}| + | + ]]) + end) + local function with_ext_multigrid(multigrid) local screen, attrs before_each(function() @@ -1046,6 +1137,8 @@ describe('float window', function() [26] = {blend = 80, background = Screen.colors.Gray0}; [27] = {foreground = Screen.colors.Black, background = Screen.colors.LightGrey}; [28] = {foreground = Screen.colors.DarkBlue, background = Screen.colors.LightGrey}; + [29] = {background = Screen.colors.Yellow1, foreground = Screen.colors.Blue4}; + [30] = {background = Screen.colors.Grey, foreground = Screen.colors.Blue4, bold = true}; } screen:set_default_attr_ids(attrs) end) @@ -1451,14 +1544,14 @@ describe('float window', function() [2:----------------------------------------]|*6 [3:----------------------------------------]| ## grid 2 - {19: }{17:𐌢̀́̂̃̅̄𐌢̀́̂̃̅̄}{20: 1 }{22:^x}{21: }| + {19: }{29:𐌢̀́̂̃̅̄𐌢̀́̂̃̅̄}{20: 1 }{22:^x}{21: }| {19: }{14: 2 }{22:y} | {19: }{14: 3 }{22: } | {0:~ }|*3 ## grid 3 | ## grid 4 - {17:𐌢̀́̂̃̅̄𐌢̀́̂̃̅̄}{15:x }| + {29:𐌢̀́̂̃̅̄𐌢̀́̂̃̅̄}{15:x }| {19: }{15:y }| {19: }{15: }| {15: }| @@ -1466,9 +1559,9 @@ describe('float window', function() else screen:expect([[ - {19: }{17:𐌢̀́̂̃̅̄𐌢̀́̂̃̅̄}{20: 1 }{22:^x}{21: }| + {19: }{29:𐌢̀́̂̃̅̄𐌢̀́̂̃̅̄}{20: 1 }{22:^x}{21: }| {19: }{14: 2 }{22:y} | - {19: }{14: 3 }{22: } {17:𐌢̀́̂̃̅̄𐌢̀́̂̃̅̄}{15:x } | + {19: }{14: 3 }{22: } {29:𐌢̀́̂̃̅̄𐌢̀́̂̃̅̄}{15:x } | {0:~ }{19: }{15:y }{0: }| {0:~ }{19: }{15: }{0: }| {0:~ }{15: }{0: }| @@ -1551,14 +1644,14 @@ describe('float window', function() [2:----------------------------------------]|*6 [3:----------------------------------------]| ## grid 2 - {19: }{17:𐌢̀́̂̃̅̄𐌢̀́̂̃̅̄}{20: 1 }{22:^x}{21: }| + {19: }{29:𐌢̀́̂̃̅̄𐌢̀́̂̃̅̄}{20: 1 }{22:^x}{21: }| {19: }{14: 2 }{22:y} | {19: }{14: 3 }{22: } | {0:~ }|*3 ## grid 3 | ## grid 4 - {17:𐌢̀́̂̃̅̄𐌢̀́̂̃̅̄}{15:x }| + {29:𐌢̀́̂̃̅̄𐌢̀́̂̃̅̄}{15:x }| {19: }{15:y }| {19: }{15: }| {15: }| @@ -1566,9 +1659,9 @@ describe('float window', function() else screen:expect([[ - {19: }{17:𐌢̀́̂̃̅̄𐌢̀́̂̃̅̄}{20: 1 }{22:^x}{21: }| + {19: }{29:𐌢̀́̂̃̅̄𐌢̀́̂̃̅̄}{20: 1 }{22:^x}{21: }| {19: }{14: 2 }{22:y} | - {19: }{14: 3 }{22: } {17:𐌢̀́̂̃̅̄𐌢̀́̂̃̅̄}{15:x } | + {19: }{14: 3 }{22: } {29:𐌢̀́̂̃̅̄𐌢̀́̂̃̅̄}{15:x } | {0:~ }{19: }{15:y }{0: }| {0:~ }{19: }{15: }{0: }| {0:~ }{15: }{0: }| @@ -1616,31 +1709,34 @@ describe('float window', function() feed('ix<cr>y<cr><esc>gg') api.nvim_open_win(0, false, {relative='editor', width=20, height=4, row=4, col=10, style='minimal'}) if multigrid then - screen:expect{grid=[[ - ## grid 1 - [2:----------------------------------------]|*6 - [3:----------------------------------------]| - ## grid 2 - {20: 1}{19: }{22:^x}{21: }| - {14: 2}{19: }{22:y} | - {14: 3}{19: }{22: } | - {0:~ }|*3 - ## grid 3 - | - ## grid 4 - {15:x }| - {15:y }| - {15: }|*2 - ]], float_pos={[4] = {1001, "NW", 1, 4, 10, true}}} + screen:expect({ + grid = [[ + ## grid 1 + [2:----------------------------------------]|*6 + [3:----------------------------------------]| + ## grid 2 + {20: 1}{19: }{22:^x}{21: }| + {14: 2}{19: }{22:y} | + {14: 3}{19: }{22: } | + {0:~ }|*3 + ## grid 3 + | + ## grid 4 + {15:x }| + {15:y }| + {15: }|*2 + ]], + float_pos = { [4] = { 1001, "NW", 1, 4, 10, true, 50 } }, + }) else - screen:expect{grid=[[ + screen:expect([[ {20: 1}{19: }{22:^x}{21: }| {14: 2}{19: }{22:y} | {14: 3}{19: }{22: } {15:x } | {0:~ }{15:y }{0: }| {0:~ }{15: }{0: }|*2 | - ]]} + ]]) end end) @@ -2237,6 +2333,61 @@ describe('float window', function() | ]]} end + + -- reuse before title pos + api.nvim_win_set_config(win, {title= 'new'}) + if multigrid then + screen:expect({ + grid = [[ + ## grid 1 + [2:----------------------------------------]|*6 + [3:----------------------------------------]| + ## grid 2 + ^ | + {0:~ }|*5 + ## grid 3 + | + ## grid 4 + {5:╔══════}{11:new}{5:╗}| + {5:║}{1: halloj! }{5:║}| + {5:║}{1: BORDAA }{5:║}| + {5:╚═════════╝}| + ]], + float_pos = { + [4] = {1001, "NW", 1, 2, 5, true, 50}; + }, + win_viewport = { + [2] = {win = 1000, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0}; + [4] = {win = 1001, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 2, sum_scroll_delta = 0}; + }, + win_viewport_margins = { + [2] = { + bottom = 0, + left = 0, + right = 0, + top = 0, + win = 1000 + }, + [4] = { + bottom = 1, + left = 1, + right = 1, + top = 1, + win = 1001 + } + }, + }) + else + screen:expect([[ + ^ | + {0:~ }| + {0:~ }{5:╔══════}{11:new}{5:╗}{0: }| + {0:~ }{5:║}{1: halloj! }{5:║}{0: }| + {0:~ }{5:║}{1: BORDAA }{5:║}{0: }| + {0:~ }{5:╚═════════╝}{0: }| + | + ]]) + end end) it('border with footer', function() @@ -2382,6 +2533,61 @@ describe('float window', function() | ]]} end + + -- reuse before footer pos + api.nvim_win_set_config(win, { footer = 'new' }) + if multigrid then + screen:expect({ + grid = [[ + ## grid 1 + [2:----------------------------------------]|*6 + [3:----------------------------------------]| + ## grid 2 + ^ | + {0:~ }|*5 + ## grid 3 + | + ## grid 4 + {5:╔═════════╗}| + {5:║}{1: halloj! }{5:║}| + {5:║}{1: BORDAA }{5:║}| + {5:╚══════}{11:new}{5:╝}| + ]], + float_pos = { + [4] = {1001, "NW", 1, 2, 5, true, 50}; + }, + win_viewport = { + [2] = {win = 1000, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0}; + [4] = {win = 1001, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 2, sum_scroll_delta = 0}; + }, + win_viewport_margins = { + [2] = { + bottom = 0, + left = 0, + right = 0, + top = 0, + win = 1000 + }, + [4] = { + bottom = 1, + left = 1, + right = 1, + top = 1, + win = 1001 + } + }, + }) + else + screen:expect([[ + ^ | + {0:~ }| + {0:~ }{5:╔═════════╗}{0: }| + {0:~ }{5:║}{1: halloj! }{5:║}{0: }| + {0:~ }{5:║}{1: BORDAA }{5:║}{0: }| + {0:~ }{5:╚══════}{11:new}{5:╝}{0: }| + | + ]]) + end end) it('border with title and footer', function() @@ -8542,6 +8748,131 @@ describe('float window', function() | ]]} end + + -- + -- Check that floats are positioned correctly after changing the zindexes. + -- + command('fclose') + exec_lua([[ + local win1, win3 = ... + vim.api.nvim_win_set_config(win1, { zindex = 400, title = 'win_400', title_pos = 'center', border = 'double' }) + vim.api.nvim_win_set_config(win3, { zindex = 300, title = 'win_300', title_pos = 'center', border = 'single' }) + ]], win1, win3) + if multigrid then + screen:expect({ + grid = [[ + ## grid 1 + [2:----------------------------------------]|*6 + [3:----------------------------------------]| + ## grid 2 + ^ | + {0:~ }|*5 + ## grid 3 + | + ## grid 4 + {5:╔══════}{11:win_400}{5:═══════╗}| + {5:║}{7: }{5:║}| + {5:║}{7:~ }{5:║}|*2 + {5:╚════════════════════╝}| + ## grid 6 + {5:┌──────}{11:win_300}{5:───────┐}| + {5:│}{8: }{5:│}| + {5:│}{8:~ }{5:│}|*2 + {5:└────────────────────┘}| + ]], float_pos={ + [4] = {1001, "NW", 1, 1, 5, true, 400}; + [6] = {1003, "NW", 1, 3, 7, true, 300}; + }, win_viewport={ + [2] = {win = 1000, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0}; + [4] = {win = 1001, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0}; + [6] = {win = 1003, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0}; + }, win_viewport_margins={ + [2] = { bottom = 0, left = 0, right = 0, top = 0, win = 1000 }, + [4] = { bottom = 1, left = 1, right = 1, top = 1, win = 1001 }, + [6] = { bottom = 1, left = 1, right = 1, top = 1, win = 1003 } + }}) + else + screen:expect({ + grid = [[ + ^ | + {0:~ }{5:╔══════}{11:win_400}{5:═══════╗}{0: }| + {0:~ }{5:║}{7: }{5:║─┐}{0: }| + {0:~ }{5:║}{7:~ }{5:║}{8: }{5:│}{0: }|*2 + {0:~ }{5:╚════════════════════╝}{8: }{5:│}{0: }| + {5:└────────────────────┘} | + ]] + }) + end + exec_lua([[ + local win1, win3 = ... + vim.api.nvim_win_set_config(win1, { zindex = 100, title='win_100' }) + vim.api.nvim_win_set_config(win3, { zindex = 150, title='win_150' }) + ]], win1, win3) + if multigrid then + screen:expect({ + grid = [[ + ## grid 1 + [2:----------------------------------------]|*6 + [3:----------------------------------------]| + ## grid 2 + ^ | + {0:~ }|*5 + ## grid 3 + | + ## grid 4 + {5:╔══════}{11:win_100}{5:═══════╗}| + {5:║}{7: }{5:║}| + {5:║}{7:~ }{5:║}|*2 + {5:╚════════════════════╝}| + ## grid 6 + {5:┌──────}{11:win_150}{5:───────┐}| + {5:│}{8: }{5:│}| + {5:│}{8:~ }{5:│}|*2 + {5:└────────────────────┘}| + ]], + float_pos = { + [4] = {1001, "NW", 1, 1, 5, true, 100}; + [6] = {1003, "NW", 1, 3, 7, true, 150}; + }, + win_viewport = { + [2] = {win = 1000, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0}; + [4] = {win = 1001, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0}; + [6] = {win = 1003, topline = 0, botline = 2, curline = 0, curcol = 0, linecount = 1, sum_scroll_delta = 0}; + }, + win_viewport_margins = { + [2] = { + bottom = 0, + left = 0, + right = 0, + top = 0, + win = 1000 + }, + [4] = { + bottom = 1, + left = 1, + right = 1, + top = 1, + win = 1001 + }, + [6] = { + bottom = 1, + left = 1, + right = 1, + top = 1, + win = 1003 + } + }, + }) + else + screen:expect([[ + ^ | + {0:~ }{5:╔═┌──────}{11:win_150}{5:───────┐}{0: }| + {0:~ }{5:║}{7: }{5:│}{8: }{5:│}{0: }| + {0:~ }{5:║}{7:~}{5:│}{8:~ }{5:│}{0: }|*2 + {0:~ }{5:╚═└────────────────────┘}{0: }| + | + ]]) + end end) it('can use winbar', function() @@ -9523,4 +9854,3 @@ describe('float window', function() with_ext_multigrid(false) end) end) - diff --git a/test/functional/ui/hlstate_spec.lua b/test/functional/ui/hlstate_spec.lua index f8f5ee9488..e10c79fa48 100644 --- a/test/functional/ui/hlstate_spec.lua +++ b/test/functional/ui/hlstate_spec.lua @@ -224,10 +224,10 @@ describe('ext_hlstate detailed highlights', function() [6] = { { foreground = tonumber('0x40ffff'), fg_indexed = true }, { 5, 1 } }, [7] = { {}, { { hi_name = 'MsgArea', ui_name = 'MsgArea', kind = 'ui' } } }, }) - command(("enew | call termopen(['%s'])"):format(testprg('tty-test'))) + command(("enew | call jobstart(['%s'],{'term':v:true})"):format(testprg('tty-test'))) screen:expect([[ ^tty ready | - {1: } | + | |*5 {7: }| ]]) @@ -242,7 +242,7 @@ describe('ext_hlstate detailed highlights', function() screen:expect([[ ^tty ready | x {5:y z} | - {1: } | + | |*4 {7: }| ]]) @@ -250,7 +250,7 @@ describe('ext_hlstate detailed highlights', function() screen:expect([[ ^tty ready | x {2:y }{3:z} | - {1: } | + | |*4 {7: }| ]]) @@ -268,7 +268,7 @@ describe('ext_hlstate detailed highlights', function() else screen:expect([[ ^tty ready | - x {4:y}{2: }{3:z} | + x {2:y }{3:z} | |*5 {7: }| ]]) diff --git a/test/functional/ui/inccommand_user_spec.lua b/test/functional/ui/inccommand_user_spec.lua index 2d26d2c5e0..3eee9a6e07 100644 --- a/test/functional/ui/inccommand_user_spec.lua +++ b/test/functional/ui/inccommand_user_spec.lua @@ -253,6 +253,50 @@ describe("'inccommand' for user commands", function() ]] end) + it("can preview 'nomodifiable' buffer", function() + exec_lua([[ + vim.api.nvim_create_user_command("PreviewTest", function() end, { + preview = function(ev) + vim.bo.modifiable = true + vim.api.nvim_buf_set_lines(0, 0, -1, false, {"cats"}) + return 2 + end, + }) + ]]) + command('set inccommand=split') + + command('set nomodifiable') + eq(false, api.nvim_get_option_value('modifiable', { buf = 0 })) + + feed(':PreviewTest') + + screen:expect([[ + cats | + {1:~ }|*8 + {3:[No Name] [+] }| + | + {1:~ }|*4 + {2:[Preview] }| + :PreviewTest^ | + ]]) + feed('<Esc>') + screen:expect([[ + text on line 1 | + more text on line 2 | + oh no, even more text | + will the text ever stop | + oh well | + did the text stop | + why won't it stop | + make the text stop | + ^ | + {1:~ }|*7 + | + ]]) + + eq(false, api.nvim_get_option_value('modifiable', { buf = 0 })) + end) + it('works with inccommand=nosplit', function() command('set inccommand=nosplit') feed(':Replace text cats') diff --git a/test/functional/ui/input_spec.lua b/test/functional/ui/input_spec.lua index 90e0b3e380..98312c42c9 100644 --- a/test/functional/ui/input_spec.lua +++ b/test/functional/ui/input_spec.lua @@ -368,7 +368,7 @@ describe('input non-printable chars', function() "Xtest-overwrite" | {9:WARNING: The file has been changed since reading it!!!} | {6:Do you really want to write to it (y/n)?}u | - {6:Do you really want to write to it (y/n)?} | + {6:Do you really want to write to it (y/n)?}{18:^E} | {6:Do you really want to write to it (y/n)?}^ | ]]) @@ -379,7 +379,7 @@ describe('input non-printable chars', function() "Xtest-overwrite" | {9:WARNING: The file has been changed since reading it!!!} | {6:Do you really want to write to it (y/n)?}u | - {6:Do you really want to write to it (y/n)?} | + {6:Do you really want to write to it (y/n)?}{18:^E} | {6:Do you really want to write to it (y/n)?}n | {6:Press ENTER or type command to continue}^ | ]]) diff --git a/test/functional/ui/linematch_spec.lua b/test/functional/ui/linematch_spec.lua index b564c01eaa..3593604c49 100644 --- a/test/functional/ui/linematch_spec.lua +++ b/test/functional/ui/linematch_spec.lua @@ -1147,4 +1147,32 @@ describe('regressions', function() }, } end) + + -- oldtest: Test_linematch_3diffs_sanity_check() + it('sanity check with 3 diff buffers', function() + clear() + screen = Screen.new(75, 20) + n.api.nvim_buf_set_lines(0, 0, -1, false, { 'abcd', 'def', 'hij' }) + n.exec('rightbelow vnew') + n.api.nvim_buf_set_lines(0, 0, -1, false, { 'defq', 'hijk', 'nopq' }) + n.exec('rightbelow vnew') + n.api.nvim_buf_set_lines(0, 0, -1, false, { 'hijklm', 'nopqr', 'stuv' }) + n.exec([[ + set diffopt+=linematch:60 + windo diffthis | wincmd t + call feedkeys("Aq\<esc>") + call feedkeys("GAklm\<esc>") + call feedkeys("o") + ]]) + screen:expect([[ + {7: }{22:abcdq }│{7: }{23:----------------------}│{7: }{23:-----------------------}| + {7: }{4:def }│{7: }{4:def}{27:q}{4: }│{7: }{23:-----------------------}| + {7: }{4:hijk}{27:lm}{4: }│{7: }{4:hijk }│{7: }{4:hijk}{27:lm}{4: }| + {7: }{23:----------------------}│{7: }{4:nopq }│{7: }{4:nopq}{27:r}{4: }| + {7: }{4:^ }│{7: }{23:----------------------}│{7: }{27:stuv}{4: }| + {1:~ }│{1:~ }│{1:~ }|*13 + {3:[No Name] [+] }{2:[No Name] [+] [No Name] [+] }| + {5:-- INSERT --} | + ]]) + end) end) diff --git a/test/functional/ui/messages_spec.lua b/test/functional/ui/messages_spec.lua index 734877d262..5c55dfe910 100644 --- a/test/functional/ui/messages_spec.lua +++ b/test/functional/ui/messages_spec.lua @@ -42,104 +42,130 @@ describe('ui/ext_messages', function() it('msg_clear follows msg_show kind of confirm', function() feed('iline 1<esc>') feed(':call confirm("test")<cr>') - screen:expect { + screen:expect({ grid = [[ - line ^1 | - {1:~ }|*4 - ]], + line ^1 | + {1:~ }|*4 + ]], + cmdline = { + { + content = { { '' } }, + hl_id = 10, + pos = 0, + prompt = '[O]k: ', + }, + }, messages = { { - content = { { '\ntest\n[O]k: ', 6, 11 } }, + content = { { '\ntest\n', 6, 10 } }, + history = false, kind = 'confirm', }, }, - } - + }) feed('<cr>') - screen:expect { + screen:expect({ grid = [[ - line ^1 | - {1:~ }|*4 - ]], - } + line ^1 | + {1:~ }|*4 + ]], + cmdline = { { abort = false } }, + }) end) - it('msg_show kind=confirm,confirm_sub,emsg,wmsg,quickfix', function() + it('msg_show kinds', function() feed('iline 1\nline 2<esc>') - -- kind=confirm + -- confirm is now cmdline prompt feed(':echo confirm("test")<cr>') - screen:expect { + screen:expect({ grid = [[ - line 1 | - line ^2 | - {1:~ }|*3 - ]], + line 1 | + line ^2 | + {1:~ }|*3 + ]], + cmdline = { + { + content = { { '' } }, + hl_id = 10, + pos = 0, + prompt = '[O]k: ', + }, + }, messages = { { - content = { { '\ntest\n[O]k: ', 6, 11 } }, + content = { { '\ntest\n', 6, 10 } }, + history = false, kind = 'confirm', }, }, - } - feed('<cr><cr>') - screen:expect { + }) + feed('<cr>') + screen:expect({ grid = [[ - line 1 | - line ^2 | - {1:~ }|*3 - ]], + line 1 | + line ^2 | + {1:~ }|*3 + ]], + cmdline = { { abort = false } }, messages = { { - content = { { '\ntest\n[O]k: ', 6, 11 } }, + content = { { '\ntest\n', 6, 10 } }, + history = false, kind = 'confirm', }, { content = { { '1' } }, + history = false, kind = 'echo', }, { - content = { { 'Press ENTER or type command to continue', 6, 19 } }, + content = { { 'Press ENTER or type command to continue', 6, 18 } }, + history = false, kind = 'return_prompt', }, }, - } - feed('<cr><cr>') + }) + feed('<cr>') - -- kind=confirm_sub + -- :substitute confirm is now cmdline prompt feed(':%s/i/X/gc<cr>') - screen:expect { + screen:expect({ grid = [[ - l{2:i}ne 1 | - l{10:i}ne ^2 | - {1:~ }|*3 - ]], - messages = { + l{2:^i}ne 1 | + l{10:i}ne 2 | + {1:~ }|*3 + ]], + cmdline = { { - content = { { 'replace with X (y/n/a/q/l/^E/^Y)?', 6, 19 } }, - kind = 'confirm_sub', + content = { { '' } }, + hl_id = 18, + pos = 0, + prompt = 'replace with X? (y)es/(n)o/(a)ll/(q)uit/(l)ast/scroll up(^E)/down(^Y)', }, }, - } + }) feed('nq') -- kind=wmsg (editing readonly file) command('write ' .. fname) command('set readonly nohls') feed('G$x') - screen:expect { + screen:expect({ grid = [[ line 1 | - line ^2 | + line^ | {1:~ }|*3 ]], + cmdline = { { abort = false } }, messages = { { - content = { { 'W10: Warning: Changing a readonly file', 19, 27 } }, + content = { { 'W10: Warning: Changing a readonly file', 19, 26 } }, + history = true, kind = 'wmsg', }, }, - } + }) -- kind=wmsg ('wrapscan' after search reaches EOF) feed('uG$/i<cr>') @@ -149,9 +175,11 @@ describe('ui/ext_messages', function() line 2 | {1:~ }|*3 ]], + cmdline = { { abort = false } }, messages = { { - content = { { 'search hit BOTTOM, continuing at TOP', 19, 27 } }, + content = { { 'search hit BOTTOM, continuing at TOP', 19, 26 } }, + history = true, kind = 'wmsg', }, }, @@ -160,22 +188,21 @@ describe('ui/ext_messages', function() -- kind=emsg after :throw feed(':throw "foo"<cr>') screen:expect { - grid = [[ - l^ine 1 | - line 2 | - {1:~ }|*3 - ]], + cmdline = { { abort = false } }, messages = { { - content = { { 'Error detected while processing :', 9, 7 } }, + content = { { 'Error detected while processing :', 9, 6 } }, + history = true, kind = 'emsg', }, { - content = { { 'E605: Exception not caught: foo', 9, 7 } }, - kind = '', + content = { { 'E605: Exception not caught: foo', 9, 6 } }, + history = true, + kind = 'emsg', }, { - content = { { 'Press ENTER or type command to continue', 6, 19 } }, + content = { { 'Press ENTER or type command to continue', 6, 18 } }, + history = false, kind = 'return_prompt', }, }, @@ -191,13 +218,213 @@ describe('ui/ext_messages', function() ^line 2 | {1:~ }|*3 ]], + cmdline = { { abort = false } }, messages = { { content = { { '(2 of 2): line2' } }, + history = true, kind = 'quickfix', }, }, } + + -- search_cmd + feed('?line<cr>') + screen:expect({ + grid = [[ + ^line 1 | + line 2 | + {1:~ }|*3 + ]], + cmdline = { { abort = false } }, + messages = { + { + content = { { '?line ' } }, + history = false, + kind = 'search_cmd', + }, + }, + }) + + -- highlight + feed(':filter character highlight<CR>') + screen:expect({ + cmdline = { { abort = false } }, + messages = { + { + content = { + { '\n@character ' }, + { 'xxx', 26, 155 }, + { ' ' }, + { 'links to', 18, 5 }, + { ' Character\n@character.special ' }, + { 'xxx', 16, 156 }, + { ' ' }, + { 'links to', 18, 5 }, + { ' SpecialChar' }, + }, + history = false, + kind = 'list_cmd', + }, + }, + }) + + -- undo + feed('uu') + screen:expect({ + grid = [[ + ^ | + {1:~ }|*4 + ]], + messages = { + { + content = { { 'Already at oldest change' } }, + history = true, + kind = 'undo', + }, + }, + }) + + feed('<C-r><C-r><C-r>') + screen:expect({ + grid = [[ + line 1 | + line^ | + {1:~ }|*3 + ]], + messages = { + { + content = { { 'Already at newest change' } }, + history = true, + kind = 'undo', + }, + }, + }) + + -- kind=completion + command('set noshowmode') + feed('i<C-n>') + screen:expect({ + messages = { + { + content = { { 'The only match' } }, + history = false, + kind = 'completion', + }, + }, + }) + feed('<Esc>') + command('set showmode') + + -- kind=echoerr for nvim_echo() err + feed(':call nvim_echo([["Error"], ["Message", "Special"]], 1, #{ err:1 })<CR>') + screen:expect({ + cmdline = { { abort = false } }, + messages = { + { + content = { { 'Error', 9, 6 }, { 'Message', 16, 99 } }, + history = true, + kind = 'echoerr', + }, + }, + }) + + -- kind=verbose for nvim_echo() verbose + feed(':call nvim_echo([["Verbose Message"]], 1, #{ verbose:1 })<CR>') + screen:expect({ + cmdline = { { abort = false } }, + messages = { + { + content = { { 'Verbose Message' } }, + history = true, + kind = 'verbose', + }, + }, + }) + + -- kind=verbose for :verbose messages + feed(':1verbose filter Diff[AC] hi<CR>') + screen:expect({ + cmdline = { { abort = false } }, + messages = { + { + content = { + { '\nDiffAdd ' }, + { 'xxx', 22, 30 }, + { ' ' }, + { 'ctermbg=', 18, 5 }, + { '81 ' }, + { 'guibg=', 18, 5 }, + { 'LightBlue' }, + }, + history = false, + kind = 'list_cmd', + }, + { + content = { { '\n\tLast set from Lua (run Nvim with -V1 for more details)' } }, + history = false, + kind = 'verbose', + }, + { + content = { + { '\nDiffChange ' }, + { 'xxx', 4, 31 }, + { ' ' }, + { 'ctermbg=', 18, 5 }, + { '225 ' }, + { 'guibg=', 18, 5 }, + { 'LightMagenta' }, + }, + history = false, + kind = 'list_cmd', + }, + { + content = { { '\n\tLast set from Lua (run Nvim with -V1 for more details)' } }, + history = false, + kind = 'verbose', + }, + { + content = { { 'Press ENTER or type command to continue', 6, 18 } }, + history = false, + kind = 'return_prompt', + }, + }, + }) + + -- kind=shell for :!cmd messages + local cmd = t.is_os('win') and 'echo stdout& echo stderr>&2& exit 3' + or '{ echo stdout; echo stderr >&2; exit 3; }' + feed(('<CR>:!%s<CR>'):format(cmd)) + screen:expect({ + cmdline = { { abort = false } }, + messages = { + { + content = { { (':!%s\r\n[No write since last change]\n'):format(cmd) } }, + history = false, + kind = '', + }, + { + content = { { ('stdout%s\n'):format(t.is_os('win') and '\r' or '') } }, + history = false, + kind = 'shell_out', + }, + { + content = { { ('stderr%s\n'):format(t.is_os('win') and '\r' or ''), 9, 6 } }, + history = false, + kind = 'shell_err', + }, + { + content = { { '\nshell returned 3\n\n' } }, + history = false, + kind = 'shell_ret', + }, + { + content = { { 'Press ENTER or type command to continue', 6, 18 } }, + history = false, + kind = 'return_prompt', + }, + }, + }) end) it(':echoerr', function() @@ -207,10 +434,14 @@ describe('ui/ext_messages', function() ^ | {1:~ }|*4 ]], - messages = { { - content = { { 'raa', 9, 7 } }, - kind = 'echoerr', - } }, + cmdline = { { abort = false } }, + messages = { + { + content = { { 'raa', 9, 6 } }, + history = true, + kind = 'echoerr', + }, + }, } -- cmdline in a later input cycle clears error message @@ -233,17 +464,21 @@ describe('ui/ext_messages', function() ^ | {1:~ }|*4 ]], + cmdline = { { abort = false } }, messages = { { - content = { { 'bork', 9, 7 } }, + content = { { 'bork', 9, 6 } }, + history = true, kind = 'echoerr', }, { - content = { { 'fail', 9, 7 } }, + content = { { 'fail', 9, 6 } }, + history = true, kind = 'echoerr', }, { - content = { { 'Press ENTER or type command to continue', 6, 19 } }, + content = { { 'Press ENTER or type command to continue', 6, 18 } }, + history = false, kind = 'return_prompt', }, }, @@ -255,21 +490,26 @@ describe('ui/ext_messages', function() ^ | {1:~ }|*4 ]], + cmdline = { { abort = false } }, messages = { { - content = { { 'bork', 9, 7 } }, + content = { { 'bork', 9, 6 } }, + history = true, kind = 'echoerr', }, { - content = { { 'fail', 9, 7 } }, + content = { { 'fail', 9, 6 } }, + history = true, kind = 'echoerr', }, { - content = { { 'extrafail', 9, 7 } }, + content = { { 'extrafail', 9, 6 } }, + history = true, kind = 'echoerr', }, { - content = { { 'Press ENTER or type command to continue', 6, 19 } }, + content = { { 'Press ENTER or type command to continue', 6, 18 } }, + history = false, kind = 'return_prompt', }, }, @@ -290,13 +530,17 @@ describe('ui/ext_messages', function() ^ | {1:~ }|*4 ]], - messages = { { - content = { { 'problem', 9, 7 } }, - kind = 'echoerr', - } }, + messages = { + { + content = { { 'problem', 9, 6 } }, + history = true, + kind = 'echoerr', + }, + }, cmdline = { { prompt = 'foo> ', + hl_id = 0, content = { { '' } }, pos = 0, }, @@ -309,6 +553,7 @@ describe('ui/ext_messages', function() ^ | {1:~ }|*4 ]], + cmdline = { { abort = false } }, } eq('solution', eval('x')) @@ -318,16 +563,18 @@ describe('ui/ext_messages', function() ^ | {1:~ }|*4 ]], + cmdline = { { abort = false } }, msg_history = { - { kind = 'echoerr', content = { { 'raa', 9, 7 } } }, - { kind = 'echoerr', content = { { 'bork', 9, 7 } } }, - { kind = 'echoerr', content = { { 'fail', 9, 7 } } }, - { kind = 'echoerr', content = { { 'extrafail', 9, 7 } } }, - { kind = 'echoerr', content = { { 'problem', 9, 7 } } }, + { kind = 'echoerr', content = { { 'raa', 9, 6 } } }, + { kind = 'echoerr', content = { { 'bork', 9, 6 } } }, + { kind = 'echoerr', content = { { 'fail', 9, 6 } } }, + { kind = 'echoerr', content = { { 'extrafail', 9, 6 } } }, + { kind = 'echoerr', content = { { 'problem', 9, 6 } } }, }, messages = { { - content = { { 'Press ENTER or type command to continue', 6, 19 } }, + content = { { 'Press ENTER or type command to continue', 6, 18 } }, + history = false, kind = 'return_prompt', }, }, @@ -350,9 +597,11 @@ describe('ui/ext_messages', function() ^ | {1:~ }|*4 ]], + cmdline = { { abort = false } }, messages = { { - content = { { 'bork\nfail', 9, 7 } }, + content = { { 'bork\nfail', 9, 6 } }, + history = true, kind = 'echoerr', }, }, @@ -364,15 +613,17 @@ describe('ui/ext_messages', function() ^ | {1:~ }|*4 ]], + cmdline = { { abort = false } }, messages = { { - content = { { 'Press ENTER or type command to continue', 6, 19 } }, + content = { { 'Press ENTER or type command to continue', 6, 18 } }, + history = false, kind = 'return_prompt', }, }, msg_history = { { - content = { { 'bork\nfail', 9, 7 } }, + content = { { 'bork\nfail', 9, 6 } }, kind = 'echoerr', }, }, @@ -390,8 +641,9 @@ describe('ui/ext_messages', function() {10:line} 2 | {1:~ }|*3 ]], + cmdline = { { abort = false } }, messages = { - { content = { { '/line W [1/2]' } }, kind = 'search_count' }, + { content = { { '/line W [1/2]' } }, kind = 'search_count', history = false }, }, } @@ -403,35 +655,7 @@ describe('ui/ext_messages', function() {1:~ }|*3 ]], messages = { - { content = { { '/line [2/2]' } }, kind = 'search_count' }, - }, - } - end) - - it(':hi Group output', function() - feed(':hi ErrorMsg<cr>') - screen:expect { - grid = [[ - ^ | - {1:~ }|*4 - ]], - messages = { - { - content = { - { '\nErrorMsg ' }, - { 'xxx', 9, 7 }, - { ' ' }, - { 'ctermfg=', 18, 6 }, - { '15 ' }, - { 'ctermbg=', 18, 6 }, - { '1 ' }, - { 'guifg=', 18, 6 }, - { 'White ' }, - { 'guibg=', 18, 6 }, - { 'Red' }, - }, - kind = '', - }, + { content = { { '/line [2/2]' } }, kind = 'search_count', history = false }, }, } end) @@ -444,11 +668,13 @@ describe('ui/ext_messages', function() ^ | {1:~ }|*4 ]], + cmdline = { { abort = false } }, messages = { - { content = { { 'x #1' } }, kind = '' }, - { content = { { 'y #2' } }, kind = '' }, + { content = { { 'x #1' } }, kind = 'list_cmd', history = false }, + { content = { { 'y #2' } }, kind = 'list_cmd', history = false }, { - content = { { 'Press ENTER or type command to continue', 6, 19 } }, + content = { { 'Press ENTER or type command to continue', 6, 18 } }, + history = false, kind = 'return_prompt', }, }, @@ -463,7 +689,7 @@ describe('ui/ext_messages', function() ^ | {1:~ }|*4 ]], - showmode = { { '-- INSERT --', 5, 12 } }, + showmode = { { '-- INSERT --', 5, 11 } }, } feed('alphpabet<cr>alphanum<cr>') @@ -474,7 +700,7 @@ describe('ui/ext_messages', function() ^ | {1:~ }|*2 ]], - showmode = { { '-- INSERT --', 5, 12 } }, + showmode = { { '-- INSERT --', 5, 11 } }, } feed('<c-x>') @@ -485,7 +711,7 @@ describe('ui/ext_messages', function() ^ | {1:~ }|*2 ]], - showmode = { { '-- ^X mode (^]^D^E^F^I^K^L^N^O^Ps^U^V^Y)', 5, 12 } }, + showmode = { { '-- ^X mode (^]^D^E^F^I^K^L^N^O^Ps^U^V^Y)', 5, 11 } }, } feed('<c-p>') @@ -501,7 +727,7 @@ describe('ui/ext_messages', function() items = { { 'alphpabet', '', '', '' }, { 'alphanum', '', '', '' } }, pos = 1, }, - showmode = { { '-- Keyword Local completion (^N^P) ', 5, 12 }, { 'match 1 of 2', 6, 19 } }, + showmode = { { '-- Keyword Local completion (^N^P) ', 5, 11 }, { 'match 1 of 2', 6, 18 } }, } -- echomsg and showmode don't overwrite each other, this is the same @@ -519,11 +745,14 @@ describe('ui/ext_messages', function() items = { { 'alphpabet', '', '', '' }, { 'alphanum', '', '', '' } }, pos = 1, }, - messages = { { - content = { { 'stuff' } }, - kind = 'echomsg', - } }, - showmode = { { '-- Keyword Local completion (^N^P) ', 5, 12 }, { 'match 1 of 2', 6, 19 } }, + messages = { + { + content = { { 'stuff' } }, + history = true, + kind = 'echomsg', + }, + }, + showmode = { { '-- Keyword Local completion (^N^P) ', 5, 11 }, { 'match 1 of 2', 6, 18 } }, } feed('<c-p>') @@ -539,11 +768,14 @@ describe('ui/ext_messages', function() items = { { 'alphpabet', '', '', '' }, { 'alphanum', '', '', '' } }, pos = 0, }, - messages = { { - content = { { 'stuff' } }, - kind = 'echomsg', - } }, - showmode = { { '-- Keyword Local completion (^N^P) ', 5, 12 }, { 'match 2 of 2', 6, 19 } }, + messages = { + { + content = { { 'stuff' } }, + history = true, + kind = 'echomsg', + }, + }, + showmode = { { '-- Keyword Local completion (^N^P) ', 5, 11 }, { 'match 2 of 2', 6, 18 } }, } feed('<esc>:messages<cr>') @@ -554,13 +786,15 @@ describe('ui/ext_messages', function() alphpabe^t | {1:~ }|*2 ]], + cmdline = { { abort = false } }, msg_history = { { content = { { 'stuff' } }, kind = 'echomsg', } }, messages = { { - content = { { 'Press ENTER or type command to continue', 6, 19 } }, + content = { { 'Press ENTER or type command to continue', 6, 18 } }, + history = false, kind = 'return_prompt', }, }, @@ -574,7 +808,7 @@ describe('ui/ext_messages', function() ^ | {1:~ }|*4 ]], - showmode = { { 'recording @q', 5, 12 } }, + showmode = { { 'recording @q', 5, 11 } }, } feed('i') @@ -583,7 +817,7 @@ describe('ui/ext_messages', function() ^ | {1:~ }|*4 ]], - showmode = { { '-- INSERT --recording @q', 5, 12 } }, + showmode = { { '-- INSERT --recording @q', 5, 11 } }, } feed('<esc>') @@ -592,7 +826,7 @@ describe('ui/ext_messages', function() ^ | {1:~ }|*4 ]], - showmode = { { 'recording @q', 5, 12 } }, + showmode = { { 'recording @q', 5, 11 } }, } feed('q') @@ -611,7 +845,7 @@ describe('ui/ext_messages', function() ^ | {1:~ }|*4 ]], - showmode = { { 'recording @q', 5, 12 } }, + showmode = { { 'recording @q', 5, 11 } }, mode = 'normal', } @@ -621,7 +855,7 @@ describe('ui/ext_messages', function() ^ | {1:~ }|*4 ]], - showmode = { { 'recording @q', 5, 12 } }, + showmode = { { 'recording @q', 5, 11 } }, mode = 'insert', } @@ -631,7 +865,7 @@ describe('ui/ext_messages', function() ^ | {1:~ }|*4 ]], - showmode = { { 'recording @q', 5, 12 } }, + showmode = { { 'recording @q', 5, 11 } }, mode = 'normal', } @@ -653,7 +887,7 @@ describe('ui/ext_messages', function() ^ | {1:~ }|*4 ]], - ruler = { { '0,0-1 All', 9, 62 } }, + ruler = { { '0,0-1 All', 9, 61 } }, }) command('hi clear MsgArea') feed('i') @@ -662,7 +896,7 @@ describe('ui/ext_messages', function() ^ | {1:~ }|*4 ]], - showmode = { { '-- INSERT --', 5, 12 } }, + showmode = { { '-- INSERT --', 5, 11 } }, ruler = { { '0,1 All' } }, } feed('abcde<cr>12345<esc>') @@ -700,7 +934,7 @@ describe('ui/ext_messages', function() {17:123}45 | {1:~ }|*3 ]], - showmode = { { '-- VISUAL BLOCK --', 5, 12 } }, + showmode = { { '-- VISUAL BLOCK --', 5, 11 } }, showcmd = { { '2x3' } }, ruler = { { '1,3 All' } }, }) @@ -752,10 +986,14 @@ describe('ui/ext_messages', function() ^ | {1:~ }|*4 ]], - messages = { { - content = { { 'howdy' } }, - kind = 'echomsg', - } }, + cmdline = { { abort = false } }, + messages = { + { + content = { { 'howdy' } }, + history = true, + kind = 'echomsg', + }, + }, } -- always test a message without kind. If this one gets promoted to a @@ -769,6 +1007,7 @@ describe('ui/ext_messages', function() messages = { { content = { { 'Type :qa and press <Enter> to exit Nvim' } }, + history = true, kind = '', }, }, @@ -780,10 +1019,14 @@ describe('ui/ext_messages', function() ^ | {1:~ }|*4 ]], - messages = { { - content = { { 'bork', 9, 7 } }, - kind = 'echoerr', - } }, + cmdline = { { abort = false } }, + messages = { + { + content = { { 'bork', 9, 6 } }, + history = true, + kind = 'echoerr', + }, + }, } feed(':echo "xyz"<cr>') @@ -792,10 +1035,14 @@ describe('ui/ext_messages', function() ^ | {1:~ }|*4 ]], - messages = { { - content = { { 'xyz' } }, - kind = 'echo', - } }, + cmdline = { { abort = false } }, + messages = { + { + content = { { 'xyz' } }, + history = false, + kind = 'echo', + }, + }, } feed(':call nosuchfunction()<cr>') @@ -804,9 +1051,11 @@ describe('ui/ext_messages', function() ^ | {1:~ }|*4 ]], + cmdline = { { abort = false } }, messages = { { - content = { { 'E117: Unknown function: nosuchfunction', 9, 7 } }, + content = { { 'E117: Unknown function: nosuchfunction', 9, 6 } }, + history = true, kind = 'emsg', }, }, @@ -818,15 +1067,17 @@ describe('ui/ext_messages', function() ^ | {1:~ }|*4 ]], + cmdline = { { abort = false } }, msg_history = { { kind = 'echomsg', content = { { 'howdy' } } }, { kind = '', content = { { 'Type :qa and press <Enter> to exit Nvim' } } }, - { kind = 'echoerr', content = { { 'bork', 9, 7 } } }, - { kind = 'emsg', content = { { 'E117: Unknown function: nosuchfunction', 9, 7 } } }, + { kind = 'echoerr', content = { { 'bork', 9, 6 } } }, + { kind = 'emsg', content = { { 'E117: Unknown function: nosuchfunction', 9, 6 } } }, }, messages = { { - content = { { 'Press ENTER or type command to continue', 6, 19 } }, + content = { { 'Press ENTER or type command to continue', 6, 18 } }, + history = false, kind = 'return_prompt', }, }, @@ -851,11 +1102,14 @@ describe('ui/ext_messages', function() } feed('<cr>') - screen:expect([[ - ^ | - {1:~ }|*3 - | - ]]) + screen:expect({ + grid = [[ + ^ | + {1:~ }|*3 + | + ]], + cmdline = { { abort = false } }, + }) eq(1, eval('&cmdheight')) feed(':set cmdheight=0') @@ -874,10 +1128,13 @@ describe('ui/ext_messages', function() }, } feed('<cr>') - screen:expect([[ - ^ | - {1:~ }|*4 - ]]) + screen:expect({ + grid = [[ + ^ | + {1:~ }|*4 + ]], + cmdline = { { abort = false } }, + }) eq(0, eval('&cmdheight')) end) @@ -888,6 +1145,7 @@ describe('ui/ext_messages', function() ^ | {1:~ }|*4 ]], + cmdline = { { abort = false } }, messages = { { content = { @@ -899,9 +1157,10 @@ stack traceback: [C]: in function 'error' [string ":lua"]:1: in main chunk]], 9, - 7, + 6, }, }, + history = true, kind = 'lua_error', }, }, @@ -916,11 +1175,13 @@ stack traceback: ^ | {1:~ }|*4 ]], + cmdline = { { abort = false } }, messages = { { content = { - { "Error invoking 'test_method' on channel 1:\ncomplete\nerror\n\nmessage", 9, 7 }, + { "Error invoking 'test_method' on channel 1:\ncomplete\nerror\n\nmessage", 9, 6 }, }, + history = true, kind = 'rpc_error', }, }, @@ -940,6 +1201,7 @@ stack traceback: feed(':map<cr>') screen:expect { + cmdline = { { abort = false } }, messages = { { content = { @@ -947,7 +1209,8 @@ stack traceback: { '*', 18, 1 }, { ' k' }, }, - kind = '', + history = false, + kind = 'list_cmd', }, }, } @@ -964,10 +1227,13 @@ stack traceback: ^ | {1:~ }|*6 ]], - messages = { { - content = { { 'wildmenu wildmode' } }, - kind = '', - } }, + messages = { + { + content = { { 'wildmenu wildmode\n' } }, + history = false, + kind = 'wildlist', + }, + }, cmdline = { { firstc = ':', @@ -983,51 +1249,94 @@ stack traceback: feed('ihelllo<esc>') feed('z=') - screen:expect { + screen:expect({ grid = [[ - {100:helllo} | - {1:~ }|*3 - {1:^~ }| - ]], + {100:^helllo} | + {1:~ }|*4 + ]], + cmdline = { + { + content = { { '' } }, + hl_id = 0, + pos = 0, + prompt = 'Type number and <Enter> or click with the mouse (q or empty cancels): ', + }, + }, messages = { { - content = { - { - 'Change "helllo" to:\n 1 "Hello"\n 2 "Hallo"\n 3 "Hullo"\nType number and <Enter> or click with the mouse (q or empty cancels): ', - }, - }, - kind = '', + content = { { 'Change "helllo" to:\n 1 "Hello"\n 2 "Hallo"\n 3 "Hullo"\n' } }, + history = false, + kind = 'list_cmd', }, }, - } + }) feed('1') - screen:expect { + screen:expect({ grid = [[ - {100:helllo} | - {1:~ }|*3 - {1:^~ }| - ]], + {100:^helllo} | + {1:~ }|*4 + ]], + cmdline = { + { + content = { { '1' } }, + hl_id = 0, + pos = 1, + prompt = 'Type number and <Enter> or click with the mouse (q or empty cancels): ', + }, + }, messages = { { - content = { - { - 'Change "helllo" to:\n 1 "Hello"\n 2 "Hallo"\n 3 "Hullo"\nType number and <Enter> or click with the mouse (q or empty cancels): ', - }, - }, - kind = '', + content = { { 'Change "helllo" to:\n 1 "Hello"\n 2 "Hallo"\n 3 "Hullo"\n' } }, + history = false, + kind = 'list_cmd', }, - { content = { { '1' } }, kind = '' }, }, - } + }) feed('<cr>') - screen:expect { + screen:expect({ grid = [[ - ^Hello | - {1:~ }|*4 - ]], - } + ^Hello | + {1:~ }|*4 + ]], + cmdline = { { abort = false } }, + }) + + async_meths.nvim_command("let g:n = inputlist(['input0', 'input1'])") + screen:expect({ + grid = [[ + ^Hello | + {1:~ }|*4 + ]], + cmdline = { + { + content = { { '' } }, + hl_id = 0, + pos = 0, + prompt = 'Type number and <Enter> or click with the mouse (q or empty cancels): ', + }, + }, + messages = { + { + content = { { 'input0\ninput1\n' } }, + history = false, + kind = 'list_cmd', + }, + }, + }) + + feed('42<CR>') + screen:expect({ + grid = [[ + ^Hello | + {1:~ }|*4 + ]], + cmdline = { { + abort = false, + } }, + }) + eq(42, eval('g:n')) end) it('supports nvim_echo messages with multiple attrs', function() @@ -1043,7 +1352,8 @@ stack traceback: ]], messages = { { - content = { { 'wow, ', 10, 9 }, { 'such\n\nvery ', 9, 7 }, { 'color', 8, 13 } }, + content = { { 'wow, ', 10, 8 }, { 'such\n\nvery ', 9, 6 }, { 'color', 8, 12 } }, + history = true, kind = 'echomsg', }, }, @@ -1055,8 +1365,13 @@ stack traceback: ^ | {1:~ }|*4 ]], + cmdline = { { abort = false } }, messages = { - { content = { { '\n 1 %a "[No Name]" line 1' } }, kind = '' }, + { + content = { { '\n 1 %a "[No Name]" line 1' } }, + kind = 'list_cmd', + history = false, + }, }, } @@ -1066,15 +1381,17 @@ stack traceback: ^ | {1:~ }|*4 ]], + cmdline = { { abort = false } }, messages = { { - content = { { 'Press ENTER or type command to continue', 6, 19 } }, + content = { { 'Press ENTER or type command to continue', 6, 18 } }, + history = false, kind = 'return_prompt', }, }, msg_history = { { - content = { { 'wow, ', 10, 9 }, { 'such\n\nvery ', 9, 7 }, { 'color', 8, 13 } }, + content = { { 'wow, ', 10, 8 }, { 'such\n\nvery ', 9, 6 }, { 'color', 8, 12 } }, kind = 'echomsg', }, }, @@ -1093,7 +1410,11 @@ stack traceback: command('write ' .. fname) screen:expect({ messages = { - { content = { { string.format('"%s" [New] 0L, 0B written', fname) } }, kind = '' }, + { + content = { { string.format('"%s" [New] 0L, 0B written', fname) } }, + kind = 'bufwrite', + history = true, + }, }, }) end) @@ -1105,13 +1426,25 @@ stack traceback: screen_showmode(...) showmode = showmode + 1 end + local s1 = [[ + ^ | + {1:~ }|*4 + ]] + screen:expect(s1) + eq(showmode, 0) + feed('i') screen:expect({ - grid = [[ - ^ | - {1:~ }|*4 - ]], + grid = s1, + showmode = { { '-- INSERT --', 5, 11 } }, }) - eq(showmode, 1) + eq(showmode, 2) + command('set noshowmode') + feed('<Esc>') + screen:expect(s1) + eq(showmode, 3) + feed('i') + screen:expect_unchanged() + eq(showmode, 3) end) it('emits single message for multiline print())', function() @@ -1120,6 +1453,7 @@ stack traceback: messages = { { content = { { 'foo\nbar\nbaz' } }, + history = true, kind = 'lua_print', }, }, @@ -1133,6 +1467,7 @@ stack traceback: messages = { { content = { { '{\n foo = "bar"\n}' } }, + history = true, kind = 'lua_print', }, }, @@ -1140,6 +1475,36 @@ stack traceback: exec_lua([[vim.print({ foo = "bar" })]]) screen:expect_unchanged() end) + + it('ruler redraw does not crash due to double grid_line_start()', function() + exec_lua([[ + local ns = vim.api.nvim_create_namespace('') + vim.ui_attach(ns, { ext_messages = true }, function(event, ...) + if event == 'msg_ruler' then + vim.api.nvim__redraw({ flush = true }) + end + end) + vim.o.ruler = true + vim.o.laststatus = 0 + ]]) + feed('i') + n.assert_alive() + end) + + it(':digraph contains newlines', function() + command('digraph') + screen:expect({ + condition = function() + local nl = 0 + eq('list_cmd', screen.messages[1].kind) + for _, chunk in ipairs(screen.messages[1].content) do + nl = nl + (chunk[2]:find('\n') and 1 or 0) + end + eq(682, nl) + screen.messages = {} + end, + }) + end) end) describe('ui/builtin messages', function() @@ -1719,7 +2084,7 @@ describe('ui/ext_messages', function() {1:~ }type :help iccf{18:<Enter>} for information {1: }| {1:~ }|*5 ]] - local showmode = { { '-- INSERT --', 5, 12 } } + local showmode = { { '-- INSERT --', 5, 11 } } screen:expect(introscreen) -- <c-l> (same as :mode) does _not_ clear intro message @@ -1792,9 +2157,11 @@ describe('ui/ext_messages', function() type :help iccf{18:<Enter>} for information | |*5 ]], + cmdline = { { abort = false } }, messages = { { - content = { { 'Press ENTER or type command to continue', 6, 19 } }, + content = { { 'Press ENTER or type command to continue', 6, 18 } }, + history = false, kind = 'return_prompt', }, }, @@ -1874,8 +2241,9 @@ describe('ui/ext_messages', function() {1:~ }|*10 {3:[No Name] }| ]], + cmdline = { { abort = false } }, messages = { - { content = { { ' cmdheight=0' } }, kind = '' }, + { content = { { ' cmdheight=0' } }, kind = 'list_cmd', history = false }, }, }) @@ -1890,8 +2258,9 @@ describe('ui/ext_messages', function() {1:~ }|*9 {3:[No Name] }| ]], + cmdline = { { abort = false } }, messages = { - { content = { { ' laststatus=3' } }, kind = '' }, + { content = { { ' laststatus=3' } }, kind = 'list_cmd', history = false }, }, }) @@ -1910,8 +2279,9 @@ describe('ui/ext_messages', function() {1:~ }|*10 {3:[No Name] }| ]], + cmdline = { { abort = false } }, messages = { - { content = { { ' cmdheight=0' } }, kind = '' }, + { content = { { ' cmdheight=0' } }, kind = 'list_cmd', history = false }, }, }) end) @@ -2015,7 +2385,7 @@ describe('ui/msg_puts_printf', function() ) cmd = cmd .. '"' .. nvim_prog .. '" -u NONE -i NONE -Es -V1' - command([[call termopen(']] .. cmd .. [[')]]) + command([[call jobstart(']] .. cmd .. [[',{'term':v:true})]]) screen:expect([[ ^Exモードに入ります。ノー | マルモードに戻るには "vis| diff --git a/test/functional/ui/mode_spec.lua b/test/functional/ui/mode_spec.lua index 8c6a284cd6..01f4dda227 100644 --- a/test/functional/ui/mode_spec.lua +++ b/test/functional/ui/mode_spec.lua @@ -94,6 +94,46 @@ describe('ui mode_change event', function() } end) + -- oldtest: Test_mouse_shape_indent_norm_with_gq() + it('is restored to Normal mode after "gq" indents using :normal #12309', function() + screen:try_resize(60, 6) + n.exec([[ + func Indent() + exe "normal! \<Ignore>" + return 0 + endfunc + + setlocal indentexpr=Indent() + call setline(1, [repeat('a', 80), repeat('b', 80)]) + ]]) + + feed('ggVG') + screen:expect { + grid = [[ + {17:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}| + {17:aaaaaaaaaaaaaaaaaaaa} | + ^b{17:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb}| + {17:bbbbbbbbbbbbbbbbbbbb} | + {1:~ }| + {5:-- VISUAL LINE --} | + ]], + mode = 'visual', + } + + feed('gq') + screen:expect { + grid = [[ + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + aaaaaaaaaaaaaaaaaaaa | + ^bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb| + bbbbbbbbbbbbbbbbbbbb | + {1:~ }| + | + ]], + mode = 'normal', + } + end) + it('works in insert mode', function() feed('i') screen:expect { diff --git a/test/functional/ui/multigrid_spec.lua b/test/functional/ui/multigrid_spec.lua index 3afda0c4af..cac7174cb6 100644 --- a/test/functional/ui/multigrid_spec.lua +++ b/test/functional/ui/multigrid_spec.lua @@ -1094,7 +1094,7 @@ describe('ext_multigrid', function() end) it('supports mouse', function() - command('autocmd! nvim_popupmenu') -- Delete the default MenuPopup event handler. + command('autocmd! nvim.popupmenu') -- Delete the default MenuPopup event handler. insert('some text\nto be clicked') screen:expect{grid=[[ ## grid 1 diff --git a/test/functional/ui/output_spec.lua b/test/functional/ui/output_spec.lua index b5a09d814c..37e0e1344b 100644 --- a/test/functional/ui/output_spec.lua +++ b/test/functional/ui/output_spec.lua @@ -34,7 +34,7 @@ describe('shell command :!', function() n.nvim_set .. ' notermguicolors', }) screen:expect([[ - {1: } | + ^ | {4:~ }|*4 | {3:-- TERMINAL --} | @@ -78,7 +78,7 @@ describe('shell command :!', function() 29999: foo | 30000: foo | | - {10:Press ENTER or type command to continue}{1: } | + {10:Press ENTER or type command to continue}^ | {3:-- TERMINAL --} | ]], { diff --git a/test/functional/ui/popupmenu_spec.lua b/test/functional/ui/popupmenu_spec.lua index 8fe8975b4a..4c5b1d2bd2 100644 --- a/test/functional/ui/popupmenu_spec.lua +++ b/test/functional/ui/popupmenu_spec.lua @@ -841,7 +841,7 @@ describe('ui/ext_popupmenu', function() aunmenu PopUp " Delete the default MenuPopup event handler. - autocmd! nvim_popupmenu + autocmd! nvim.popupmenu menu PopUp.foo :let g:menustr = 'foo'<CR> menu PopUp.bar :let g:menustr = 'bar'<CR> menu PopUp.baz :let g:menustr = 'baz'<CR> @@ -1162,6 +1162,8 @@ describe('builtin popupmenu', function() [6] = { foreground = Screen.colors.White, background = Screen.colors.Red }, [7] = { background = Screen.colors.Yellow }, -- Search [8] = { foreground = Screen.colors.Red }, + [9] = { foreground = Screen.colors.Yellow, background = Screen.colors.Green }, + [10] = { foreground = Screen.colors.White, background = Screen.colors.Green }, ks = { foreground = Screen.colors.Red, background = Screen.colors.Grey }, kn = { foreground = Screen.colors.Red, background = Screen.colors.Plum1 }, xs = { foreground = Screen.colors.Black, background = Screen.colors.Grey }, @@ -1542,6 +1544,81 @@ describe('builtin popupmenu', function() end) if not multigrid then + describe('popup and preview window do not overlap', function() + before_each(function() + screen:try_resize(53, 20) + end) + + -- oldtest: Test_popup_and_previewwindow_dump_pedit() + it('with :pedit', function() + exec([[ + set previewheight=9 + silent! pedit + call setline(1, map(repeat(["ab"], 10), "v:val .. v:key")) + exec "norm! G\<C-E>\<C-E>" + ]]) + feed('o') + n.poke_eventloop() + feed('<C-X><C-N>') + screen:expect([[ + ab0 | + ab1 | + ab2 | + ab3 | + ab4 | + ab5 | + ab6 | + ab7 | + ab8 | + {s:ab0 }{c: }{3:ew][+] }| + {n:ab1 }{c: } | + {n:ab2 }{c: } | + {n:ab3 }{c: } | + {n:ab4 }{s: } | + {n:ab5 }{s: } | + {n:ab6 }{s: } | + ab0^ | + {1:~ }| + {4:[No Name] [+] }| + {2:-- Keyword Local completion (^N^P) }{5:match 1 of 10} | + ]]) + end) + + -- oldtest: Test_popup_and_previewwindow_dump_pbuffer() + it('with :pbuffer', function() + exec([[ + set previewheight=9 + silent! pbuffer + call setline(1, map(repeat(["ab"], 10), "v:val .. v:key")) + exec "norm! G\<C-E>\<C-E>\<C-E>" + ]]) + feed('o') + n.poke_eventloop() + feed('<C-X><C-N>') + screen:expect([[ + ab0 | + ab1 | + ab2 | + ab3 | + ab4 | + ab5 | + ab6 | + ab7 | + ab8 | + {s:ab0 }{c: }{3:ew][+] }| + {n:ab1 }{c: } | + {n:ab2 }{c: } | + {n:ab3 }{s: } | + {n:ab4 }{s: } | + {n:ab5 }{s: } | + ab0^ | + {1:~ }|*2 + {4:[No Name] [+] }| + {2:-- Keyword Local completion (^N^P) }{5:match 1 of 10} | + ]]) + end) + end) + -- oldtest: Test_pum_with_preview_win() it('preview window opened during completion', function() exec([[ @@ -1603,7 +1680,7 @@ describe('builtin popupmenu', function() end) end - describe('floating window preview #popup', function() + describe('floating window preview popup', function() it('pum popup preview', function() --row must > 10 screen:try_resize(40, 11) @@ -1616,14 +1693,29 @@ describe('builtin popupmenu', function() endfunc set omnifunc=Omni_test set completeopt=menu,popup - funct Set_info() let comp_info = complete_info() if comp_info['selected'] == 2 call nvim__complete_set(comp_info['selected'], {"info": "3info"}) endif endfunc - autocmd CompleteChanged * call Set_info() + funct TsHl() + let comp_info = complete_info() + if get(comp_info, 'previewbufnr', 0) > 0 + call v:lua.vim.treesitter.start(comp_info['preview_bufnr'], 'markdown') + endif + if comp_info['selected'] == 0 + call nvim__complete_set(comp_info['selected'], {"info": "```lua\nfunction test()\n print('foo')\nend\n```"}) + endif + endfunc + augroup Group + au! + autocmd CompleteChanged * :call Set_info() + augroup END + funct TestTs() + autocmd! Group + autocmd CompleteChanged * call TsHl() + endfunc ]]) feed('Gi<C-x><C-o>') --floating preview in right @@ -1684,25 +1776,26 @@ describe('builtin popupmenu', function() } end - -- info window position should be adjusted when new leader add - feed('<C-P>o') + -- delete one character make the pum width smaller than before + -- info window position should be adjusted when popupmenu width changed + feed('<BS>') if multigrid then - screen:expect { + screen:expect({ grid = [[ - ## grid 1 - [2:----------------------------------------]|*10 - [3:----------------------------------------]| - ## grid 2 - o^ | - {1:~ }|*9 - ## grid 3 - {2:-- }{8:Back at original} | - ## grid 4 - {n:1info}| - {n: }| - ## grid 5 - {n:one }| - ]], + ## grid 1 + [2:----------------------------------------]|*10 + [3:----------------------------------------]| + ## grid 2 + on^ | + {1:~ }|*9 + ## grid 3 + {2:-- }{5:match 1 of 3} | + ## grid 4 + {n:1info}| + {n: }| + ## grid 5 + {s:one }| + ]], float_pos = { [5] = { -1, 'NW', 2, 1, 0, false, 100 }, [4] = { 1001, 'NW', 1, 1, 15, false, 50 }, @@ -1713,7 +1806,7 @@ describe('builtin popupmenu', function() topline = 0, botline = 2, curline = 0, - curcol = 1, + curcol = 2, linecount = 1, sum_scroll_delta = 0, }, @@ -1727,22 +1820,88 @@ describe('builtin popupmenu', function() sum_scroll_delta = 0, }, }, - } + win_viewport_margins = { + [2] = { + bottom = 0, + left = 0, + right = 0, + top = 0, + win = 1000, + }, + [4] = { + bottom = 0, + left = 0, + right = 0, + top = 0, + win = 1001, + }, + }, + }) else - screen:expect { + screen:expect({ grid = [[ - o^ | - {n:one 1info}{1: }| - {1:~ }{n: }{1: }| - {1:~ }|*7 - {2:-- }{8:Back at original} | - ]], - } + on^ | + {s:one }{n:1info}{1: }| + {1:~ }{n: }{1: }| + {1:~ }|*7 + {2:-- }{5:match 1 of 3} | + ]], + }) + end + + -- when back to original the preview float should be closed. + feed('<C-P>') + if multigrid then + screen:expect({ + grid = [[ + ## grid 1 + [2:----------------------------------------]|*10 + [3:----------------------------------------]| + ## grid 2 + on^ | + {1:~ }|*9 + ## grid 3 + {2:-- }{8:Back at original} | + ## grid 5 + {n:one }| + ]], + float_pos = { + [5] = { -1, 'NW', 2, 1, 0, false, 100 }, + }, + win_viewport = { + [2] = { + win = 1000, + topline = 0, + botline = 2, + curline = 0, + curcol = 2, + linecount = 1, + sum_scroll_delta = 0, + }, + }, + win_viewport_margins = { + [2] = { + bottom = 0, + left = 0, + right = 0, + top = 0, + win = 1000, + }, + }, + }) + else + screen:expect({ + grid = [[ + on^ | + {n:one }{1: }| + {1:~ }|*8 + {2:-- }{8:Back at original} | + ]], + }) end -- test nvim__complete_set_info - feed('<ESC>cc<C-X><C-O><C-N><C-N>') - vim.uv.sleep(10) + feed('<ESC>S<C-X><C-O><C-N><C-N>') if multigrid then screen:expect { grid = [[ @@ -1758,13 +1917,13 @@ describe('builtin popupmenu', function() {n:one }| {n:two }| {s:looooooooooooooong }| - ## grid 6 + ## grid 7 {n:3info}| {n: }| ]], float_pos = { [5] = { -1, 'NW', 2, 1, 0, false, 100 }, - [6] = { 1002, 'NW', 1, 1, 19, false, 50 }, + [7] = { 1003, 'NW', 1, 1, 19, false, 50 }, }, win_viewport = { [2] = { @@ -1776,8 +1935,8 @@ describe('builtin popupmenu', function() linecount = 1, sum_scroll_delta = 0, }, - [6] = { - win = 1002, + [7] = { + win = 1003, topline = 0, botline = 2, curline = 0, @@ -1819,12 +1978,12 @@ describe('builtin popupmenu', function() {s: one }| {n: two }| {n: looooooooooooooong }| - ## grid 7 + ## grid 8 {n:1info}| {n: }| ]], float_pos = { - [7] = { 1003, 'NW', 1, 1, 14, false, 50 }, + [8] = { 1004, 'NW', 1, 1, 14, false, 50 }, [5] = { -1, 'NW', 2, 1, 19, false, 100 }, }, win_viewport = { @@ -1837,8 +1996,8 @@ describe('builtin popupmenu', function() linecount = 1, sum_scroll_delta = 0, }, - [7] = { - win = 1003, + [8] = { + win = 1004, topline = 0, botline = 2, curline = 0, @@ -1860,6 +2019,90 @@ describe('builtin popupmenu', function() ]], } end + feed('<C-E><Esc>') + + -- works when scroll with treesitter highlight + command('call TestTs()') + feed('S<C-x><C-o>') + if multigrid then + screen:expect({ + grid = [[ + ## grid 1 + [2:----------------------------------------]|*10 + [3:----------------------------------------]| + ## grid 2 + one^ | + {1:~ }|*9 + ## grid 3 + {2:-- }{5:match 1 of 3} | + ## grid 5 + {s:one }| + {n:two }| + {n:looooooooooooooong }| + ## grid 9 + {n:```lua }| + {n:function test()}| + {n: print('foo') }| + {n:end }| + {n:``` }| + {n: }| + ]], + float_pos = { + [5] = { -1, 'NW', 2, 1, 0, false, 100 }, + [9] = { 1005, 'NW', 1, 1, 19, false, 50 }, + }, + win_viewport = { + [2] = { + win = 1000, + topline = 0, + botline = 2, + curline = 0, + curcol = 3, + linecount = 1, + sum_scroll_delta = 0, + }, + [9] = { + win = 1005, + topline = 0, + botline = 6, + curline = 0, + curcol = 0, + linecount = 5, + sum_scroll_delta = 0, + }, + }, + win_viewport_margins = { + [2] = { + bottom = 0, + left = 0, + right = 0, + top = 0, + win = 1000, + }, + [9] = { + bottom = 0, + left = 0, + right = 0, + top = 0, + win = 1005, + }, + }, + }) + else + screen:expect({ + grid = [[ + one^ | + {s:one }{n:```lua }{1: }| + {n:two function test()}{1: }| + {n:looooooooooooooong print('foo') }{1: }| + {1:~ }{n:end }{1: }| + {1:~ }{n:``` }{1: }| + {1:~ }{n: }{1: }| + {1:~ }|*3 + {2:-- }{5:match 1 of 3} | + ]], + }) + end end) end) @@ -3846,7 +4089,7 @@ describe('builtin popupmenu', function() set mouse=a mousemodel=popup " Delete the default MenuPopup event handler. - autocmd! nvim_popupmenu + autocmd! nvim.popupmenu aunmenu PopUp menu PopUp.foo :let g:menustr = 'foo'<CR> menu PopUp.bar :let g:menustr = 'bar'<CR> @@ -4703,7 +4946,7 @@ describe('builtin popupmenu', function() it(':popup command', function() exec([[ " Delete the default MenuPopup event handler. - autocmd! nvim_popupmenu + autocmd! nvim.popupmenu func ChangeMenu() aunmenu PopUp.&Paste @@ -4863,7 +5106,7 @@ describe('builtin popupmenu', function() exec([[ set mousemodel=popup_setpos " Delete the default MenuPopup event handler. - autocmd! nvim_popupmenu + autocmd! nvim.popupmenu aunmenu * source $VIMRUNTIME/menu.vim call setline(1, join(range(20))) @@ -5172,6 +5415,45 @@ describe('builtin popupmenu', function() feed('<C-E><Esc>') end) + -- oldtest: Test_pum_highlights_match_with_abbr() + it('can highlight matched text with abbr', function() + exec([[ + func Omni_test(findstart, base) + if a:findstart + return col(".") + endif + return { + \ 'words': [ + \ { 'word': 'foobar', 'abbr': "foobar\t\t!" }, + \ { 'word': 'foobaz', 'abbr': "foobaz\t\t!" }, + \]} + endfunc + + set omnifunc=Omni_test + set completeopt=menuone,noinsert + hi PmenuMatchSel guifg=Blue guibg=Grey + hi PmenuMatch guifg=Blue guibg=Plum1 + ]]) + feed('i<C-X><C-O>') + screen:expect([[ + ^ | + {s:foobar ! }{1: }| + {n:foobaz ! }{1: }| + {1:~ }|*16 + {2:-- }{5:match 1 of 2} | + ]]) + feed('foo') + screen:expect([[ + foo^ | + {ms:foo}{s:bar ! }{1: }| + {mn:foo}{n:baz ! }{1: }| + {1:~ }|*16 + {2:-- }{5:match 1 of 2} | + ]]) + + feed('<C-E><Esc>') + end) + -- oldtest: Test_pum_user_abbr_hlgroup() it('custom abbr_hlgroup override', function() exec([[ @@ -5419,6 +5701,240 @@ describe('builtin popupmenu', function() ]]) feed('<C-E><ESC>') end) + + -- oldtest: Test_pum_matchins_highlight() + it('with ComplMatchIns highlight', function() + exec([[ + let g:change = 0 + func Omni_test(findstart, base) + if a:findstart + return col(".") + endif + if g:change == 0 + return [#{word: "foo"}, #{word: "bar"}, #{word: "你好"}] + endif + return [#{word: "foo", info: "info"}, #{word: "bar"}, #{word: "你好"}] + endfunc + set omnifunc=Omni_test + hi ComplMatchIns guifg=red + ]]) + + feed('Sαβγ <C-X><C-O>') + screen:expect([[ + αβγ {8:foo}^ | + {1:~ }{s: foo }{1: }| + {1:~ }{n: bar }{1: }| + {1:~ }{n: 你好 }{1: }| + {1:~ }|*15 + {2:-- }{5:match 1 of 3} | + ]]) + feed('<C-E><Esc>') + + feed('Sαβγ <C-X><C-O><C-N>') + screen:expect([[ + αβγ {8:bar}^ | + {1:~ }{n: foo }{1: }| + {1:~ }{s: bar }{1: }| + {1:~ }{n: 你好 }{1: }| + {1:~ }|*15 + {2:-- }{5:match 2 of 3} | + ]]) + feed('<C-E><Esc>') + + feed('Sαβγ <C-X><C-O><C-N><C-N>') + screen:expect([[ + αβγ {8:你好}^ | + {1:~ }{n: foo }{1: }| + {1:~ }{n: bar }{1: }| + {1:~ }{s: 你好 }{1: }| + {1:~ }|*15 + {2:-- }{5:match 3 of 3} | + ]]) + feed('<C-E><Esc>') + + -- restore after accept + feed('Sαβγ <C-X><C-O><C-Y>') + screen:expect([[ + αβγ foo^ | + {1:~ }|*18 + {2:-- INSERT --} | + ]]) + feed('<Esc>') + + -- restore after cancel completion + feed('Sαβγ <C-X><C-O><Space>') + screen:expect([[ + αβγ foo ^ | + {1:~ }|*18 + {2:-- INSERT --} | + ]]) + feed('<Esc>') + + -- text after the inserted text shouldn't be highlighted + feed('0ea <C-X><C-O>') + screen:expect([[ + αβγ {8:foo}^ foo | + {1:~ }{s: foo }{1: }| + {1:~ }{n: bar }{1: }| + {1:~ }{n: 你好 }{1: }| + {1:~ }|*15 + {2:-- }{5:match 1 of 3} | + ]]) + feed('<C-P>') + screen:expect([[ + αβγ ^ foo | + {1:~ }{n: foo }{1: }| + {1:~ }{n: bar }{1: }| + {1:~ }{n: 你好 }{1: }| + {1:~ }|*15 + {2:-- }{8:Back at original} | + ]]) + feed('<C-P>') + screen:expect([[ + αβγ {8:你好}^ foo | + {1:~ }{n: foo }{1: }| + {1:~ }{n: bar }{1: }| + {1:~ }{s: 你好 }{1: }| + {1:~ }|*15 + {2:-- }{5:match 3 of 3} | + ]]) + feed('<C-Y>') + screen:expect([[ + αβγ 你好^ foo | + {1:~ }|*18 + {2:-- INSERT --} | + ]]) + feed('<Esc>') + + feed(':let g:change=1<CR>S<C-X><C-O>') + screen:expect([[ + info | + {1:~ }|*2 + {3:[Scratch] [Preview] }| + {8:foo}^ | + {s:foo }{1: }| + {n:bar }{1: }| + {n:你好 }{1: }| + {1:~ }|*10 + {4:[No Name] [+] }| + {2:-- }{5:match 1 of 3} | + ]]) + feed('<Esc>') + end) + + -- oldtest: Test_pum_matchins_highlight_combine() + it('with ComplMatchIns, Normal and CursorLine highlights', function() + exec([[ + func Omni_test(findstart, base) + if a:findstart + return col(".") + endif + return [#{word: "foo"}, #{word: "bar"}, #{word: "你好"}] + endfunc + set omnifunc=Omni_test + hi Normal guibg=blue + hi CursorLine guibg=green guifg=white + set cursorline + call setline(1, 'aaa bbb') + ]]) + + -- when ComplMatchIns is not set, CursorLine applies normally + feed('0ea <C-X><C-O>') + screen:expect([[ + {10:aaa foo^ bbb }| + {1:~ }{s: foo }{1: }| + {1:~ }{n: bar }{1: }| + {1:~ }{n: 你好 }{1: }| + {1:~ }|*15 + {2:-- }{5:match 1 of 3} | + ]]) + feed('<C-E>') + screen:expect([[ + {10:aaa ^ bbb }| + {1:~ }|*18 + {2:-- INSERT --} | + ]]) + feed('<BS><Esc>') + + -- when ComplMatchIns is set, it is applied over CursorLine + command('hi ComplMatchIns guifg=Yellow') + feed('0ea <C-X><C-O>') + screen:expect([[ + {10:aaa }{9:foo}{10:^ bbb }| + {1:~ }{s: foo }{1: }| + {1:~ }{n: bar }{1: }| + {1:~ }{n: 你好 }{1: }| + {1:~ }|*15 + {2:-- }{5:match 1 of 3} | + ]]) + feed('<C-P>') + screen:expect([[ + {10:aaa ^ bbb }| + {1:~ }{n: foo }{1: }| + {1:~ }{n: bar }{1: }| + {1:~ }{n: 你好 }{1: }| + {1:~ }|*15 + {2:-- }{8:Back at original} | + ]]) + feed('<C-P>') + screen:expect([[ + {10:aaa }{9:你好}{10:^ bbb }| + {1:~ }{n: foo }{1: }| + {1:~ }{n: bar }{1: }| + {1:~ }{s: 你好 }{1: }| + {1:~ }|*15 + {2:-- }{5:match 3 of 3} | + ]]) + feed('<C-E>') + screen:expect([[ + {10:aaa ^ bbb }| + {1:~ }|*18 + {2:-- INSERT --} | + ]]) + feed('<Esc>') + + -- Does not highlight the compl leader + command('set cot+=menuone,noselect') + feed('S<C-X><C-O>') + local pum_start = [[ + {10:^ }| + {n:foo }{1: }| + {n:bar }{1: }| + {n:你好 }{1: }| + {1:~ }|*15 + {2:-- }{8:Back at original} | + ]] + screen:expect(pum_start) + feed('f<C-N>') + screen:expect([[ + {10:f}{9:oo}{10:^ }| + {s:foo }{1: }| + {1:~ }|*17 + {2:-- }{5:match 1 of 3} | + ]]) + feed('<C-E><ESC>') + + command('set cot+=fuzzy') + feed('S<C-X><C-O>') + screen:expect(pum_start) + feed('f<C-N>') + screen:expect([[ + {10:foo^ }| + {s:foo }{1: }| + {1:~ }|*17 + {2:-- }{5:match 1 of 3} | + ]]) + feed('<C-E><Esc>') + + command('set cot-=fuzzy') + feed('Sf<C-N>') + screen:expect([[ + {10:f^ }| + {1:~ }|*18 + {2:-- }{6:Pattern not found} | + ]]) + feed('<C-E><Esc>') + end) end end diff --git a/test/functional/ui/screen.lua b/test/functional/ui/screen.lua index 8e15e6c35f..6a8e7df6a0 100644 --- a/test/functional/ui/screen.lua +++ b/test/functional/ui/screen.lua @@ -37,10 +37,10 @@ -- Tests will often share a group of extra attribute sets to expect(). Those can be -- defined at the beginning of a test: -- --- screen:add_extra_attr_ids { +-- screen:add_extra_attr_ids({ -- [100] = { background = Screen.colors.Plum1, underline = true }, -- [101] = { background = Screen.colors.Red1, bold = true, underline = true }, --- } +-- }) -- -- To help write screen tests, see Screen:snapshot_util(). -- To debug screen tests, see Screen:redraw_debug(). @@ -79,6 +79,7 @@ end --- @field win_position table<integer,table<string,integer>> --- @field float_pos table<integer,table> --- @field cmdline table<integer,table> +--- @field cmdline_hide_level integer? --- @field cmdline_block table[] --- @field hl_groups table<string,integer> --- @field messages table<integer,table> @@ -454,7 +455,7 @@ end --- screen:expect(grid, [attr_ids]) --- screen:expect(condition) --- or keyword args (supports more options): ---- screen:expect{grid=[[...]], cmdline={...}, condition=function() ... end} +--- screen:expect({ grid=[[...]], cmdline={...}, condition=function() ... end }) --- --- @param expected string|function|test.function.ui.screen.Expect --- @param attr_ids? table<integer,table<string,any>> @@ -654,6 +655,12 @@ screen:redraw_debug() to show all intermediate screen states.]] end end + -- Only test the abort state of a cmdline level once. + if self.cmdline_hide_level ~= nil then + self.cmdline[self.cmdline_hide_level] = nil + self.cmdline_hide_level = nil + end + if expected.hl_groups ~= nil then for name, id in pairs(expected.hl_groups) do local expected_hl = attr_state.ids[id] @@ -967,11 +974,11 @@ function Screen:_handle_mode_info_set(cursor_style_enabled, mode_info) self._cursor_style_enabled = cursor_style_enabled for _, item in pairs(mode_info) do -- attr IDs are not stable, but their value should be - if item.attr_id ~= nil then + if item.attr_id ~= nil and self._attr_table[item.attr_id] ~= nil then item.attr = self._attr_table[item.attr_id][1] item.attr_id = nil end - if item.attr_id_lm ~= nil then + if item.attr_id_lm ~= nil and self._attr_table[item.attr_id_lm] ~= nil then item.attr_lm = self._attr_table[item.attr_id_lm][1] item.attr_id_lm = nil end @@ -1296,7 +1303,7 @@ function Screen:_handle_popupmenu_hide() self.popupmenu = nil end -function Screen:_handle_cmdline_show(content, pos, firstc, prompt, indent, level) +function Screen:_handle_cmdline_show(content, pos, firstc, prompt, indent, level, hl_id) if firstc == '' then firstc = nil end @@ -1320,11 +1327,13 @@ function Screen:_handle_cmdline_show(content, pos, firstc, prompt, indent, level firstc = firstc, prompt = prompt, indent = indent, + hl_id = prompt and hl_id, } end -function Screen:_handle_cmdline_hide(level) - self.cmdline[level] = nil +function Screen:_handle_cmdline_hide(level, abort) + self.cmdline[level] = { abort = abort } + self.cmdline_hide_level = level end function Screen:_handle_cmdline_special_char(char, shift, level) @@ -1360,12 +1369,12 @@ function Screen:_handle_wildmenu_hide() self.wildmenu_items, self.wildmenu_pos = nil, nil end -function Screen:_handle_msg_show(kind, chunks, replace_last) +function Screen:_handle_msg_show(kind, chunks, replace_last, history) local pos = #self.messages if not replace_last or pos == 0 then pos = pos + 1 end - self.messages[pos] = { kind = kind, content = chunks } + self.messages[pos] = { kind = kind, content = chunks, history = history } end function Screen:_handle_msg_clear() @@ -1468,7 +1477,9 @@ function Screen:_extstate_repr(attr_state) local cmdline = {} for i, entry in pairs(self.cmdline) do entry = shallowcopy(entry) - entry.content = self:_chunks_repr(entry.content, attr_state) + if entry.content ~= nil then + entry.content = self:_chunks_repr(entry.content, attr_state) + end cmdline[i] = entry end @@ -1479,7 +1490,11 @@ function Screen:_extstate_repr(attr_state) local messages = {} for i, entry in ipairs(self.messages) do - messages[i] = { kind = entry.kind, content = self:_chunks_repr(entry.content, attr_state) } + messages[i] = { + kind = entry.kind, + content = self:_chunks_repr(entry.content, attr_state), + history = entry.history, + } end local msg_history = {} @@ -1713,21 +1728,24 @@ function Screen:_print_snapshot() end end local fn_name = modify_attrs and 'add_extra_attr_ids' or 'set_default_attr_ids' - attrstr = ('screen:' .. fn_name .. ' {\n' .. table.concat(attrstrs, '\n') .. '\n}\n\n') + attrstr = ('screen:' .. fn_name .. '({\n' .. table.concat(attrstrs, '\n') .. '\n})\n\n') end - local result = ('%sscreen:expect({\n grid = [[\n %s\n ]]'):format( - attrstr, - kwargs.grid:gsub('\n', '\n ') - ) + local extstr = '' for _, k in ipairs(ext_keys) do if ext_state[k] ~= nil and not (k == 'win_viewport' and not self.options.ext_multigrid) then - result = result .. ', ' .. k .. '=' .. fmt_ext_state(k, ext_state[k]) + extstr = extstr .. '\n ' .. k .. ' = ' .. fmt_ext_state(k, ext_state[k]) .. ',' end end - result = result .. '\n})' - return result + return ('%sscreen:expect(%s%s%s%s%s'):format( + attrstr, + #extstr > 0 and '{\n grid = [[\n ' or '[[\n', + #extstr > 0 and kwargs.grid:gsub('\n', '\n ') or kwargs.grid, + #extstr > 0 and '\n ]],' or '\n]]', + extstr, + #extstr > 0 and '\n})' or ')' + ) end function Screen:print_snapshot() diff --git a/test/functional/ui/screen_basic_spec.lua b/test/functional/ui/screen_basic_spec.lua index f39e9ecc33..295c40b9b6 100644 --- a/test/functional/ui/screen_basic_spec.lua +++ b/test/functional/ui/screen_basic_spec.lua @@ -2,7 +2,7 @@ local t = require('test.testutil') local n = require('test.functional.testnvim')() local Screen = require('test.functional.ui.screen') -local spawn, set_session, clear = n.spawn, n.set_session, n.clear +local set_session, clear = n.set_session, n.clear local feed, command = n.feed, n.command local exec = n.exec local insert = n.insert @@ -26,7 +26,7 @@ describe('screen', function() } before_each(function() - local screen_nvim = spawn(nvim_argv) + local screen_nvim = n.new_session(false, { args = nvim_argv, merge = false }) set_session(screen_nvim) screen = Screen.new() end) @@ -645,6 +645,59 @@ local function screen_tests(linegrid) | ]]) end) + + it('clamps &cmdheight for current tabpage', function() + command('set cmdheight=10 laststatus=2') + screen:expect([[ + ^ | + {0:~ }|*2 + {1:[No Name] }| + |*10 + ]]) + screen:try_resize(53, 8) + screen:expect([[ + ^ | + {1:[No Name] }| + |*6 + ]]) + eq(6, api.nvim_get_option_value('cmdheight', {})) + end) + + it('clamps &cmdheight for another tabpage #31380', function() + command('tabnew') + command('set cmdheight=9 laststatus=2') + screen:expect([[ + {4: [No Name] }{2: [No Name] }{3: }{4:X}| + ^ | + {0:~ }|*2 + {1:[No Name] }| + |*9 + ]]) + command('tabprev') + screen:expect([[ + {2: [No Name] }{4: [No Name] }{3: }{4:X}| + ^ | + {0:~ }|*10 + {1:[No Name] }| + | + ]]) + screen:try_resize(53, 8) + screen:expect([[ + {2: [No Name] }{4: [No Name] }{3: }{4:X}| + ^ | + {0:~ }|*4 + {1:[No Name] }| + | + ]]) + command('tabnext') + screen:expect([[ + {4: [No Name] }{2: [No Name] }{3: }{4:X}| + ^ | + {1:[No Name] }| + |*5 + ]]) + eq(5, api.nvim_get_option_value('cmdheight', {})) + end) end) describe('press enter', function() @@ -713,7 +766,7 @@ describe('Screen default colors', function() 'colorscheme vim', '--embed', } - local screen_nvim = spawn(nvim_argv) + local screen_nvim = n.new_session(false, { args = nvim_argv, merge = false }) set_session(screen_nvim) screen = Screen.new(53, 14, { rgb = true, ext_termcolors = termcolors or nil }) end diff --git a/test/functional/ui/sign_spec.lua b/test/functional/ui/sign_spec.lua index 7874c04c39..ff03d86979 100644 --- a/test/functional/ui/sign_spec.lua +++ b/test/functional/ui/sign_spec.lua @@ -14,6 +14,10 @@ describe('Signs', function() screen = Screen.new() screen:add_extra_attr_ids { [100] = { bold = true, foreground = Screen.colors.Magenta1 }, + [101] = { foreground = Screen.colors.DarkBlue, background = Screen.colors.Yellow1 }, + [102] = { foreground = Screen.colors.Brown, background = Screen.colors.Yellow }, + [103] = { background = Screen.colors.Yellow, reverse = true }, + [104] = { reverse = true, foreground = Screen.colors.Grey100, background = Screen.colors.Red }, } end) @@ -27,8 +31,8 @@ describe('Signs', function() sign place 2 line=2 name=piet2 buffer=1 ]]) screen:expect([[ - {10:𐌢̀́̂̃̅̄𐌢̀́̂̃̅̄}a | - {10:𠜎̀́̂̃̄̅}b | + {101:𐌢̀́̂̃̅̄𐌢̀́̂̃̅̄}a | + {101:𠜎̀́̂̃̄̅}b | {7: }^ | {1:~ }|*10 | @@ -45,9 +49,9 @@ describe('Signs', function() sign place 3 line=1 name=pietx buffer=1 ]]) screen:expect([[ - {10:>!}a | + {101:>!}a | {7: }b | - {10:>>}c | + {101:>>}c | {7: }^ | {1:~ }|*9 | @@ -80,13 +84,13 @@ describe('Signs', function() ]]) screen:expect([[ {7: }{21:^a }| - {10:>>}b | + {101:>>}b | {7: }c | {7: } | {1:~ }|*2 {3:[No Name] [+] }| {7: }{21:a }| - {10:>>}b | + {101:>>}b | {7: }c | {7: } | {1:~ }| @@ -110,10 +114,10 @@ describe('Signs', function() sign place 6 line=4 name=pietxx buffer=1 ]]) screen:expect([[ - {10:>>}{8: 1 }a | + {101:>>}{8: 1 }a | {7: }{8: 2 }{9:b }| {7: }{13: 3 }c | - {10:>>}{13: 4 }{9:^ }| + {101:>>}{13: 4 }{9:^ }| {1:~ }|*9 | ]]) @@ -132,33 +136,33 @@ describe('Signs', function() set cursorline ]]) screen:expect([[ - {10:>>}a | - {10:>>}b | + {101:>>}a | + {101:>>}b | {9:>>}{21:^c }| {1:~ }|*10 | ]]) feed('k') screen:expect([[ - {10:>>}a | + {101:>>}a | {9:>>}{21:^b }| - {10:>>}c | + {101:>>}c | {1:~ }|*10 | ]]) exec('set nocursorline') screen:expect([[ - {10:>>}a | - {10:>>}^b | - {10:>>}c | + {101:>>}a | + {101:>>}^b | + {101:>>}c | {1:~ }|*10 | ]]) exec('set cursorline cursorlineopt=line') screen:expect([[ - {10:>>}a | - {10:>>}{21:^b }| - {10:>>}c | + {101:>>}a | + {101:>>}{21:^b }| + {101:>>}c | {1:~ }|*10 | ]]) @@ -166,13 +170,14 @@ describe('Signs', function() exec('hi! link SignColumn IncSearch') feed('Go<esc>2G') screen:expect([[ - {10:>>}a | - {9:>>}^b | - {10:>>}c | + {103:>>}a | + {104:>>}^b | + {103:>>}c | {2: } | {1:~ }|*9 | ]]) + -- Check that 'statuscolumn' cursorline/signcolumn highlights are the same (#21726) exec('set statuscolumn=%s') screen:expect_unchanged() @@ -196,7 +201,7 @@ describe('Signs', function() screen:expect([[ {7: }{8: 1 }a | {7: }{8: 2 }b | - WW{10:>>}{8: 3 }c | + {7:WW}{101:>>}{8: 3 }c | {7: }{8: 4 }^ | {1:~ }|*9 | @@ -209,9 +214,9 @@ describe('Signs', function() sign place 3 line=2 name=pietError buffer=1 ]]) screen:expect([[ - {9:XX}{10:>>}{8: 1 }a | - {10:>>}{9:XX}{8: 2 }b | - WW{10:>>}{8: 3 }c | + {9:XX}{101:>>}{8: 1 }a | + {101:>>}{9:XX}{8: 2 }b | + {7:WW}{101:>>}{8: 3 }c | {7: }{8: 4 }^ | {1:~ }|*9 | @@ -220,8 +225,8 @@ describe('Signs', function() exec('set signcolumn=yes:1') screen:expect([[ {9:XX}{8: 1 }a | - {10:>>}{8: 2 }b | - WW{8: 3 }c | + {101:>>}{8: 2 }b | + {7:WW}{8: 3 }c | {7: }{8: 4 }^ | {1:~ }|*9 | @@ -229,9 +234,9 @@ describe('Signs', function() -- "auto:3" accommodates all the signs we defined so far. exec('set signcolumn=auto:3') local s3 = [[ - {9:XX}{10:>>}{7: }{8: 1 }a | - {10:>>}{9:XX}{7: }{8: 2 }b | - WW{10:>>}{9:XX}{8: 3 }c | + {9:XX}{101:>>}{7: }{8: 1 }a | + {101:>>}{9:XX}{7: }{8: 2 }b | + {7:WW}{101:>>}{9:XX}{8: 3 }c | {7: }{8: 4 }^ | {1:~ }|*9 | @@ -240,9 +245,9 @@ describe('Signs', function() -- Check "yes:9". exec('set signcolumn=yes:9') screen:expect([[ - {9:XX}{10:>>}{7: }{8: 1 }a | - {10:>>}{9:XX}{7: }{8: 2 }b | - WW{10:>>}{9:XX}{7: }{8: 3 }c | + {9:XX}{101:>>}{7: }{8: 1 }a | + {101:>>}{9:XX}{7: }{8: 2 }b | + {7:WW}{101:>>}{9:XX}{7: }{8: 3 }c | {7: }{8: 4 }^ | {1:~ }|*9 | @@ -255,8 +260,8 @@ describe('Signs', function() exec('3move1') exec('2d') screen:expect([[ - {9:XX}{10:>>}{8: 1 }a | - {10:>>}{9:XX}{8: 2 }^b | + {9:XX}{101:>>}{8: 1 }a | + {101:>>}{9:XX}{8: 2 }^b | {7: }{8: 3 } | {1:~ }|*10 | @@ -264,8 +269,8 @@ describe('Signs', function() -- character deletion does not delete signs. feed('x') screen:expect([[ - {9:XX}{10:>>}{8: 1 }a | - {10:>>}{9:XX}{8: 2 }^ | + {9:XX}{101:>>}{8: 1 }a | + {101:>>}{9:XX}{8: 2 }^ | {7: }{8: 3 } | {1:~ }|*10 | @@ -301,7 +306,7 @@ describe('Signs', function() exec('sign define pietSearch text=>> texthl=Search') exec('sign place 1 line=1 name=pietSearch buffer=1') screen:expect([[ - {10:>>}{7: }{8: 1 }a | + {101:>>}{7: }{8: 1 }a | {7: }{8: 2 }b | {7: }{8: 3 }c | {7: }{8: 4 }^ | @@ -316,7 +321,7 @@ describe('Signs', function() sign place 4 line=1 name=pietSearch buffer=1 ]]) screen:expect([[ - {10:>>>>>>>>}{8: 1 }a | + {101:>>>>>>>>}{8: 1 }a | {7: }{8: 2 }b | {7: }{8: 3 }c | {7: }{8: 4 }^ | @@ -328,7 +333,7 @@ describe('Signs', function() screen:expect_unchanged() exec('sign unplace 4') screen:expect([[ - {10:>>>>>>}{8: 1 }a | + {101:>>>>>>}{8: 1 }a | {7: }{8: 2 }b | {7: }{8: 3 }c | {7: }{8: 4 }^ | @@ -345,7 +350,7 @@ describe('Signs', function() sign place 8 line=1 name=pietSearch buffer=1 ]]) screen:expect([[ - {10:>>>>>>>>>>}{8: 1 }a | + {101:>>>>>>>>>>}{8: 1 }a | {7: }{8: 2 }b | {7: }{8: 3 }c | {7: }{8: 4 }^ | @@ -375,7 +380,7 @@ describe('Signs', function() -- single column with 1 sign with text and one sign without exec('sign place 1 line=1 name=pietSearch buffer=1') screen:expect([[ - {10:>>}{8: 1 }a | + {101:>>}{8: 1 }a | {7: }{8: 2 }b | {7: }{8: 3 }c | {7: }{8: 4 }^ | @@ -396,7 +401,7 @@ describe('Signs', function() -- line number should be drawn if sign has no text -- no signcolumn, line number for "a" is Search, for "b" is Error, for "c" is LineNr screen:expect([[ - {10: >> }a | + {101: >> }a | {9: 2 }b | {8: 3 }c | {8: 4 }^ | @@ -406,7 +411,7 @@ describe('Signs', function() -- number column on wrapped part of a line should be empty feed('gg100aa<Esc>') screen:expect([[ - {10: >> }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {101: >> }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {9: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {9: }aa^a | {9: 2 }b | @@ -423,7 +428,7 @@ describe('Signs', function() -- number column on virtual lines should be empty screen:expect([[ {8: }VIRT LINES | - {10: >> }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {101: >> }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {9: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {9: }aa^a | {9: 2 }b | @@ -439,7 +444,7 @@ describe('Signs', function() exec('sign place 100000 line=1 name=piet buffer=1') feed(':sign place<cr>') screen:expect([[ - {10:>>} | + {101:>>} | {1:~ }|*6 {3: }| :sign place | @@ -452,7 +457,7 @@ describe('Signs', function() feed('<cr>') screen:expect([[ - {10:>>}^ | + {101:>>}^ | {1:~ }|*12 | ]]) @@ -470,7 +475,7 @@ describe('Signs', function() {7: }a | {7: }^c | {7: }d | - >>e | + {7:>>}e | {1:~ }|*9 | ]]) @@ -498,7 +503,7 @@ describe('Signs', function() {7: }b | {7: }c | {7: }d | - >>e | + {7:>>}e | {1:~ }|*7 | ]]) @@ -550,7 +555,7 @@ describe('Signs', function() exec('silent undo') screen:expect([[ {7: }1 | - S1^2 | + {7:S1}^2 | {7: }3 | {7: }4 | {1:~ }|*9 @@ -575,23 +580,19 @@ describe('Signs', function() sign place 2 line=9 name=S2 ]]) -- Now placed at end of buffer - local s1 = { - grid = [[ - S2^ | - {1:~ }|*12 - | - ]], - } + local s1 = [[ + {7:S2}^ | + {1:~ }|*12 + | + ]] screen:expect(s1) -- Signcolumn tracking used to not count signs placed beyond end of buffer here exec('set signcolumn=auto:9') - screen:expect({ - grid = [[ - S2S1^ | - {1:~ }|*12 - | - ]], - }) + screen:expect([[ + {7:S2S1}^ | + {1:~ }|*12 + | + ]]) -- Unplacing the sign does not crash by decrementing tracked signs below zero exec('sign unplace 1') screen:expect(s1) @@ -607,4 +608,77 @@ describe('Signs', function() eq(6, infos[1].textoff) eq(6, infos[2].textoff) end) + + it('auto width updated in all windows after sign placed in on_win #31438', function() + exec_lua([[ + vim.cmd.call('setline(1, range(1, 500))') + vim.cmd('wincmd s | wincmd v | wincmd j | wincmd v') + + _G.log, _G.needs_clear = {}, false + local ns_id, mark_id = vim.api.nvim_create_namespace('test'), nil + + -- Add decoration which possibly clears all extmarks and adds one on line 499 + local on_win = function(_, winid, bufnr, toprow, botrow) + if _G.needs_clear then + vim.api.nvim_buf_clear_namespace(bufnr, ns_id, 0, -1) + _G.needs_clear = false + end + + if toprow < 499 and 499 <= botrow then + mark_id = vim.api.nvim_buf_set_extmark(bufnr, ns_id, 499, 0, { id = mark_id, sign_text = '!', invalidate = true }) + end + end + vim.api.nvim_set_decoration_provider(ns_id, { on_win = on_win }) + ]]) + screen:expect([[ + 1 │1 | + 2 │2 | + 3 │3 | + 4 │4 | + 5 │5 | + 6 │6 | + {2:[No Name] [+] [No Name] [+] }| + ^1 │1 | + 2 │2 | + 3 │3 | + 4 │4 | + 5 │5 | + {3:[No Name] [+] }{2:[No Name] [+] }| + | + ]]) + feed('G') + screen:expect([[ + {7: }1 │{7: }1 | + {7: }2 │{7: }2 | + {7: }3 │{7: }3 | + {7: }4 │{7: }4 | + {7: }5 │{7: }5 | + {7: }6 │{7: }6 | + {2:[No Name] [+] [No Name] [+] }| + {7: }496 │{7: }1 | + {7: }497 │{7: }2 | + {7: }498 │{7: }3 | + {7: }499 │{7: }4 | + {7:! }^500 │{7: }5 | + {3:[No Name] [+] }{2:[No Name] [+] }| + | + ]]) + feed(':lua log, needs_clear = {}, true<CR>') + screen:expect([[ + {7: }1 │{7: }1 | + {7: }2 │{7: }2 | + {7: }3 │{7: }3 | + {7: }4 │{7: }4 | + {7: }5 │{7: }5 | + {7: }6 │{7: }6 | + {2:[No Name] [+] [No Name] [+] }| + {7: }496 │{7: }1 | + {7: }497 │{7: }2 | + {7: }498 │{7: }3 | + {7: }499 │{7: }4 | + {7:! }^500 │{7: }5 | + {3:[No Name] [+] }{2:[No Name] [+] }| + :lua log, needs_clear = {}, true | + ]]) + end) end) diff --git a/test/functional/ui/statuscolumn_spec.lua b/test/functional/ui/statuscolumn_spec.lua index 268e7173e6..328e212a22 100644 --- a/test/functional/ui/statuscolumn_spec.lua +++ b/test/functional/ui/statuscolumn_spec.lua @@ -13,8 +13,6 @@ local api = n.api local pcall_err = t.pcall_err local assert_alive = n.assert_alive -local mousemodels = { 'extend', 'popup', 'popup_setpos' } - describe('statuscolumn', function() local screen before_each(function() @@ -229,15 +227,28 @@ describe('statuscolumn', function() {1: }{8:8│}aaaaa | | ]]) + -- Last segment and fillchar are highlighted properly + command("set stc=%#Error#%{v:relnum?'Foo':'FooBar'}") + screen:expect([[ + {9:Foo }aaaaa |*4 + {9:FooBar}^aaaaa | + {9:Foo }aaaaa |*8 + | + ]]) end) it('works with wrapped lines, signs and folds', function() - command([[set stc=%C%s%=%{v:virtnum?'':v:lnum}│\ ]]) - command("call setline(1,repeat([repeat('aaaaa',10)],16))") screen:add_extra_attr_ids { [100] = { foreground = Screen.colors.Red, background = Screen.colors.LightGray }, + [101] = { background = Screen.colors.Gray90, bold = true }, + [102] = { foreground = Screen.colors.Brown, background = Screen.colors.Grey }, + [103] = { bold = true, background = Screen.colors.Grey, foreground = Screen.colors.Blue1 }, } - command('hi! CursorLine guifg=Red guibg=NONE') + command([[set cursorline stc=%C%s%=%{v:virtnum?'':v:lnum}│\ ]]) + command("call setline(1,repeat([repeat('aaaaa',10)],16))") + command('hi! CursorLine gui=bold') + command('sign define num1 numhl=Special') + command('sign place 1 line=8 name=num1 buffer=1') screen:expect([[ {8: 4│ }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {8: │ }a | @@ -247,8 +258,8 @@ describe('statuscolumn', function() {8: │ }a | {8: 7│ }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {8: │ }a | - {8: 8│ }^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| - {8: │ }a | + {29: 8│ }{101:^aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}| + {29: │ }{101:a }| {8: 9│ }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {8: │ }a | {8:10│ }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa{1:@@@}| @@ -256,7 +267,8 @@ describe('statuscolumn', function() ]]) command([[set stc=%C%s%=%l│\ ]]) screen:expect_unchanged() - command('set signcolumn=auto:2 foldcolumn=auto') + command('hi! CursorLine guifg=Red guibg=NONE gui=NONE') + command('set nocursorline signcolumn=auto:2 foldcolumn=auto') command('sign define piet1 text=>> texthl=LineNr') command('sign define piet2 text=>! texthl=NonText') command('sign place 1 line=4 name=piet1 buffer=1') @@ -264,11 +276,11 @@ describe('statuscolumn', function() command('sign place 3 line=6 name=piet1 buffer=1') command('sign place 4 line=6 name=piet2 buffer=1') screen:expect([[ - {8:>>}{7: }{8: 4│ }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {102:>>}{7: }{8: 4│ }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: │ }aaaaa | - {1:>!}{7: }{8: 5│ }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {103:>!}{7: }{8: 5│ }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: │ }aaaaa | - {1:>!}{8:>> 6│ }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {103:>!}{102:>>}{8: 6│ }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: │ }aaaaa | {7: }{8: 7│ }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: │ }aaaaa | @@ -283,11 +295,11 @@ describe('statuscolumn', function() -- Check that alignment works properly with signs after %= command([[set stc=%C%=%{v:virtnum?'':v:lnum}│%s\ ]]) screen:expect([[ - {7: }{8: 4│>>}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {7: }{8: 4│}{102:>>}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: │}{7: }{8: }aaaaaa | - {7: }{8: 5│}{1:>!}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {7: }{8: 5│}{103:>!}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: │}{7: }{8: }aaaaaa | - {7: }{8: 6│}{1:>!}{8:>> }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {7: }{8: 6│}{103:>!}{102:>>}{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: │}{7: }{8: }aaaaaa | {7: }{8: 7│}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: │}{7: }{8: }aaaaaa | @@ -300,11 +312,11 @@ describe('statuscolumn', function() ]]) command('set cursorline') screen:expect([[ - {7: }{8: 4│>>}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {7: }{8: 4│}{102:>>}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: │}{7: }{8: }aaaaaa | - {7: }{8: 5│}{1:>!}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {7: }{8: 5│}{103:>!}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: │}{7: }{8: }aaaaaa | - {7: }{8: 6│}{1:>!}{8:>> }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {7: }{8: 6│}{103:>!}{102:>>}{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: │}{7: }{8: }aaaaaa | {7: }{8: 7│}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: │}{7: }{8: }aaaaaa | @@ -318,11 +330,11 @@ describe('statuscolumn', function() -- v:lnum is the same value on wrapped lines command([[set stc=%C%=%{v:lnum}│%s\ ]]) screen:expect([[ - {7: }{8: 4│>>}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {7: }{8: 4│}{102:>>}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: 4│}{7: }{8: }aaaaaa | - {7: }{8: 5│}{1:>!}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {7: }{8: 5│}{103:>!}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: 5│}{7: }{8: }aaaaaa | - {7: }{8: 6│}{1:>!}{8:>> }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {7: }{8: 6│}{103:>!}{102:>>}{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: 6│}{7: }{8: }aaaaaa | {7: }{8: 7│}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: 7│}{7: }{8: }aaaaaa | @@ -336,11 +348,11 @@ describe('statuscolumn', function() -- v:relnum is the same value on wrapped lines command([[set stc=%C%=\ %{v:relnum}│%s\ ]]) screen:expect([[ - {7: }{8: 4│>>}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {7: }{8: 4│}{102:>>}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: 4│}{7: }{8: }aaaaaaa | - {7: }{8: 3│}{1:>!}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {7: }{8: 3│}{103:>!}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: 3│}{7: }{8: }aaaaaaa | - {7: }{8: 2│}{1:>!}{8:>> }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {7: }{8: 2│}{103:>!}{102:>>}{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: 2│}{7: }{8: }aaaaaaa | {7: }{8: 1│}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: 1│}{7: }{8: }aaaaaaa | @@ -353,11 +365,11 @@ describe('statuscolumn', function() ]]) command([[set stc=%C%=\ %{v:virtnum?'':v:relnum}│%s\ ]]) screen:expect([[ - {7: }{8: 4│>>}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {7: }{8: 4│}{102:>>}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: │}{7: }{8: }aaaaaaa | - {7: }{8: 3│}{1:>!}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {7: }{8: 3│}{103:>!}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: │}{7: }{8: }aaaaaaa | - {7: }{8: 2│}{1:>!}{8:>> }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {7: }{8: 2│}{103:>!}{102:>>}{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: │}{7: }{8: }aaaaaaa | {7: }{8: 1│}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: │}{7: }{8: }aaaaaaa | @@ -378,11 +390,11 @@ describe('statuscolumn', function() command('sign place 10 line=6 name=piet2 buffer=1') command('sign place 11 line=6 name=piet1 buffer=1') screen:expect([[ - {7: }{8: 4│>>}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {7: }{8: 4│}{102:>>}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: │}{7: }{8: }aaaaaaaaaaaaaaaaaaaaa | - {7: }{8: 3│}{1:>!}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {7: }{8: 3│}{103:>!}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: │}{7: }{8: }aaaaaaaaaaaaaaaaaaaaa | - {7: }{8: 2│>>}{1:>!}{8:>>}{1:>!}{8:>>}{1:>!}{8:>>}{1:>!}{8:>> }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {7: }{8: 2│}{102:>>}{103:>!}{102:>>}{103:>!}{102:>>}{103:>!}{102:>>}{103:>!}{102:>>}{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: │}{7: }{8: }aaaaaaaaaaaaaaaaaaaaa | {7: }{8: 1│}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }{8: │}{7: }{8: }aaaaaaaaaaaaaaaaaaaaa | @@ -397,11 +409,11 @@ describe('statuscolumn', function() command('set cpoptions+=n') feed('Hgjg0') screen:expect([[ - {7: }{15: 0│}{8:>>}{7: }{15: }{19:aaaaaaaaaaaaaaaaaaaaaaaaaaaaa}| + {7: }{15: 0│}{102:>>}{7: }{15: }{19:aaaaaaaaaaaaaaaaaaaaaaaaaaaaa}| {7: }{19:^aaaaaaaaaaaaaaaaaaaaa }| - {7: }{8: 3│}{1:>!}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {7: }{8: 3│}{103:>!}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }aaaaaaaaaaaaaaaaaaaaa | - {7: }{8: 2│>>}{1:>!}{8:>>}{1:>!}{8:>>}{1:>!}{8:>>}{1:>!}{8:>> }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {7: }{8: 2│}{102:>>}{103:>!}{102:>>}{103:>!}{102:>>}{103:>!}{102:>>}{103:>!}{102:>>}{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }aaaaaaaaaaaaaaaaaaaaa | {7: }{8: 1│}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }aaaaaaaaaaaaaaaaaaaaa | @@ -416,11 +428,11 @@ describe('statuscolumn', function() command('sign unplace 2') feed('J2gjg0') screen:expect([[ - {7: }{15: 0│}{8:>>}{7: }{15: }{19:aaaaaaaaaaaaaaaaaaaaaaaaaaaaa}| + {7: }{15: 0│}{102:>>}{7: }{15: }{19:aaaaaaaaaaaaaaaaaaaaaaaaaaaaa}| {7: } {19:aaaaaaaaaaaaaaaaaaaaa aaaaaaa}| {7: } {19:aaaaaaaaaaaaaaaaaaaaaaaaaaaaa}| {7: } {19:^aaaaaaaaaaaaaa }| - {7: }{8: 1│>>}{1:>!}{8:>>}{1:>!}{8:>>}{1:>!}{8:>>}{1:>!}{8:>> }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {7: }{8: 1│}{102:>>}{103:>!}{102:>>}{103:>!}{102:>>}{103:>!}{102:>>}{103:>!}{102:>>}{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: } aaaaaaaaaaaaaaaaaaaaa | {7: }{8: 2│}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: } aaaaaaaaaaaaaaaaaaaaa | @@ -434,11 +446,11 @@ describe('statuscolumn', function() command('set nobreakindent') feed('$g0') screen:expect([[ - {7: }{15: 0│}{8:>>}{7: }{15: }{19:aaaaaaaaaaaaaaaaaaaaaaaaaaaaa}| + {7: }{15: 0│}{102:>>}{7: }{15: }{19:aaaaaaaaaaaaaaaaaaaaaaaaaaaaa}| {7: }{19:aaaaaaaaaaaaaaaaaaaaa aaaaaaaaaaaa}| {7: }{19:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}| {7: }{19:^aaaa }| - {7: }{8: 1│>>}{1:>!}{8:>>}{1:>!}{8:>>}{1:>!}{8:>>}{1:>!}{8:>> }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa| + {7: }{8: 1│}{102:>>}{103:>!}{102:>>}{103:>!}{102:>>}{103:>!}{102:>>}{103:>!}{102:>>}{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }aaaaaaaaaaaaaaaaaaaaa | {7: }{8: 2│}{7: }{8: }aaaaaaaaaaaaaaaaaaaaaaaaaaaaa| {7: }aaaaaaaaaaaaaaaaaaaaa | @@ -460,11 +472,11 @@ describe('statuscolumn', function() ]]) command('set foldcolumn=0 signcolumn=number stc=%l') screen:expect([[ - {8:>>}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | + {102:>>}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | {8: 5}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | {8: }virt_line | {8: }virt_line above | - {8:>>}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | + {102:>>}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | {8: 7}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | {15: 8}{100:^+-- 1 line: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}| {8: 9}aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | @@ -580,13 +592,13 @@ describe('statuscolumn', function() command([[set stc=%6s\ %l]]) exec_lua('vim.api.nvim_buf_set_extmark(0, ns, 7, 0, {sign_text = "𒀀"})') screen:expect([[ - {8: 𒀀 8}^aaaaa | + {8: }{7:𒀀 }{8: 8}^aaaaa | {8: }{7: }{8: 9}aaaaa | | ]]) end) - for _, model in ipairs(mousemodels) do + for _, model in ipairs({ 'extend', 'popup', 'popup_setpos' }) do describe('with mousemodel=' .. model, function() before_each(function() command('set mousemodel=' .. model) @@ -645,23 +657,56 @@ describe('statuscolumn', function() -- Check that statusline click doesn't register as statuscolumn click api.nvim_input_mouse('right', 'press', '', 0, 12, 0) eq('', eval('g:testvar')) + -- Check that rightclick still opens popupmenu if there is no clickdef + if model == 'popup' then + api.nvim_set_option_value('statuscolumn', '%0@MyClickFunc@%=%l%TNoClick', {}) + api.nvim_input_mouse('right', 'press', '', 0, 1, 0) + screen:expect([[ + {5:[No Name] }| + {8: 4NoClick}^aaaaa | + {8: 5NoClick}aaaaa | + {8: 6NoClick}aaaaa | + {8: 7NoClick}aaaaa | + {8: 8NoClick}aaaaa | + {8: 9NoClick}aaaaa | + {8:10NoClick}aaaaa | + {8:11NoClick}aaaaa | + {8:12NoClick}aaaaa | + {8:13NoClick}aaaaa | + {8:14NoClick}aaaaa | + {3:[No Name] [+] }| + | + ]]) + api.nvim_input_mouse('right', 'press', '', 0, 1, 3) + screen:expect([[ + {5:[No Name] }| + {8: 4NoClick}^aaaaa | + {8: 5}{4: Inspect } | + {8: 6}{4: } | + {8: 7}{4: Paste } | + {8: 8}{4: Select All } | + {8: 9}{4: } | + {8:10}{4: How-to disable mouse } | + {8:11NoClick}aaaaa | + {8:12NoClick}aaaaa | + {8:13NoClick}aaaaa | + {8:14NoClick}aaaaa | + {3:[No Name] [+] }| + | + ]]) + end end) it('clicks and highlights work with control characters', function() api.nvim_set_option_value('statuscolumn', '\t%#NonText#\1%0@MyClickFunc@\t\1%T\t%##\1', {}) - screen:expect { - grid = [[ - {1:^I}{0:^A^I^A^I}{1:^A}aaaaa |*4 - {1:^I}{0:^A^I^A^I}{1:^A}^aaaaa | - {1:^I}{0:^A^I^A^I}{1:^A}aaaaa |*8 + screen:expect([[ + {8:^I}{1:^A^I^A^I}{8:^A}aaaaa |*4 + {8:^I}{1:^A^I^A^I}{8:^A}^aaaaa | + {8:^I}{1:^A^I^A^I}{8:^A}aaaaa |*8 | - ]], - attr_ids = { - [0] = { foreground = Screen.colors.Blue, bold = true }, -- NonText - [1] = { foreground = Screen.colors.Brown }, -- LineNr - }, - } + ]]) api.nvim_input_mouse('right', 'press', '', 0, 4, 3) + feed('<Esc>') -- Close popupmenu eq('', eval('g:testvar')) api.nvim_input_mouse('left', 'press', '', 0, 5, 8) eq('', eval('g:testvar')) @@ -707,6 +752,36 @@ describe('statuscolumn', function() | ]]) end) + + it('foldcolumn item can be clicked', function() + api.nvim_set_option_value('statuscolumn', '|%C|', {}) + api.nvim_set_option_value('foldcolumn', '2', {}) + api.nvim_set_option_value('mousetime', 0, {}) + feed('ggzfjzfjzo') + local s1 = [[ + {8:|}{7:-+}{8:|}{13:^+--- 2 lines: aaaaa·····························}| + {8:|}{7:│ }{8:|}aaaaa | + {8:|}{7: }{8:|}aaaaa |*11 + | + ]] + screen:expect(s1) + api.nvim_input_mouse('left', 'press', '', 0, 0, 2) + screen:expect([[ + {8:|}{7:--}{8:|}^aaaaa | + {8:|}{7:││}{8:|}aaaaa | + {8:|}{7:│ }{8:|}aaaaa | + {8:|}{7: }{8:|}aaaaa |*10 + | + ]]) + api.nvim_input_mouse('left', 'press', '', 0, 0, 1) + screen:expect(s1) + api.nvim_input_mouse('left', 'press', '', 0, 0, 1) + screen:expect([[ + {8:|}{7:+ }{8:|}{13:^+-- 3 lines: aaaaa······························}| + {8:|}{7: }{8:|}aaaaa |*12 + | + ]]) + end) end) end diff --git a/test/functional/ui/statusline_spec.lua b/test/functional/ui/statusline_spec.lua index 1d0f181244..50e31ac6a9 100644 --- a/test/functional/ui/statusline_spec.lua +++ b/test/functional/ui/statusline_spec.lua @@ -507,268 +507,263 @@ describe('global statusline', function() end) end) -it('statusline does not crash if it has Arabic characters #19447', function() - clear() - api.nvim_set_option_value('statusline', 'غً', {}) - api.nvim_set_option_value('laststatus', 2, {}) - command('redraw!') - assert_alive() -end) +describe('statusline', function() + local screen + before_each(function() + clear() + screen = Screen.new(40, 8) + screen:add_extra_attr_ids { + [100] = { bold = true, reverse = true, foreground = Screen.colors.Blue }, + [101] = { reverse = true, bold = true, foreground = Screen.colors.SlateBlue }, + } + end) -it('statusline is redrawn with :resize from <Cmd> mapping #19629', function() - clear() - local screen = Screen.new(40, 8) - exec([[ - set laststatus=2 - nnoremap <Up> <cmd>resize -1<CR> - nnoremap <Down> <cmd>resize +1<CR> - ]]) - feed('<Up>') - screen:expect([[ - ^ | - {1:~ }|*4 - {3:[No Name] }| - |*2 - ]]) - feed('<Down>') - screen:expect([[ - ^ | - {1:~ }|*5 - {3:[No Name] }| - | - ]]) -end) + it('does not crash if it has Arabic characters #19447', function() + api.nvim_set_option_value('statusline', 'غً', {}) + api.nvim_set_option_value('laststatus', 2, {}) + command('redraw!') + assert_alive() + end) -it('showcmdloc=statusline does not show if statusline is too narrow', function() - clear() - local screen = Screen.new(40, 8) - command('set showcmd') - command('set showcmdloc=statusline') - command('1vsplit') - screen:expect([[ - ^ │ | - {1:~}│{1:~ }|*5 - {3:< }{2:[No Name] }| - | - ]]) - feed('1234') - screen:expect_unchanged() -end) + it('is redrawn with :resize from <Cmd> mapping #19629', function() + exec([[ + set laststatus=2 + nnoremap <Up> <cmd>resize -1<CR> + nnoremap <Down> <cmd>resize +1<CR> + ]]) + feed('<Up>') + screen:expect([[ + ^ | + {1:~ }|*4 + {3:[No Name] }| + |*2 + ]]) + feed('<Down>') + screen:expect([[ + ^ | + {1:~ }|*5 + {3:[No Name] }| + | + ]]) + end) -it('K_EVENT does not trigger a statusline redraw unnecessarily', function() - clear() - local _ = Screen.new(40, 8) - -- does not redraw on vim.schedule (#17937) - command([[ - set laststatus=2 - let g:counter = 0 - func Status() - let g:counter += 1 - lua vim.schedule(function() end) - return g:counter - endfunc - set statusline=%!Status() - ]]) - sleep(50) - eq(1, eval('g:counter < 50'), 'g:counter=' .. eval('g:counter')) - -- also in insert mode - feed('i') - sleep(50) - eq(1, eval('g:counter < 50'), 'g:counter=' .. eval('g:counter')) - -- does not redraw on timer call (#14303) - command([[ - let g:counter = 0 - func Timer(timer) - endfunc - call timer_start(1, 'Timer', {'repeat': 100}) - ]]) - sleep(50) - eq(1, eval('g:counter < 50'), 'g:counter=' .. eval('g:counter')) -end) + it('does not contain showmcd with showcmdloc=statusline when too narrow', function() + command('set showcmd') + command('set showcmdloc=statusline') + command('1vsplit') + screen:expect([[ + ^ │ | + {1:~}│{1:~ }|*5 + {3:< }{2:[No Name] }| + | + ]]) + feed('1234') + screen:expect_unchanged() + end) -it('statusline is redrawn on various state changes', function() - clear() - local screen = Screen.new(40, 4) - - -- recording state change #22683 - command('set ls=2 stl=%{repeat(reg_recording(),5)}') - screen:expect([[ - ^ | - {1:~ }| - {3: }| - | - ]]) - feed('qQ') - screen:expect([[ - ^ | - {1:~ }| - {3:QQQQQ }| - {5:recording @Q} | - ]]) - feed('q') - screen:expect([[ - ^ | - {1:~ }| - {3: }| - | - ]]) - - -- Visual mode change #23932 - command('set ls=2 stl=%{mode(1)}') - screen:expect([[ - ^ | - {1:~ }| - {3:n }| - | - ]]) - feed('v') - screen:expect([[ - ^ | - {1:~ }| - {3:v }| - {5:-- VISUAL --} | - ]]) - feed('V') - screen:expect([[ - ^ | - {1:~ }| - {3:V }| - {5:-- VISUAL LINE --} | - ]]) - feed('<C-V>') - screen:expect([[ - ^ | - {1:~ }| - {3:^V }| - {5:-- VISUAL BLOCK --} | - ]]) - feed('<Esc>') - screen:expect([[ - ^ | - {1:~ }| - {3:n }| - | - ]]) -end) + it('does not redraw unnecessarily after K_EVENT', function() + -- does not redraw on vim.schedule (#17937) + command([[ + set laststatus=2 + let g:counter = 0 + func Status() + let g:counter += 1 + lua vim.schedule(function() end) + return g:counter + endfunc + set statusline=%!Status() + ]]) + sleep(50) + eq(1, eval('g:counter < 50'), 'g:counter=' .. eval('g:counter')) + -- also in insert mode + feed('i') + sleep(50) + eq(1, eval('g:counter < 50'), 'g:counter=' .. eval('g:counter')) + -- does not redraw on timer call (#14303) + command([[ + let g:counter = 0 + func Timer(timer) + endfunc + call timer_start(1, 'Timer', {'repeat': 100}) + ]]) + sleep(50) + eq(1, eval('g:counter < 50'), 'g:counter=' .. eval('g:counter')) + end) -it('ruler is redrawn in cmdline with redrawstatus #22804', function() - clear() - local screen = Screen.new(40, 2) - command([[ - let g:n = 'initial value' - set ls=1 ru ruf=%{g:n} - redraw - let g:n = 'other value' - redrawstatus - ]]) - screen:expect([[ - ^ | - other value | - ]]) -end) + it('is redrawn on various state changes', function() + -- recording state change #22683 + command('set ls=2 stl=%{repeat(reg_recording(),5)}') + local s1 = [[ + ^ | + {1:~ }|*5 + {3: }| + | + ]] + screen:expect(s1) + feed('qQ') + screen:expect([[ + ^ | + {1:~ }|*5 + {3:QQQQQ }| + {5:recording @Q} | + ]]) + feed('q') + screen:expect(s1) + + -- Visual mode change #23932 + command('set ls=2 stl=%{mode(1)}') + local s2 = [[ + ^ | + {1:~ }|*5 + {3:n }| + | + ]] + screen:expect(s2) + feed('v') + screen:expect([[ + ^ | + {1:~ }|*5 + {3:v }| + {5:-- VISUAL --} | + ]]) + feed('V') + screen:expect([[ + ^ | + {1:~ }|*5 + {3:V }| + {5:-- VISUAL LINE --} | + ]]) + feed('<C-V>') + screen:expect([[ + ^ | + {1:~ }|*5 + {3:^V }| + {5:-- VISUAL BLOCK --} | + ]]) + feed('<Esc>') + screen:expect(s2) + end) -it('shows correct ruler in cmdline with no statusline', function() - clear() - local screen = Screen.new(30, 8) - -- Use long ruler to check 'ruler' with 'rulerformat' set has correct width. - command [[ - set ruler rulerformat=%{winnr()}longlonglong ls=0 winwidth=10 - split - wincmd b - vsplit - wincmd t - wincmd | - mode - ]] - -- Window 1 is current. It has a statusline, so cmdline should show the - -- last window's ruler, which has no statusline. - command '1wincmd w' - screen:expect [[ - ^ | - {1:~ }|*2 - {3:[No Name] 1longlonglong }| - │ | - {1:~ }│{1:~ }|*2 - 3longlonglong | - ]] - -- Window 2 is current. It has no statusline, so cmdline should show its - -- ruler instead. - command '2wincmd w' - screen:expect [[ - | - {1:~ }|*2 - {2:[No Name] 1longlonglong }| - ^ │ | - {1:~ }│{1:~ }|*2 - 2longlonglong | - ]] - -- Window 3 is current. Cmdline should again show its ruler. - command '3wincmd w' - screen:expect [[ - | - {1:~ }|*2 - {2:[No Name] 1longlonglong }| - │^ | - {1:~ }│{1:~ }|*2 - 3longlonglong | - ]] -end) + it('ruler is redrawn in cmdline with redrawstatus #22804', function() + command([[ + let g:n = 'initial value' + set ls=1 ru ruf=%{g:n} + redraw + let g:n = 'other value' + redrawstatus + ]]) + screen:expect([[ + ^ | + {1:~ }|*6 + other value | + ]]) + end) -it('uses "stl" and "stlnc" fillchars even if they are the same #19803', function() - clear() - local screen = Screen.new(53, 4) - command('hi clear StatusLine') - command('hi clear StatusLineNC') - command('vsplit') - screen:expect { - grid = [[ - ^ │ | - {1:~ }│{1:~ }| - [No Name] [No Name] | - | - ]], - } -end) + it('hidden moves ruler to cmdline', function() + -- Use long ruler to check 'ruler' with 'rulerformat' set has correct width. + command [[ + set ruler rulerformat=%{winnr()}longlonglong ls=0 winwidth=10 + split + wincmd b + vsplit + wincmd t + wincmd | + mode + ]] + -- Window 1 is current. It has a statusline, so cmdline should show the + -- last window's ruler, which has no statusline. + command '1wincmd w' + screen:expect([[ + ^ | + {1:~ }|*2 + {3:[No Name] 1longlonglong }| + │ | + {1:~ }│{1:~ }|*2 + 3longlonglong | + ]]) + -- Window 2 is current. It has no statusline, so cmdline should show its + -- ruler instead. + command '2wincmd w' + screen:expect([[ + | + {1:~ }|*2 + {2:[No Name] 1longlonglong }| + ^ │ | + {1:~ }│{1:~ }|*2 + 2longlonglong | + ]]) + -- Window 3 is current. Cmdline should again show its ruler. + command '3wincmd w' + screen:expect([[ + | + {1:~ }|*2 + {2:[No Name] 1longlonglong }| + │^ | + {1:~ }│{1:~ }|*2 + 3longlonglong | + ]]) + end) -it('showcmdloc=statusline works with vertical splits', function() - clear() - local screen = Screen.new(53, 4) - command('rightbelow vsplit') - command('set showcmd showcmdloc=statusline') - feed('1234') - screen:expect([[ - │^ | - {1:~ }│{1:~ }| - {2:[No Name] }{3:[No Name] 1234 }| - | - ]]) - feed('<Esc>') - command('set laststatus=3') - feed('1234') - screen:expect([[ - │^ | - {1:~ }│{1:~ }| - {3:[No Name] 1234 }| - | - ]]) -end) + it('uses "stl" and "stlnc" fillchars even if they are the same #19803', function() + command('hi clear StatusLine') + command('hi clear StatusLineNC') + command('vsplit') + screen:expect([[ + ^ │ | + {1:~ }│{1:~ }|*5 + [No Name] [No Name] | + | + ]]) + end) -it('keymap is shown with vertical splits #27269', function() - clear() - local screen = Screen.new(53, 4) - command('setlocal keymap=dvorak') - command('rightbelow vsplit') - screen:expect([[ - │^ | - {1:~ }│{1:~ }| - {2:[No Name] <en-dv> }{3:[No Name] <en-dv> }| - | - ]]) - command('set laststatus=3') - screen:expect([[ - │^ | - {1:~ }│{1:~ }| - {3:[No Name] <en-dv> }| - | - ]]) + it('showcmdloc=statusline works with vertical splits', function() + command('rightbelow vsplit') + command('set showcmd showcmdloc=statusline') + feed('1234') + screen:expect([[ + │^ | + {1:~ }│{1:~ }|*5 + {2:[No Name] }{3:[No Name] 1234 }| + | + ]]) + feed('<Esc>') + command('set laststatus=3') + feed('1234') + screen:expect([[ + │^ | + {1:~ }│{1:~ }|*5 + {3:[No Name] 1234 }| + | + ]]) + end) + + it('keymap is shown with vertical splits #27269', function() + command('setlocal keymap=dvorak') + command('rightbelow vsplit') + screen:expect([[ + │^ | + {1:~ }│{1:~ }|*5 + {2:[No Name] <en-dv> }{3:[No Name] <en-dv> }| + | + ]]) + + command('set laststatus=3') + screen:expect([[ + │^ | + {1:~ }│{1:~ }|*5 + {3:[No Name] <en-dv> }| + | + ]]) + end) + + it("nested call from nvim_eval_statusline() doesn't overwrite items #32259", function() + exec_lua('vim.o.laststatus = 2') + exec_lua([[vim.o.statusline = '%#Special#B:%{nvim_eval_statusline("%f", []).str}']]) + screen:expect([[ + ^ | + {1:~ }|*5 + {101:B:[No Name] }| + | + ]]) + end) end) diff --git a/test/functional/ui/syntax_conceal_spec.lua b/test/functional/ui/syntax_conceal_spec.lua index 57d76e54df..80e38d974a 100644 --- a/test/functional/ui/syntax_conceal_spec.lua +++ b/test/functional/ui/syntax_conceal_spec.lua @@ -198,7 +198,7 @@ describe('Screen', function() end) end) -- a region of text (implicit concealing) - it('cursor position is correct when entering Insert mode with cocu=ni #13916', function() + it('cursor position when entering Insert mode with cocu=ni #13916', function() insert([[foobarfoobarfoobar]]) -- move to end of line feed('$') @@ -217,6 +217,37 @@ describe('Screen', function() {4:-- INSERT --} | ]]) end) + + it('cursor position when scrolling in Normal mode with cocu=n #31271', function() + insert(('foo\n'):rep(9) .. 'foofoobarfoofoo' .. ('\nfoo'):rep(9)) + command('set concealcursor=n') + command('syn match Foo /bar/ conceal cchar=&') + feed('gg5<C-E>10gg$') + screen:expect([[ + foo |*4 + foofoo{1:&}foofo^o | + foo |*4 + | + ]]) + feed('zz') + screen:expect_unchanged() + feed('zt') + screen:expect([[ + foofoo{1:&}foofo^o | + foo |*8 + | + ]]) + feed('zt') + screen:expect_unchanged() + feed('zb') + screen:expect([[ + foo |*8 + foofoo{1:&}foofo^o | + | + ]]) + feed('zb') + screen:expect_unchanged() + end) end) -- match and conceal describe('let the conceal level be', function() diff --git a/test/functional/ui/title_spec.lua b/test/functional/ui/title_spec.lua index 66eb15478b..2de1e71457 100644 --- a/test/functional/ui/title_spec.lua +++ b/test/functional/ui/title_spec.lua @@ -37,6 +37,63 @@ describe('title', function() end) end) + it('is updated in Insert mode', function() + api.nvim_set_option_value('title', true, {}) + screen:expect(function() + eq('[No Name] - Nvim', screen.title) + end) + feed('ifoo') + screen:expect(function() + eq('[No Name] + - Nvim', screen.title) + end) + feed('<Esc>') + api.nvim_set_option_value('titlestring', '%m %f (%{mode(1)}) | nvim', {}) + screen:expect(function() + eq('[+] [No Name] (n) | nvim', screen.title) + end) + feed('i') + screen:expect(function() + eq('[+] [No Name] (i) | nvim', screen.title) + end) + feed('<Esc>') + screen:expect(function() + eq('[+] [No Name] (n) | nvim', screen.title) + end) + end) + + it('is updated in Cmdline mode', function() + api.nvim_set_option_value('title', true, {}) + api.nvim_set_option_value('titlestring', '%f (%{mode(1)}) | nvim', {}) + screen:expect(function() + eq('[No Name] (n) | nvim', screen.title) + end) + feed(':') + screen:expect(function() + eq('[No Name] (c) | nvim', screen.title) + end) + feed('<Esc>') + screen:expect(function() + eq('[No Name] (n) | nvim', screen.title) + end) + end) + + it('is updated in Terminal mode', function() + api.nvim_set_option_value('title', true, {}) + api.nvim_set_option_value('titlestring', '(%{mode(1)}) | nvim', {}) + fn.jobstart({ n.testprg('shell-test'), 'INTERACT' }, { term = true }) + screen:expect(function() + eq('(nt) | nvim', screen.title) + end) + feed('i') + screen:expect(function() + eq('(t) | nvim', screen.title) + end) + feed([[<C-\><C-N>]]) + screen:expect(function() + eq('(nt) | nvim', screen.title) + end) + end) + describe('is not changed by', function() local file1 = is_os('win') and 'C:\\mydir\\myfile1' or '/mydir/myfile1' local file2 = is_os('win') and 'C:\\mydir\\myfile2' or '/mydir/myfile2' diff --git a/test/functional/vimscript/ctx_functions_spec.lua b/test/functional/vimscript/ctx_functions_spec.lua index 873e4f820d..85a74c0ab6 100644 --- a/test/functional/vimscript/ctx_functions_spec.lua +++ b/test/functional/vimscript/ctx_functions_spec.lua @@ -188,7 +188,10 @@ describe('context functions', function() function RestoreFuncs() call ctxpop() endfunction + + let g:sid = expand('<SID>') ]]) + local sid = api.nvim_get_var('sid') eq('Hello, World!', exec_capture([[call Greet('World')]])) eq( @@ -200,11 +203,11 @@ describe('context functions', function() call('DeleteSFuncs') eq( - 'function Greet, line 1: Vim(call):E117: Unknown function: s:greet', + ('function Greet, line 1: Vim(call):E117: Unknown function: %sgreet'):format(sid), pcall_err(command, [[call Greet('World')]]) ) eq( - 'function GreetAll, line 1: Vim(call):E117: Unknown function: s:greet_all', + ('function GreetAll, line 1: Vim(call):E117: Unknown function: %sgreet_all'):format(sid), pcall_err(command, [[call GreetAll('World', 'One', 'Two', 'Three')]]) ) diff --git a/test/functional/vimscript/getchar_spec.lua b/test/functional/vimscript/getchar_spec.lua new file mode 100644 index 0000000000..4ecf082f97 --- /dev/null +++ b/test/functional/vimscript/getchar_spec.lua @@ -0,0 +1,95 @@ +local n = require('test.functional.testnvim')() +local Screen = require('test.functional.ui.screen') + +local clear = n.clear +local exec = n.exec +local feed = n.feed +local async_command = n.async_meths.nvim_command +local poke_eventloop = n.poke_eventloop + +describe('getchar()', function() + before_each(clear) + + -- oldtest: Test_getchar_cursor_position() + it('cursor positioning', function() + local screen = Screen.new(40, 6) + exec([[ + call setline(1, ['foobar', 'foobar', 'foobar']) + call cursor(3, 6) + ]]) + screen:expect([[ + foobar |*2 + fooba^r | + {1:~ }|*2 + | + ]]) + + -- Default: behaves like "msg" when immediately after printing a message, + -- even if :sleep moved cursor elsewhere. + for _, cmd in ipairs({ + 'echo 1234 | call getchar()', + 'echo 1234 | call getchar(-1, {})', + "echo 1234 | call getchar(-1, #{cursor: 'msg'})", + 'echo 1234 | sleep 1m | call getchar()', + 'echo 1234 | sleep 1m | call getchar(-1, {})', + "echo 1234 | sleep 1m | call getchar(-1, #{cursor: 'msg'})", + }) do + async_command(cmd) + screen:expect([[ + foobar |*3 + {1:~ }|*2 + 1234^ | + ]]) + feed('a') + screen:expect([[ + foobar |*2 + fooba^r | + {1:~ }|*2 + 1234 | + ]]) + end + + -- Default: behaves like "keep" when not immediately after printing a message. + for _, cmd in ipairs({ + 'call getchar()', + 'call getchar(-1, {})', + "call getchar(-1, #{cursor: 'keep'})", + "echo 1234 | sleep 1m | call getchar(-1, #{cursor: 'keep'})", + }) do + async_command(cmd) + poke_eventloop() + screen:expect_unchanged() + feed('a') + poke_eventloop() + screen:expect_unchanged() + end + + async_command("call getchar(-1, #{cursor: 'msg'})") + screen:expect([[ + foobar |*3 + {1:~ }|*2 + ^1234 | + ]]) + feed('a') + screen:expect([[ + foobar |*2 + fooba^r | + {1:~ }|*2 + 1234 | + ]]) + + async_command("call getchar(-1, #{cursor: 'hide'})") + screen:expect([[ + foobar |*3 + {1:~ }|*2 + 1234 | + ]]) + feed('a') + screen:expect([[ + foobar |*2 + fooba^r | + {1:~ }|*2 + 1234 | + ]]) + end) +end) diff --git a/test/functional/vimscript/null_spec.lua b/test/functional/vimscript/null_spec.lua index 9a27239a6d..afd50f7cf9 100644 --- a/test/functional/vimscript/null_spec.lua +++ b/test/functional/vimscript/null_spec.lua @@ -116,7 +116,7 @@ describe('NULL', function() null_expr_test( 'is accepted as an empty list by inputlist()', '[feedkeys("\\n"), inputlist(L)]', - 'Type number and <Enter> or click with the mouse (q or empty cancels): ', + '', { 0, 0 } ) null_expr_test( diff --git a/test/functional/vimscript/timer_spec.lua b/test/functional/vimscript/timer_spec.lua index d1b8bfe5d9..3e4e6de35c 100644 --- a/test/functional/vimscript/timer_spec.lua +++ b/test/functional/vimscript/timer_spec.lua @@ -94,12 +94,56 @@ describe('timers', function() assert(0 <= diff and diff <= 4, 'expected (0 <= diff <= 4), got: ' .. tostring(diff)) end) + it('are triggered in inputlist() call #7857', function() + async_meths.nvim_exec2( + [[ + call timer_start(5, 'MyHandler', {'repeat': -1}) + let g:val = 0 + let g:n = inputlist(['input0', 'input1']) + ]], + {} + ) + retry(nil, nil, function() + local val = eval('g:val') + ok(val >= 2, '>= 2', tostring(val)) + eq(0, eval("exists('g:n')")) + end) + feed('42<CR>') + eq(42, eval('g:n')) + end) + + it('are triggered in confirm() call', function() + api.nvim_ui_attach(80, 24, {}) -- needed for confirm() to work + async_meths.nvim_exec2( + [[ + call timer_start(5, 'MyHandler', {'repeat': -1}) + let g:val = 0 + let g:n = confirm('Are you sure?', "&Yes\n&No\n&Cancel") + ]], + {} + ) + retry(nil, nil, function() + local val = eval('g:val') + ok(val >= 2, '>= 2', tostring(val)) + eq(0, eval("exists('g:n')")) + end) + feed('c') + eq(3, eval('g:n')) + end) + it('are triggered in blocking getchar() call', function() - command("call timer_start(5, 'MyHandler', {'repeat': -1})") - async_meths.nvim_command('let g:val = 0 | let g:c = getchar()') + async_meths.nvim_exec2( + [[ + call timer_start(5, 'MyHandler', {'repeat': -1}) + let g:val = 0 + let g:c = getchar() + ]], + {} + ) retry(nil, nil, function() local val = eval('g:val') ok(val >= 2, '>= 2', tostring(val)) + eq(0, eval("exists('g:c')")) eq(0, eval('getchar(1)')) end) feed('c') @@ -126,39 +170,36 @@ describe('timers', function() redraw endfunc ]]) - async_meths.nvim_command('let g:c2 = getchar()') + async_meths.nvim_command("let g:c2 = getchar(-1, {'cursor': 'msg'})") async_meths.nvim_command( 'call timer_start(' .. load_adjust(100) .. ", 'AddItem', {'repeat': -1})" ) screen:expect([[ - ^ITEM 1 | + ITEM 1 | ITEM 2 | {1:~ }|*3 - | + ^ | ]]) async_meths.nvim_command('let g:cont = 1') screen:expect([[ - ^ITEM 1 | + ITEM 1 | ITEM 2 | ITEM 3 | {1:~ }|*2 - | + ^ | ]]) feed('3') eq(51, eval('g:c2')) - screen:expect { - grid = [[ + screen:expect([[ ^ITEM 1 | ITEM 2 | ITEM 3 | {1:~ }|*2 | - ]], - unchanged = true, - } + ]]) end) it('can be stopped', function() diff --git a/test/old/testdir/gen_opt_test.vim b/test/old/testdir/gen_opt_test.vim index be5a7e6ee4..f12ce8c7c4 100644 --- a/test/old/testdir/gen_opt_test.vim +++ b/test/old/testdir/gen_opt_test.vim @@ -87,7 +87,7 @@ let test_values = { \ ['xxx', 'help,nofile']], \ 'clipboard': [['', 'unnamed'], ['xxx', '\ze*', 'exclude:\\%(']], \ 'completeopt': [['', 'menu', 'menuone', 'longest', 'preview', 'popup', - \ 'noinsert', 'noselect', 'fuzzy', 'menu,longest'], + \ 'noinsert', 'noselect', 'fuzzy', 'preinsert', 'menu,longest'], \ ['xxx', 'menu,,,longest,']], \ 'encoding': [['utf8'], []], \ 'foldcolumn': [[0, 1, 4, 'auto', 'auto:1', 'auto:9'], [-1, 13, 999]], @@ -117,12 +117,11 @@ let test_values = { "\ 'imstyle': [[0, 1], [-1, 2, 999]], \ 'lines': [[2, 24, 1000], [-1, 0, 1]], \ 'linespace': [[-1, 0, 2, 4, 999], ['']], - \ 'msghistory': [[0, 1, 100, 10000], [-1, 10001]], \ 'numberwidth': [[1, 4, 8, 10, 11, 20], [-1, 0, 21]], \ 'regexpengine': [[0, 1, 2], [-1, 3, 999]], \ 'report': [[0, 1, 2, 9999], [-1]], - \ 'scroll': [[0, 1, 2, 20], [-1, 999]], - \ 'scrolljump': [[-100, -1, 0, 1, 2, 20], [-101, 999]], + \ 'scroll': [[0, 1, 2, 15], [-1, 999]], + \ 'scrolljump': [[-100, -1, 0, 1, 2, 15], [-101, 999]], \ 'scrolloff': [[0, 1, 8, 999], [-1]], \ 'shiftwidth': [[0, 1, 8, 999], [-1]], \ 'sidescroll': [[0, 1, 8, 999], [-1]], @@ -146,8 +145,8 @@ let test_values = { \ 'winwidth': [[1, 10, 999], [-1, 0]], \ "\ string options - \ 'ambiwidth': [['', 'single', 'double'], ['xxx']], - \ 'background': [['', 'light', 'dark'], ['xxx']], + \ 'ambiwidth': [['single', 'double'], ['xxx']], + \ 'background': [['light', 'dark'], ['xxx']], "\ 'backspace': [[0, 1, 2, 3, '', 'indent', 'eol', 'start', 'nostop', "\ " 'eol,start', 'indent,eol,nostop'], "\ " [-1, 4, 'xxx']], @@ -187,7 +186,7 @@ let test_values = { \ ['xxx']], \ 'concealcursor': [['', 'n', 'v', 'i', 'c', 'nvic'], ['xxx']], "\ 'completeopt': [['', 'menu', 'menuone', 'longest', 'preview', 'popup', - "\ " 'popuphidden', 'noinsert', 'noselect', 'fuzzy', 'menu,longest'], + "\ " 'popuphidden', 'noinsert', 'noselect', 'fuzzy', 'preinsert', 'menu,longest'], "\ " ['xxx', 'menu,,,longest,']], \ 'completeitemalign': [['abbr,kind,menu', 'menu,abbr,kind'], \ ['', 'xxx', 'abbr', 'abbr,menu', 'abbr,menu,kind,abbr', @@ -210,17 +209,19 @@ let test_values = { \ 'icase', 'iwhite', 'iwhiteall', 'horizontal', 'vertical', \ 'closeoff', 'hiddenoff', 'foldcolumn:0', 'foldcolumn:12', \ 'followwrap', 'internal', 'indent-heuristic', 'algorithm:myers', - \ 'algorithm:minimal', 'algorithm:patience', - \ 'algorithm:histogram', 'icase,iwhite'], - \ ['xxx', 'foldcolumn:xxx', 'algorithm:xxx', 'algorithm:']], + \ 'icase,iwhite', 'algorithm:minimal', 'algorithm:patience', + \ 'algorithm:histogram', 'linematch:5'], + \ ['xxx', 'foldcolumn:', 'foldcolumn:x', 'foldcolumn:xxx', + \ 'linematch:', 'linematch:x', 'linematch:xxx', 'algorithm:', + \ 'algorithm:xxx', 'context:', 'context:x', 'context:xxx']], \ 'display': [['', 'lastline', 'truncate', 'uhex', 'lastline,uhex'], \ ['xxx']], - \ 'eadirection': [['', 'both', 'ver', 'hor'], ['xxx', 'ver,hor']], + \ 'eadirection': [['both', 'ver', 'hor'], ['xxx', 'ver,hor']], "\ 'encoding': [['latin1'], ['xxx', '']], \ 'eventignore': [['', 'WinEnter', 'WinLeave,winenter', 'all,WinEnter'], \ ['xxx']], \ 'fileencoding': [['', 'latin1', 'xxx'], []], - \ 'fileformat': [['', 'dos', 'unix', 'mac'], ['xxx']], + \ 'fileformat': [['dos', 'unix', 'mac'], ['xxx']], \ 'fileformats': [['', 'dos', 'dos,unix'], ['xxx']], \ 'fillchars': [['', 'stl:x', 'stlnc:x', 'vert:x', 'fold:x', 'foldopen:x', \ 'foldclose:x', 'foldsep:x', 'diff:x', 'eob:x', 'lastline:x', @@ -264,10 +265,18 @@ let test_values = { \ 'eol:\\u21b5', 'eol:\\U000021b5', 'eol:x,space:y'], \ ['xxx', 'eol:']], \ 'matchpairs': [['', '(:)', '(:),<:>'], ['xxx']], + \ 'messagesopt': [['hit-enter,history:1', 'hit-enter,history:10000', + \ 'history:100,wait:100', 'history:0,wait:0', + \ 'hit-enter,history:1,wait:1'], + \ ['xxx', 'history:500', 'hit-enter,history:-1', + \ 'hit-enter,history:10001', 'history:0,wait:10001', + \ 'hit-enter', 'history:10,wait:99999999999999999999', + \ 'history:99999999999999999999,wait:10', 'wait:10', + \ 'history:-10', 'history:10,wait:-10']], \ 'mkspellmem': [['10000,100,12'], ['', 'xxx', '10000,100']], \ 'mouse': [['', 'n', 'v', 'i', 'c', 'h', 'a', 'r', 'nvi'], \ ['xxx', 'n,v,i']], - \ 'mousemodel': [['', 'extend', 'popup', 'popup_setpos'], ['xxx']], + \ 'mousemodel': [['extend', 'popup', 'popup_setpos'], ['xxx']], \ 'mouseshape': [['', 'n:arrow'], ['xxx']], \ 'nrformats': [['', 'alpha', 'octal', 'hex', 'bin', 'unsigned', 'blank', \ 'alpha,hex,bin'], @@ -292,7 +301,7 @@ let test_values = { \ 'sessionoptions': [['', 'blank', 'curdir', 'sesdir', \ 'help,options,slash'], \ ['xxx', 'curdir,sesdir']], - \ 'showcmdloc': [['', 'last', 'statusline', 'tabline'], ['xxx']], + \ 'showcmdloc': [['last', 'statusline', 'tabline'], ['xxx']], "\ 'signcolumn': [['', 'auto', 'no', 'yes', 'number'], ['xxx', 'no,yes']], \ 'spellfile': [['', 'file.en.add', 'xxx.en.add,yyy.gb.add,zzz.ja.add', \ '/tmp/dir\ with\ space/en.utf-8.add', @@ -304,7 +313,7 @@ let test_values = { \ 'spellsuggest': [['', 'best', 'double', 'fast', '100', 'timeout:100', \ 'timeout:-1', 'file:/tmp/file', 'expr:Func()', 'double,33'], \ ['xxx', '-1', 'timeout:', 'best,double', 'double,fast']], - \ 'splitkeep': [['', 'cursor', 'screen', 'topline'], ['xxx']], + \ 'splitkeep': [['cursor', 'screen', 'topline'], ['xxx']], \ 'statusline': [['', 'xxx'], ['%$', '%{', '%{%', '%{%}', '%(', '%)']], "\ 'swapsync': [['', 'sync', 'fsync'], ['xxx']], \ 'switchbuf': [['', 'useopen', 'usetab', 'split', 'vsplit', 'newtab', diff --git a/test/old/testdir/runnvim.vim b/test/old/testdir/runnvim.vim index 578614c8a1..f5945aeaee 100644 --- a/test/old/testdir/runnvim.vim +++ b/test/old/testdir/runnvim.vim @@ -8,6 +8,7 @@ function s:logger.on_exit(id, data, event) endfunction let s:logger.env = #{VIMRUNTIME: $VIMRUNTIME} +let s:logger.term = v:true " Replace non-printable chars by special sequence, or "<%x>". let s:escaped_char = {"\n": '\n', "\r": '\r', "\t": '\t'} @@ -25,7 +26,7 @@ function Main() set lines=25 set columns=80 enew - let job = termopen(args, s:logger) + let job = jobstart(args, s:logger) let results = jobwait([job], 5 * 60 * 1000) " TODO(ZyX-I): Get colors let screen = getline(1, '$') diff --git a/test/old/testdir/runtest.vim b/test/old/testdir/runtest.vim index 058635c332..3d210695c4 100644 --- a/test/old/testdir/runtest.vim +++ b/test/old/testdir/runtest.vim @@ -55,6 +55,10 @@ silent! endwhile " In the GUI we can always change the screen size. if has('gui_running') + if has('gui_gtk') + " to keep screendump size unchanged + set guifont=Monospace\ 10 + endif set columns=80 lines=25 endif diff --git a/test/old/testdir/setup.vim b/test/old/testdir/setup.vim index e7b4bb1a88..61596fc83a 100644 --- a/test/old/testdir/setup.vim +++ b/test/old/testdir/setup.vim @@ -1,6 +1,5 @@ if exists('s:did_load') " Align Nvim defaults to Vim. - set backspace= set commentstring=/*\ %s\ */ set complete=.,w,b,u,t,i set define=^\\s*#\\s*define @@ -66,7 +65,7 @@ mapclear mapclear! aunmenu * tlunmenu * -autocmd! nvim_popupmenu +autocmd! nvim.popupmenu " Undo the 'grepprg' and 'grepformat' setting in _defaults.lua. set grepprg& grepformat& diff --git a/test/old/testdir/test_autocmd.vim b/test/old/testdir/test_autocmd.vim index 64599c869a..d5f7c928de 100644 --- a/test/old/testdir/test_autocmd.vim +++ b/test/old/testdir/test_autocmd.vim @@ -1198,8 +1198,8 @@ func Test_OptionSet() call assert_equal(g:opt[0], g:opt[1]) " 14: Setting option backspace through :let" - let g:options = [['backspace', '', '', '', 'eol,indent,start', 'global', 'set']] - let &bs = "eol,indent,start" + let g:options = [['backspace', 'indent,eol,start', 'indent,eol,start', 'indent,eol,start', '', 'global', 'set']] + let &bs = '' call assert_equal([], g:options) call assert_equal(g:opt[0], g:opt[1]) @@ -4181,4 +4181,28 @@ func Test_autocmd_BufWinLeave_with_vsp() exe "bw! " .. dummy endfunc +func Test_OptionSet_cmdheight() + set mouse=a laststatus=2 + au OptionSet cmdheight :let &l:ch = v:option_new + + resize -1 + call assert_equal(2, &l:ch) + resize +1 + call assert_equal(1, &l:ch) + + call Ntest_setmouse(&lines - 1, 1) + call feedkeys("\<LeftMouse>", 'xt') + call Ntest_setmouse(&lines - 2, 1) + call feedkeys("\<LeftDrag>", 'xt') + call assert_equal(2, &l:ch) + + tabnew | resize +1 + call assert_equal(1, &l:ch) + tabfirst + call assert_equal(2, &l:ch) + + tabonly + set cmdheight& mouse& laststatus& +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/test_bufwintabinfo.vim b/test/old/testdir/test_bufwintabinfo.vim index 57492e07c9..0a4bd0b674 100644 --- a/test/old/testdir/test_bufwintabinfo.vim +++ b/test/old/testdir/test_bufwintabinfo.vim @@ -114,6 +114,18 @@ func Test_getbufwintabinfo() wincmd t | only endfunc +function Test_get_wininfo_leftcol() + set nowrap + set winwidth=10 + vsp + call setline(1, ['abcdefghijklmnopqrstuvwxyz']) + norm! 5zl + call assert_equal(5, getwininfo()[0].leftcol) + bwipe! + set wrap& + set winwidth& +endfunc + function Test_get_buf_options() let opts = bufnr()->getbufvar('&') call assert_equal(v:t_dict, type(opts)) diff --git a/test/old/testdir/test_cmdline.vim b/test/old/testdir/test_cmdline.vim index 290af4a4ca..d4ad63d43e 100644 --- a/test/old/testdir/test_cmdline.vim +++ b/test/old/testdir/test_cmdline.vim @@ -291,8 +291,8 @@ func Test_changing_cmdheight() call term_sendkeys(buf, ":resize -3\<CR>") call VerifyScreenDump(buf, 'Test_changing_cmdheight_1', {}) - " using the space available doesn't change the status line - call term_sendkeys(buf, ":set cmdheight+=3\<CR>") + " :resize now also changes 'cmdheight' accordingly + call term_sendkeys(buf, ":set cmdheight+=1\<CR>") call VerifyScreenDump(buf, 'Test_changing_cmdheight_2', {}) " using more space moves the status line up @@ -300,10 +300,10 @@ func Test_changing_cmdheight() call VerifyScreenDump(buf, 'Test_changing_cmdheight_3', {}) " reducing cmdheight moves status line down - call term_sendkeys(buf, ":set cmdheight-=2\<CR>") + call term_sendkeys(buf, ":set cmdheight-=3\<CR>") call VerifyScreenDump(buf, 'Test_changing_cmdheight_4', {}) - " reducing window size and then setting cmdheight + " reducing window size and then setting cmdheight call term_sendkeys(buf, ":resize -1\<CR>") call term_sendkeys(buf, ":set cmdheight=1\<CR>") call VerifyScreenDump(buf, 'Test_changing_cmdheight_5', {}) @@ -316,6 +316,10 @@ func Test_changing_cmdheight() call term_sendkeys(buf, ":call EchoOne()\<CR>") call VerifyScreenDump(buf, 'Test_changing_cmdheight_7', {}) + " window commands do not reduce 'cmdheight' to value lower than :set by user + call term_sendkeys(buf, "\<CR>:wincmd _\<CR>") + call VerifyScreenDump(buf, 'Test_changing_cmdheight_8', {}) + " clean up call StopVimInTerminal(buf) endfunc @@ -4036,6 +4040,27 @@ func Test_rulerformat_position() call StopVimInTerminal(buf) endfunc +" Test for using "%!" in 'rulerformat' to use a function +func Test_rulerformat_function() + CheckScreendump + + let lines =<< trim END + func TestRulerFn() + return '10,20%=30%%' + endfunc + END + call writefile(lines, 'Xrulerformat_function', 'D') + + let buf = RunVimInTerminal('-S Xrulerformat_function', #{rows: 2, cols: 40}) + call term_sendkeys(buf, ":set ruler rulerformat=%!TestRulerFn()\<CR>") + call term_sendkeys(buf, ":redraw!\<CR>") + call term_wait(buf) + call VerifyScreenDump(buf, 'Test_rulerformat_function', {}) + + " clean up + call StopVimInTerminal(buf) +endfunc + func Test_getcompletion_usercmd() command! -nargs=* -complete=command TestCompletion echo <q-args> diff --git a/test/old/testdir/test_compiler.vim b/test/old/testdir/test_compiler.vim index 07b57b76d9..1310cbadfc 100644 --- a/test/old/testdir/test_compiler.vim +++ b/test/old/testdir/test_compiler.vim @@ -18,6 +18,8 @@ func Test_compiler() endif e Xfoo.pl + " Play nice with other tests. + defer setqflist([]) compiler perl call assert_equal('perl', b:current_compiler) call assert_fails('let g:current_compiler', 'E121:') @@ -65,10 +67,10 @@ func Test_compiler_completion() call assert_match('^"compiler ' .. clist .. '$', @:) call feedkeys(":compiler p\<C-A>\<C-B>\"\<CR>", 'tx') - call assert_match('"compiler pandoc pbx perl\( p[a-z_]\+\)\+ pylint pyunit', @:) + call assert_match('"compiler pandoc pbx perl\( p[a-z_]\+\)\+ pyunit', @:) call feedkeys(":compiler! p\<C-A>\<C-B>\"\<CR>", 'tx') - call assert_match('"compiler! pandoc pbx perl\( p[a-z_]\+\)\+ pylint pyunit', @:) + call assert_match('"compiler! pandoc pbx perl\( p[a-z_]\+\)\+ pyunit', @:) endfunc func Test_compiler_error() @@ -78,3 +80,634 @@ func Test_compiler_error() call assert_fails('compiler! doesnotexist', 'E666:') unlet! g:current_compiler endfunc + +func s:SpotBugsParseFilterMakePrg(dirname, makeprg) + let result = {} + let result.sourcepath = '' + let result.classfiles = [] + + " Get the argument after the rightmost occurrence of "-sourcepath". + let offset = strridx(a:makeprg, '-sourcepath') + if offset < 0 + return result + endif + let offset += 1 + strlen('-sourcepath') + let result.sourcepath = matchstr(strpart(a:makeprg, offset), '.\{-}\ze[ \t]') + let offset += 1 + strlen(result.sourcepath) + + " Get the class file arguments, dropping the pathname prefix. + let offset = stridx(a:makeprg, a:dirname, offset) + if offset < 0 + return result + endif + + while offset > -1 + let candidate = matchstr(a:makeprg, '[^ \t]\{-}\.class\>', offset) + if empty(candidate) + break + endif + call add(result.classfiles, candidate) + let offset = stridx(a:makeprg, a:dirname, (1 + strlen(candidate) + offset)) + endwhile + + call sort(result.classfiles) + return result +endfunc + +func Test_compiler_spotbugs_makeprg() + let save_shellslash = &shellslash + set shellslash + + call assert_true(mkdir('Xspotbugs/src/tests/α/β/γ/δ', 'pR')) + call assert_true(mkdir('Xspotbugs/tests/α/β/γ/δ', 'pR')) + + let lines =<< trim END + // EOL comment. /* + abstract class + 𐌂1 /* Multiline comment. */ { + /* Multiline comment. */ // EOL comment. /* + static final String COMMENT_A_LIKE = "/*"; + { new Object() {/* Try globbing. */}; } + static { interface 𐌉𐌉1 {} } + static class 𐌂11 { interface 𐌉𐌉2 {} } + } + /* Multiline comment. */ // EOL comment. /* + final class 𐌂2 { + public static void main(String... aa) { + record 𐌓() {} + enum 𐌄 {} + } + } // class + END + + " THE EXPECTED RESULTS. + let results = {} + let results['Xspotbugs/src/tests/𐌂1.java'] = { + \ 'Sourcepath': {-> fnamemodify('Xspotbugs/src/tests/𐌂1.java', + \ ':p:h:S')}, + \ 'classfiles': sort([ + \ 'Xspotbugs/tests/𐌂1$1.class', + \ 'Xspotbugs/tests/𐌂1$1𐌉𐌉1.class', + \ 'Xspotbugs/tests/𐌂1$𐌂11$𐌉𐌉2.class', + \ 'Xspotbugs/tests/𐌂1$𐌂11.class', + \ 'Xspotbugs/tests/𐌂1.class', + \ 'Xspotbugs/tests/𐌂2$1𐌄.class', + \ 'Xspotbugs/tests/𐌂2$1𐌓.class', + \ 'Xspotbugs/tests/𐌂2.class']), + \ } + " No class file for an empty source file even with "-Xpkginfo:always". + let results['Xspotbugs/src/tests/package-info.java'] = { + \ 'Sourcepath': {-> ''}, + \ 'classfiles': [], + \ } + let results['Xspotbugs/src/tests/α/𐌂1.java'] = { + \ 'Sourcepath': {-> fnamemodify('Xspotbugs/src/tests/α/𐌂1.java', + \ ':p:h:h:S')}, + \ 'classfiles': sort([ + \ 'Xspotbugs/tests/α/𐌂1$1.class', + \ 'Xspotbugs/tests/α/𐌂1$1𐌉𐌉1.class', + \ 'Xspotbugs/tests/α/𐌂1$𐌂11$𐌉𐌉2.class', + \ 'Xspotbugs/tests/α/𐌂1$𐌂11.class', + \ 'Xspotbugs/tests/α/𐌂1.class', + \ 'Xspotbugs/tests/α/𐌂2$1𐌄.class', + \ 'Xspotbugs/tests/α/𐌂2$1𐌓.class', + \ 'Xspotbugs/tests/α/𐌂2.class']), + \ } + let results['Xspotbugs/src/tests/α/package-info.java'] = { + \ 'Sourcepath': {-> fnamemodify('Xspotbugs/src/tests/α/package-info.java', + \ ':p:h:S')}, + \ 'classfiles': ['Xspotbugs/tests/α/package-info.class'], + \ } + let results['Xspotbugs/src/tests/α/β/𐌂1.java'] = { + \ 'Sourcepath': {-> fnamemodify('Xspotbugs/src/tests/α/β/𐌂1.java', + \ ':p:h:h:h:S')}, + \ 'classfiles': sort([ + \ 'Xspotbugs/tests/α/β/𐌂1$1.class', + \ 'Xspotbugs/tests/α/β/𐌂1$1𐌉𐌉1.class', + \ 'Xspotbugs/tests/α/β/𐌂1$𐌂11$𐌉𐌉2.class', + \ 'Xspotbugs/tests/α/β/𐌂1$𐌂11.class', + \ 'Xspotbugs/tests/α/β/𐌂1.class', + \ 'Xspotbugs/tests/α/β/𐌂2$1𐌄.class', + \ 'Xspotbugs/tests/α/β/𐌂2$1𐌓.class', + \ 'Xspotbugs/tests/α/β/𐌂2.class']), + \ } + let results['Xspotbugs/src/tests/α/β/package-info.java'] = { + \ 'Sourcepath': {-> fnamemodify('Xspotbugs/src/tests/α/β/package-info.java', + \ ':p:h:S')}, + \ 'classfiles': ['Xspotbugs/tests/α/β/package-info.class'], + \ } + let results['Xspotbugs/src/tests/α/β/γ/𐌂1.java'] = { + \ 'Sourcepath': {-> fnamemodify('Xspotbugs/src/tests/α/β/γ/𐌂1.java', + \ ':p:h:h:h:h:S')}, + \ 'classfiles': sort([ + \ 'Xspotbugs/tests/α/β/γ/𐌂1$1.class', + \ 'Xspotbugs/tests/α/β/γ/𐌂1$1𐌉𐌉1.class', + \ 'Xspotbugs/tests/α/β/γ/𐌂1$𐌂11$𐌉𐌉2.class', + \ 'Xspotbugs/tests/α/β/γ/𐌂1$𐌂11.class', + \ 'Xspotbugs/tests/α/β/γ/𐌂1.class', + \ 'Xspotbugs/tests/α/β/γ/𐌂2$1𐌄.class', + \ 'Xspotbugs/tests/α/β/γ/𐌂2$1𐌓.class', + \ 'Xspotbugs/tests/α/β/γ/𐌂2.class']), + \ } + let results['Xspotbugs/src/tests/α/β/γ/package-info.java'] = { + \ 'Sourcepath': {-> fnamemodify('Xspotbugs/src/tests/α/β/γ/package-info.java', + \ ':p:h:S')}, + \ 'classfiles': ['Xspotbugs/tests/α/β/γ/package-info.class'], + \ } + let results['Xspotbugs/src/tests/α/β/γ/δ/𐌂1.java'] = { + \ 'Sourcepath': {-> fnamemodify('Xspotbugs/src/tests/α/β/γ/δ/𐌂1.java', + \ ':p:h:h:h:h:h:S')}, + \ 'classfiles': sort([ + \ 'Xspotbugs/tests/α/β/γ/δ/𐌂1$1.class', + \ 'Xspotbugs/tests/α/β/γ/δ/𐌂1$1𐌉𐌉1.class', + \ 'Xspotbugs/tests/α/β/γ/δ/𐌂1$𐌂11$𐌉𐌉2.class', + \ 'Xspotbugs/tests/α/β/γ/δ/𐌂1$𐌂11.class', + \ 'Xspotbugs/tests/α/β/γ/δ/𐌂1.class', + \ 'Xspotbugs/tests/α/β/γ/δ/𐌂2$1𐌄.class', + \ 'Xspotbugs/tests/α/β/γ/δ/𐌂2$1𐌓.class', + \ 'Xspotbugs/tests/α/β/γ/δ/𐌂2.class']), + \ } + let results['Xspotbugs/src/tests/α/β/γ/δ/package-info.java'] = { + \ 'Sourcepath': {-> fnamemodify('Xspotbugs/src/tests/α/β/γ/δ/package-info.java', + \ ':p:h:S')}, + \ 'classfiles': ['Xspotbugs/tests/α/β/γ/δ/package-info.class'], + \ } + + " MAKE CLASS FILES DISCOVERABLE! + let g:spotbugs_properties = { + \ 'sourceDirPath': ['src/tests'], + \ 'classDirPath': ['tests'], + \ } + + call assert_true(has_key(s:SpotBugsParseFilterMakePrg('Xspotbugs', ''), 'sourcepath')) + call assert_true(has_key(s:SpotBugsParseFilterMakePrg('Xspotbugs', ''), 'classfiles')) + + " Write 45 mock-up class files for 10 source files. + for [class_dir, src_dir, package] in [ + \ ['Xspotbugs/tests/', 'Xspotbugs/src/tests/', ''], + \ ['Xspotbugs/tests/α/', 'Xspotbugs/src/tests/α/', 'package α;'], + \ ['Xspotbugs/tests/α/β/', 'Xspotbugs/src/tests/α/β/', 'package α.β;'], + \ ['Xspotbugs/tests/α/β/γ/', 'Xspotbugs/src/tests/α/β/γ/', 'package α.β.γ;'], + \ ['Xspotbugs/tests/α/β/γ/δ/', 'Xspotbugs/src/tests/α/β/γ/δ/', 'package α.β.γ.δ;']] + for class_file in ['𐌂1$1.class', '𐌂1$1𐌉𐌉1.class', '𐌂1$𐌂11$𐌉𐌉2.class', + \ '𐌂1$𐌂11.class', '𐌂1.class', '𐌂2$1𐌄.class', '𐌂2$1𐌓.class', '𐌂2.class'] + call writefile(0zcafe.babe.0000.0041, class_dir .. class_file) + endfor + call writefile(0zcafe.babe.0000.0041, class_dir .. 'package-info.class') + + " Write Java source files. + let type_file = src_dir .. '𐌂1.java' + call writefile(insert(copy(lines), package), type_file) + let package_file = src_dir .. 'package-info.java' + call writefile([package], src_dir .. 'package-info.java') + + " Note that using "off" for the first _outer_ iteration is preferable + " because only then "hlexists()" may be 0 (see "compiler/spotbugs.vim"). + for s in ['off', 'on'] + execute 'syntax ' .. s + + execute 'edit ' .. type_file + compiler spotbugs + let result = s:SpotBugsParseFilterMakePrg('Xspotbugs', &l:makeprg) + call assert_equal(results[type_file].Sourcepath(), result.sourcepath) + call assert_equal(results[type_file].classfiles, result.classfiles) + bwipeout + + execute 'edit ' .. package_file + compiler spotbugs + let result = s:SpotBugsParseFilterMakePrg('Xspotbugs', &l:makeprg) + call assert_equal(results[package_file].Sourcepath(), result.sourcepath) + call assert_equal(results[package_file].classfiles, result.classfiles) + bwipeout + endfor + endfor + + let &shellslash = save_shellslash +endfunc + +func s:SpotBugsBeforeFileTypeTryPluginAndClearCache(state) + " Ponder over "extend(spotbugs#DefaultProperties(), g:spotbugs_properties)" + " in "ftplugin/java.vim". + let g:spotbugs#state = a:state + runtime autoload/spotbugs.vim +endfunc + +func Test_compiler_spotbugs_properties() + let save_shellslash = &shellslash + set shellslash + setlocal makeprg= + filetype plugin on + + call assert_true(mkdir('Xspotbugs/src', 'pR')) + call assert_true(mkdir('Xspotbugs/tests', 'pR')) + let type_file = 'Xspotbugs/src/𐌄.java' + let test_file = 'Xspotbugs/tests/𐌄$.java' + call writefile(['enum 𐌄{}'], type_file) + call writefile(['class 𐌄${}'], test_file) + + " TEST INTEGRATION WITH A BOGUS COMPILER PLUGIN. + if !filereadable($VIMRUNTIME .. '/compiler/foo.vim') && !executable('foo') + let g:spotbugs_properties = {'compiler': 'foo'} + " XXX: In case this "if" block is no longer first. + call s:SpotBugsBeforeFileTypeTryPluginAndClearCache({ + \ 'compiler': g:spotbugs_properties.compiler, + \ }) + execute 'edit ' .. type_file + call assert_equal('java', &l:filetype) + " This variable will indefinitely keep the compiler name. + call assert_equal('foo', g:spotbugs#state.compiler) + " The "compiler" entry should be gone after FileType and default entries + " should only appear for a supported compiler. + call assert_false(has_key(g:spotbugs_properties, 'compiler')) + call assert_true(empty(g:spotbugs_properties)) + " Query default implementations. + call assert_true(exists('*spotbugs#DefaultProperties')) + call assert_true(exists('*spotbugs#DefaultPreCompilerAction')) + call assert_true(exists('*spotbugs#DefaultPreCompilerTestAction')) + call assert_true(empty(spotbugs#DefaultProperties())) + " Get a ":message". + redir => out + call spotbugs#DefaultPreCompilerAction() + redir END + call assert_equal('Not supported: "foo"', out[stridx(out, 'Not') :]) + " Get a ":message". + redir => out + call spotbugs#DefaultPreCompilerTestAction() + redir END + call assert_equal('Not supported: "foo"', out[stridx(out, 'Not') :]) + " No ":autocmd"s without one of "PreCompiler*Action", "PostCompilerAction". + call assert_false(exists('#java_spotbugs')) + bwipeout + endif + + let s:spotbugs_results = { + \ 'preActionDone': 0, + \ 'preTestActionDone': 0, + \ 'preTestLocalActionDone': 0, + \ 'postActionDone': 0, + \ 'preCommandArguments': '', + \ 'preTestCommandArguments': '', + \ 'postCommandArguments': '', + \ } + defer execute('unlet s:spotbugs_results') + + func! g:SpotBugsPreAction() abort + let s:spotbugs_results.preActionDone = 1 + " XXX: Notify the spotbugs compiler about success or failure. + cc + endfunc + defer execute('delfunction g:SpotBugsPreAction') + + func! g:SpotBugsPreTestAction() abort + let s:spotbugs_results.preTestActionDone = 1 + " XXX: Let see compilation fail. + throw 'Oops' + endfunc + defer execute('delfunction g:SpotBugsPreTestAction') + + func! g:SpotBugsPreTestLocalAction() abort + let s:spotbugs_results.preTestLocalActionDone = 1 + " XXX: Notify the spotbugs compiler about success or failure. + cc + endfunc + defer execute('delfunction g:SpotBugsPreTestLocalAction') + + func! g:SpotBugsPostAction() abort + let s:spotbugs_results.postActionDone = 1 + endfunc + defer execute('delfunction g:SpotBugsPostAction') + + func! g:SpotBugsPreCommand(arguments) abort + let s:spotbugs_results.preActionDone = 1 + let s:spotbugs_results.preCommandArguments = a:arguments + " XXX: Notify the spotbugs compiler about success or failure. + cc + endfunc + defer execute('delfunction g:SpotBugsPreCommand') + + func! g:SpotBugsPreTestCommand(arguments) abort + let s:spotbugs_results.preTestActionDone = 1 + let s:spotbugs_results.preTestCommandArguments = a:arguments + " XXX: Notify the spotbugs compiler about success or failure. + cc + endfunc + defer execute('delfunction g:SpotBugsPreTestCommand') + + func! g:SpotBugsPostCommand(arguments) abort + let s:spotbugs_results.postActionDone = 1 + let s:spotbugs_results.postCommandArguments = a:arguments + endfunc + defer execute('delfunction g:SpotBugsPostCommand') + + func! g:SpotBugsPostCompilerActionExecutor(action) abort + try + " XXX: Notify the spotbugs compiler about success or failure. + cc + catch /\<E42:/ + execute a:action + endtry + endfunc + defer execute('delfunction g:SpotBugsPostCompilerActionExecutor') + + " TEST INTEGRATION WITH A SUPPORTED COMPILER PLUGIN. + if filereadable($VIMRUNTIME .. '/compiler/maven.vim') + let save_PATH = $PATH + if !executable('mvn') + if has('win32') + let $PATH = 'Xspotbugs;' .. $PATH + " This is what ":help executable()" suggests. + call writefile([], 'Xspotbugs/mvn.cmd') + else + let $PATH = 'Xspotbugs:' .. $PATH + call writefile([], 'Xspotbugs/mvn') + call setfperm('Xspotbugs/mvn', 'rwx------') + endif + endif + + let g:spotbugs_properties = { + \ 'compiler': 'maven', + \ 'PreCompilerAction': function('g:SpotBugsPreAction'), + \ 'PreCompilerTestAction': function('g:SpotBugsPreTestAction'), + \ 'PostCompilerAction': function('g:SpotBugsPostAction'), + \ } + " XXX: In case this is a runner-up ":edit". + call s:SpotBugsBeforeFileTypeTryPluginAndClearCache({ + \ 'compiler': g:spotbugs_properties.compiler, + \ }) + execute 'edit ' .. type_file + call assert_equal('java', &l:filetype) + call assert_equal('maven', g:spotbugs#state.compiler) + call assert_false(has_key(g:spotbugs_properties, 'compiler')) + call assert_false(empty(g:spotbugs_properties)) + " Query default implementations. + call assert_true(exists('*spotbugs#DefaultProperties')) + call assert_equal(sort([ + \ 'PreCompilerAction', + \ 'PreCompilerTestAction', + \ 'PostCompilerAction', + \ 'sourceDirPath', + \ 'classDirPath', + \ 'testSourceDirPath', + \ 'testClassDirPath', + \ ]), + \ sort(keys(spotbugs#DefaultProperties()))) + " Some ":autocmd"s with one of "PreCompiler*Action", "PostCompilerAction". + call assert_true(exists('#java_spotbugs')) + call assert_true(exists('#java_spotbugs#Syntax')) + call assert_true(exists('#java_spotbugs#User')) + call assert_equal(2, exists(':SpotBugsDefineBufferAutocmd')) + " SpotBugsDefineBufferAutocmd SigUSR1 User SigUSR1 User SigUSR1 User + " call assert_true(exists('#java_spotbugs#SigUSR1')) + SpotBugsDefineBufferAutocmd Signal User Signal User Signal User + call assert_true(exists('#java_spotbugs#Signal')) + call assert_true(exists('#java_spotbugs#Syntax')) + call assert_true(exists('#java_spotbugs#User')) + call assert_equal(2, exists(':SpotBugsRemoveBufferAutocmd')) + " SpotBugsRemoveBufferAutocmd SigUSR1 User SigUSR1 User UserGettingBored + " call assert_false(exists('#java_spotbugs#SigUSR1')) + SpotBugsRemoveBufferAutocmd Signal User Signal User UserGettingBored + call assert_false(exists('#java_spotbugs#Signal')) + call assert_true(exists('#java_spotbugs#Syntax')) + call assert_true(exists('#java_spotbugs#User')) + + let s:spotbugs_results.preActionDone = 0 + let s:spotbugs_results.preTestActionDone = 0 + let s:spotbugs_results.postActionDone = 0 + + doautocmd java_spotbugs Syntax + call assert_false(exists('#java_spotbugs#Syntax')) + + " No match: "type_file !~# 'src/main/java'". + call assert_false(s:spotbugs_results.preActionDone) + " No match: "type_file !~# 'src/test/java'". + call assert_false(s:spotbugs_results.preTestActionDone) + " No pre-match, no post-action. + call assert_false(s:spotbugs_results.postActionDone) + " Without a match, confirm that ":compiler spotbugs" has NOT run. + call assert_true(empty(&l:makeprg)) + + let s:spotbugs_results.preActionDone = 0 + let s:spotbugs_results.preTestActionDone = 0 + let s:spotbugs_results.postActionDone = 0 + " Update path entries. (Note that we cannot use just "src" because there + " is another "src" directory nearer the filesystem root directory, i.e. + " "vim/vim/src/testdir/Xspotbugs/src", and "s:DispatchAction()" (see + " "ftplugin/java.vim") will match "vim/vim/src/testdir/Xspotbugs/tests" + " against "src".) + let g:spotbugs_properties.sourceDirPath = ['Xspotbugs/src'] + let g:spotbugs_properties.classDirPath = ['Xspotbugs/src'] + let g:spotbugs_properties.testSourceDirPath = ['tests'] + let g:spotbugs_properties.testClassDirPath = ['tests'] + + doautocmd java_spotbugs User + " No match: "type_file !~# 'src/main/java'" (with old "*DirPath" values + " cached). + call assert_false(s:spotbugs_results.preActionDone) + " No match: "type_file !~# 'src/test/java'" (with old "*DirPath" values + " cached). + call assert_false(s:spotbugs_results.preTestActionDone) + " No pre-match, no post-action. + call assert_false(s:spotbugs_results.postActionDone) + " Without a match, confirm that ":compiler spotbugs" has NOT run. + call assert_true(empty(&l:makeprg)) + + let s:spotbugs_results.preActionDone = 0 + let s:spotbugs_results.preTestActionDone = 0 + let s:spotbugs_results.postActionDone = 0 + " XXX: Re-build ":autocmd"s from scratch with new values applied. + doautocmd FileType + + call assert_true(exists('b:spotbugs_syntax_once')) + doautocmd java_spotbugs User + " A match: "type_file =~# 'Xspotbugs/src'" (with new "*DirPath" values + " cached). + call assert_true(s:spotbugs_results.preActionDone) + " No match: "type_file !~# 'tests'" (with new "*DirPath" values cached). + call assert_false(s:spotbugs_results.preTestActionDone) + " For a pre-match, a post-action. + call assert_true(s:spotbugs_results.postActionDone) + + " With a match, confirm that ":compiler spotbugs" has run. + if has('win32') + call assert_match('^spotbugs\.bat\s', &l:makeprg) + else + call assert_match('^spotbugs\s', &l:makeprg) + endif + + bwipeout + setlocal makeprg= + let s:spotbugs_results.preActionDone = 0 + let s:spotbugs_results.preTestActionDone = 0 + let s:spotbugs_results.preTestLocalActionDone = 0 + let s:spotbugs_results.postActionDone = 0 + + execute 'edit ' .. test_file + " Prepare a buffer-local, incomplete variant of properties, relying on + " "ftplugin/java.vim" to take care of merging in unique entries, if any, + " from "g:spotbugs_properties". + let b:spotbugs_properties = { + \ 'PreCompilerTestAction': function('g:SpotBugsPreTestLocalAction'), + \ } + call assert_equal('java', &l:filetype) + call assert_true(exists('#java_spotbugs')) + call assert_true(exists('#java_spotbugs#Syntax')) + call assert_true(exists('#java_spotbugs#User')) + call assert_fails('doautocmd java_spotbugs Syntax', 'Oops') + call assert_false(exists('#java_spotbugs#Syntax')) + " No match: "test_file !~# 'Xspotbugs/src'". + call assert_false(s:spotbugs_results.preActionDone) + " A match: "test_file =~# 'tests'". + call assert_true(s:spotbugs_results.preTestActionDone) + call assert_false(s:spotbugs_results.preTestLocalActionDone) + " No action after pre-failure (the thrown "Oops" doesn't qualify for ":cc"). + call assert_false(s:spotbugs_results.postActionDone) + " No ":compiler spotbugs" will be run after pre-failure. + call assert_true(empty(&l:makeprg)) + + let s:spotbugs_results.preActionDone = 0 + let s:spotbugs_results.preTestActionDone = 0 + let s:spotbugs_results.preTestLocalActionDone = 0 + let s:spotbugs_results.postActionDone = 0 + " XXX: Re-build ":autocmd"s from scratch with buffer-local values applied. + doautocmd FileType + + call assert_true(exists('b:spotbugs_syntax_once')) + doautocmd java_spotbugs User + " No match: "test_file !~# 'Xspotbugs/src'". + call assert_false(s:spotbugs_results.preActionDone) + " A match: "test_file =~# 'tests'". + call assert_true(s:spotbugs_results.preTestLocalActionDone) + call assert_false(s:spotbugs_results.preTestActionDone) + " For a pre-match, a post-action. + call assert_true(s:spotbugs_results.postActionDone) + + " With a match, confirm that ":compiler spotbugs" has run. + if has('win32') + call assert_match('^spotbugs\.bat\s', &l:makeprg) + else + call assert_match('^spotbugs\s', &l:makeprg) + endif + + setlocal makeprg= + let s:spotbugs_results.preActionDone = 0 + let s:spotbugs_results.preTestActionDone = 0 + let s:spotbugs_results.preTestLocalActionDone = 0 + let s:spotbugs_results.postActionDone = 0 + let s:spotbugs_results.preCommandArguments = '' + let s:spotbugs_results.preTestCommandArguments = '' + let s:spotbugs_results.postCommandArguments = '' + " XXX: Compose the assigned "*Command"s with the default Maven "*Action"s. + let b:spotbugs_properties = { + \ 'compiler': 'maven', + \ 'DefaultPreCompilerTestCommand': function('g:SpotBugsPreTestCommand'), + \ 'DefaultPreCompilerCommand': function('g:SpotBugsPreCommand'), + \ 'DefaultPostCompilerCommand': function('g:SpotBugsPostCommand'), + \ 'PostCompilerActionExecutor': function('g:SpotBugsPostCompilerActionExecutor'), + \ 'augroupForPostCompilerAction': 'java_spotbugs_test', + \ 'sourceDirPath': ['Xspotbugs/src'], + \ 'classDirPath': ['Xspotbugs/src'], + \ 'testSourceDirPath': ['tests'], + \ 'testClassDirPath': ['tests'], + \ } + unlet g:spotbugs_properties + " XXX: Re-build ":autocmd"s from scratch with buffer-local values applied. + call s:SpotBugsBeforeFileTypeTryPluginAndClearCache({ + \ 'compiler': b:spotbugs_properties.compiler, + \ 'commands': { + \ 'DefaultPreCompilerTestCommand': + \ b:spotbugs_properties.DefaultPreCompilerTestCommand, + \ 'DefaultPreCompilerCommand': + \ b:spotbugs_properties.DefaultPreCompilerCommand, + \ 'DefaultPostCompilerCommand': + \ b:spotbugs_properties.DefaultPostCompilerCommand, + \ }, + \ }) + doautocmd FileType + + call assert_equal('maven', g:spotbugs#state.compiler) + call assert_equal(sort([ + \ 'DefaultPreCompilerTestCommand', + \ 'DefaultPreCompilerCommand', + \ 'DefaultPostCompilerCommand', + \ ]), + \ sort(keys(g:spotbugs#state.commands))) + call assert_true(exists('b:spotbugs_syntax_once')) + doautocmd java_spotbugs User + " No match: "test_file !~# 'Xspotbugs/src'". + call assert_false(s:spotbugs_results.preActionDone) + call assert_true(empty(s:spotbugs_results.preCommandArguments)) + " A match: "test_file =~# 'tests'". + call assert_true(s:spotbugs_results.preTestActionDone) + call assert_equal('test-compile', s:spotbugs_results.preTestCommandArguments) + " For a pre-match, a post-action. + call assert_true(s:spotbugs_results.postActionDone) + call assert_equal('%:S', s:spotbugs_results.postCommandArguments) + + " With a match, confirm that ":compiler spotbugs" has run. + if has('win32') + call assert_match('^spotbugs\.bat\s', &l:makeprg) + else + call assert_match('^spotbugs\s', &l:makeprg) + endif + + setlocal makeprg= + let s:spotbugs_results.preActionDone = 0 + let s:spotbugs_results.preTestActionOtherDone = 0 + let s:spotbugs_results.preTestLocalActionDone = 0 + let s:spotbugs_results.postActionDone = 0 + let s:spotbugs_results.preCommandArguments = '' + let s:spotbugs_results.preTestCommandArguments = '' + let s:spotbugs_results.postCommandArguments = '' + + " When "PostCompilerActionExecutor", "Pre*Action" and/or "Pre*TestAction", + " and "Post*Action" are available, "#java_spotbugs_post" must be defined. + call assert_true(exists('#java_spotbugs_post')) + call assert_true(exists('#java_spotbugs_post#User')) + call assert_false(exists('#java_spotbugs_post#ShellCmdPost')) + call assert_false(exists('#java_spotbugs_test#ShellCmdPost')) + + " Re-link a Funcref on the fly. + func! g:SpotBugsPreTestCommand(arguments) abort + let s:spotbugs_results.preTestActionOtherDone = 1 + let s:spotbugs_results.preTestCommandArguments = a:arguments + " Define a once-only ":autocmd" for "#java_spotbugs_test#ShellCmdPost". + doautocmd java_spotbugs_post User + " XXX: Do NOT use ":cc" to notify the spotbugs compiler about success or + " failure, and assume the transfer of control to a ShellCmdPost command. + endfunc + + doautocmd java_spotbugs User + " No match: "test_file !~# 'Xspotbugs/src'". + call assert_false(s:spotbugs_results.preActionDone) + call assert_true(empty(s:spotbugs_results.preCommandArguments)) + " A match: "test_file =~# 'tests'". + call assert_true(s:spotbugs_results.preTestActionOtherDone) + call assert_equal('test-compile', s:spotbugs_results.preTestCommandArguments) + " For a pre-match, no post-action (without ":cc") UNLESS a ShellCmdPost + " event is consumed whose command will invoke "PostCompilerActionExecutor" + " and the latter will accept a post-compiler action argument. + call assert_false(s:spotbugs_results.postActionDone) + call assert_true(exists('#java_spotbugs_test#ShellCmdPost')) + doautocmd ShellCmdPost + call assert_false(exists('#java_spotbugs_test#ShellCmdPost')) + call assert_true(s:spotbugs_results.postActionDone) + call assert_equal('%:S', s:spotbugs_results.postCommandArguments) + + " With a match, confirm that ":compiler spotbugs" has run. + if has('win32') + call assert_match('^spotbugs\.bat\s', &l:makeprg) + else + call assert_match('^spotbugs\s', &l:makeprg) + endif + + bwipeout + setlocal makeprg= + let $PATH = save_PATH + endif + + filetype plugin off + setlocal makeprg= + let &shellslash = save_shellslash +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/test_debugger.vim b/test/old/testdir/test_debugger.vim index 3a469e8a17..5820b58247 100644 --- a/test/old/testdir/test_debugger.vim +++ b/test/old/testdir/test_debugger.vim @@ -6,10 +6,11 @@ source check.vim func CheckCWD() " Check that the longer lines don't wrap due to the length of the script name - " in cwd + " in cwd. Need to subtract by 1 since Vim will still wrap the message if it + " just fits. let script_len = len( getcwd() .. '/Xtest1.vim' ) let longest_line = len( 'Breakpoint in "" line 1' ) - if script_len > ( 75 - longest_line ) + if script_len > ( 75 - longest_line - 1 ) throw 'Skipped: Your CWD has too many characters' endif endfunc diff --git a/test/old/testdir/test_diffmode.vim b/test/old/testdir/test_diffmode.vim index 880286d329..ab64658bd0 100644 --- a/test/old/testdir/test_diffmode.vim +++ b/test/old/testdir/test_diffmode.vim @@ -1017,6 +1017,41 @@ func Test_diff_screen() call WriteDiffFiles(buf, [], [0]) call VerifyBoth(buf, "Test_diff_22", "") + call WriteDiffFiles(buf, ['?a', '?b', '?c'], ['!b']) + call VerifyInternal(buf, 'Test_diff_23', " diffopt+=linematch:30") + + call WriteDiffFiles(buf, ['', + \ 'common line', + \ 'common line', + \ '', + \ 'DEFabc', + \ 'xyz', + \ 'xyz', + \ 'xyz', + \ 'DEFabc', + \ 'DEFabc', + \ 'DEFabc', + \ 'common line', + \ 'common line', + \ 'DEF', + \ 'common line', + \ 'DEF', + \ 'something' ], + \ ['', + \ 'common line', + \ 'common line', + \ '', + \ 'ABCabc', + \ 'ABCabc', + \ 'ABCabc', + \ 'ABCabc', + \ 'common line', + \ 'common line', + \ 'common line', + \ 'something']) + call VerifyInternal(buf, 'Test_diff_24', " diffopt+=linematch:30") + + " clean up call StopVimInTerminal(buf) call delete('Xdifile1') @@ -1212,7 +1247,7 @@ func CloseoffSetup() call setline(1, ['one', 'tow', 'three']) diffthis call assert_equal(1, &diff) - only! + bw! endfunc func Test_diff_closeoff() @@ -2004,6 +2039,12 @@ func Test_diff_overlapped_diff_blocks_will_be_merged() call WriteDiffFiles3(buf, ["a", "b", "c"], ["a", "x", "c"], ["a", "b", "y", "c"]) call VerifyBoth(buf, "Test_diff_overlapped_3.37", "") + call WriteDiffFiles3(buf, ["a", "b", "c"], ["d", "e"], ["b", "f"]) + call VerifyBoth(buf, "Test_diff_overlapped_3.38", "") + + call WriteDiffFiles3(buf, ["a", "b", "c"], ["d", "e"], ["b"]) + call VerifyBoth(buf, "Test_diff_overlapped_3.39", "") + call StopVimInTerminal(buf) endfunc @@ -2034,4 +2075,440 @@ func Test_diff_topline_noscroll() call StopVimInTerminal(buf) endfunc +func Test_diffget_diffput_linematch() + CheckScreendump + call delete('.Xdifile1.swp') + call delete('.Xdifile2.swp') + call WriteDiffFiles(0, [], []) + let buf = RunVimInTerminal('-d Xdifile1 Xdifile2', {}) + call term_sendkeys(buf, ":set autoread\<CR>\<c-w>w:set autoread\<CR>\<c-w>w") + + " enable linematch + call term_sendkeys(buf, ":set diffopt+=linematch:30\<CR>") + call WriteDiffFiles(buf, ['', + \ 'common line', + \ 'common line', + \ '', + \ 'ABCabc', + \ 'ABCabc', + \ 'ABCabc', + \ 'ABCabc', + \ 'common line', + \ 'common line', + \ 'common line', + \ 'something' ], + \ ['', + \ 'common line', + \ 'common line', + \ '', + \ 'DEFabc', + \ 'xyz', + \ 'xyz', + \ 'xyz', + \ 'DEFabc', + \ 'DEFabc', + \ 'DEFabc', + \ 'common line', + \ 'common line', + \ 'DEF', + \ 'common line', + \ 'DEF', + \ 'something']) + call VerifyScreenDump(buf, 'Test_diff_get_put_linematch_1', {}) + + " get from window 1 from line 5 to 9 + call term_sendkeys(buf, "1\<c-w>w") + call term_sendkeys(buf, ":5,9diffget\<CR>") + call VerifyScreenDump(buf, 'Test_diff_get_put_linematch_2', {}) + + " undo the last diffget + call term_sendkeys(buf, "u") + + " get from window 2 from line 5 to 10 + call term_sendkeys(buf, "2\<c-w>w") + call term_sendkeys(buf, ":5,10diffget\<CR>") + call VerifyScreenDump(buf, 'Test_diff_get_put_linematch_3', {}) + + " undo the last diffget + call term_sendkeys(buf, "u") + + " get all from window 2 + call term_sendkeys(buf, "2\<c-w>w") + call term_sendkeys(buf, ":4,17diffget\<CR>") + call VerifyScreenDump(buf, 'Test_diff_get_put_linematch_4', {}) + + " undo the last diffget + call term_sendkeys(buf, "u") + + " get all from window 1 + call term_sendkeys(buf, "1\<c-w>w") + call term_sendkeys(buf, ":4,12diffget\<CR>") + call VerifyScreenDump(buf, 'Test_diff_get_put_linematch_5', {}) + + " undo the last diffget + call term_sendkeys(buf, "u") + + " get from window 1 using do 1 line 5 + call term_sendkeys(buf, "1\<c-w>w") + call term_sendkeys(buf, "5gg") + call term_sendkeys(buf, ":diffget\<CR>") + call VerifyScreenDump(buf, 'Test_diff_get_put_linematch_6', {}) + + " undo the last diffget + call term_sendkeys(buf, "u") + + " get from window 1 using do 2 line 6 + call term_sendkeys(buf, "1\<c-w>w") + call term_sendkeys(buf, "6gg") + call term_sendkeys(buf, ":diffget\<CR>") + call VerifyScreenDump(buf, 'Test_diff_get_put_linematch_7', {}) + + " undo the last diffget + call term_sendkeys(buf, "u") + + " get from window 1 using do 2 line 7 + call term_sendkeys(buf, "1\<c-w>w") + call term_sendkeys(buf, "7gg") + call term_sendkeys(buf, ":diffget\<CR>") + call VerifyScreenDump(buf, 'Test_diff_get_put_linematch_8', {}) + + " undo the last diffget + call term_sendkeys(buf, "u") + + " get from window 1 using do 2 line 11 + call term_sendkeys(buf, "1\<c-w>w") + call term_sendkeys(buf, "11gg") + call term_sendkeys(buf, ":diffget\<CR>") + call VerifyScreenDump(buf, 'Test_diff_get_put_linematch_9', {}) + + " undo the last diffget + call term_sendkeys(buf, "u") + + " get from window 1 using do 2 line 12 + call term_sendkeys(buf, "1\<c-w>w") + call term_sendkeys(buf, "12gg") + call term_sendkeys(buf, ":diffget\<CR>") + call VerifyScreenDump(buf, 'Test_diff_get_put_linematch_10', {}) + + " undo the last diffget + call term_sendkeys(buf, "u") + + " put from window 1 using dp 1 line 5 + call term_sendkeys(buf, "1\<c-w>w") + call term_sendkeys(buf, "5gg") + call term_sendkeys(buf, ":diffput\<CR>") + call VerifyScreenDump(buf, 'Test_diff_get_put_linematch_11', {}) + + " undo the last diffput + call term_sendkeys(buf, "2\<c-w>w") + call term_sendkeys(buf, "u") + + " put from window 1 using dp 2 line 6 + call term_sendkeys(buf, "1\<c-w>w") + call term_sendkeys(buf, "6gg") + call term_sendkeys(buf, ":diffput\<CR>") + call VerifyScreenDump(buf, 'Test_diff_get_put_linematch_12', {}) + + " undo the last diffput + call term_sendkeys(buf, "2\<c-w>w") + call term_sendkeys(buf, "u") + + " put from window 1 using dp 2 line 7 + call term_sendkeys(buf, "1\<c-w>w") + call term_sendkeys(buf, "7gg") + call term_sendkeys(buf, ":diffput\<CR>") + call VerifyScreenDump(buf, 'Test_diff_get_put_linematch_13', {}) + + " undo the last diffput + call term_sendkeys(buf, "2\<c-w>w") + call term_sendkeys(buf, "u") + + " put from window 1 using dp 2 line 11 + call term_sendkeys(buf, "1\<c-w>w") + call term_sendkeys(buf, "11gg") + call term_sendkeys(buf, ":diffput\<CR>") + call VerifyScreenDump(buf, 'Test_diff_get_put_linematch_14', {}) + + " undo the last diffput + call term_sendkeys(buf, "2\<c-w>w") + call term_sendkeys(buf, "u") + + " put from window 1 using dp 2 line 12 + call term_sendkeys(buf, "1\<c-w>w") + call term_sendkeys(buf, "12gg") + call term_sendkeys(buf, ":diffput\<CR>") + call VerifyScreenDump(buf, 'Test_diff_get_put_linematch_15', {}) + + " undo the last diffput + call term_sendkeys(buf, "2\<c-w>w") + call term_sendkeys(buf, "u") + + " put from window 2 using dp line 6 + call term_sendkeys(buf, "2\<c-w>w") + call term_sendkeys(buf, "6gg") + call term_sendkeys(buf, ":diffput\<CR>") + call VerifyScreenDump(buf, 'Test_diff_get_put_linematch_16', {}) + + " undo the last diffput + call term_sendkeys(buf, "1\<c-w>w") + call term_sendkeys(buf, "u") + + " put from window 2 using dp line 8 + call term_sendkeys(buf, "2\<c-w>w") + call term_sendkeys(buf, "8gg") + call term_sendkeys(buf, ":diffput\<CR>") + call VerifyScreenDump(buf, 'Test_diff_get_put_linematch_17', {}) + + " undo the last diffput + call term_sendkeys(buf, "1\<c-w>w") + call term_sendkeys(buf, "u") + + " put from window 2 using dp line 9 + call term_sendkeys(buf, "2\<c-w>w") + call term_sendkeys(buf, "9gg") + call term_sendkeys(buf, ":diffput\<CR>") + call VerifyScreenDump(buf, 'Test_diff_get_put_linematch_18', {}) + + " undo the last diffput + call term_sendkeys(buf, "1\<c-w>w") + call term_sendkeys(buf, "u") + + " put from window 2 using dp line 17 + call term_sendkeys(buf, "2\<c-w>w") + call term_sendkeys(buf, "17gg") + call term_sendkeys(buf, ":diffput\<CR>") + call VerifyScreenDump(buf, 'Test_diff_get_put_linematch_19', {}) + " clean up + call StopVimInTerminal(buf) +endfunc + +func Test_linematch_diff() + CheckScreendump + call delete('.Xdifile1.swp') + call delete('.Xdifile2.swp') + call WriteDiffFiles(0, [], []) + let buf = RunVimInTerminal('-d Xdifile1 Xdifile2', {}) + call term_sendkeys(buf, ":set autoread\<CR>\<c-w>w:set autoread\<CR>\<c-w>w") + + " enable linematch + call term_sendkeys(buf, ":set diffopt+=linematch:30\<CR>") + call WriteDiffFiles(buf, ['// abc d?', + \ '// d?', + \ '// d?' ], + \ ['!', + \ 'abc d!', + \ 'd!']) + call VerifyScreenDump(buf, 'Test_linematch_diff1', {}) + " clean up + call StopVimInTerminal(buf) +endfunc + +func Test_linematch_diff_iwhite() + CheckScreendump + call delete('.Xdifile1.swp') + call delete('.Xdifile2.swp') + call WriteDiffFiles(0, [], []) + let buf = RunVimInTerminal('-d Xdifile1 Xdifile2', {}) + call term_sendkeys(buf, ":set autoread\<CR>\<c-w>w:set autoread\<CR>\<c-w>w") + + " setup a diff with 2 files and set linematch:30, with ignore white + call term_sendkeys(buf, ":set diffopt+=linematch:30\<CR>") + call WriteDiffFiles(buf, ['void testFunction () {', + \ ' for (int i = 0; i < 10; i++) {', + \ ' for (int j = 0; j < 10; j++) {', + \ ' }', + \ ' }', + \ '}' ], + \ ['void testFunction () {', + \ ' // for (int j = 0; j < 10; i++) {', + \ ' // }', + \ '}']) + call VerifyScreenDump(buf, 'Test_linematch_diff_iwhite1', {}) + call term_sendkeys(buf, ":set diffopt+=iwhiteall\<CR>") + call VerifyScreenDump(buf, 'Test_linematch_diff_iwhite2', {}) + " clean up + call StopVimInTerminal(buf) +endfunc + +func Test_linematch_diff_grouping() + CheckScreendump + call delete('.Xdifile1.swp') + call delete('.Xdifile2.swp') + call WriteDiffFiles(0, [], []) + let buf = RunVimInTerminal('-d Xdifile1 Xdifile2', {}) + call term_sendkeys(buf, ":set autoread\<CR>\<c-w>w:set autoread\<CR>\<c-w>w") + + " a diff that would result in multiple groups before grouping optimization + call term_sendkeys(buf, ":set diffopt+=linematch:30\<CR>") + call WriteDiffFiles(buf, ['!A', + \ '!B', + \ '!C' ], + \ ['?Z', + \ '?A', + \ '?B', + \ '?C', + \ '?A', + \ '?B', + \ '?B', + \ '?C']) + call VerifyScreenDump(buf, 'Test_linematch_diff_grouping1', {}) + call WriteDiffFiles(buf, ['!A', + \ '!B', + \ '!C' ], + \ ['?A', + \ '?Z', + \ '?B', + \ '?C', + \ '?A', + \ '?B', + \ '?C', + \ '?C']) + call VerifyScreenDump(buf, 'Test_linematch_diff_grouping2', {}) + " clean up + call StopVimInTerminal(buf) +endfunc + +func Test_linematch_diff_scroll() + CheckScreendump + call delete('.Xdifile1.swp') + call delete('.Xdifile2.swp') + call WriteDiffFiles(0, [], []) + let buf = RunVimInTerminal('-d Xdifile1 Xdifile2', {}) + call term_sendkeys(buf, ":set autoread\<CR>\<c-w>w:set autoread\<CR>\<c-w>w") + + " a diff that would result in multiple groups before grouping optimization + call term_sendkeys(buf, ":set diffopt+=linematch:30\<CR>") + call WriteDiffFiles(buf, ['!A', + \ '!B', + \ '!C' ], + \ ['?A', + \ '?Z', + \ '?B', + \ '?C', + \ '?A', + \ '?B', + \ '?C', + \ '?C']) + " scroll down to show calculation of top fill and scroll to correct line in + " both windows + call VerifyScreenDump(buf, 'Test_linematch_diff_grouping_scroll0', {}) + call term_sendkeys(buf, "3\<c-e>") + call VerifyScreenDump(buf, 'Test_linematch_diff_grouping_scroll1', {}) + call term_sendkeys(buf, "3\<c-e>") + call VerifyScreenDump(buf, 'Test_linematch_diff_grouping_scroll2', {}) + " clean up + call StopVimInTerminal(buf) +endfunc + +func Test_linematch_line_limit_exceeded() + CheckScreendump + call delete('.Xdifile1.swp') + call delete('.Xdifile2.swp') + call WriteDiffFiles(0, [], []) + let buf = RunVimInTerminal('-d Xdifile1 Xdifile2', {}) + call term_sendkeys(buf, ":set autoread\<CR>\<c-w>w:set autoread\<CR>\<c-w>w") + + call term_sendkeys(buf, ":set diffopt+=linematch:10\<CR>") + " a diff block will not be aligned with linematch because it's contents + " exceed 10 lines + call WriteDiffFiles(buf, + \ ['common line', + \ 'HIL', + \ '', + \ 'aABCabc', + \ 'aABCabc', + \ 'aABCabc', + \ 'aABCabc', + \ 'common line', + \ 'HIL', + \ 'common line', + \ 'something'], + \ ['common line', + \ 'DEF', + \ 'GHI', + \ 'something', + \ '', + \ 'aDEFabc', + \ 'xyz', + \ 'xyz', + \ 'xyz', + \ 'aDEFabc', + \ 'aDEFabc', + \ 'aDEFabc', + \ 'common line', + \ 'DEF', + \ 'GHI', + \ 'something else', + \ 'common line', + \ 'something']) + call VerifyScreenDump(buf, 'Test_linematch_line_limit_exceeded1', {}) + " after increasing the count to 30, the limit is not exceeded, and the + " alignment algorithm will run on the largest diff block here + call term_sendkeys(buf, ":set diffopt+=linematch:30\<CR>") + call VerifyScreenDump(buf, 'Test_linematch_line_limit_exceeded2', {}) + " clean up + call StopVimInTerminal(buf) +endfunc + +func Test_linematch_3diffs() + CheckScreendump + call delete('.Xdifile1.swp') + call delete('.Xdifile2.swp') + call delete('.Xdifile3.swp') + call WriteDiffFiles3(0, [], [], []) + let buf = RunVimInTerminal('-d Xdifile1 Xdifile2 Xdifile3', {}) + call term_sendkeys(buf, "1\<c-w>w:set autoread\<CR>") + call term_sendkeys(buf, "2\<c-w>w:set autoread\<CR>") + call term_sendkeys(buf, "3\<c-w>w:set autoread\<CR>") + call term_sendkeys(buf, ":set diffopt+=linematch:30\<CR>") + call WriteDiffFiles3(buf, + \ ["", + \ " common line", + \ " AAA", + \ " AAA", + \ " AAA"], + \ ["", + \ " common line", + \ " <<<<<<< HEAD", + \ " AAA", + \ " AAA", + \ " AAA", + \ " =======", + \ " BBB", + \ " BBB", + \ " BBB", + \ " >>>>>>> branch1"], + \ ["", + \ " common line", + \ " BBB", + \ " BBB", + \ " BBB"]) + call VerifyScreenDump(buf, 'Test_linematch_3diffs1', {}) + " clean up + call StopVimInTerminal(buf) +endfunc + +" this used to access invalid memory +func Test_linematch_3diffs_sanity_check() + CheckScreendump + call delete('.Xfile_linematch1.swp') + call delete('.Xfile_linematch2.swp') + call delete('.Xfile_linematch3.swp') + let lines =<< trim END + set diffopt+=linematch:60 + call feedkeys("Aq\<esc>") + call feedkeys("GAklm\<esc>") + call feedkeys("o") + END + call writefile(lines, 'Xlinematch_3diffs.vim', 'D') + call writefile(['abcd', 'def', 'hij'], 'Xfile_linematch1', 'D') + call writefile(['defq', 'hijk', 'nopq'], 'Xfile_linematch2', 'D') + call writefile(['hijklm', 'nopqr', 'stuv'], 'Xfile_linematch3', 'D') + call WriteDiffFiles3(0, [], [], []) + let buf = RunVimInTerminal('-d -S Xlinematch_3diffs.vim Xfile_linematch1 Xfile_linematch2 Xfile_linematch3', {}) + call VerifyScreenDump(buf, 'Test_linematch_3diffs2', {}) + + " clean up + call StopVimInTerminal(buf) +endfunc " vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/test_digraph.vim b/test/old/testdir/test_digraph.vim index 8fbcd4d8ca..0a71cbba99 100644 --- a/test/old/testdir/test_digraph.vim +++ b/test/old/testdir/test_digraph.vim @@ -40,6 +40,9 @@ func Test_digraphs() " Quadruple prime call Put_Dig("'4") call assert_equal("⁗", getline('.')) + " APPROACHES THE LIMIT + call Put_Dig(".=") + call assert_equal("≐", getline('.')) " Not a digraph call Put_Dig("a\<bs>") call Put_Dig("\<bs>a") @@ -250,9 +253,12 @@ func Test_digraphs_option() call Put_Dig_BS("P","=") call assert_equal(['Р']+repeat(["₽"],2)+['П'], getline(line('.')-3,line('.'))) " Not a digraph: this is different from <c-k>! + let _bs = &bs + set bs= call Put_Dig_BS("a","\<bs>") call Put_Dig_BS("\<bs>","a") call assert_equal(['','a'], getline(line('.')-1,line('.'))) + let &bs = _bs " Grave call Put_Dig_BS("a","!") call Put_Dig_BS("!","e") @@ -604,8 +610,10 @@ func Test_digraph_getlist_function() " of digraphs returned. call assert_equal(digraph_getlist()->len(), digraph_getlist(0)->len()) call assert_notequal(digraph_getlist()->len(), digraph_getlist(1)->len()) + call assert_equal(digraph_getlist()->len(), digraph_getlist(v:false)->len()) + call assert_notequal(digraph_getlist()->len(), digraph_getlist(v:true)->len()) - call assert_fails('call digraph_getlist(0z12)', 'E974: Using a Blob as a Number') + call assert_fails('call digraph_getlist(0z12)', 'E1212: Bool required for argument 1') endfunc diff --git a/test/old/testdir/test_filetype.vim b/test/old/testdir/test_filetype.vim index 19b7d41552..9398cea786 100644 --- a/test/old/testdir/test_filetype.vim +++ b/test/old/testdir/test_filetype.vim @@ -108,12 +108,14 @@ func s:GetFilenameChecks() abort \ '/etc/httpd/sites-some/file', '/etc/httpd/conf.file/conf'], \ 'apachestyle': ['/etc/proftpd/file.config,/etc/proftpd/conf.file/file', '/etc/proftpd/conf.file/file', '/etc/proftpd/file.conf', '/etc/proftpd/file.conf-file', \ 'any/etc/proftpd/conf.file/file', 'any/etc/proftpd/file.conf', 'any/etc/proftpd/file.conf-file', 'proftpd.conf', 'proftpd.conf-file'], + \ 'apkbuild': ['APKBUILD'], \ 'applescript': ['file.scpt'], \ 'aptconf': ['apt.conf', '/.aptitude/config', 'any/.aptitude/config'], \ 'arch': ['.arch-inventory', '=tagging-method'], \ 'arduino': ['file.ino', 'file.pde'], \ 'art': ['file.art'], \ 'asciidoc': ['file.asciidoc', 'file.adoc'], + \ 'asm': ['file.s', 'file.S', 'file.a', 'file.A'], \ 'asn': ['file.asn', 'file.asn1'], \ 'asterisk': ['asterisk/file.conf', 'asterisk/file.conf-file', 'some-asterisk/file.conf', 'some-asterisk/file.conf-file'], \ 'astro': ['file.astro'], @@ -144,6 +146,7 @@ func s:GetFilenameChecks() abort \ 'bzl': ['file.bazel', 'file.bzl', 'WORKSPACE', 'WORKSPACE.bzlmod'], \ 'bzr': ['bzr_log.any', 'bzr_log.file'], \ 'c': ['enlightenment/file.cfg', 'file.qc', 'file.c', 'some-enlightenment/file.cfg', 'file.mdh', 'file.epro'], + \ 'c3': ['file.c3', 'file.c3i', 'file.c3t'], \ 'cabal': ['file.cabal'], \ 'cabalconfig': ['cabal.config', expand("$HOME/.config/cabal/config")] + s:WhenConfigHome('$XDG_CONFIG_HOME/cabal/config'), \ 'cabalproject': ['cabal.project', 'cabal.project.local'], @@ -151,6 +154,7 @@ func s:GetFilenameChecks() abort \ 'calendar': ['calendar', '/.calendar/file', '/share/calendar/any/calendar.file', '/share/calendar/calendar.file', 'any/share/calendar/any/calendar.file', 'any/share/calendar/calendar.file'], \ 'capnp': ['file.capnp'], \ 'catalog': ['catalog', 'sgml.catalogfile', 'sgml.catalog', 'sgml.catalog-file'], + \ 'cdc': ['file.cdc'], \ 'cdl': ['file.cdl'], \ 'cdrdaoconf': ['/etc/cdrdao.conf', '/etc/defaults/cdrdao', '/etc/default/cdrdao', '.cdrdao', 'any/etc/cdrdao.conf', 'any/etc/default/cdrdao', 'any/etc/defaults/cdrdao'], \ 'cdrtoc': ['file.toc'], @@ -188,13 +192,13 @@ func s:GetFilenameChecks() abort \ 'crm': ['file.crm'], \ 'crontab': ['crontab', 'crontab.file', '/etc/cron.d/file', 'any/etc/cron.d/file'], \ 'crystal': ['file.cr'], - \ 'cs': ['file.cs', 'file.csx'], + \ 'cs': ['file.cs', 'file.csx', 'file.cake'], \ 'csc': ['file.csc'], \ 'csdl': ['file.csdl'], \ 'csp': ['file.csp', 'file.fdr'], \ 'css': ['file.css'], - \ 'cterm': ['file.con'], \ 'csv': ['file.csv'], + \ 'cterm': ['file.con'], \ 'cucumber': ['file.feature'], \ 'cuda': ['file.cu', 'file.cuh'], \ 'cue': ['file.cue'], @@ -209,11 +213,11 @@ func s:GetFilenameChecks() abort \ 'dart': ['file.dart', 'file.drt'], \ 'datascript': ['file.ds'], \ 'dcd': ['file.dcd'], + \ 'deb822sources': ['/etc/apt/sources.list.d/file.sources', 'any/etc/apt/sources.list.d/file.sources'], \ 'debchangelog': ['changelog.Debian', 'changelog.dch', 'NEWS.Debian', 'NEWS.dch', '/debian/changelog'], - \ 'debcontrol': ['/debian/control', 'any/debian/control'], + \ 'debcontrol': ['/debian/control', 'any/debian/control', 'any/DEBIAN/control'], \ 'debcopyright': ['/debian/copyright', 'any/debian/copyright'], \ 'debsources': ['/etc/apt/sources.list', '/etc/apt/sources.list.d/file.list', 'any/etc/apt/sources.list', 'any/etc/apt/sources.list.d/file.list'], - \ 'deb822sources': ['/etc/apt/sources.list.d/file.sources', 'any/etc/apt/sources.list.d/file.sources'], \ 'def': ['file.def'], \ 'denyhosts': ['denyhosts.conf'], \ 'desc': ['file.desc'], @@ -271,7 +275,7 @@ func s:GetFilenameChecks() abort \ 'falcon': ['file.fal'], \ 'fan': ['file.fan', 'file.fwt'], \ 'faust': ['file.dsp', 'file.lib'], - \ 'fennel': ['file.fnl'], + \ 'fennel': ['file.fnl', '.fennelrc', 'fennelrc'], \ 'fetchmail': ['.fetchmailrc'], \ 'fgl': ['file.4gl', 'file.4gh', 'file.m4gl'], \ 'firrtl': ['file.fir'], @@ -295,12 +299,13 @@ func s:GetFilenameChecks() abort \ 'gdscript': ['file.gd'], \ 'gdshader': ['file.gdshader', 'file.shader'], \ 'gedcom': ['file.ged', 'lltxxxxx.txt', '/tmp/lltmp', '/tmp/lltmp-file', 'any/tmp/lltmp', 'any/tmp/lltmp-file'], + \ 'gel': ['file.gel'], \ 'gemtext': ['file.gmi', 'file.gemini'], \ 'gift': ['file.gift'], \ 'gitattributes': ['file.git/info/attributes', '.gitattributes', '/.config/git/attributes', '/etc/gitattributes', '/usr/local/etc/gitattributes', 'some.git/info/attributes'] + s:WhenConfigHome('$XDG_CONFIG_HOME/git/attributes'), \ 'gitcommit': ['COMMIT_EDITMSG', 'MERGE_MSG', 'TAG_EDITMSG', 'NOTES_EDITMSG', 'EDIT_DESCRIPTION'], \ 'gitconfig': ['file.git/config', 'file.git/config.worktree', 'file.git/worktrees/x/config.worktree', '.gitconfig', '.gitmodules', 'file.git/modules//config', '/.config/git/config', '/etc/gitconfig', '/usr/local/etc/gitconfig', '/etc/gitconfig.d/file', 'any/etc/gitconfig.d/file', '/.gitconfig.d/file', 'any/.config/git/config', 'any/.gitconfig.d/file', 'some.git/config', 'some.git/modules/any/config'] + s:WhenConfigHome('$XDG_CONFIG_HOME/git/config'), - \ 'gitignore': ['file.git/info/exclude', '.gitignore', '/.config/git/ignore', 'some.git/info/exclude'] + s:WhenConfigHome('$XDG_CONFIG_HOME/git/ignore') + ['.prettierignore'], + \ 'gitignore': ['file.git/info/exclude', '.gitignore', '/.config/git/ignore', 'some.git/info/exclude'] + s:WhenConfigHome('$XDG_CONFIG_HOME/git/ignore') + ['.prettierignore', '.fdignore', '/.config/fd/ignore', '.ignore', '.rgignore', '.dockerignore', '.npmignore', '.vscodeignore'], \ 'gitolite': ['gitolite.conf', '/gitolite-admin/conf/file', 'any/gitolite-admin/conf/file'], \ 'gitrebase': ['git-rebase-todo'], \ 'gitsendemail': ['.gitsendemail.msg.xxxxxx'], @@ -349,11 +354,12 @@ func s:GetFilenameChecks() abort \ 'hostconf': ['/etc/host.conf', 'any/etc/host.conf'], \ 'hostsaccess': ['/etc/hosts.allow', '/etc/hosts.deny', 'any/etc/hosts.allow', 'any/etc/hosts.deny'], \ 'html': ['file.html', 'file.htm', 'file.cshtml', 'file.component.html'], - \ 'http': ['file.http'], \ 'htmlm4': ['file.html.m4'], \ 'httest': ['file.htt', 'file.htb'], + \ 'http': ['file.http'], \ 'hurl': ['file.hurl'], - \ 'hyprlang': ['hyprlock.conf', 'hyprland.conf', 'hypridle.conf', 'hyprpaper.conf'], + \ 'hy': ['file.hy', '.hy-history'], + \ 'hyprlang': ['hyprlock.conf', 'hyprland.conf', 'hypridle.conf', 'hyprpaper.conf', '/hypr/foo.conf'], \ 'i3config': ['/home/user/.i3/config', '/home/user/.config/i3/config', '/etc/i3/config', '/etc/xdg/i3/config'], \ 'ibasic': ['file.iba', 'file.ibi'], \ 'icemenu': ['/.icewm/menu', 'any/.icewm/menu'], @@ -372,7 +378,7 @@ func s:GetFilenameChecks() abort \ 'jal': ['file.jal', 'file.JAL'], \ 'jam': ['file.jpl', 'file.jpr', 'JAM-file.file', 'JAM.file', 'Prl-file.file', 'Prl.file'], \ 'janet': ['file.janet'], - \ 'java': ['file.java', 'file.jav'], + \ 'java': ['file.java', 'file.jav', 'file.jsh'], \ 'javacc': ['file.jj', 'file.jjt'], \ 'javascript': ['file.js', 'file.jsm', 'file.javascript', 'file.es', 'file.mjs', 'file.cjs', '.node_repl_history', '.bun_repl_history', 'deno_history.txt'], \ 'javascript.glimmer': ['file.gjs'], @@ -380,18 +386,19 @@ func s:GetFilenameChecks() abort \ 'jess': ['file.clp'], \ 'jgraph': ['file.jgr'], \ 'jinja': ['file.jinja'], - \ 'jj': ['file.jjdescription'], - \ 'jq': ['file.jq'], + \ 'jjdescription': ['file.jjdescription'], \ 'jovial': ['file.jov', 'file.j73', 'file.jovial'], \ 'jproperties': ['file.properties', 'file.properties_xx', 'file.properties_xx_xx', 'some.properties_xx_xx_file', 'org.eclipse.xyz.prefs'], - \ 'json': ['file.json', 'file.jsonp', 'file.json-patch', 'file.geojson', 'file.webmanifest', 'Pipfile.lock', 'file.ipynb', 'file.jupyterlab-settings', '.prettierrc', '.firebaserc', '.stylelintrc', '.lintstagedrc', 'file.slnf', 'file.sublime-project', 'file.sublime-settings', 'file.sublime-workspace', 'file.bd', 'file.bda', 'file.xci', 'flake.lock', 'pack.mcmeta', 'deno.lock'], + \ 'jq': ['file.jq'], + \ 'json': ['file.json', 'file.jsonp', 'file.json-patch', 'file.geojson', 'file.webmanifest', 'Pipfile.lock', 'file.ipynb', 'file.jupyterlab-settings', '.prettierrc', '.firebaserc', '.stylelintrc', '.lintstagedrc', 'file.slnf', 'file.sublime-project', 'file.sublime-settings', 'file.sublime-workspace', 'file.bd', 'file.bda', 'file.xci', 'flake.lock', 'pack.mcmeta', 'deno.lock', '.swcrc'], \ 'json5': ['file.json5'], - \ 'jsonc': ['file.jsonc', '.babelrc', '.eslintrc', '.jsfmtrc', '.jshintrc', '.jscsrc', '.vsconfig', '.hintrc', '.swrc', 'jsconfig.json', 'tsconfig.json', 'tsconfig.test.json', 'tsconfig-test.json', '.luaurc'], + \ 'jsonc': ['file.jsonc', '.babelrc', '.eslintrc', '.jsfmtrc', '.jshintrc', '.jscsrc', '.vsconfig', '.hintrc', '.swrc', 'jsconfig.json', 'tsconfig.json', 'tsconfig.test.json', 'tsconfig-test.json', '.luaurc', 'bun.lock', expand("$HOME/.config/VSCodium/User/settings.json")], \ 'jsonl': ['file.jsonl'], \ 'jsonnet': ['file.jsonnet', 'file.libsonnet'], \ 'jsp': ['file.jsp'], \ 'julia': ['file.jl'], \ 'just': ['justfile', 'Justfile', '.justfile', 'config.just'], + \ 'karel': ['file.kl', 'file.KL'], \ 'kconfig': ['Kconfig', 'Kconfig.debug', 'Kconfig.file', 'Config.in', 'Config.in.host'], \ 'kdl': ['file.kdl'], \ 'kivy': ['file.kv'], @@ -401,14 +408,15 @@ func s:GetFilenameChecks() abort \ 'kscript': ['file.ks'], \ 'kwt': ['file.k'], \ 'lace': ['file.ace', 'file.ACE'], + \ 'lalrpop': ['file.lalrpop'], \ 'latte': ['file.latte', 'file.lte'], \ 'ld': ['file.ld', 'any/usr/lib/aarch64-xilinx-linux/ldscripts/aarch64elf32b.x'], \ 'ldapconf': ['ldap.conf', '.ldaprc', 'ldaprc'], \ 'ldif': ['file.ldif'], \ 'lean': ['file.lean'], \ 'ledger': ['file.ldg', 'file.ledger', 'file.journal'], - \ 'less': ['file.less'], \ 'leo': ['file.leo'], + \ 'less': ['file.less'], \ 'lex': ['file.lex', 'file.l', 'file.lxx', 'file.l++'], \ 'lf': ['lfrc'], \ 'lftp': ['lftp.conf', '.lftprc', 'anylftp/rc', 'lftp/rc', 'some-lftp/rc'], @@ -419,13 +427,13 @@ func s:GetFilenameChecks() abort \ 'lilo': ['lilo.conf', 'lilo.conf-file'], \ 'lilypond': ['file.ly', 'file.ily'], \ 'limits': ['/etc/limits', '/etc/anylimits.conf', '/etc/anylimits.d/file.conf', '/etc/limits.conf', '/etc/limits.d/file.conf', '/etc/some-limits.conf', '/etc/some-limits.d/file.conf', 'any/etc/limits', 'any/etc/limits.conf', 'any/etc/limits.d/file.conf', 'any/etc/some-limits.conf', 'any/etc/some-limits.d/file.conf'], - \ 'liquidsoap': ['file.liq'], \ 'liquid': ['file.liquid'], - \ 'lisp': ['file.lsp', 'file.lisp', 'file.asd', 'file.el', 'file.cl', '.emacs', '.sawfishrc', 'sbclrc', '.sbclrc', 'file.stsg', 'any/local/share/supertux2/config'], + \ 'liquidsoap': ['file.liq'], + \ 'lisp': ['file.lsp', 'file.lisp', 'file.asd', 'file.el', '.emacs', '.sawfishrc', 'sbclrc', '.sbclrc'], \ 'lite': ['file.lite', 'file.lt'], \ 'litestep': ['/LiteStep/any/file.rc', 'any/LiteStep/any/file.rc'], - \ 'logcheck': ['/etc/logcheck/file.d-some/file', '/etc/logcheck/file.d/file', 'any/etc/logcheck/file.d-some/file', 'any/etc/logcheck/file.d/file'], \ 'livebook': ['file.livemd'], + \ 'logcheck': ['/etc/logcheck/file.d-some/file', '/etc/logcheck/file.d/file', 'any/etc/logcheck/file.d-some/file', 'any/etc/logcheck/file.d/file'], \ 'loginaccess': ['/etc/login.access', 'any/etc/login.access'], \ 'logindefs': ['/etc/login.defs', 'any/etc/login.defs'], \ 'logtalk': ['file.lgt'], @@ -448,9 +456,9 @@ func s:GetFilenameChecks() abort \ 'mallard': ['file.page'], "\ 'man': ['file.man'], \ 'manconf': ['/etc/man.conf', 'man.config', 'any/etc/man.conf'], - \ 'map': ['file.map'], \ 'maple': ['file.mv', 'file.mpl', 'file.mws'], \ 'markdown': ['file.markdown', 'file.mdown', 'file.mkd', 'file.mkdn', 'file.mdwn', 'file.md'], + \ 'masm': ['file.masm'], \ 'mason': ['file.mason', 'file.mhtml'], \ 'master': ['file.mas', 'file.master'], \ 'matlab': ['file.m'], @@ -496,6 +504,7 @@ func s:GetFilenameChecks() abort \ 'mmp': ['file.mmp'], \ 'modconf': ['/etc/modules.conf', '/etc/modules', '/etc/conf.modules', '/etc/modprobe.file', 'any/etc/conf.modules', 'any/etc/modprobe.file', 'any/etc/modules', 'any/etc/modules.conf'], \ 'modula3': ['file.m3', 'file.mg', 'file.i3', 'file.ig', 'file.lm3'], + \ 'mojo': ['file.mojo', 'file.🔥'], \ 'monk': ['file.isc', 'file.monk', 'file.ssc', 'file.tsc'], \ 'moo': ['file.moo'], \ 'moonscript': ['file.moon'], @@ -504,10 +513,9 @@ func s:GetFilenameChecks() abort \ 'mplayerconf': ['mplayer.conf', '/.mplayer/config', 'any/.mplayer/config'], \ 'mrxvtrc': ['mrxvtrc', '.mrxvtrc'], \ 'msidl': ['file.odl', 'file.mof'], - \ 'mss': ['file.mss'], - \ 'msql': ['file.msql'], - \ 'mojo': ['file.mojo', 'file.🔥'], \ 'msmtp': ['.msmtprc'], + \ 'msql': ['file.msql'], + \ 'mss': ['file.mss'], \ 'mupad': ['file.mu'], \ 'mush': ['file.mush'], \ 'mustache': ['file.mustache'], @@ -527,6 +535,7 @@ func s:GetFilenameChecks() abort \ 'n1ql': ['file.n1ql', 'file.nql'], \ 'named': ['namedfile.conf', 'rndcfile.conf', 'named-file.conf', 'named.conf', 'rndc-file.conf', 'rndc-file.key', 'rndc.conf', 'rndc.key'], \ 'nanorc': ['/etc/nanorc', 'file.nanorc', 'any/etc/nanorc'], + \ 'nasm': ['file.nasm'], \ 'natural': ['file.NSA', 'file.NSC', 'file.NSG', 'file.NSL', 'file.NSM', 'file.NSN', 'file.NSP', 'file.NSS'], \ 'ncf': ['file.ncf'], \ 'neomuttlog': ['/home/user/.neomuttdebug1'], @@ -540,6 +549,7 @@ func s:GetFilenameChecks() abort \ 'nqc': ['file.nqc'], \ 'nroff': ['file.tr', 'file.nr', 'file.roff', 'file.tmac', 'file.mom', 'tmac.file'], \ 'nsis': ['file.nsi', 'file.nsh'], + \ 'ntriples': ['file.nt'], \ 'nu': ['file.nu'], \ 'obj': ['file.obj'], \ 'objdump': ['file.objdump', 'file.cppobjdump'], @@ -599,16 +609,18 @@ func s:GetFilenameChecks() abort \ 'promela': ['file.pml'], \ 'proto': ['file.proto'], \ 'protocols': ['/etc/protocols', 'any/etc/protocols'], + \ 'prql': ['file.prql'], \ 'ps1': ['file.ps1', 'file.psd1', 'file.psm1', 'file.pssc'], \ 'ps1xml': ['file.ps1xml'], \ 'psf': ['file.psf'], \ 'psl': ['file.psl'], + \ 'ptx': ['file.ptx'], \ 'pug': ['file.pug'], \ 'puppet': ['file.pp'], \ 'purescript': ['file.purs'], \ 'pymanifest': ['MANIFEST.in'], \ 'pyret': ['file.arr'], - \ 'pyrex': ['file.pyx', 'file.pxd'], + \ 'pyrex': ['file.pyx', 'file.pxd', 'file.pxi', 'file.pyx+'], \ 'python': ['file.py', 'file.pyw', '.pythonstartup', '.pythonrc', '.python_history', '.jline-jython.history', 'file.ptl', 'file.pyi', 'SConstruct'], \ 'ql': ['file.ql', 'file.qll'], \ 'qml': ['file.qml', 'file.qbs'], @@ -640,27 +652,27 @@ func s:GetFilenameChecks() abort \ 'rnc': ['file.rnc'], \ 'rng': ['file.rng'], \ 'rnoweb': ['file.rnw', 'file.snw'], - \ 'rpgle': ['file.rpgle', 'file.rpgleinc'], \ 'robot': ['file.robot', 'file.resource'], \ 'robots': ['robots.txt'], \ 'roc': ['file.roc'], \ 'ron': ['file.ron'], \ 'routeros': ['file.rsc'], \ 'rpcgen': ['file.x'], + \ 'rpgle': ['file.rpgle', 'file.rpgleinc'], \ 'rpl': ['file.rpl'], \ 'rrst': ['file.rrst', 'file.srst'], \ 'rst': ['file.rst'], \ 'rtf': ['file.rtf'], \ 'ruby': ['.irbrc', 'irbrc', '.irb_history', 'irb_history', 'file.rb', 'file.rbw', 'file.gemspec', 'file.ru', 'Gemfile', 'file.builder', 'file.rxml', 'file.rjs', 'file.rant', 'file.rake', 'rakefile', 'Rakefile', 'rantfile', 'Rantfile', 'rakefile-file', 'Rakefile-file', 'Puppetfile', 'Vagrantfile'], \ 'rust': ['file.rs'], + \ 'sage': ['file.sage'], \ 'salt': ['file.sls'], \ 'samba': ['smb.conf'], \ 'sas': ['file.sas'], \ 'sass': ['file.sass'], - \ 'sather': ['file.sa'], \ 'sbt': ['file.sbt'], \ 'scala': ['file.scala'], - \ 'scheme': ['file.scm', 'file.ss', 'file.sld'], + \ 'scheme': ['file.scm', 'file.ss', 'file.sld', 'file.stsg', 'any/local/share/supertux2/config', '.lips_repl_history'], \ 'scilab': ['file.sci', 'file.sce'], \ 'screen': ['.screenrc', 'screenrc'], \ 'scss': ['file.scss'], @@ -673,19 +685,18 @@ func s:GetFilenameChecks() abort \ 'setserial': ['/etc/serial.conf', 'any/etc/serial.conf'], \ 'sexplib': ['file.sexp'], \ 'sh': ['.bashrc', '.bash_profile', '.bash-profile', '.bash_logout', '.bash-logout', '.bash_aliases', '.bash-aliases', '.bash_history', '.bash-history', - \ '/tmp/bash-fc-3Ozjlw', '/tmp/bash-fc.3Ozjlw', 'PKGBUILD', 'APKBUILD', 'file.bash', '/usr/share/doc/bash-completion/filter.sh', + \ '/tmp/bash-fc-3Ozjlw', '/tmp/bash-fc.3Ozjlw', 'PKGBUILD', 'file.bash', '/usr/share/doc/bash-completion/filter.sh', \ '/etc/udev/cdsymlinks.conf', 'any/etc/udev/cdsymlinks.conf', 'file.bats', '.ash_history', 'any/etc/neofetch/config.conf', '.xprofile', \ 'user-dirs.defaults', 'user-dirs.dirs', 'makepkg.conf', '.makepkg.conf', 'file.mdd', 'file.cygport', '.env', '.envrc', 'devscripts.conf', \ '.devscripts', 'file.lo', 'file.la', 'file.lai'], + \ 'shaderslang': ['file.slang'], \ 'sieve': ['file.siv', 'file.sieve'], \ 'sil': ['file.sil'], \ 'simula': ['file.sim'], \ 'sinda': ['file.sin', 'file.s85'], \ 'sisu': ['file.sst', 'file.ssm', 'file.ssi', 'file.-sst', 'file._sst', 'file.sst.meta', 'file.-sst.meta', 'file._sst.meta'], \ 'skill': ['file.il', 'file.ils', 'file.cdf'], - \ 'cdc': ['file.cdc'], \ 'slang': ['file.sl'], - \ 'sage': ['file.sage'], \ 'slice': ['file.ice'], \ 'slint': ['file.slint'], \ 'slpconf': ['/etc/slp.conf', 'any/etc/slp.conf'], @@ -711,7 +722,6 @@ func s:GetFilenameChecks() abort \ 'spyce': ['file.spy', 'file.spi'], \ 'sql': ['file.tyb', 'file.tyc', 'file.pkb', 'file.pks', '.sqlite_history'], \ 'sqlj': ['file.sqlj'], - \ 'prql': ['file.prql'], \ 'sqr': ['file.sqr', 'file.sqi'], \ 'squid': ['squid.conf'], \ 'squirrel': ['file.nut'], @@ -777,14 +787,13 @@ func s:GetFilenameChecks() abort \ 'any/etc/systemd/system/file.d/.#-file', \ 'any/etc/systemd/system/file.d/file.conf'], \ 'systemverilog': ['file.sv', 'file.svh'], - \ 'trace32': ['file.cmm', 'file.t32'], + \ 'tablegen': ['file.td'], \ 'tags': ['tags'], \ 'tak': ['file.tak'], \ 'tal': ['file.tal'], \ 'taskdata': ['pending.data', 'completed.data', 'undo.data'], \ 'taskedit': ['file.task'], \ 'tcl': ['file.tcl', 'file.tm', 'file.tk', 'file.itcl', 'file.itk', 'file.jacl', '.tclshrc', 'tclsh.rc', '.wishrc', '.tclsh-history', '.xsctcmdhistory', '.xsdbcmdhistory'], - \ 'tablegen': ['file.td'], \ 'teal': ['file.tl'], \ 'templ': ['file.templ'], \ 'template': ['file.tmpl'], @@ -804,7 +813,9 @@ func s:GetFilenameChecks() abort \ 'tmux': ['tmuxfile.conf', '.tmuxfile.conf', '.tmux-file.conf', '.tmux.conf', 'tmux-file.conf', 'tmux.conf', 'tmux.conf.local'], \ 'toml': ['file.toml', 'Gopkg.lock', 'Pipfile', '/home/user/.cargo/config', '.black'], \ 'tpp': ['file.tpp'], + \ 'trace32': ['file.cmm', 'file.t32'], \ 'treetop': ['file.treetop'], + \ 'trig': ['file.trig'], \ 'trustees': ['trustees.conf'], \ 'tsalt': ['file.slt'], \ 'tsscl': ['file.tsscl'], @@ -816,12 +827,12 @@ func s:GetFilenameChecks() abort \ 'typescript.glimmer': ['file.gts'], \ 'typescriptreact': ['file.tsx'], \ 'typespec': ['file.tsp'], - \ 'ungrammar': ['file.ungram'], \ 'uc': ['file.uc'], \ 'udevconf': ['/etc/udev/udev.conf', 'any/etc/udev/udev.conf'], \ 'udevperm': ['/etc/udev/permissions.d/file.permissions', 'any/etc/udev/permissions.d/file.permissions'], \ 'udevrules': ['/etc/udev/rules.d/file.rules', '/usr/lib/udev/rules.d/file.rules', '/lib/udev/rules.d/file.rules'], \ 'uil': ['file.uit', 'file.uil'], + \ 'ungrammar': ['file.ungram'], \ 'unison': ['file.u', 'file.uu'], \ 'updatedb': ['/etc/updatedb.conf', 'any/etc/updatedb.conf'], \ 'upstart': ['/usr/share/upstart/file.conf', '/usr/share/upstart/file.override', '/etc/init/file.conf', '/etc/init/file.override', '/.init/file.conf', '/.init/file.override', '/.config/upstart/file.conf', '/.config/upstart/file.override', 'any/.config/upstart/file.conf', 'any/.config/upstart/file.override', 'any/.init/file.conf', 'any/.init/file.override', 'any/etc/init/file.conf', 'any/etc/init/file.override', 'any/usr/share/upstart/file.conf', 'any/usr/share/upstart/file.override'], @@ -874,7 +885,7 @@ func s:GetFilenameChecks() abort \ 'xml': ['/etc/blkid.tab', '/etc/blkid.tab.old', 'file.xmi', 'file.csproj', 'file.csproj.user', 'file.fsproj', 'file.fsproj.user', 'file.vbproj', 'file.vbproj.user', 'file.ui', \ 'file.tpm', '/etc/xdg/menus/file.menu', 'fglrxrc', 'file.xlf', 'file.xliff', 'file.xul', 'file.wsdl', 'file.wpl', 'any/etc/blkid.tab', 'any/etc/blkid.tab.old', \ 'any/etc/xdg/menus/file.menu', 'file.atom', 'file.rss', 'file.cdxml', 'file.psc1', 'file.mpd', 'fonts.conf', 'file.xcu', 'file.xlb', 'file.xlc', 'file.xba', 'file.xpr', - \ 'file.xpfm', 'file.spfm', 'file.bxml', 'file.mmi'], + \ 'file.xpfm', 'file.spfm', 'file.bxml', 'file.mmi', 'file.slnx', 'Directory.Packages.props', 'Directory.Build.targets', 'Directory.Build.props'], \ 'xmodmap': ['anyXmodmap', 'Xmodmap', 'some-Xmodmap', 'some-xmodmap', 'some-xmodmap-file', 'xmodmap', 'xmodmap-file'], \ 'xpm': ['file.xpm'], \ 'xpm2': ['file.xpm2'], @@ -884,7 +895,7 @@ func s:GetFilenameChecks() abort \ 'xslt': ['file.xsl', 'file.xslt'], \ 'yacc': ['file.yy', 'file.yxx', 'file.y++'], \ 'yaml': ['file.yaml', 'file.yml', 'file.eyaml', 'any/.bundle/config', '.clangd', '.clang-format', '.clang-tidy', 'file.mplstyle', 'matplotlibrc', 'yarn.lock', - \ '/home/user/.kube/config'], + \ '/home/user/.kube/config', '.condarc', 'condarc', 'pixi.lock'], \ 'yang': ['file.yang'], \ 'yuck': ['file.yuck'], \ 'z8a': ['file.z8a'], @@ -986,6 +997,7 @@ func s:GetScriptChecks() abort \ 'expect': [['#!/path/expect']], \ 'execline': [['#!/sbin/execlineb -S0'], ['#!/usr/bin/execlineb']], \ 'gnuplot': [['#!/path/gnuplot']], + \ 'just': [['#!/path/just']], \ 'make': [['#!/path/make']], \ 'nix': [['#!/path/nix-shell']], \ 'pike': [['#!/path/pike'], @@ -1191,6 +1203,22 @@ func Test_cfg_file() filetype off endfunc +func Test_cl_file() + filetype on + + call writefile(['/*', ' * Xfile.cl', ' */', 'int f() {}'], 'Xfile.cl') + split Xfile.cl + call assert_equal('opencl', &filetype) + bwipe! + + call writefile(['()'], 'Xfile.cl') + split Xfile.cl + call assert_equal('lisp', &filetype) + bwipe! + + filetype off +endfunc + func Test_d_file() filetype on @@ -2273,6 +2301,43 @@ func Test_cls_file() filetype off endfunc +func Test_cmd_file() + filetype on + + call writefile(['--rom_model'], 'Xfile.cmd') + split Xfile.cmd + call assert_equal('lnk', &filetype) + bwipe! + + call writefile(['/* comment */'], 'Xfile.cmd') + split Xfile.cmd + call assert_equal('rexx', &filetype) + bwipe! + + call writefile(['REM comment'], 'Xfile.cmd') + split Xfile.cmd + call assert_equal('dosbatch', &filetype) + bwipe! + + filetype off +endfunc + +func Test_sa_file() + filetype on + + call writefile([';* XXX-a.sa: XXX for TI C6000 DSP *;', '.no_mdep'], 'Xfile.sa') + split Xfile.sa + call assert_equal('tiasm', &filetype) + bwipe! + + call writefile(['-- comment'], 'Xfile.sa') + split Xfile.sa + call assert_equal('sather', &filetype) + bwipe! + + filetype off +endfunc + func Test_sig_file() filetype on @@ -2737,6 +2802,24 @@ func Test_make_file() filetype off endfunc +func Test_map_file() + filetype on + + " TI linker map file + call writefile(['******************************************************************************', ' TMS320C6x Linker Unix v7.4.24 ', '******************************************************************************'], 'Xfile.map', 'D') + split Xfile.map + call assert_equal('lnkmap', &filetype) + bwipe! + + " UMN mapserver config file + call writefile(['MAP', 'NAME "local-demo"', 'END'], 'Xfile.map', 'D') + split Xfile.map + call assert_equal('map', &filetype) + bwipe! + + filetype off +endfunc + func Test_org_file() filetype on diff --git a/test/old/testdir/test_functions.vim b/test/old/testdir/test_functions.vim index 327ea98e1c..738a417b86 100644 --- a/test/old/testdir/test_functions.vim +++ b/test/old/testdir/test_functions.vim @@ -2390,6 +2390,85 @@ func Test_getchar() call assert_equal("\<M-F2>", getchar(0)) call assert_equal(0, getchar(0)) + call feedkeys("\<Tab>", '') + call assert_equal(char2nr("\<Tab>"), getchar()) + call feedkeys("\<Tab>", '') + call assert_equal(char2nr("\<Tab>"), getchar(-1)) + call feedkeys("\<Tab>", '') + call assert_equal(char2nr("\<Tab>"), getchar(-1, {})) + call feedkeys("\<Tab>", '') + call assert_equal(char2nr("\<Tab>"), getchar(-1, #{number: v:true})) + call assert_equal(0, getchar(0)) + call assert_equal(0, getchar(1)) + call assert_equal(0, getchar(0, #{number: v:true})) + call assert_equal(0, getchar(1, #{number: v:true})) + + call feedkeys("\<Tab>", '') + call assert_equal("\<Tab>", getcharstr()) + call feedkeys("\<Tab>", '') + call assert_equal("\<Tab>", getcharstr(-1)) + call feedkeys("\<Tab>", '') + call assert_equal("\<Tab>", getcharstr(-1, {})) + call feedkeys("\<Tab>", '') + call assert_equal("\<Tab>", getchar(-1, #{number: v:false})) + call assert_equal('', getcharstr(0)) + call assert_equal('', getcharstr(1)) + call assert_equal('', getchar(0, #{number: v:false})) + call assert_equal('', getchar(1, #{number: v:false})) + + " Nvim: <M-x> is never simplified + " for key in ["C-I", "C-X", "M-x"] + for key in ["C-I", "C-X"] + let lines =<< eval trim END + call feedkeys("\<*{key}>", '') + call assert_equal(char2nr("\<{key}>"), getchar()) + call feedkeys("\<*{key}>", '') + call assert_equal(char2nr("\<{key}>"), getchar(-1)) + call feedkeys("\<*{key}>", '') + call assert_equal(char2nr("\<{key}>"), getchar(-1, {{}})) + call feedkeys("\<*{key}>", '') + call assert_equal(char2nr("\<{key}>"), getchar(-1, {{'number': 1}})) + call feedkeys("\<*{key}>", '') + call assert_equal(char2nr("\<{key}>"), getchar(-1, {{'simplify': 1}})) + call feedkeys("\<*{key}>", '') + call assert_equal("\<*{key}>", getchar(-1, {{'simplify': v:false}})) + call assert_equal(0, getchar(0)) + call assert_equal(0, getchar(1)) + END + call CheckLegacyAndVim9Success(lines) + + let lines =<< eval trim END + call feedkeys("\<*{key}>", '') + call assert_equal("\<{key}>", getcharstr()) + call feedkeys("\<*{key}>", '') + call assert_equal("\<{key}>", getcharstr(-1)) + call feedkeys("\<*{key}>", '') + call assert_equal("\<{key}>", getcharstr(-1, {{}})) + call feedkeys("\<*{key}>", '') + call assert_equal("\<{key}>", getchar(-1, {{'number': 0}})) + call feedkeys("\<*{key}>", '') + call assert_equal("\<{key}>", getcharstr(-1, {{'simplify': 1}})) + call feedkeys("\<*{key}>", '') + call assert_equal("\<*{key}>", getcharstr(-1, {{'simplify': v:false}})) + call assert_equal('', getcharstr(0)) + call assert_equal('', getcharstr(1)) + END + call CheckLegacyAndVim9Success(lines) + endfor + + call assert_fails('call getchar(1, 1)', 'E1206:') + call assert_fails('call getcharstr(1, 1)', 'E1206:') + call assert_fails('call getchar(1, #{cursor: "foo"})', 'E475:') + call assert_fails('call getcharstr(1, #{cursor: "foo"})', 'E475:') + call assert_fails('call getchar(1, #{cursor: 0z})', 'E976:') + call assert_fails('call getcharstr(1, #{cursor: 0z})', 'E976:') + call assert_fails('call getchar(1, #{simplify: 0z})', 'E974:') + call assert_fails('call getcharstr(1, #{simplify: 0z})', 'E974:') + call assert_fails('call getchar(1, #{number: []})', 'E745:') + call assert_fails('call getchar(1, #{number: {}})', 'E728:') + call assert_fails('call getcharstr(1, #{number: v:true})', 'E475:') + call assert_fails('call getcharstr(1, #{number: v:false})', 'E475:') + call setline(1, 'xxxx') call Ntest_setmouse(1, 3) let v:mouse_win = 9 @@ -2405,10 +2484,59 @@ func Test_getchar() enew! endfunc +func Test_getchar_cursor_position() + CheckRunVimInTerminal + + let lines =<< trim END + call setline(1, ['foobar', 'foobar', 'foobar']) + call cursor(3, 6) + nnoremap <F1> <Cmd>echo 1234<Bar>call getchar()<CR> + nnoremap <F2> <Cmd>call getchar()<CR> + nnoremap <F3> <Cmd>call getchar(-1, {})<CR> + nnoremap <F4> <Cmd>call getchar(-1, #{cursor: 'msg'})<CR> + nnoremap <F5> <Cmd>call getchar(-1, #{cursor: 'keep'})<CR> + nnoremap <F6> <Cmd>call getchar(-1, #{cursor: 'hide'})<CR> + END + call writefile(lines, 'XgetcharCursorPos', 'D') + let buf = RunVimInTerminal('-S XgetcharCursorPos', {'rows': 6}) + call WaitForAssert({-> assert_equal([3, 6], term_getcursor(buf)[0:1])}) + + call term_sendkeys(buf, "\<F1>") + call WaitForAssert({-> assert_equal([6, 5], term_getcursor(buf)[0:1])}) + call assert_true(term_getcursor(buf)[2].visible) + call term_sendkeys(buf, 'a') + call WaitForAssert({-> assert_equal([3, 6], term_getcursor(buf)[0:1])}) + call assert_true(term_getcursor(buf)[2].visible) + + for key in ["\<F2>", "\<F3>", "\<F4>"] + call term_sendkeys(buf, key) + call WaitForAssert({-> assert_equal([6, 1], term_getcursor(buf)[0:1])}) + call assert_true(term_getcursor(buf)[2].visible) + call term_sendkeys(buf, 'a') + call WaitForAssert({-> assert_equal([3, 6], term_getcursor(buf)[0:1])}) + call assert_true(term_getcursor(buf)[2].visible) + endfor + + call term_sendkeys(buf, "\<F5>") + call TermWait(buf, 50) + call assert_equal([3, 6], term_getcursor(buf)[0:1]) + call assert_true(term_getcursor(buf)[2].visible) + call term_sendkeys(buf, 'a') + call TermWait(buf, 50) + call assert_equal([3, 6], term_getcursor(buf)[0:1]) + call assert_true(term_getcursor(buf)[2].visible) + + call term_sendkeys(buf, "\<F6>") + call WaitForAssert({-> assert_false(term_getcursor(buf)[2].visible)}) + call term_sendkeys(buf, 'a') + call WaitForAssert({-> assert_true(term_getcursor(buf)[2].visible)}) + call assert_equal([3, 6], term_getcursor(buf)[0:1]) + + call StopVimInTerminal(buf) +endfunc + func Test_libcall_libcallnr() - if !has('libcall') - return - endif + CheckFeature libcall if has('win32') let libc = 'msvcrt.dll' @@ -2672,7 +2800,9 @@ endfunc func Test_call() call assert_equal(3, call('len', [123])) call assert_equal(3, 'len'->call([123])) - call assert_fails("call call('len', 123)", 'E714:') + call assert_equal(4, call({ x -> len(x) }, ['xxxx'])) + call assert_equal(2, call(function('len'), ['xx'])) + call assert_fails("call call('len', 123)", 'E1211:') call assert_equal(0, call('', [])) call assert_equal(0, call('len', v:_null_list)) diff --git a/test/old/testdir/test_highlight.vim b/test/old/testdir/test_highlight.vim index 0a64c63d30..56c7a9656f 100644 --- a/test/old/testdir/test_highlight.vim +++ b/test/old/testdir/test_highlight.vim @@ -785,8 +785,8 @@ func Test_1_highlight_Normalgroup_exists() if !has('gui_running') call assert_match('hi Normal\s*clear', hlNormal) elseif has('gui_gtk2') || has('gui_gnome') || has('gui_gtk3') - " expect is DEFAULT_FONT of gui_gtk_x11.c - call assert_match('hi Normal\s*font=Monospace 10', hlNormal) + " expect is DEFAULT_FONT of gui_gtk_x11.c (any size) + call assert_match('hi Normal\s*font=Monospace\>', hlNormal) elseif has('gui_motif') " expect is DEFAULT_FONT of gui_x11.c call assert_match('hi Normal\s*font=7x13', hlNormal) diff --git a/test/old/testdir/test_indent.vim b/test/old/testdir/test_indent.vim index dcacc11663..630beed810 100644 --- a/test/old/testdir/test_indent.vim +++ b/test/old/testdir/test_indent.vim @@ -1,5 +1,8 @@ " Test for various indent options +source shared.vim +source check.vim + func Test_preserveindent() new " Test for autoindent copying indent from the previous line @@ -276,4 +279,77 @@ func Test_formatting_keeps_first_line_indent() bwipe! endfunc +" Test for indenting with large amount, causes overflow +func Test_indent_overflow_count() + throw 'skipped: TODO: ' + new + setl sw=8 + call setline(1, "abc") + norm! V2147483647> + " indents by INT_MAX + call assert_equal(2147483647, indent(1)) + close! +endfunc + +func Test_indent_overflow_count2() + throw 'skipped: Nvim does not support 64-bit number options' + new + " this only works, when long is 64bits + try + setl sw=0x180000000 + catch /^Vim\%((\a\+)\)\=:E487:/ + throw 'Skipped: value negative on this platform' + endtry + call setline(1, "\tabc") + norm! << + call assert_equal(0, indent(1)) + close! +endfunc + +" Test that mouse shape is restored to Normal mode after using "gq" when +" 'indentexpr' executes :normal. +func Test_mouse_shape_indent_norm_with_gq() + CheckFeature mouseshape + CheckCanRunGui + + let lines =<< trim END + func Indent() + exe "normal! \<Ignore>" + return 0 + endfunc + + setlocal indentexpr=Indent() + END + call writefile(lines, 'Xindentexpr.vim', 'D') + + let lines =<< trim END + vim9script + var mouse_shapes = [] + + setline(1, [repeat('a', 80), repeat('b', 80)]) + + feedkeys('ggVG') + timer_start(50, (_) => { + mouse_shapes += [getmouseshape()] + timer_start(50, (_) => { + feedkeys('gq') + timer_start(50, (_) => { + mouse_shapes += [getmouseshape()] + timer_start(50, (_) => { + writefile(mouse_shapes, 'Xmouseshapes') + quit! + }) + }) + }) + }) + END + call writefile(lines, 'Xmouseshape.vim', 'D') + + call RunVim([], [], "-g -S Xindentexpr.vim -S Xmouseshape.vim") + call WaitForAssert({-> assert_equal(['rightup-arrow', 'arrow'], + \ readfile('Xmouseshapes'))}, 300) + + call delete('Xmouseshapes') +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/test_ins_complete.vim b/test/old/testdir/test_ins_complete.vim index c02aa1db62..2fb1715634 100644 --- a/test/old/testdir/test_ins_complete.vim +++ b/test/old/testdir/test_ins_complete.vim @@ -253,6 +253,91 @@ func Test_CompleteDoneNone() au! CompleteDone endfunc +func Test_CompleteDone_vevent_keys() + func OnDone() + let g:complete_word = get(v:event, 'complete_word', v:null) + let g:complete_type = get(v:event, 'complete_type', v:null) + endfunction + + autocmd CompleteDone * :call OnDone() + + func CompleteFunc(findstart, base) + if a:findstart + return col(".") + endif + return [#{word: "foo"}, #{word: "bar"}] + endfunc + set omnifunc=CompleteFunc + set completefunc=CompleteFunc + set completeopt+=menuone + + new + call feedkeys("A\<C-X>\<C-O>\<Esc>", 'tx') + call assert_equal('', g:complete_word) + call assert_equal('omni', g:complete_type) + + call feedkeys("S\<C-X>\<C-O>\<C-Y>\<Esc>", 'tx') + call assert_equal('foo', g:complete_word) + call assert_equal('omni', g:complete_type) + + call feedkeys("S\<C-X>\<C-O>\<C-N>\<C-Y>\<Esc>0", 'tx') + call assert_equal('bar', g:complete_word) + call assert_equal('omni', g:complete_type) + + call feedkeys("Shello vim visual v\<C-X>\<C-N>\<ESC>", 'tx') + call assert_equal('', g:complete_word) + call assert_equal('keyword', g:complete_type) + + call feedkeys("Shello vim visual v\<C-X>\<C-N>\<C-Y>", 'tx') + call assert_equal('vim', g:complete_word) + call assert_equal('keyword', g:complete_type) + + call feedkeys("Shello vim visual v\<C-X>\<C-N>\<C-Y>", 'tx') + call assert_equal('vim', g:complete_word) + call assert_equal('keyword', g:complete_type) + + call feedkeys("Shello vim\<CR>completion test\<CR>\<C-X>\<C-l>\<C-Y>", 'tx') + call assert_equal('completion test', g:complete_word) + call assert_equal('whole_line', g:complete_type) + + call feedkeys("S\<C-X>\<C-U>\<C-Y>", 'tx') + call assert_equal('foo', g:complete_word) + call assert_equal('function', g:complete_type) + + inoremap <buffer> <f3> <cmd>call complete(1, ["red", "blue"])<cr> + call feedkeys("S\<f3>\<C-Y>", 'tx') + call assert_equal('red', g:complete_word) + call assert_equal('eval', g:complete_type) + + call feedkeys("S\<C-X>\<C-V>\<C-Y>", 'tx') + call assert_equal('!', g:complete_word) + call assert_equal('cmdline', g:complete_type) + + call writefile([''], 'foo_test', 'D') + call feedkeys("Sfoo\<C-X>\<C-F>\<C-Y>\<Esc>", 'tx') + call assert_equal('foo_test', g:complete_word) + call assert_equal('files', g:complete_type) + + call writefile(['hello help'], 'test_case.txt', 'D') + set dictionary=test_case.txt + call feedkeys("ggdGSh\<C-X>\<C-K>\<C-Y>\<Esc>", 'tx') + call assert_equal('hello', g:complete_word) + call assert_equal('dictionary', g:complete_type) + + set spell spelllang=en_us + call feedkeys("STheatre\<C-X>s\<C-Y>\<Esc>", 'tx') + call assert_equal('Theater', g:complete_word) + call assert_equal('spell', g:complete_type) + + bwipe! + set completeopt& omnifunc& completefunc& spell& spelllang& dictionary& + autocmd! CompleteDone + delfunc OnDone + delfunc CompleteFunc + unlet g:complete_word + unlet g:complete_type +endfunc + func Test_CompleteDoneDict() au CompleteDonePre * :call <SID>CompleteDone_CheckCompletedItemDict(1) au CompleteDone * :call <SID>CompleteDone_CheckCompletedItemDict(0) @@ -1509,7 +1594,7 @@ func Test_complete_item_refresh_always() set completefunc=Tcomplete exe "normal! iup\<C-X>\<C-U>\<BS>\<BS>\<BS>\<BS>\<BS>" call assert_equal('up', getline(1)) - call assert_equal(2, g:CallCount) + call assert_equal(6, g:CallCount) set completeopt& set completefunc& bw! @@ -2693,7 +2778,7 @@ func Test_complete_fuzzy_match() if a:findstart return col(".") endif - return [#{word: "foo"}, #{word: "foobar"}, #{word: "fooBaz"}, #{word: "foobala"}] + return [#{word: "foo"}, #{word: "foobar"}, #{word: "fooBaz"}, #{word: "foobala"}, #{word: "你好吗"}, #{word: "我好"}] endfunc new @@ -2752,6 +2837,26 @@ func Test_complete_fuzzy_match() call feedkeys("STe\<C-X>\<C-N>x\<CR>\<Esc>0", 'tx!') call assert_equal('Tex', getline('.')) + " test case for nosort option + set cot=menuone,menu,noinsert,fuzzy,nosort + " "fooBaz" should have a higher score when the leader is "fb". + " With "nosort", "foobar" should still be shown first in the popup menu. + call feedkeys("S\<C-x>\<C-o>fb", 'tx') + call assert_equal('foobar', g:word) + call feedkeys("S\<C-x>\<C-o>好", 'tx') + call assert_equal("你好吗", g:word) + + set cot+=noselect + call feedkeys("S\<C-x>\<C-o>好", 'tx') + call assert_equal(v:null, g:word) + call feedkeys("S\<C-x>\<C-o>好\<C-N>", 'tx') + call assert_equal('你好吗', g:word) + + " "nosort" shouldn't enable fuzzy filtering when "fuzzy" isn't present. + set cot=menuone,noinsert,nosort + call feedkeys("S\<C-x>\<C-o>fooB\<C-Y>", 'tx') + call assert_equal('fooBaz', getline('.')) + " clean up set omnifunc= bw! @@ -2785,4 +2890,203 @@ func Test_complete_fuzzy_match_tie() set completeopt& endfunc +func Test_complete_info_matches() + let g:what = ['matches'] + func ShownInfo() + let g:compl_info = complete_info(g:what) + return '' + endfunc + set completeopt+=noinsert + + new + call setline(1, ['aaa', 'aab', 'aba', 'abb']) + inoremap <buffer><F5> <C-R>=ShownInfo()<CR> + + call feedkeys("Go\<C-X>\<C-N>\<F5>\<Esc>dd", 'tx') + call assert_equal([ + \ {'word': 'aaa', 'menu': '', 'user_data': '', 'info': '', 'kind': '', 'abbr': ''}, + \ {'word': 'aab', 'menu': '', 'user_data': '', 'info': '', 'kind': '', 'abbr': ''}, + \ {'word': 'aba', 'menu': '', 'user_data': '', 'info': '', 'kind': '', 'abbr': ''}, + \ {'word': 'abb', 'menu': '', 'user_data': '', 'info': '', 'kind': '', 'abbr': ''}, + \], g:compl_info['matches']) + + call feedkeys("Goa\<C-X>\<C-N>b\<F5>\<Esc>dd", 'tx') + call assert_equal([ + \ {'word': 'aba', 'menu': '', 'user_data': '', 'info': '', 'kind': '', 'abbr': ''}, + \ {'word': 'abb', 'menu': '', 'user_data': '', 'info': '', 'kind': '', 'abbr': ''}, + \], g:compl_info['matches']) + + " items and matches both in what + let g:what = ['items', 'matches'] + call feedkeys("Goa\<C-X>\<C-N>b\<F5>\<Esc>dd", 'tx') + call assert_equal([ + \ {'word': 'aaa', 'menu': '', 'user_data': '', 'match': v:false, 'info': '', 'kind': '', 'abbr': ''}, + \ {'word': 'aab', 'menu': '', 'user_data': '', 'match': v:false, 'info': '', 'kind': '', 'abbr': ''}, + \ {'word': 'aba', 'menu': '', 'user_data': '', 'match': v:true, 'info': '', 'kind': '', 'abbr': ''}, + \ {'word': 'abb', 'menu': '', 'user_data': '', 'match': v:true, 'info': '', 'kind': '', 'abbr': ''}, + \], g:compl_info['items']) + call assert_false(has_key(g:compl_info, 'matches')) + + bw! + unlet g:what + delfunc ShownInfo + set cot& +endfunc + +func Test_complete_info_completed() + func ShownInfo() + let g:compl_info = complete_info(['completed']) + return '' + endfunc + set completeopt+=noinsert + + new + call setline(1, ['aaa', 'aab', 'aba', 'abb']) + inoremap <buffer><F5> <C-R>=ShownInfo()<CR> + + call feedkeys("Go\<C-X>\<C-N>\<F5>\<Esc>dd", 'tx') + call assert_equal({'word': 'aaa', 'menu': '', 'user_data': '', 'info': '', 'kind': '', 'abbr': ''}, g:compl_info['completed']) + + call feedkeys("Go\<C-X>\<C-N>\<C-N>\<F5>\<Esc>dd", 'tx') + call assert_equal({'word': 'aab', 'menu': '', 'user_data': '', 'info': '', 'kind': '', 'abbr': ''}, g:compl_info['completed']) + + call feedkeys("Go\<C-X>\<C-N>\<C-N>\<C-N>\<C-N>\<F5>\<Esc>dd", 'tx') + call assert_equal({'word': 'abb', 'menu': '', 'user_data': '', 'info': '', 'kind': '', 'abbr': ''}, g:compl_info['completed']) + + set completeopt+=noselect + call feedkeys("Go\<C-X>\<C-N>\<F5>\<Esc>dd", 'tx') + call assert_equal({}, g:compl_info) + + bw! + delfunc ShownInfo + set cot& +endfunc + +function Test_completeopt_preinsert() + func Omni_test(findstart, base) + if a:findstart + return col(".") + endif + return [#{word: "fobar"}, #{word: "foobar"}, #{word: "你的"}, #{word: "你好世界"}] + endfunc + set omnifunc=Omni_test + set completeopt=menu,menuone,preinsert + + new + call feedkeys("S\<C-X>\<C-O>f", 'tx') + call assert_equal("fobar", getline('.')) + call feedkeys("\<C-E>\<ESC>", 'tx') + + call feedkeys("S\<C-X>\<C-O>foo", 'tx') + call assert_equal("foobar", getline('.')) + call feedkeys("\<C-E>\<ESC>", 'tx') + + call feedkeys("S\<C-X>\<C-O>foo\<BS>\<BS>\<BS>", 'tx') + call assert_equal("", getline('.')) + call feedkeys("\<C-E>\<ESC>", 'tx') + + " delete a character and input new leader + call feedkeys("S\<C-X>\<C-O>foo\<BS>b", 'tx') + call assert_equal("fobar", getline('.')) + call feedkeys("\<C-E>\<ESC>", 'tx') + + " delete preinsert when prepare completion + call feedkeys("S\<C-X>\<C-O>f\<Space>", 'tx') + call assert_equal("f ", getline('.')) + call feedkeys("\<C-E>\<ESC>", 'tx') + + call feedkeys("S\<C-X>\<C-O>你", 'tx') + call assert_equal("你的", getline('.')) + call feedkeys("\<C-E>\<ESC>", 'tx') + + call feedkeys("S\<C-X>\<C-O>你好", 'tx') + call assert_equal("你好世界", getline('.')) + call feedkeys("\<C-E>\<ESC>", 'tx') + + call feedkeys("Shello wo\<Left>\<Left>\<Left>\<C-X>\<C-O>f", 'tx') + call assert_equal("hello fobar wo", getline('.')) + call feedkeys("\<C-E>\<ESC>", 'tx') + + call feedkeys("Shello wo\<Left>\<Left>\<Left>\<C-X>\<C-O>f\<BS>", 'tx') + call assert_equal("hello wo", getline('.')) + call feedkeys("\<C-E>\<ESC>", 'tx') + + call feedkeys("Shello wo\<Left>\<Left>\<Left>\<C-X>\<C-O>foo", 'tx') + call assert_equal("hello foobar wo", getline('.')) + call feedkeys("\<C-E>\<ESC>", 'tx') + + call feedkeys("Shello wo\<Left>\<Left>\<Left>\<C-X>\<C-O>foo\<BS>b", 'tx') + call assert_equal("hello fobar wo", getline('.')) + call feedkeys("\<C-E>\<ESC>", 'tx') + + " confirm + call feedkeys("S\<C-X>\<C-O>f\<C-Y>", 'tx') + call assert_equal("fobar", getline('.')) + call assert_equal(5, col('.')) + + " cancel + call feedkeys("S\<C-X>\<C-O>fo\<C-E>", 'tx') + call assert_equal("fo", getline('.')) + call assert_equal(2, col('.')) + + call feedkeys("S hello hero\<CR>h\<C-X>\<C-N>", 'tx') + call assert_equal("hello", getline('.')) + call assert_equal(1, col('.')) + + call feedkeys("Sh\<C-X>\<C-N>\<C-Y>", 'tx') + call assert_equal("hello", getline('.')) + call assert_equal(5, col('.')) + + " delete preinsert part + call feedkeys("S\<C-X>\<C-O>fo ", 'tx') + call assert_equal("fo ", getline('.')) + call assert_equal(3, col('.')) + + call feedkeys("She\<C-X>\<C-N>\<C-U>", 'tx') + call assert_equal("", getline('.')) + call assert_equal(1, col('.')) + + call feedkeys("She\<C-X>\<C-N>\<C-W>", 'tx') + call assert_equal("", getline('.')) + call assert_equal(1, col('.')) + + " whole line + call feedkeys("Shello hero\<CR>\<C-X>\<C-L>", 'tx') + call assert_equal("hello hero", getline('.')) + call assert_equal(1, col('.')) + + call feedkeys("Shello hero\<CR>he\<C-X>\<C-L>", 'tx') + call assert_equal("hello hero", getline('.')) + call assert_equal(2, col('.')) + + call feedkeys("Shello hero\<CR>h\<C-X>\<C-N>er", 'tx') + call assert_equal("hero", getline('.')) + call assert_equal(3, col('.')) + + " can not work with fuzzy + set cot+=fuzzy + call feedkeys("S\<C-X>\<C-O>", 'tx') + call assert_equal("fobar", getline('.')) + call assert_equal(5, col('.')) + + " test for fuzzy and noinsert + set cot+=noinsert + call feedkeys("S\<C-X>\<C-O>fb", 'tx') + call assert_equal("fb", getline('.')) + call assert_equal(2, col('.')) + + call feedkeys("S\<C-X>\<C-O>你", 'tx') + call assert_equal("你", getline('.')) + call assert_equal(1, col('.')) + + call feedkeys("S\<C-X>\<C-O>fb\<C-Y>", 'tx') + call assert_equal("fobar", getline('.')) + call assert_equal(5, col('.')) + + bw! + set cot& + set omnifunc& + delfunc Omni_test +endfunc + " vim: shiftwidth=2 sts=2 expandtab nofoldenable diff --git a/test/old/testdir/test_let.vim b/test/old/testdir/test_let.vim index 44852c1d38..22a3a35f87 100644 --- a/test/old/testdir/test_let.vim +++ b/test/old/testdir/test_let.vim @@ -436,6 +436,24 @@ func Test_let_heredoc_fails() call assert_report('Caught exception: ' .. v:exception) endtry + try + let v =<< trim trimm + trimm + call assert_report('No exception thrown') + catch /E221:/ + catch + call assert_report('Caught exception: ' .. v:exception) + endtry + + try + let v =<< trim trim evall + evall + call assert_report('No exception thrown') + catch /E221:/ + catch + call assert_report('Caught exception: ' .. v:exception) + endtry + let text =<< trim END func WrongSyntax() let v =<< that there diff --git a/test/old/testdir/test_matchfuzzy.vim b/test/old/testdir/test_matchfuzzy.vim index 90f3366b23..0c982c19f1 100644 --- a/test/old/testdir/test_matchfuzzy.vim +++ b/test/old/testdir/test_matchfuzzy.vim @@ -23,6 +23,8 @@ func Test_matchfuzzy() call assert_equal(['aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'], matchfuzzy(['aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'], 'aa')) call assert_equal(256, matchfuzzy([repeat('a', 256)], repeat('a', 256))[0]->len()) call assert_equal([], matchfuzzy([repeat('a', 300)], repeat('a', 257))) + " full match has highest score + call assert_equal(['Cursor', 'lCursor'], matchfuzzy(["hello", "lCursor", "Cursor"], "Cursor")) " matches with same score should not be reordered let l = ['abc1', 'abc2', 'abc3'] call assert_equal(l, l->matchfuzzy('abc')) @@ -98,15 +100,15 @@ endfunc " Test for the matchfuzzypos() function func Test_matchfuzzypos() - call assert_equal([['curl', 'world'], [[2,3], [2,3]], [128, 127]], matchfuzzypos(['world', 'curl'], 'rl')) - call assert_equal([['curl', 'world'], [[2,3], [2,3]], [128, 127]], matchfuzzypos(['world', 'one', 'curl'], 'rl')) + call assert_equal([['curl', 'world'], [[2,3], [2,3]], [178, 177]], matchfuzzypos(['world', 'curl'], 'rl')) + call assert_equal([['curl', 'world'], [[2,3], [2,3]], [178, 177]], matchfuzzypos(['world', 'one', 'curl'], 'rl')) call assert_equal([['hello', 'hello world hello world'], - \ [[0, 1, 2, 3, 4], [0, 1, 2, 3, 4]], [275, 257]], + \ [[0, 1, 2, 3, 4], [0, 1, 2, 3, 4]], [500, 382]], \ matchfuzzypos(['hello world hello world', 'hello', 'world'], 'hello')) - call assert_equal([['aaaaaaa'], [[0, 1, 2]], [191]], matchfuzzypos(['aaaaaaa'], 'aaa')) - call assert_equal([['a b'], [[0, 3]], [219]], matchfuzzypos(['a b'], 'a b')) - call assert_equal([['a b'], [[0, 3]], [219]], matchfuzzypos(['a b'], 'a b')) - call assert_equal([['a b'], [[0]], [112]], matchfuzzypos(['a b'], ' a ')) + call assert_equal([['aaaaaaa'], [[0, 1, 2]], [266]], matchfuzzypos(['aaaaaaa'], 'aaa')) + call assert_equal([['a b'], [[0, 3]], [269]], matchfuzzypos(['a b'], 'a b')) + call assert_equal([['a b'], [[0, 3]], [269]], matchfuzzypos(['a b'], 'a b')) + call assert_equal([['a b'], [[0]], [137]], matchfuzzypos(['a b'], ' a ')) call assert_equal([[], [], []], matchfuzzypos(['a b'], ' ')) call assert_equal([[], [], []], matchfuzzypos(['world', 'curl'], 'ab')) let x = matchfuzzypos([repeat('a', 256)], repeat('a', 256)) @@ -115,33 +117,33 @@ func Test_matchfuzzypos() call assert_equal([[], [], []], matchfuzzypos([], 'abc')) " match in a long string - call assert_equal([[repeat('x', 300) .. 'abc'], [[300, 301, 302]], [-135]], + call assert_equal([[repeat('x', 300) .. 'abc'], [[300, 301, 302]], [-60]], \ matchfuzzypos([repeat('x', 300) .. 'abc'], 'abc')) " preference for camel case match - call assert_equal([['xabcxxaBc'], [[6, 7, 8]], [189]], matchfuzzypos(['xabcxxaBc'], 'abc')) + call assert_equal([['xabcxxaBc'], [[6, 7, 8]], [269]], matchfuzzypos(['xabcxxaBc'], 'abc')) " preference for match after a separator (_ or space) - call assert_equal([['xabx_ab'], [[5, 6]], [145]], matchfuzzypos(['xabx_ab'], 'ab')) + call assert_equal([['xabx_ab'], [[5, 6]], [195]], matchfuzzypos(['xabx_ab'], 'ab')) " preference for leading letter match - call assert_equal([['abcxabc'], [[0, 1]], [150]], matchfuzzypos(['abcxabc'], 'ab')) + call assert_equal([['abcxabc'], [[0, 1]], [200]], matchfuzzypos(['abcxabc'], 'ab')) " preference for sequential match - call assert_equal([['aobncedone'], [[7, 8, 9]], [158]], matchfuzzypos(['aobncedone'], 'one')) + call assert_equal([['aobncedone'], [[7, 8, 9]], [233]], matchfuzzypos(['aobncedone'], 'one')) " best recursive match - call assert_equal([['xoone'], [[2, 3, 4]], [168]], matchfuzzypos(['xoone'], 'one')) + call assert_equal([['xoone'], [[2, 3, 4]], [243]], matchfuzzypos(['xoone'], 'one')) " match multiple words (separated by space) - call assert_equal([['foo bar baz'], [[8, 9, 10, 0, 1, 2]], [369]], ['foo bar baz', 'foo', 'foo bar', 'baz bar']->matchfuzzypos('baz foo')) + call assert_equal([['foo bar baz'], [[8, 9, 10, 0, 1, 2]], [519]], ['foo bar baz', 'foo', 'foo bar', 'baz bar']->matchfuzzypos('baz foo')) call assert_equal([[], [], []], ['foo bar baz', 'foo', 'foo bar', 'baz bar']->matchfuzzypos('baz foo', {'matchseq': 1})) - call assert_equal([['foo bar baz'], [[0, 1, 2, 8, 9, 10]], [369]], ['foo bar baz', 'foo', 'foo bar', 'baz bar']->matchfuzzypos('foo baz')) - call assert_equal([['foo bar baz'], [[0, 1, 2, 3, 4, 5, 10]], [326]], ['foo bar baz', 'foo', 'foo bar', 'baz bar']->matchfuzzypos('foo baz', {'matchseq': 1})) + call assert_equal([['foo bar baz'], [[0, 1, 2, 8, 9, 10]], [519]], ['foo bar baz', 'foo', 'foo bar', 'baz bar']->matchfuzzypos('foo baz')) + call assert_equal([['foo bar baz'], [[0, 1, 2, 3, 4, 5, 10]], [476]], ['foo bar baz', 'foo', 'foo bar', 'baz bar']->matchfuzzypos('foo baz', {'matchseq': 1})) call assert_equal([[], [], []], ['foo bar baz', 'foo', 'foo bar', 'baz bar']->matchfuzzypos('one two')) call assert_equal([[], [], []], ['foo bar']->matchfuzzypos(" \t ")) - call assert_equal([['grace'], [[1, 2, 3, 4, 2, 3, 4, 0, 1, 2, 3, 4]], [657]], ['grace']->matchfuzzypos('race ace grace')) + call assert_equal([['grace'], [[1, 2, 3, 4, 2, 3, 4, 0, 1, 2, 3, 4]], [1057]], ['grace']->matchfuzzypos('race ace grace')) let l = [{'id' : 5, 'val' : 'crayon'}, {'id' : 6, 'val' : 'camera'}] - call assert_equal([[{'id' : 6, 'val' : 'camera'}], [[0, 1, 2]], [192]], + call assert_equal([[{'id' : 6, 'val' : 'camera'}], [[0, 1, 2]], [267]], \ matchfuzzypos(l, 'cam', {'text_cb' : {v -> v.val}})) - call assert_equal([[{'id' : 6, 'val' : 'camera'}], [[0, 1, 2]], [192]], + call assert_equal([[{'id' : 6, 'val' : 'camera'}], [[0, 1, 2]], [267]], \ matchfuzzypos(l, 'cam', {'key' : 'val'})) call assert_equal([[], [], []], matchfuzzypos(l, 'day', {'text_cb' : {v -> v.val}})) call assert_equal([[], [], []], matchfuzzypos(l, 'day', {'key' : 'val'})) @@ -159,6 +161,18 @@ func Test_matchfuzzypos() " Nvim doesn't have null functions " call assert_fails("let x = matchfuzzypos(l, 'foo', {'text_cb' : test_null_function()})", 'E475:') + " case match + call assert_equal([['Match', 'match'], [[0, 1], [0, 1]], [202, 177]], matchfuzzypos(['match', 'Match'], 'Ma')) + call assert_equal([['match', 'Match'], [[0, 1], [0, 1]], [202, 177]], matchfuzzypos(['Match', 'match'], 'ma')) + " CamelCase has high weight even case match + call assert_equal(['MyTestCase', 'mytestcase'], matchfuzzy(['mytestcase', 'MyTestCase'], 'mtc')) + call assert_equal(['MyTestCase', 'mytestcase'], matchfuzzy(['MyTestCase', 'mytestcase'], 'mtc')) + call assert_equal(['MyTest', 'Mytest', 'mytest', ],matchfuzzy(['Mytest', 'mytest', 'MyTest'], 'MyT')) + call assert_equal(['CamelCaseMatchIngAlg', 'camelCaseMatchingAlg', 'camelcasematchingalg'], + \ matchfuzzy(['CamelCaseMatchIngAlg', 'camelcasematchingalg', 'camelCaseMatchingAlg'], 'CamelCase')) + call assert_equal(['CamelCaseMatchIngAlg', 'camelCaseMatchingAlg', 'camelcasematchingalg'], + \ matchfuzzy(['CamelCaseMatchIngAlg', 'camelcasematchingalg', 'camelCaseMatchingAlg'], 'CamelcaseM')) + let l = [{'id' : 5, 'name' : 'foo'}, {'id' : 6, 'name' : []}, {'id' : 7}] call assert_fails("let x = matchfuzzypos(l, 'foo', {'key' : 'name'})", 'E730:') endfunc @@ -209,12 +223,12 @@ func Test_matchfuzzypos_mbyte() call assert_equal([['ンヹㄇヺヴ'], [[1, 3]], [88]], matchfuzzypos(['ンヹㄇヺヴ'], 'ヹヺ')) " reverse the order of characters call assert_equal([[], [], []], matchfuzzypos(['ンヹㄇヺヴ'], 'ヺヹ')) - call assert_equal([['αβΩxxx', 'xαxβxΩx'], [[0, 1, 2], [1, 3, 5]], [222, 113]], + call assert_equal([['αβΩxxx', 'xαxβxΩx'], [[0, 1, 2], [1, 3, 5]], [252, 143]], \ matchfuzzypos(['αβΩxxx', 'xαxβxΩx'], 'αβΩ')) call assert_equal([['ππbbππ', 'πππbbbπππ', 'ππππbbbbππππ', 'πbπ'], - \ [[0, 1], [0, 1], [0, 1], [0, 2]], [151, 148, 145, 110]], + \ [[0, 1], [0, 1], [0, 1], [0, 2]], [176, 173, 170, 135]], \ matchfuzzypos(['πbπ', 'ππbbππ', 'πππbbbπππ', 'ππππbbbbππππ'], 'ππ')) - call assert_equal([['ααααααα'], [[0, 1, 2]], [191]], + call assert_equal([['ααααααα'], [[0, 1, 2]], [216]], \ matchfuzzypos(['ααααααα'], 'ααα')) call assert_equal([[], [], []], matchfuzzypos(['ンヹㄇ', 'ŗŝţ'], 'fffifl')) @@ -227,10 +241,10 @@ func Test_matchfuzzypos_mbyte() call assert_equal([[], [], []], ['세 마리의 작은 돼지', '마리의', '마리의 작은', '작은 돼지']->matchfuzzypos('파란 하늘')) " match in a long string - call assert_equal([[repeat('ぶ', 300) .. 'ẼẼẼ'], [[300, 301, 302]], [-135]], + call assert_equal([[repeat('ぶ', 300) .. 'ẼẼẼ'], [[300, 301, 302]], [-110]], \ matchfuzzypos([repeat('ぶ', 300) .. 'ẼẼẼ'], 'ẼẼẼ')) " preference for camel case match - call assert_equal([['xѳѵҁxxѳѴҁ'], [[6, 7, 8]], [189]], matchfuzzypos(['xѳѵҁxxѳѴҁ'], 'ѳѵҁ')) + call assert_equal([['xѳѵҁxxѳѴҁ'], [[6, 7, 8]], [219]], matchfuzzypos(['xѳѵҁxxѳѴҁ'], 'ѳѵҁ')) " preference for match after a separator (_ or space) call assert_equal([['xちだx_ちだ'], [[5, 6]], [145]], matchfuzzypos(['xちだx_ちだ'], 'ちだ')) " preference for leading letter match diff --git a/test/old/testdir/test_messages.vim b/test/old/testdir/test_messages.vim index ac5184645f..bfead20142 100644 --- a/test/old/testdir/test_messages.vim +++ b/test/old/testdir/test_messages.vim @@ -216,6 +216,7 @@ endfunc " Test more-prompt (see :help more-prompt). func Test_message_more() CheckRunVimInTerminal + let buf = RunVimInTerminal('', {'rows': 6}) call term_sendkeys(buf, ":call setline(1, range(1, 100))\n") @@ -611,4 +612,50 @@ func Test_cmdheight_zero() tabonly endfunc +func Test_messagesopt_history() + " After setting 'messagesopt' "history" to 2 and outputting a message 4 times + " with :echomsg, is the number of output lines of :messages 2? + set messagesopt=hit-enter,history:2 + echomsg 'foo' + echomsg 'bar' + echomsg 'baz' + echomsg 'foobar' + call assert_equal(['baz', 'foobar'], GetMessages()) + + " When the number of messages is 10 and 'messagesopt' "history" is changed to + " 5, is the number of output lines of :messages 5? + set messagesopt=hit-enter,history:10 + for num in range(1, 10) + echomsg num + endfor + set messagesopt=hit-enter,history:5 + call assert_equal(5, len(GetMessages())) + + " Check empty list + set messagesopt=hit-enter,history:0 + call assert_true(empty(GetMessages())) + + set messagesopt& +endfunc + +func Test_messagesopt_wait() + CheckRunVimInTerminal + + let buf = RunVimInTerminal('', {'rows': 6, 'cols': 45}) + call term_sendkeys(buf, ":set cmdheight=1\n") + + " Check hit-enter prompt + call term_sendkeys(buf, ":set messagesopt=hit-enter,history:500\n") + call term_sendkeys(buf, ":echo 'foo' | echo 'bar' | echo 'baz'\n") + call WaitForAssert({-> assert_equal('Press ENTER or type command to continue', term_getline(buf, 6))}) + + " Check no hit-enter prompt when "wait:" is set + call term_sendkeys(buf, ":set messagesopt=wait:100,history:500\n") + call term_sendkeys(buf, ":echo 'foo' | echo 'bar' | echo 'baz'\n") + call WaitForAssert({-> assert_equal(' 0,0-1 All', term_getline(buf, 6))}) + + " clean up + call StopVimInTerminal(buf) +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/test_normal.vim b/test/old/testdir/test_normal.vim index c89e73bada..1d9609cbe1 100644 --- a/test/old/testdir/test_normal.vim +++ b/test/old/testdir/test_normal.vim @@ -1338,11 +1338,27 @@ func Test_scroll_in_ex_mode() call writefile(['done'], 'Xdone') qa! END - call writefile(lines, 'Xscript') + call writefile(lines, 'Xscript', 'D') call assert_equal(1, RunVim([], [], '--clean -X -Z -e -s -S Xscript')) call assert_equal(['done'], readfile('Xdone')) - call delete('Xscript') + call delete('Xdone') +endfunc + +func Test_scroll_and_paste_in_ex_mode() + throw 'Skipped: does not work when Nvim is run from :!' + " This used to crash because of moving cursor to line 0. + let lines =<< trim END + v/foo/vi|YY9PYQ + v/bar/vi|YY9PYQ + v/bar/exe line('.') == 1 ? "vi|Y\<C-B>9PYQ" : "vi|YQ" + call writefile(['done'], 'Xdone') + qa! + END + call writefile(lines, 'Xscript', 'D') + call assert_equal(1, RunVim([], [], '-u NONE -i NONE -n -X -Z -e -s -S Xscript')) + call assert_equal(['done'], readfile('Xdone')) + call delete('Xdone') endfunc @@ -4303,4 +4319,5 @@ func Test_normal_go() bwipe! endfunc + " vim: shiftwidth=2 sts=2 expandtab nofoldenable diff --git a/test/old/testdir/test_options.vim b/test/old/testdir/test_options.vim index b6bdb1be52..2479f0ca51 100644 --- a/test/old/testdir/test_options.vim +++ b/test/old/testdir/test_options.vim @@ -496,7 +496,7 @@ func Test_set_completion_string_values() " but don't exhaustively validate their results. call assert_equal('single', getcompletion('set ambw=', 'cmdline')[0]) call assert_match('light\|dark', getcompletion('set bg=', 'cmdline')[1]) - call assert_equal('indent', getcompletion('set backspace=', 'cmdline')[0]) + call assert_equal('indent,eol,start', getcompletion('set backspace=', 'cmdline')[0]) call assert_equal('yes', getcompletion('set backupcopy=', 'cmdline')[1]) call assert_equal('backspace', getcompletion('set belloff=', 'cmdline')[1]) call assert_equal('min:', getcompletion('set briopt=', 'cmdline')[1]) @@ -504,7 +504,8 @@ func Test_set_completion_string_values() call assert_equal('current', getcompletion('set browsedir=', 'cmdline')[1]) endif call assert_equal('unload', getcompletion('set bufhidden=', 'cmdline')[1]) - call assert_equal('nowrite', getcompletion('set buftype=', 'cmdline')[1]) + "call assert_equal('nowrite', getcompletion('set buftype=', 'cmdline')[1]) + call assert_equal('help', getcompletion('set buftype=', 'cmdline')[1]) call assert_equal('internal', getcompletion('set casemap=', 'cmdline')[1]) if exists('+clipboard') " call assert_match('unnamed', getcompletion('set clipboard=', 'cmdline')[1]) @@ -644,6 +645,10 @@ func Test_set_completion_string_values() " call feedkeys(":set hl=8b i\<Left>\<Left>\<Tab>\<C-B>\"\<CR>", 'xt') " call assert_equal("\"set hl=8bi i", @:) + " messagesopt + call assert_equal(['history:', 'hit-enter', 'wait:'], + \ getcompletion('set messagesopt+=', 'cmdline')->sort()) + " " Test flag lists " @@ -706,6 +711,10 @@ func Test_set_completion_string_values() " Test empty option set diffopt= call assert_equal([], getcompletion('set diffopt-=', 'cmdline')) + " Test all possible values + call assert_equal(['filler', 'context:', 'iblank', 'icase', 'iwhite', 'iwhiteall', 'iwhiteeol', 'horizontal', + \ 'vertical', 'closeoff', 'hiddenoff', 'foldcolumn:', 'followwrap', 'internal', 'indent-heuristic', 'algorithm:', 'linematch:'], + \ getcompletion('set diffopt=', 'cmdline')) set diffopt& " Test escaping output @@ -743,7 +752,6 @@ func Test_set_option_errors() call assert_fails('set backupcopy=', 'E474:') call assert_fails('set regexpengine=3', 'E474:') call assert_fails('set history=10001', 'E474:') - call assert_fails('set msghistory=10001', 'E474:') call assert_fails('set numberwidth=21', 'E474:') call assert_fails('set colorcolumn=-a', 'E474:') call assert_fails('set colorcolumn=a', 'E474:') @@ -757,7 +765,6 @@ func Test_set_option_errors() endif call assert_fails('set helpheight=-1', 'E487:') call assert_fails('set history=-1', 'E487:') - call assert_fails('set msghistory=-1', 'E487:') call assert_fails('set report=-1', 'E487:') call assert_fails('set shiftwidth=-1', 'E487:') call assert_fails('set sidescroll=-1', 'E487:') @@ -2252,16 +2259,57 @@ func Test_opt_default() call assert_equal('vt', &formatoptions) set formatoptions&vim call assert_equal('tcq', &formatoptions) + + call assert_equal('ucs-bom,utf-8,default,latin1', &fencs) + set fencs=latin1 + set fencs& + call assert_equal('ucs-bom,utf-8,default,latin1', &fencs) + set fencs=latin1 + set all& + call assert_equal('ucs-bom,utf-8,default,latin1', &fencs) endfunc " Test for the 'cmdheight' option -func Test_cmdheight() +func Test_opt_cmdheight() %bw! let ht = &lines set cmdheight=9999 call assert_equal(1, winheight(0)) call assert_equal(ht - 1, &cmdheight) set cmdheight& + + " The status line should be taken into account. + set laststatus=2 + set cmdheight=9999 + call assert_equal(ht - 2, &cmdheight) + set cmdheight& laststatus=1 " Accommodate Nvim default + + " The tabline should be taken into account only non-GUI. + set showtabline=2 + set cmdheight=9999 + if has('gui_running') + call assert_equal(ht - 1, &cmdheight) + else + call assert_equal(ht - 2, &cmdheight) + endif + set cmdheight& showtabline& + + " The 'winminheight' should be taken into account. + set winheight=3 winminheight=3 + split + set cmdheight=9999 + call assert_equal(ht - 8, &cmdheight) + %bw! + set cmdheight& winminheight& winheight& + + " Only the windows in the current tabpage are taken into account. + set winheight=3 winminheight=3 showtabline=0 + split + tabnew + set cmdheight=9999 + call assert_equal(ht - 3, &cmdheight) + %bw! + set cmdheight& winminheight& winheight& showtabline& endfunc " To specify a control character as an option value, '^' can be used @@ -2476,6 +2524,7 @@ func Test_string_option_revert_on_failure() \ ['lispoptions', 'expr:1', 'a123'], \ ['listchars', 'tab:->', 'tab:'], \ ['matchpairs', '<:>', '<:'], + \ ['messagesopt', 'hit-enter,history:100', 'a123'], \ ['mkspellmem', '100000,1000,100', '100000'], \ ['mouse', 'nvi', 'z'], \ ['mousemodel', 'extend', 'a123'], diff --git a/test/old/testdir/test_perl.vim b/test/old/testdir/test_perl.vim index c08a042dae..2d7f8fdc10 100644 --- a/test/old/testdir/test_perl.vim +++ b/test/old/testdir/test_perl.vim @@ -316,7 +316,10 @@ VIM::DoCommand('let s ..= "B"') perl << trim eof VIM::DoCommand('let s ..= "E"') eof - call assert_equal('ABCDE', s) + perl << trimm +VIM::DoCommand('let s ..= "F"') +trimm + call assert_equal('ABCDEF', s) endfunc " vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/test_popup.vim b/test/old/testdir/test_popup.vim index 601ba6c688..e4abf978ab 100644 --- a/test/old/testdir/test_popup.vim +++ b/test/old/testdir/test_popup.vim @@ -748,17 +748,11 @@ func Test_popup_and_preview_autocommand() bw! endfunc -func Test_popup_and_previewwindow_dump() +func s:run_popup_and_previewwindow_dump(lines, dumpfile) CheckScreendump CheckFeature quickfix - let lines =<< trim END - set previewheight=9 - silent! pedit - call setline(1, map(repeat(["ab"], 10), "v:val .. v:key")) - exec "norm! G\<C-E>\<C-E>" - END - call writefile(lines, 'Xscript') + call writefile(a:lines, 'Xscript', 'D') let buf = RunVimInTerminal('-S Xscript', {}) " wait for the script to finish @@ -768,11 +762,30 @@ func Test_popup_and_previewwindow_dump() call term_sendkeys(buf, "o") call TermWait(buf, 50) call term_sendkeys(buf, "\<C-X>\<C-N>") - call VerifyScreenDump(buf, 'Test_popup_and_previewwindow_01', {}) + call VerifyScreenDump(buf, a:dumpfile, {}) call term_sendkeys(buf, "\<Esc>u") call StopVimInTerminal(buf) - call delete('Xscript') +endfunc + +func Test_popup_and_previewwindow_dump_pedit() + let lines =<< trim END + set previewheight=9 + silent! pedit + call setline(1, map(repeat(["ab"], 10), "v:val .. v:key")) + exec "norm! G\<C-E>\<C-E>" + END + call s:run_popup_and_previewwindow_dump(lines, 'Test_popup_and_previewwindow_pedit') +endfunc + +func Test_popup_and_previewwindow_dump_pbuffer() + let lines =<< trim END + set previewheight=9 + silent! pbuffer + call setline(1, map(repeat(["ab"], 10), "v:val .. v:key")) + exec "norm! G\<C-E>\<C-E>\<C-E>" + END + call s:run_popup_and_previewwindow_dump(lines, 'Test_popup_and_previewwindow_pbuffer') endfunc func Test_balloon_split() @@ -1506,6 +1519,39 @@ func Test_pum_highlights_match() call StopVimInTerminal(buf) endfunc +func Test_pum_highlights_match_with_abbr() + CheckScreendump + let lines =<< trim END + func Omni_test(findstart, base) + if a:findstart + return col(".") + endif + return { + \ 'words': [ + \ { 'word': 'foobar', 'abbr': "foobar\t\t!" }, + \ { 'word': 'foobaz', 'abbr': "foobaz\t\t!" }, + \]} + endfunc + + set omnifunc=Omni_test + set completeopt=menuone,noinsert + hi PmenuMatchSel ctermfg=6 ctermbg=7 + hi PmenuMatch ctermfg=4 ctermbg=225 + END + call writefile(lines, 'Xscript', 'D') + let buf = RunVimInTerminal('-S Xscript', {}) + call TermWait(buf) + call term_sendkeys(buf, "i\<C-X>\<C-O>") + call TermWait(buf, 50) + call term_sendkeys(buf, "foo") + call VerifyScreenDump(buf, 'Test_pum_highlights_19', {}) + + call term_sendkeys(buf, "\<C-E>\<Esc>") + call TermWait(buf) + + call StopVimInTerminal(buf) +endfunc + func Test_pum_user_abbr_hlgroup() CheckScreendump let lines =<< trim END @@ -1675,4 +1721,168 @@ func Test_pum_completeitemalign() call StopVimInTerminal(buf) endfunc +func Test_pum_keep_select() + CheckScreendump + let lines =<< trim END + set completeopt=menu,menuone,noinsert + END + call writefile(lines, 'Xscript', 'D') + let buf = RunVimInTerminal('-S Xscript', {}) + call TermWait(buf) + + call term_sendkeys(buf, "ggSFab\<CR>Five\<CR>find\<CR>film\<CR>\<C-X>\<C-P>") + call TermWait(buf, 50) + call VerifyScreenDump(buf, 'Test_pum_keep_select_01', {}) + call term_sendkeys(buf, "\<C-E>\<Esc>") + call TermWait(buf, 50) + + call term_sendkeys(buf, "S\<C-X>\<C-P>") + call TermWait(buf, 50) + call term_sendkeys(buf, "F") + call VerifyScreenDump(buf, 'Test_pum_keep_select_02', {}) + call term_sendkeys(buf, "\<C-E>\<Esc>") + + call TermWait(buf, 50) + call StopVimInTerminal(buf) +endfunc + +func Test_pum_matchins_highlight() + CheckScreendump + let lines =<< trim END + let g:change = 0 + func Omni_test(findstart, base) + if a:findstart + return col(".") + endif + if g:change == 0 + return [#{word: "foo"}, #{word: "bar"}, #{word: "你好"}] + endif + return [#{word: "foo", info: "info"}, #{word: "bar"}, #{word: "你好"}] + endfunc + set omnifunc=Omni_test + hi ComplMatchIns ctermfg=red + END + call writefile(lines, 'Xscript', 'D') + let buf = RunVimInTerminal('-S Xscript', {}) + + call TermWait(buf) + call term_sendkeys(buf, "Sαβγ \<C-X>\<C-O>") + call VerifyScreenDump(buf, 'Test_pum_matchins_01', {}) + call term_sendkeys(buf, "\<C-E>\<Esc>") + + call TermWait(buf) + call term_sendkeys(buf, "Sαβγ \<C-X>\<C-O>\<C-N>") + call VerifyScreenDump(buf, 'Test_pum_matchins_02', {}) + call term_sendkeys(buf, "\<C-E>\<Esc>") + + call TermWait(buf) + call term_sendkeys(buf, "Sαβγ \<C-X>\<C-O>\<C-N>\<C-N>") + call VerifyScreenDump(buf, 'Test_pum_matchins_03', {}) + call term_sendkeys(buf, "\<C-E>\<Esc>") + + " restore after accept + call TermWait(buf) + call term_sendkeys(buf, "Sαβγ \<C-X>\<C-O>\<C-Y>") + call VerifyScreenDump(buf, 'Test_pum_matchins_04', {}) + call term_sendkeys(buf, "\<Esc>") + + " restore after cancel completion + call TermWait(buf) + call term_sendkeys(buf, "Sαβγ \<C-X>\<C-O>\<Space>") + call VerifyScreenDump(buf, 'Test_pum_matchins_05', {}) + call term_sendkeys(buf, "\<Esc>") + + " text after the inserted text shouldn't be highlighted + call TermWait(buf) + call term_sendkeys(buf, "0ea \<C-X>\<C-O>") + call VerifyScreenDump(buf, 'Test_pum_matchins_07', {}) + call term_sendkeys(buf, "\<C-P>") + call VerifyScreenDump(buf, 'Test_pum_matchins_08', {}) + call term_sendkeys(buf, "\<C-P>") + call VerifyScreenDump(buf, 'Test_pum_matchins_09', {}) + call term_sendkeys(buf, "\<C-Y>") + call VerifyScreenDump(buf, 'Test_pum_matchins_10', {}) + call term_sendkeys(buf, "\<Esc>") + + call term_sendkeys(buf, ":let g:change=1\<CR>S\<C-X>\<C-O>") + call VerifyScreenDump(buf, 'Test_pum_matchins_11', {}) + call term_sendkeys(buf, "\<Esc>") + + call StopVimInTerminal(buf) +endfunc + +func Test_pum_matchins_highlight_combine() + CheckScreendump + let lines =<< trim END + func Omni_test(findstart, base) + if a:findstart + return col(".") + endif + return [#{word: "foo"}, #{word: "bar"}, #{word: "你好"}] + endfunc + set omnifunc=Omni_test + hi Normal ctermbg=blue + hi CursorLine cterm=underline ctermbg=green + set cursorline + call setline(1, 'aaa bbb') + END + call writefile(lines, 'Xscript', 'D') + let buf = RunVimInTerminal('-S Xscript', {}) + + " when ComplMatchIns is not set, CursorLine applies normally + call term_sendkeys(buf, "0ea \<C-X>\<C-O>") + call VerifyScreenDump(buf, 'Test_pum_matchins_combine_01', {}) + call term_sendkeys(buf, "\<C-E>") + call VerifyScreenDump(buf, 'Test_pum_matchins_combine_02', {}) + call term_sendkeys(buf, "\<BS>\<Esc>") + + " when ComplMatchIns is set, it is applied over CursorLine + call TermWait(buf) + call term_sendkeys(buf, ":hi ComplMatchIns ctermbg=red ctermfg=yellow\<CR>") + call TermWait(buf) + call term_sendkeys(buf, "0ea \<C-X>\<C-O>") + call VerifyScreenDump(buf, 'Test_pum_matchins_combine_03', {}) + call term_sendkeys(buf, "\<C-P>") + call VerifyScreenDump(buf, 'Test_pum_matchins_combine_04', {}) + call term_sendkeys(buf, "\<C-P>") + call VerifyScreenDump(buf, 'Test_pum_matchins_combine_05', {}) + call term_sendkeys(buf, "\<C-E>") + call VerifyScreenDump(buf, 'Test_pum_matchins_combine_06', {}) + call term_sendkeys(buf, "\<Esc>") + + " Does not highlight the compl leader + call TermWait(buf) + call term_sendkeys(buf, ":set cot+=menuone,noselect\<CR>") + call TermWait(buf) + call term_sendkeys(buf, "S\<C-X>\<C-O>f\<C-N>") + call VerifyScreenDump(buf, 'Test_pum_matchins_combine_07', {}) + call term_sendkeys(buf, "\<C-E>\<Esc>") + + call term_sendkeys(buf, ":set cot+=fuzzy\<CR>") + call TermWait(buf) + call term_sendkeys(buf, "S\<C-X>\<C-O>f\<C-N>") + call VerifyScreenDump(buf, 'Test_pum_matchins_combine_08', {}) + call term_sendkeys(buf, "\<C-E>\<Esc>") + call TermWait(buf) + + call term_sendkeys(buf, ":set cot-=fuzzy\<CR>") + call TermWait(buf) + call term_sendkeys(buf, "Sf\<C-N>") + call VerifyScreenDump(buf, 'Test_pum_matchins_combine_09', {}) + call term_sendkeys(buf, "\<C-E>\<Esc>") + + call StopVimInTerminal(buf) +endfunc + +" this used to crash +func Test_popup_completion_many_ctrlp() + new + let candidates=repeat(['a0'], 99) + call setline(1, candidates) + exe ":norm! VGg\<C-A>" + norm! G + call feedkeys("o" .. repeat("\<c-p>", 100), 'tx') + bw! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/test_preview.vim b/test/old/testdir/test_preview.vim index b7b908e761..422c50ac77 100644 --- a/test/old/testdir/test_preview.vim +++ b/test/old/testdir/test_preview.vim @@ -15,6 +15,20 @@ func Test_Psearch() bwipe endfunc +func s:goto_preview_and_close() + " Go to the preview window + wincmd P + call assert_equal(1, &previewwindow) + call assert_equal('preview', win_gettype()) + + " Close preview window + wincmd z + call assert_equal(1, winnr('$')) + call assert_equal(0, &previewwindow) + + call assert_fails('wincmd P', 'E441:') +endfunc + func Test_window_preview() CheckFeature quickfix @@ -23,17 +37,48 @@ func Test_window_preview() call assert_equal(2, winnr('$')) call assert_equal(0, &previewwindow) - " Go to the preview window - wincmd P - call assert_equal(1, &previewwindow) - call assert_equal('preview', win_gettype()) + call s:goto_preview_and_close() +endfunc + +func Test_window_preview_from_pbuffer() + CheckFeature quickfix + + call writefile(['/* some C code */'], 'Xpreview.c', 'D') + edit Xpreview.c + const buf_num = bufnr('%') + enew + + call feedkeys(":pbuffer Xpre\<C-A>\<C-B>\"\<CR>", 'xt') + call assert_equal("\"pbuffer Xpreview.c", @:) - " Close preview window - wincmd z call assert_equal(1, winnr('$')) + exe 'pbuffer ' . buf_num + call assert_equal(2, winnr('$')) call assert_equal(0, &previewwindow) - call assert_fails('wincmd P', 'E441:') + call s:goto_preview_and_close() + + call assert_equal(1, winnr('$')) + pbuffer Xpreview.c + call assert_equal(2, winnr('$')) + call assert_equal(0, &previewwindow) + + call s:goto_preview_and_close() +endfunc + +func Test_window_preview_terminal() + CheckFeature quickfix + " CheckFeature terminal + + " term ++curwin + term + const buf_num = bufnr('$') + call assert_equal(1, winnr('$')) + exe 'pbuffer' . buf_num + call assert_equal(2, winnr('$')) + call assert_equal(0, &previewwindow) + + call s:goto_preview_and_close() endfunc func Test_window_preview_from_help() diff --git a/test/old/testdir/test_python3.vim b/test/old/testdir/test_python3.vim index c9dbc0b84e..2436587100 100644 --- a/test/old/testdir/test_python3.vim +++ b/test/old/testdir/test_python3.vim @@ -284,7 +284,10 @@ s+='B' python3 << trim eof s+='E' eof - call assert_equal('ABCDE', pyxeval('s')) + python3 << trimm +s+='F' +trimm + call assert_equal('ABCDEF', pyxeval('s')) endfunc " vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/test_pyx3.vim b/test/old/testdir/test_pyx3.vim index 89a3cc22ff..8dfeaff807 100644 --- a/test/old/testdir/test_pyx3.vim +++ b/test/old/testdir/test_pyx3.vim @@ -97,7 +97,10 @@ result+='B' pyx << trim eof result+='E' eof - call assert_equal('ABCDE', pyxeval('result')) + pyx << trimm +result+='F' +trimm + call assert_equal('ABCDEF', pyxeval('result')) endfunc " vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/test_ruby.vim b/test/old/testdir/test_ruby.vim index d4a3dc3301..94941da873 100644 --- a/test/old/testdir/test_ruby.vim +++ b/test/old/testdir/test_ruby.vim @@ -282,7 +282,7 @@ func Test_ruby_Vim_buffer_get() call assert_match('Xfoo1$', rubyeval('Vim::Buffer[1].name')) call assert_match('Xfoo2$', rubyeval('Vim::Buffer[2].name')) call assert_fails('ruby print Vim::Buffer[3].name', - \ "NoMethodError: undefined method `name' for nil") + \ "NoMethodError") %bwipe endfunc @@ -364,7 +364,7 @@ func Test_ruby_Vim_evaluate_dict() redir => l:out ruby d = Vim.evaluate("d"); print d redir END - call assert_equal(['{"a"=>"foo", "b"=>123}'], split(l:out, "\n")) + call assert_equal(['{"a"=>"foo","b"=>123}'], split(substitute(l:out, '\s', '', 'g'), "\n")) endfunc " Test Vim::message({msg}) (display message {msg}) @@ -384,7 +384,7 @@ func Test_ruby_print() call assert_equal('1.23', RubyPrint('1.23')) call assert_equal('Hello World!', RubyPrint('"Hello World!"')) call assert_equal('[1, 2]', RubyPrint('[1, 2]')) - call assert_equal('{"k1"=>"v1", "k2"=>"v2"}', RubyPrint('({"k1" => "v1", "k2" => "v2"})')) + call assert_equal('{"k1"=>"v1","k2"=>"v2"}', substitute(RubyPrint('({"k1" => "v1", "k2" => "v2"})'), '\s', '', 'g')) call assert_equal('true', RubyPrint('true')) call assert_equal('false', RubyPrint('false')) call assert_equal('', RubyPrint('nil')) @@ -439,7 +439,10 @@ Vim.command('let s ..= "B"') ruby << trim eof Vim.command('let s ..= "E"') eof - call assert_equal('ABCDE', s) +ruby << trimm +Vim.command('let s ..= "F"') +trimm + call assert_equal('ABCDEF', s) endfunc " vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/test_shift.vim b/test/old/testdir/test_shift.vim index ec357dac88..f31c5a11e6 100644 --- a/test/old/testdir/test_shift.vim +++ b/test/old/testdir/test_shift.vim @@ -108,10 +108,809 @@ func Test_ex_shift_errors() call assert_fails('>!', 'E477:') call assert_fails('<!', 'E477:') - " call assert_fails('2,1>', 'E493:') - call assert_fails('execute "2,1>"', 'E493:') - " call assert_fails('2,1<', 'E493:') - call assert_fails('execute "2,1<"', 'E493:') + call assert_fails('2,1>', 'E493:') + call assert_fails('2,1<', 'E493:') +endfunc + +" Test inserting a backspace at the start of a line. +" +" This is to verify the proper behavior of tabstop_start() as called from +" ins_bs(). +" +func Test_shift_ins_bs() + set backspace=indent,start + set softtabstop=11 + + call setline(1, repeat(" ", 33) . "word") + exe "norm! I\<BS>" + call assert_equal(repeat(" ", 22) . "word", getline(1)) + call setline(1, repeat(" ", 23) . "word") + exe "norm! I\<BS>" + call assert_equal(repeat(" ", 22) . "word", getline(1)) + exe "norm! I\<BS>" + call assert_equal(repeat(" ", 11) . "word", getline(1)) + + set backspace& softtabstop& + bw! +endfunc + +" Test inserting a backspace at the start of a line, with 'varsofttabstop'. +" +func Test_shift_ins_bs_vartabs() + CheckFeature vartabs + set backspace=indent,start + set varsofttabstop=13,11,7 + + call setline(1, repeat(" ", 44) . "word") + exe "norm! I\<BS>" + call assert_equal(repeat(" ", 38) . "word", getline(1)) + call setline(1, repeat(" ", 39) . "word") + exe "norm! I\<BS>" + call assert_equal(repeat(" ", 38) . "word", getline(1)) + exe "norm! I\<BS>" + call assert_equal(repeat(" ", 31) . "word", getline(1)) + exe "norm! I\<BS>" + call assert_equal(repeat(" ", 24) . "word", getline(1)) + exe "norm! I\<BS>" + call assert_equal(repeat(" ", 13) . "word", getline(1)) + exe "norm! I\<BS>" + call assert_equal( "word", getline(1)) + exe "norm! I\<BS>" + call assert_equal( "word", getline(1)) + + set backspace& varsofttabstop& + bw! +endfunc + +" Test the >> and << normal-mode commands. +" +func Test_shift_norm() + set expandtab " Don't want to worry about tabs vs. spaces in + " results. + + set shiftwidth=5 + set tabstop=7 + + call setline(1, " word") + + " Shift by 'shiftwidth' right and left. + + norm! >> + call assert_equal(repeat(" ", 7) . "word", getline(1)) + norm! >> + call assert_equal(repeat(" ", 12) . "word", getline(1)) + norm! >> + call assert_equal(repeat(" ", 17) . "word", getline(1)) + + norm! << + call assert_equal(repeat(" ", 12) . "word", getline(1)) + norm! << + call assert_equal(repeat(" ", 7) . "word", getline(1)) + norm! << + call assert_equal(repeat(" ", 2) . "word", getline(1)) + norm! << + call assert_equal(repeat(" ", 0) . "word", getline(1)) + + " Shift by 'tabstop' right and left. + + set shiftwidth=0 + call setline(1, " word") + + norm! >> + call assert_equal(repeat(" ", 9) . "word", getline(1)) + norm! >> + call assert_equal(repeat(" ", 16) . "word", getline(1)) + norm! >> + call assert_equal(repeat(" ", 23) . "word", getline(1)) + + norm! << + call assert_equal(repeat(" ", 16) . "word", getline(1)) + norm! << + call assert_equal(repeat(" ", 9) . "word", getline(1)) + norm! << + call assert_equal(repeat(" ", 2) . "word", getline(1)) + norm! << + call assert_equal(repeat(" ", 0) . "word", getline(1)) + + set expandtab& shiftwidth& tabstop& + bw! +endfunc + +" Test the >> and << normal-mode commands, with 'vartabstop'. +" +func Test_shift_norm_vartabs() + CheckFeature vartabs + set expandtab " Don't want to worry about tabs vs. spaces in + " results. + + set shiftwidth=0 + set vartabstop=19,17,11 + + " Shift by 'vartabstop' right and left. + + call setline(1, " word") + + norm! >> + call assert_equal(repeat(" ", 21) . "word", getline(1)) + norm! >> + call assert_equal(repeat(" ", 38) . "word", getline(1)) + norm! >> + call assert_equal(repeat(" ", 49) . "word", getline(1)) + norm! >> + call assert_equal(repeat(" ", 60) . "word", getline(1)) + + norm! << + call assert_equal(repeat(" ", 49) . "word", getline(1)) + norm! << + call assert_equal(repeat(" ", 38) . "word", getline(1)) + norm! << + call assert_equal(repeat(" ", 21) . "word", getline(1)) + norm! << + call assert_equal(repeat(" ", 2) . "word", getline(1)) + norm! << + call assert_equal(repeat(" ", 0) . "word", getline(1)) + + set expandtab& shiftwidth& vartabstop& + bw! +endfunc + +" Test the >> and << normal-mode commands with 'shiftround'. +" +func Test_shift_norm_round() + set expandtab " Don't want to worry about tabs vs. spaces in + " results. + + set shiftround + set shiftwidth=5 + set tabstop=7 + + call setline(1, "word") + + " Shift by 'shiftwidth' right and left. + + exe "norm! I " + norm! >> + call assert_equal(repeat(" ", 5) . "word", getline(1)) + exe "norm! I " + norm! >> + call assert_equal(repeat(" ", 10) . "word", getline(1)) + exe "norm! I " + norm! >> + call assert_equal(repeat(" ", 15) . "word", getline(1)) + norm! >> + call assert_equal(repeat(" ", 20) . "word", getline(1)) + norm! >> + call assert_equal(repeat(" ", 25) . "word", getline(1)) + + norm! << + call assert_equal(repeat(" ", 20) . "word", getline(1)) + norm! << + call assert_equal(repeat(" ", 15) . "word", getline(1)) + norm! << + call assert_equal(repeat(" ", 10) . "word", getline(1)) + exe "norm! I " + norm! << + call assert_equal(repeat(" ", 10) . "word", getline(1)) + + call setline(1, repeat(" ", 7) . "word") + norm! << + call assert_equal(repeat(" ", 5) . "word", getline(1)) + norm! << + call assert_equal(repeat(" ", 0) . "word", getline(1)) + norm! << + call assert_equal(repeat(" ", 0) . "word", getline(1)) + call setline(1, repeat(" ", 2) . "word") + norm! << + call assert_equal(repeat(" ", 0) . "word", getline(1)) + + " Shift by 'tabstop' right and left. + + set shiftwidth=0 + call setline(1, "word") + + exe "norm! I " + norm! >> + call assert_equal(repeat(" ", 7) . "word", getline(1)) + exe "norm! I " + norm! >> + call assert_equal(repeat(" ", 14) . "word", getline(1)) + exe "norm! I " + norm! >> + call assert_equal(repeat(" ", 21) . "word", getline(1)) + norm! >> + call assert_equal(repeat(" ", 28) . "word", getline(1)) + norm! >> + call assert_equal(repeat(" ", 35) . "word", getline(1)) + + norm! << + call assert_equal(repeat(" ", 28) . "word", getline(1)) + norm! << + call assert_equal(repeat(" ", 21) . "word", getline(1)) + norm! << + call assert_equal(repeat(" ", 14) . "word", getline(1)) + exe "norm! I " + norm! << + call assert_equal(repeat(" ", 14) . "word", getline(1)) + + call setline(1, repeat(" ", 9) . "word") + norm! << + call assert_equal(repeat(" ", 7) . "word", getline(1)) + norm! << + call assert_equal(repeat(" ", 0) . "word", getline(1)) + norm! << + call assert_equal(repeat(" ", 0) . "word", getline(1)) + call setline(1, repeat(" ", 2) . "word") + norm! << + call assert_equal(repeat(" ", 0) . "word", getline(1)) + + set expandtab& shiftround& shiftwidth& tabstop& + bw! +endfunc + +" Test the >> and << normal-mode commands with 'shiftround' and 'vartabstop'. +" +func Test_shift_norm_round_vartabs() + CheckFeature vartabs + set expandtab " Don't want to worry about tabs vs. spaces in + " results. + + set shiftround + set shiftwidth=0 + set vartabstop=19,17,11 + + " Shift by 'vartabstop' right and left. + + call setline(1, "word") + + exe "norm! I " + norm! >> + call assert_equal(repeat(" ", 19) . "word", getline(1)) + exe "norm! I " + norm! >> + call assert_equal(repeat(" ", 36) . "word", getline(1)) + exe "norm! I " + norm! >> + call assert_equal(repeat(" ", 47) . "word", getline(1)) + exe "norm! I " + norm! >> + call assert_equal(repeat(" ", 58) . "word", getline(1)) + + exe "norm! I " + norm! << + call assert_equal(repeat(" ", 58) . "word", getline(1)) + norm! << + call assert_equal(repeat(" ", 47) . "word", getline(1)) + norm! << + call assert_equal(repeat(" ", 36) . "word", getline(1)) + norm! << + call assert_equal(repeat(" ", 19) . "word", getline(1)) + exe "norm! I " + norm! << + call assert_equal(repeat(" ", 19) . "word", getline(1)) + norm! << + call assert_equal(repeat(" ", 0) . "word", getline(1)) + exe "norm! I " + call assert_equal(repeat(" ", 2) . "word", getline(1)) + norm! << + call assert_equal(repeat(" ", 0) . "word", getline(1)) + norm! << + call assert_equal(repeat(" ", 0) . "word", getline(1)) + + set expandtab& shiftround& shiftwidth& vartabstop& + bw! +endfunc + +" Test the V> and V< visual-mode commands. +" +" See ":help v_<" and ":help v_>". See also the last paragraph of "3. Simple +" changes", ":help simple-change", immediately above "4. Complex changes", +" ":help complex-change". +" +func Test_shift_vis() + set expandtab " Don't want to worry about tabs vs. spaces in + " results. + + set shiftwidth=5 + set tabstop=7 + + call setline(1, " word") + + " Shift by 'shiftwidth' right and left. + + norm! V> + call assert_equal(repeat(" ", 7) . "word", getline(1)) + norm! V2> + call assert_equal(repeat(" ", 17) . "word", getline(1)) + norm! V3> + call assert_equal(repeat(" ", 32) . "word", getline(1)) + + norm! V< + call assert_equal(repeat(" ", 27) . "word", getline(1)) + norm! V2< + call assert_equal(repeat(" ", 17) . "word", getline(1)) + norm! V3< + call assert_equal(repeat(" ", 2) . "word", getline(1)) + norm! V< + call assert_equal(repeat(" ", 0) . "word", getline(1)) + norm! V3< + call assert_equal(repeat(" ", 0) . "word", getline(1)) + + " Shift by 'tabstop' right and left. + + set shiftwidth=0 + call setline(1, " word") + + norm! V> + call assert_equal(repeat(" ", 9) . "word", getline(1)) + norm! V2> + call assert_equal(repeat(" ", 23) . "word", getline(1)) + norm! V3> + call assert_equal(repeat(" ", 44) . "word", getline(1)) + + norm! V< + call assert_equal(repeat(" ", 37) . "word", getline(1)) + norm! V2< + call assert_equal(repeat(" ", 23) . "word", getline(1)) + norm! V3< + call assert_equal(repeat(" ", 2) . "word", getline(1)) + norm! V< + call assert_equal(repeat(" ", 0) . "word", getline(1)) + norm! V3< + call assert_equal(repeat(" ", 0) . "word", getline(1)) + + set expandtab& shiftwidth& tabstop& + bw! +endfunc + +" Test the V> and V< visual-mode commands, with 'vartabstop'. +" +" See ":help v_<" and ":help v_>". See also the last paragraph of "3. Simple +" changes", ":help simple-change", immediately above "4. Complex changes", +" ":help complex-change". +" +func Test_shift_vis_vartabs() + CheckFeature vartabs + set expandtab " Don't want to worry about tabs vs. spaces in + " results. + + set shiftwidth=0 + set vartabstop=19,17,11 + + " Shift by 'vartabstop' right and left. + + call setline(1, " word") + + norm! V> + call assert_equal(repeat(" ", 21) . "word", getline(1)) + norm! V2> + call assert_equal(repeat(" ", 49) . "word", getline(1)) + norm! V3> + call assert_equal(repeat(" ", 82) . "word", getline(1)) + + norm! V< + call assert_equal(repeat(" ", 71) . "word", getline(1)) + norm! V2< + call assert_equal(repeat(" ", 49) . "word", getline(1)) + norm! V3< + call assert_equal(repeat(" ", 2) . "word", getline(1)) + norm! V< + call assert_equal(repeat(" ", 0) . "word", getline(1)) + norm! V3< + call assert_equal(repeat(" ", 0) . "word", getline(1)) + + set expandtab& shiftwidth& vartabstop& + bw! +endfunc + +" Test the V> and V< visual-mode commands with 'shiftround'. +" +func Test_shift_vis_round() + set expandtab " Don't want to worry about tabs vs. spaces in + " results. + + set shiftround + set shiftwidth=5 + set tabstop=7 + + call setline(1, "word") + + " Shift by 'shiftwidth' right and left. + + exe "norm! I " + norm! V> + call assert_equal(repeat(" ", 5) . "word", getline(1)) + exe "norm! I " + norm! V2> + call assert_equal(repeat(" ", 15) . "word", getline(1)) + exe "norm! I " + norm! V3> + call assert_equal(repeat(" ", 30) . "word", getline(1)) + + exe "norm! I " + norm! V2< + call assert_equal(repeat(" ", 25) . "word", getline(1)) + norm! V3< + call assert_equal(repeat(" ", 10) . "word", getline(1)) + norm! V< + call assert_equal(repeat(" ", 5) . "word", getline(1)) + norm! V< + call assert_equal(repeat(" ", 0) . "word", getline(1)) + norm! V3< + call assert_equal(repeat(" ", 0) . "word", getline(1)) + + " Shift by 'tabstop' right and left. + + set shiftwidth=0 + call setline(1, "word") + + exe "norm! I " + norm! V> + call assert_equal(repeat(" ", 7) . "word", getline(1)) + exe "norm! I " + norm! V2> + call assert_equal(repeat(" ", 21) . "word", getline(1)) + exe "norm! I " + norm! V3> + call assert_equal(repeat(" ", 42) . "word", getline(1)) + + exe "norm! I " + norm! V< + call assert_equal(repeat(" ", 42) . "word", getline(1)) + norm! V< + call assert_equal(repeat(" ", 35) . "word", getline(1)) + norm! V2< + call assert_equal(repeat(" ", 21) . "word", getline(1)) + norm! V3< + call assert_equal(repeat(" ", 0) . "word", getline(1)) + norm! V< + call assert_equal(repeat(" ", 0) . "word", getline(1)) + norm! V3< + call assert_equal(repeat(" ", 0) . "word", getline(1)) + + call setline(1, " word") + norm! V< + call assert_equal(repeat(" ", 0) . "word", getline(1)) + + + set expandtab& shiftround& shiftwidth& tabstop& + bw! +endfunc + +" Test the V> and V< visual-mode commands with 'shiftround' and 'vartabstop'. +" +func Test_shift_vis_round_vartabs() + CheckFeature vartabs + set expandtab " Don't want to worry about tabs vs. spaces in + " results. + + set shiftround + set shiftwidth=0 + set vartabstop=19,17,11 + + " Shift by 'vartabstop' right and left. + + call setline(1, "word") + + exe "norm! I " + norm! V> + call assert_equal(repeat(" ", 19) . "word", getline(1)) + exe "norm! I " + norm! V3> + call assert_equal(repeat(" ", 58) . "word", getline(1)) + + exe "norm! I " + norm! V2< + call assert_equal(repeat(" ", 47) . "word", getline(1)) + exe "norm! I " + norm! V3< + call assert_equal(repeat(" ", 19) . "word", getline(1)) + exe "norm! I " + norm! V3< + call assert_equal(repeat(" ", 0) . "word", getline(1)) + exe "norm! I " + norm! V< + call assert_equal(repeat(" ", 0) . "word", getline(1)) + + set expandtab& shiftround& shiftwidth& vartabstop& + bw! +endfunc + +" Test the :> and :< ex-mode commands. +" +func Test_shift_ex() + set expandtab " Don't want to worry about tabs vs. spaces in + " results. + + set shiftwidth=5 + set tabstop=7 + + call setline(1, " word") + + " Shift by 'shiftwidth' right and left. + + > + call assert_equal(repeat(" ", 7) . "word", getline(1)) + >> + call assert_equal(repeat(" ", 17) . "word", getline(1)) + >>> + call assert_equal(repeat(" ", 32) . "word", getline(1)) + + <<<< + call assert_equal(repeat(" ", 12) . "word", getline(1)) + < + call assert_equal(repeat(" ", 7) . "word", getline(1)) + < + call assert_equal(repeat(" ", 2) . "word", getline(1)) + < + call assert_equal(repeat(" ", 0) . "word", getline(1)) + + " Shift by 'tabstop' right and left. + + set shiftwidth=0 + call setline(1, " word") + + > + call assert_equal(repeat(" ", 9) . "word", getline(1)) + >> + call assert_equal(repeat(" ", 23) . "word", getline(1)) + >>> + call assert_equal(repeat(" ", 44) . "word", getline(1)) + + <<<< + call assert_equal(repeat(" ", 16) . "word", getline(1)) + << + call assert_equal(repeat(" ", 2) . "word", getline(1)) + < + call assert_equal(repeat(" ", 0) . "word", getline(1)) + + set expandtab& shiftwidth& tabstop& + bw! +endfunc + +" Test the :> and :< ex-mode commands, with vartabstop. +" +func Test_shift_ex_vartabs() + CheckFeature vartabs + set expandtab " Don't want to worry about tabs vs. spaces in + " results. + + set shiftwidth=0 + set vartabstop=19,17,11 + + " Shift by 'vartabstop' right and left. + + call setline(1, " word") + + > + call assert_equal(repeat(" ", 21) . "word", getline(1)) + >> + call assert_equal(repeat(" ", 49) . "word", getline(1)) + >>> + call assert_equal(repeat(" ", 82) . "word", getline(1)) + + <<<< + call assert_equal(repeat(" ", 38) . "word", getline(1)) + << + call assert_equal(repeat(" ", 2) . "word", getline(1)) + < + call assert_equal(repeat(" ", 0) . "word", getline(1)) + + set expandtab& shiftwidth& vartabstop& + bw! +endfunc + +" Test the :> and :< ex-mode commands with 'shiftround'. +" +func Test_shift_ex_round() + set expandtab " Don't want to worry about tabs vs. spaces in + " results. + + set shiftround + set shiftwidth=5 + set tabstop=7 + + call setline(1, "word") + + " Shift by 'shiftwidth' right and left. + + exe "norm! I " + > + call assert_equal(repeat(" ", 5) . "word", getline(1)) + exe "norm! I " + >> + call assert_equal(repeat(" ", 15) . "word", getline(1)) + exe "norm! I " + >>> + call assert_equal(repeat(" ", 30) . "word", getline(1)) + + exe "norm! I " + <<<< + call assert_equal(repeat(" ", 15) . "word", getline(1)) + exe "norm! I " + << + call assert_equal(repeat(" ", 10) . "word", getline(1)) + << + call assert_equal(repeat(" ", 0) . "word", getline(1)) + >> + <<< + call assert_equal(repeat(" ", 0) . "word", getline(1)) + + " Shift by 'tabstop' right and left. + + set shiftwidth=0 + call setline(1, "word") + + exe "norm! I " + > + call assert_equal(repeat(" ", 7) . "word", getline(1)) + exe "norm! I " + >> + call assert_equal(repeat(" ", 21) . "word", getline(1)) + exe "norm! I " + >>> + call assert_equal(repeat(" ", 42) . "word", getline(1)) + + exe "norm! I " + <<<< + call assert_equal(repeat(" ", 21) . "word", getline(1)) + exe "norm! I " + << + call assert_equal(repeat(" ", 14) . "word", getline(1)) + exe "norm! I " + <<< + call assert_equal(repeat(" ", 0) . "word", getline(1)) + >> + <<< + call assert_equal(repeat(" ", 0) . "word", getline(1)) + + set expandtab& shiftround& shiftwidth& tabstop& + bw! +endfunc + +" Test the :> and :< ex-mode commands with 'shiftround' and 'vartabstop'. +" +func Test_shift_ex_round_vartabs() + CheckFeature vartabs + set expandtab " Don't want to worry about tabs vs. spaces in + " results. + + set shiftround + set shiftwidth=0 + set vartabstop=19,17,11 + + " Shift by 'vartabstop' right and left. + + call setline(1, "word") + + exe "norm! I " + > + call assert_equal(repeat(" ", 19) . "word", getline(1)) + exe "norm! I " + >> + call assert_equal(repeat(" ", 47) . "word", getline(1)) + >>> + call assert_equal(repeat(" ", 80) . "word", getline(1)) + + <<<< + call assert_equal(repeat(" ", 36) . "word", getline(1)) + exe "norm! I " + << + call assert_equal(repeat(" ", 19) . "word", getline(1)) + exe "norm! I " + << + call assert_equal(repeat(" ", 0) . "word", getline(1)) + < + call assert_equal(repeat(" ", 0) . "word", getline(1)) + + set expandtab& shiftround& shiftwidth& vartabstop& + bw! +endfunc + +" Test shifting lines with <C-T> and <C-D>. +" +func Test_shift_ins() + set expandtab " Don't want to worry about tabs vs. spaces in + " results. + + set shiftwidth=5 + set tabstop=7 + + " Shift by 'shiftwidth' right and left. + + call setline(1, repeat(" ", 7) . "word") + exe "norm! 9|i\<C-T>" + call assert_equal(repeat(" ", 10) . "word", getline(1)) + exe "norm! A\<C-T>" + call assert_equal(repeat(" ", 15) . "word", getline(1)) + exe "norm! I \<C-T>" + call assert_equal(repeat(" ", 20) . "word", getline(1)) + + exe "norm! I \<C-D>" + call assert_equal(repeat(" ", 20) . "word", getline(1)) + exe "norm! I " + exe "norm! 24|i\<C-D>" + call assert_equal(repeat(" ", 20) . "word", getline(1)) + exe "norm! A\<C-D>" + call assert_equal(repeat(" ", 15) . "word", getline(1)) + exe "norm! I " + exe "norm! A\<C-D>\<C-D>" + call assert_equal(repeat(" ", 10) . "word", getline(1)) + exe "norm! I\<C-D>\<C-D>\<C-D>" + call assert_equal(repeat(" ", 0) . "word", getline(1)) + exe "norm! I\<C-D>" + call assert_equal(repeat(" ", 0) . "word", getline(1)) + + " Shift by 'tabstop' right and left. + + set shiftwidth=0 + call setline(1, "word") + + call setline(1, repeat(" ", 9) . "word") + exe "norm! 11|i\<C-T>" + call assert_equal(repeat(" ", 14) . "word", getline(1)) + exe "norm! A\<C-T>" + call assert_equal(repeat(" ", 21) . "word", getline(1)) + exe "norm! I \<C-T>" + call assert_equal(repeat(" ", 28) . "word", getline(1)) + + exe "norm! I \<C-D>" + call assert_equal(repeat(" ", 28) . "word", getline(1)) + exe "norm! I " + exe "norm! 32|i\<C-D>" + call assert_equal(repeat(" ", 28) . "word", getline(1)) + exe "norm! A\<C-D>" + call assert_equal(repeat(" ", 21) . "word", getline(1)) + exe "norm! I " + exe "norm! A\<C-D>\<C-D>" + call assert_equal(repeat(" ", 14) . "word", getline(1)) + exe "norm! I\<C-D>\<C-D>\<C-D>" + call assert_equal(repeat(" ", 0) . "word", getline(1)) + exe "norm! I\<C-D>" + call assert_equal(repeat(" ", 0) . "word", getline(1)) + + set expandtab& shiftwidth& tabstop& + bw! +endfunc + +" Test shifting lines with <C-T> and <C-D>, with 'vartabstop'. +" +func Test_shift_ins_vartabs() + CheckFeature vartabs + set expandtab " Don't want to worry about tabs vs. spaces in + " results. + + set shiftwidth=0 + set vartabstop=19,17,11 + + " Shift by 'vartabstop' right and left. + + call setline(1, "word") + + call setline(1, repeat(" ", 9) . "word") + exe "norm! 11|i\<C-T>" + call assert_equal(repeat(" ", 19) . "word", getline(1)) + exe "norm! A\<C-T>" + call assert_equal(repeat(" ", 36) . "word", getline(1)) + exe "norm! I \<C-T>" + call assert_equal(repeat(" ", 47) . "word", getline(1)) + + exe "norm! I \<C-D>" + call assert_equal(repeat(" ", 47) . "word", getline(1)) + exe "norm! I " + exe "norm! 51|i\<C-D>" + call assert_equal(repeat(" ", 47) . "word", getline(1)) + exe "norm! A\<C-D>" + call assert_equal(repeat(" ", 36) . "word", getline(1)) + exe "norm! I " + exe "norm! A\<C-D>\<C-D>" + call assert_equal(repeat(" ", 19) . "word", getline(1)) + exe "norm! I\<C-D>\<C-D>\<C-D>" + call assert_equal(repeat(" ", 0) . "word", getline(1)) + exe "norm! I\<C-D>" + call assert_equal(repeat(" ", 0) . "word", getline(1)) + + set expandtab& shiftwidth& vartabstop& + bw! endfunc " vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/test_spell.vim b/test/old/testdir/test_spell.vim index bdd8a673fd..a5ae653369 100644 --- a/test/old/testdir/test_spell.vim +++ b/test/old/testdir/test_spell.vim @@ -471,7 +471,9 @@ func Test_spellsuggest_option_number() \ .. "Change \"baord\" to:\n" \ .. " 1 \"board\"\n" \ .. " 2 \"bard\"\n" - \ .. "Type number and <Enter> or click with the mouse (q or empty cancels): ", a) + "\ Nvim: Prompt message is sent to cmdline prompt. + "\ .. "Type number and <Enter> or click with the mouse (q or empty cancels): ", a) + \ , a) set spell spellsuggest=0 call assert_equal("\nSorry, no suggestions", execute('norm $z=')) @@ -509,7 +511,9 @@ func Test_spellsuggest_option_expr() \ .. " 1 \"BARD\"\n" \ .. " 2 \"BOARD\"\n" \ .. " 3 \"BROAD\"\n" - \ .. "Type number and <Enter> or click with the mouse (q or empty cancels): ", a) + "\ Nvim: Prompt message is sent to cmdline prompt. + "\ .. "Type number and <Enter> or click with the mouse (q or empty cancels): ", a) + \ , a) " With verbose, z= should show the score i.e. word length with " our SpellSuggest() function. @@ -521,7 +525,9 @@ func Test_spellsuggest_option_expr() \ .. " 1 \"BARD\" (4 - 0)\n" \ .. " 2 \"BOARD\" (5 - 0)\n" \ .. " 3 \"BROAD\" (5 - 0)\n" - \ .. "Type number and <Enter> or click with the mouse (q or empty cancels): ", a) + "\ Nvim: Prompt message is sent to cmdline prompt. + "\ .. "Type number and <Enter> or click with the mouse (q or empty cancels): ", a) + \ , a) set spell& spellsuggest& verbose& bwipe! diff --git a/test/old/testdir/test_stacktrace.vim b/test/old/testdir/test_stacktrace.vim new file mode 100644 index 0000000000..9e1f51e1f6 --- /dev/null +++ b/test/old/testdir/test_stacktrace.vim @@ -0,0 +1,142 @@ +" Test for getstacktrace() and v:stacktrace + +source vim9.vim + +let s:thisfile = expand('%:p') +let s:testdir = s:thisfile->fnamemodify(':h') + +func Filepath(name) + return s:testdir .. '/' .. a:name +endfunc + +func AssertStacktrace(expect, actual) + call assert_equal(Filepath('runtest.vim'), a:actual[0]['filepath']) + call assert_equal(a:expect, a:actual[-len(a:expect):]) +endfunc + +func Test_getstacktrace() + let g:stacktrace = [] + let lines1 =<< trim [SCRIPT] + " Xscript1 + source Xscript2 + func Xfunc1() + " Xfunc1 + call Xfunc2() + endfunc + [SCRIPT] + let lines2 =<< trim [SCRIPT] + " Xscript2 + func Xfunc2() + " Xfunc2 + let g:stacktrace = getstacktrace() + endfunc + [SCRIPT] + call writefile(lines1, 'Xscript1', 'D') + call writefile(lines2, 'Xscript2', 'D') + source Xscript1 + call Xfunc1() + call AssertStacktrace([ + \ #{funcref: funcref('Test_getstacktrace'), lnum: 37, filepath: s:thisfile}, + \ #{funcref: funcref('Xfunc1'), lnum: 5, filepath: Filepath('Xscript1')}, + \ #{funcref: funcref('Xfunc2'), lnum: 4, filepath: Filepath('Xscript2')}, + \ ], g:stacktrace) + unlet g:stacktrace +endfunc + +func Test_getstacktrace_event() + let g:stacktrace = [] + let lines1 =<< trim [SCRIPT] + " Xscript1 + func Xfunc() + " Xfunc + let g:stacktrace = getstacktrace() + endfunc + augroup test_stacktrace + autocmd SourcePre * call Xfunc() + augroup END + [SCRIPT] + let lines2 =<< trim [SCRIPT] + " Xscript2 + [SCRIPT] + call writefile(lines1, 'Xscript1', 'D') + call writefile(lines2, 'Xscript2', 'D') + source Xscript1 + source Xscript2 + call AssertStacktrace([ + \ #{funcref: funcref('Test_getstacktrace_event'), lnum: 64, filepath: s:thisfile}, + \ #{event: 'SourcePre Autocommands for "*"', lnum: 7, filepath: Filepath('Xscript1')}, + \ #{funcref: funcref('Xfunc'), lnum: 4, filepath: Filepath('Xscript1')}, + \ ], g:stacktrace) + augroup test_stacktrace + autocmd! + augroup END + unlet g:stacktrace +endfunc + +func Test_vstacktrace() + let lines1 =<< trim [SCRIPT] + " Xscript1 + source Xscript2 + func Xfunc1() + " Xfunc1 + call Xfunc2() + endfunc + [SCRIPT] + let lines2 =<< trim [SCRIPT] + " Xscript2 + func Xfunc2() + " Xfunc2 + throw 'Exception from Xfunc2' + endfunc + [SCRIPT] + call writefile(lines1, 'Xscript1', 'D') + call writefile(lines2, 'Xscript2', 'D') + source Xscript1 + call assert_equal([], v:stacktrace) + try + call Xfunc1() + catch + let stacktrace = v:stacktrace + try + call Xfunc1() + catch + let stacktrace_inner = v:stacktrace + endtry + let stacktrace_after = v:stacktrace " should be restored by the exception stack to the previous one + endtry + call assert_equal([], v:stacktrace) + call AssertStacktrace([ + \ #{funcref: funcref('Test_vstacktrace'), lnum: 97, filepath: s:thisfile}, + \ #{funcref: funcref('Xfunc1'), lnum: 5, filepath: Filepath('Xscript1')}, + \ #{funcref: funcref('Xfunc2'), lnum: 4, filepath: Filepath('Xscript2')}, + \ ], stacktrace) + call AssertStacktrace([ + \ #{funcref: funcref('Test_vstacktrace'), lnum: 101, filepath: s:thisfile}, + \ #{funcref: funcref('Xfunc1'), lnum: 5, filepath: Filepath('Xscript1')}, + \ #{funcref: funcref('Xfunc2'), lnum: 4, filepath: Filepath('Xscript2')}, + \ ], stacktrace_inner) + call assert_equal(stacktrace, stacktrace_after) +endfunc + +func Test_stacktrace_vim9() + let lines =<< trim [SCRIPT] + var stacktrace = getstacktrace() + assert_notequal([], stacktrace) + for d in stacktrace + assert_true(has_key(d, 'lnum')) + endfor + try + throw 'Exception from s:Func' + catch + assert_notequal([], v:stacktrace) + assert_equal(len(stacktrace), len(v:stacktrace)) + for d in v:stacktrace + assert_true(has_key(d, 'lnum')) + endfor + endtry + call assert_equal([], v:stacktrace) + [SCRIPT] + call CheckDefSuccess(lines) +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/test_statusline.vim b/test/old/testdir/test_statusline.vim index c8162ced07..c9f79dfef7 100644 --- a/test/old/testdir/test_statusline.vim +++ b/test/old/testdir/test_statusline.vim @@ -220,6 +220,10 @@ func Test_statusline() wincmd j call assert_match('^\[Preview\],PRV\s*$', s:get_statusline()) pclose + pbuffer + wincmd j + call assert_match('^\[Preview\],PRV\s*$', s:get_statusline()) + pclose " %y: Type of file in the buffer, e.g., "[vim]". See 'filetype'. " %Y: Type of file in the buffer, e.g., ",VIM". See 'filetype'. diff --git a/test/old/testdir/test_tagjump.vim b/test/old/testdir/test_tagjump.vim index 470c5c43b4..efc5e4cebe 100644 --- a/test/old/testdir/test_tagjump.vim +++ b/test/old/testdir/test_tagjump.vim @@ -1231,8 +1231,10 @@ func Test_tselect_listing() 2 FS v first Xfoo typeref:typename:char 2 -Type number and <Enter> (q or empty cancels): [DATA] +" Type number and <Enter> (q or empty cancels): +" Nvim: Prompt message is sent to cmdline prompt. + call assert_equal(expected, l) call delete('Xtags') diff --git a/test/old/testdir/test_termdebug.vim b/test/old/testdir/test_termdebug.vim index eb88ea6f5f..6ff233fff0 100644 --- a/test/old/testdir/test_termdebug.vim +++ b/test/old/testdir/test_termdebug.vim @@ -391,7 +391,8 @@ endfunc function Test_termdebug_save_restore_variables() " saved mousemodel - let &mousemodel='' + "let &mousemodel='' + let &mousemodel='extend' " saved keys nnoremap K :echo "hello world!"<cr> @@ -414,7 +415,8 @@ function Test_termdebug_save_restore_variables() quit! call WaitForAssert({-> assert_equal(1, winnr('$'))}) - call assert_true(empty(&mousemodel)) + "call assert_true(empty(&mousemodel)) + call assert_equal(&mousemodel, 'extend') call assert_true(empty(expected_map_minus)) call assert_equal(expected_map_K.rhs, maparg('K', 'n', 0, 1).rhs) diff --git a/test/old/testdir/test_user_func.vim b/test/old/testdir/test_user_func.vim index 3c24412eb7..b1543c8f24 100644 --- a/test/old/testdir/test_user_func.vim +++ b/test/old/testdir/test_user_func.vim @@ -380,7 +380,7 @@ func Test_script_local_func() " Try to call a script local function in global scope let lines =<< trim [CODE] :call assert_fails('call s:Xfunc()', 'E81:') - :call assert_fails('let x = call("<SID>Xfunc", [])', 'E120:') + :call assert_fails('let x = call("<SID>Xfunc", [])', ['E81:', 'E117:']) :call writefile(v:errors, 'Xresult') :qall @@ -421,12 +421,48 @@ func Test_func_def_error() call assert_fails('exe l', 'E717:') " Define an autoload function with an incorrect file name - call writefile(['func foo#Bar()', 'return 1', 'endfunc'], 'Xscript') + call writefile(['func foo#Bar()', 'return 1', 'endfunc'], 'Xscript', 'D') call assert_fails('source Xscript', 'E746:') - call delete('Xscript') " Try to list functions using an invalid search pattern call assert_fails('function /\%(/', 'E53:') + + " Use a script-local function to cover uf_name_exp. + func s:TestRedefine(arg1 = 1, arg2 = 10) + let caught_E122 = 0 + try + func s:TestRedefine(arg1 = 1, arg2 = 10) + endfunc + catch /E122:/ + let caught_E122 = 1 + endtry + call assert_equal(1, caught_E122) + + let caught_E127 = 0 + try + func! s:TestRedefine(arg1 = 1, arg2 = 10) + endfunc + catch /E127:/ + let caught_E127 = 1 + endtry + call assert_equal(1, caught_E127) + + " The failures above shouldn't cause heap-use-after-free here. + return [a:arg1 + a:arg2, expand('<stack>')] + endfunc + + let stacks = [] + " Call the function twice. + " Failing to redefine a function shouldn't clear its argument list. + for i in range(2) + let [val, stack] = s:TestRedefine(1000) + call assert_equal(1010, val) + call assert_match(expand('<SID>') .. 'TestRedefine\[20\]$', stack) + call add(stacks, stack) + endfor + call assert_equal(stacks[0], stacks[1]) + + delfunc s:TestRedefine endfunc " Test for deleting a function @@ -910,4 +946,36 @@ func Test_func_curly_brace_invalid_name() delfunc Fail endfunc +func Test_func_return_in_try_verbose() + func TryReturnList() + try + return [1, 2, 3] + endtry + endfunc + func TryReturnNumber() + try + return 123 + endtry + endfunc + func TryReturnOverlongString() + try + return repeat('a', 9999) + endtry + endfunc + + " This should not cause heap-use-after-free + call assert_match('\n:return \[1, 2, 3\] made pending\n', + \ execute('14verbose call TryReturnList()')) + " This should not cause stack-use-after-scope + call assert_match('\n:return 123 made pending\n', + \ execute('14verbose call TryReturnNumber()')) + " An overlong string is truncated + call assert_match('\n:return a\{100,}\.\.\.', + \ execute('14verbose call TryReturnOverlongString()')) + + delfunc TryReturnList + delfunc TryReturnNumber + delfunc TryReturnOverlongString +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/test_visual.vim b/test/old/testdir/test_visual.vim index e25327ddd4..328ac502bf 100644 --- a/test/old/testdir/test_visual.vim +++ b/test/old/testdir/test_visual.vim @@ -470,7 +470,7 @@ func Test_Visual_Block() \ "\t{", \ "\t}"], getline(1, '$')) - close! + bw! endfunc " Test for 'p'ut in visual block mode @@ -1082,7 +1082,7 @@ func Test_star_register() delmarks < > call assert_fails('*yank', 'E20:') - close! + bw! endfunc " Test for changing text in visual mode with 'exclusive' selection @@ -1098,7 +1098,7 @@ func Test_exclusive_selection() call assert_equal('l one', getline(1)) set virtualedit& set selection& - close! + bw! endfunc " Test for starting linewise visual with a count. @@ -1155,7 +1155,7 @@ func Test_visual_inner_block() 8,9d call cursor(5, 1) call assert_beeps('normal ViBiB') - close! + bw! endfunc func Test_visual_put_in_block() @@ -2718,4 +2718,68 @@ func Test_visual_block_cursor_insert_enter() bwipe! endfunc +func Test_visual_block_exclusive_selection() + new + set selection=exclusive + call setline(1, ['asöd asdf', 'asdf asdf', 'as€d asdf', 'asdf asdf']) + call cursor(1, 1) + exe ":norm! \<c-v>eh3j~" + call assert_equal(['ASÖd asdf', 'ASDf asdf', 'AS€d asdf', 'ASDf asdf'], getline(1, '$')) + exe ":norm! 1v~" + call assert_equal(['asöd asdf', 'asdf asdf', 'as€d asdf', 'asdf asdf'], getline(1, '$')) + bwipe! + set selection&vim +endfunc + +func Test_visual_block_exclusive_selection_adjusted() + new + " Test that the end-position of the visual selection is adjusted for exclusive selection + set selection=exclusive + call setline(1, ['asöd asdf ', 'asdf asdf ', 'as€d asdf ', 'asdf asdf ']) + call cursor(1, 1) + " inclusive motion + exe ":norm! \<c-v>e3jy" + call assert_equal([0, 4, 5, 0], getpos("'>")) + " exclusive motion + exe ":norm! \<c-v>ta3jy" + call assert_equal([0, 4, 6, 0], getpos("'>")) + " another inclusive motion + exe ":norm! \<c-v>g_3jy" + call assert_equal([0, 4, 10, 0], getpos("'>")) + + " Reset selection option to Vim default + set selection&vim + call cursor(1, 1) + + " inclusive motion + exe ":norm! \<c-v>e3jy" + call assert_equal([0, 4, 4, 0], getpos("'>")) + " exclusive motion + exe ":norm! \<c-v>ta3jy" + call assert_equal([0, 4, 5, 0], getpos("'>")) + " another inclusive motion + exe ":norm! \<c-v>g_3jy" + call assert_equal([0, 4, 9, 0], getpos("'>")) + bwipe! + set selection&vim +endfunc + +" the following caused a Heap-Overflow, because Vim was accessing outside of a +" line end +func Test_visual_pos_buffer_heap_overflow() + set virtualedit=all + args Xa Xb + all + call setline(1, ['', '', '']) + call cursor(3, 1) + wincmd w + call setline(1, 'foobar') + normal! $lv0 + all + call setreg('"', 'baz') + normal! [P + set virtualedit= + bw! Xa Xb +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/test_window_cmd.vim b/test/old/testdir/test_window_cmd.vim index 8048fa6ff8..24517f90cb 100644 --- a/test/old/testdir/test_window_cmd.vim +++ b/test/old/testdir/test_window_cmd.vim @@ -55,6 +55,26 @@ func Test_window_cmd_cmdwin_with_vsp() set ls&vim endfunc +func Test_cmdheight_not_changed() + set cmdheight=2 + set winminheight=0 + augroup Maximize + autocmd WinEnter * wincmd _ + augroup END + split + tabnew + tabfirst + call assert_equal(2, &cmdheight) + + tabonly! + only + set winminheight& cmdheight& + augroup Maximize + au! + augroup END + augroup! Maximize +endfunc + " Test for jumping to windows func Test_window_jump() new @@ -1164,20 +1184,20 @@ func Run_noroom_for_newwindow_test(dir_arg) let dir = (a:dir_arg == 'v') ? 'vert ' : '' " Open as many windows as possible - for i in range(500) + while v:true try exe dir . 'new' catch /E36:/ break endtry - endfor + endwhile - call writefile(['first', 'second', 'third'], 'Xfile1') - call writefile([], 'Xfile2') - call writefile([], 'Xfile3') + call writefile(['first', 'second', 'third'], 'Xnorfile1') + call writefile([], 'Xnorfile2') + call writefile([], 'Xnorfile3') " Argument list related commands - args Xfile1 Xfile2 Xfile3 + args Xnorfile1 Xnorfile2 Xnorfile3 next for cmd in ['sargument 2', 'snext', 'sprevious', 'sNext', 'srewind', \ 'sfirst', 'slast'] @@ -1188,13 +1208,13 @@ func Run_noroom_for_newwindow_test(dir_arg) " Buffer related commands set modified hide enew - for cmd in ['sbuffer Xfile1', 'sbnext', 'sbprevious', 'sbNext', 'sbrewind', + for cmd in ['sbuffer Xnorfile1', 'sbnext', 'sbprevious', 'sbNext', 'sbrewind', \ 'sbfirst', 'sblast', 'sball', 'sbmodified', 'sunhide'] call assert_fails(dir .. cmd, 'E36:') endfor " Window related commands - for cmd in ['split', 'split Xfile2', 'new', 'new Xfile3', 'sview Xfile1', + for cmd in ['split', 'split Xnorfile2', 'new', 'new Xnorfile3', 'sview Xnorfile1', \ 'sfind runtest.vim'] call assert_fails(dir .. cmd, 'E36:') endfor @@ -1217,7 +1237,8 @@ func Run_noroom_for_newwindow_test(dir_arg) call assert_fails(dir .. 'lopen', 'E36:') " Preview window - call assert_fails(dir .. 'pedit Xfile2', 'E36:') + call assert_fails(dir .. 'pedit Xnorfile2', 'E36:') + call assert_fails(dir .. 'pbuffer', 'E36:') call setline(1, 'abc') call assert_fails(dir .. 'psearch abc', 'E36:') endif @@ -1225,15 +1246,15 @@ func Run_noroom_for_newwindow_test(dir_arg) " Window commands (CTRL-W ^ and CTRL-W f) if a:dir_arg == 'h' call assert_fails('call feedkeys("\<C-W>^", "xt")', 'E36:') - call setline(1, 'Xfile1') + call setline(1, 'Xnorfile1') call assert_fails('call feedkeys("gg\<C-W>f", "xt")', 'E36:') endif enew! " Tag commands (:stag, :stselect and :stjump) call writefile(["!_TAG_FILE_ENCODING\tutf-8\t//", - \ "second\tXfile1\t2", - \ "third\tXfile1\t3",], + \ "second\tXnorfile1\t2", + \ "third\tXnorfile1\t3",], \ 'Xtags') set tags=Xtags call assert_fails(dir .. 'stag second', 'E36:') @@ -1255,9 +1276,9 @@ func Run_noroom_for_newwindow_test(dir_arg) endif %bwipe! - call delete('Xfile1') - call delete('Xfile2') - call delete('Xfile3') + call delete('Xnorfile1') + call delete('Xnorfile2') + call delete('Xnorfile3') only endfunc diff --git a/test/old/testdir/test_winfixbuf.vim b/test/old/testdir/test_winfixbuf.vim index 1777bec184..f7986fdda3 100644 --- a/test/old/testdir/test_winfixbuf.vim +++ b/test/old/testdir/test_winfixbuf.vim @@ -2545,6 +2545,18 @@ func Test_pedit() call assert_equal(l:other, bufnr()) endfunc +" Allow :pbuffer because, unlike :buffer, it uses a separate window +func Test_pbuffer() + call s:reset_all_buffers() + + let l:other = s:make_buffer_pairs() + + exe 'pbuffer ' . l:other + + execute "normal \<C-w>w" + call assert_equal(l:other, bufnr()) +endfunc + " Fail :pop but :pop! is allowed func Test_pop() call s:reset_all_buffers() @@ -2613,7 +2625,7 @@ EOF try pyxdo test_winfixbuf_Test_pythonx_pyxdo_set_buffer() - catch /pynvim\.api\.common\.NvimError: E1513:/ + catch /pynvim\.api\.common\.NvimError: Vim:E1513:/ let l:caught = 1 endtry @@ -2644,7 +2656,7 @@ func Test_pythonx_pyxfile() try pyxfile file.py - catch /pynvim\.api\.common\.NvimError: E1513:/ + catch /pynvim\.api\.common\.NvimError: Vim:E1513:/ let l:caught = 1 endtry @@ -2676,7 +2688,7 @@ import vim buffer = vim.vars["_previous_buffer"] vim.current.buffer = vim.buffers[buffer] EOF - catch /pynvim\.api\.common\.NvimError: E1513:/ + catch /pynvim\.api\.common\.NvimError: Vim:E1513:/ let l:caught = 1 endtry diff --git a/test/testutil.lua b/test/testutil.lua index 00b30d74d5..e69dcae120 100644 --- a/test/testutil.lua +++ b/test/testutil.lua @@ -22,13 +22,6 @@ local M = { paths = Paths, } ---- @param p string ---- @return string -local function relpath(p) - p = vim.fs.normalize(p) - return (p:gsub('^' .. uv.cwd, '')) -end - --- @param path string --- @return boolean function M.isdir(path) @@ -45,14 +38,15 @@ end --- (Only on Windows) Replaces yucky "\\" slashes with delicious "/" slashes in a string, or all --- string values in a table (recursively). --- ---- @param obj string|table ---- @return any +--- @generic T: string|table +--- @param obj T +--- @return T|nil function M.fix_slashes(obj) if not M.is_os('win') then return obj end if type(obj) == 'string' then - local ret = obj:gsub('\\', '/') + local ret = string.gsub(obj, '\\', '/') return ret elseif type(obj) == 'table' then --- @cast obj table<any,any> @@ -394,21 +388,35 @@ end local sysname = uv.os_uname().sysname:lower() ---- @param s 'win'|'mac'|'freebsd'|'openbsd'|'bsd' +--- @param s 'win'|'mac'|'linux'|'freebsd'|'openbsd'|'bsd' --- @return boolean function M.is_os(s) - if not (s == 'win' or s == 'mac' or s == 'freebsd' or s == 'openbsd' or s == 'bsd') then + if + not (s == 'win' or s == 'mac' or s == 'linux' or s == 'freebsd' or s == 'openbsd' or s == 'bsd') + then error('unknown platform: ' .. tostring(s)) end return not not ( (s == 'win' and (sysname:find('windows') or sysname:find('mingw'))) or (s == 'mac' and sysname == 'darwin') + or (s == 'linux' and sysname == 'linux') or (s == 'freebsd' and sysname == 'freebsd') or (s == 'openbsd' and sysname == 'openbsd') or (s == 'bsd' and sysname:find('bsd')) ) end +local architecture = uv.os_uname().machine + +--- @param s 'x86_64'|'arm64' +--- @return boolean +function M.is_arch(s) + if not (s == 'x86_64' or s == 'arm64') then + error('unknown architecture: ' .. tostring(s)) + end + return s == architecture +end + local tmpname_id = 0 local tmpdir = os.getenv('TMPDIR') or os.getenv('TEMP') local tmpdir_is_local = not not (tmpdir and tmpdir:find('Xtest')) @@ -471,7 +479,8 @@ function M.check_cores(app, force) -- luacheck: ignore -- "./Xtest-tmpdir/" => "Xtest%-tmpdir" local local_tmpdir = nil if tmpdir_is_local and tmpdir then - local_tmpdir = vim.pesc(relpath(tmpdir):gsub('^[ ./]+', ''):gsub('%/+$', '')) + local_tmpdir = + vim.pesc(vim.fs.relpath(assert(vim.uv.cwd()), tmpdir):gsub('^[ ./]+', ''):gsub('%/+$', '')) end local db_cmd --- @type string diff --git a/test/unit/fixtures/multiqueue.c b/test/unit/fixtures/multiqueue.c index 149daca893..2003bc7a5a 100644 --- a/test/unit/fixtures/multiqueue.c +++ b/test/unit/fixtures/multiqueue.c @@ -1,8 +1,9 @@ -#include <string.h> #include <stdlib.h> -#include "nvim/event/multiqueue.h" +#include <string.h> + #include "multiqueue.h" +#include "nvim/event/multiqueue.h" void ut_multiqueue_put(MultiQueue *self, const char *str) { diff --git a/test/unit/fixtures/multiqueue.h b/test/unit/fixtures/multiqueue.h index 78a3a89063..37da1d4db9 100644 --- a/test/unit/fixtures/multiqueue.h +++ b/test/unit/fixtures/multiqueue.h @@ -1,4 +1,4 @@ #include "nvim/event/multiqueue.h" -void ut_multiqueue_put(MultiQueue *queue, const char *str); -const char *ut_multiqueue_get(MultiQueue *queue); +void ut_multiqueue_put(MultiQueue *self, const char *str); +const char *ut_multiqueue_get(MultiQueue *self); diff --git a/test/unit/fixtures/posix.h b/test/unit/fixtures/posix.h index f6f24cd9dc..0d16f8aac9 100644 --- a/test/unit/fixtures/posix.h +++ b/test/unit/fixtures/posix.h @@ -1,8 +1,8 @@ -#include <unistd.h> -#include <string.h> #include <errno.h> -#include <sys/wait.h> #include <stdlib.h> +#include <string.h> +#include <sys/wait.h> +#include <unistd.h> enum { kPOSIXErrnoEINTR = EINTR, diff --git a/test/unit/fixtures/vterm_test.c b/test/unit/fixtures/vterm_test.c new file mode 100644 index 0000000000..6744305960 --- /dev/null +++ b/test/unit/fixtures/vterm_test.c @@ -0,0 +1,789 @@ +#include <stdio.h> +#include <string.h> + +#include "nvim/grid.h" +#include "nvim/mbyte.h" +#include "nvim/vterm/pen.h" +#include "nvim/vterm/screen.h" +#include "nvim/vterm/vterm_internal_defs.h" +#include "vterm_test.h" + +int parser_text(const char bytes[], size_t len, void *user) +{ + FILE *f = fopen(VTERM_TEST_FILE, "a"); + fprintf(f, "text "); + size_t i; + for (i = 0; i < len; i++) { + unsigned char b = (unsigned char)bytes[i]; + if (b < 0x20 || b == 0x7f || (b >= 0x80 && b < 0xa0)) { + break; + } + fprintf(f, i ? ",%x" : "%x", b); + } + fprintf(f, "\n"); + fclose(f); + + return (int)i; +} + +static void printchars(const char *s, size_t len, FILE *f) +{ + while (len--) { + fprintf(f, "%c", (s++)[0]); + } +} + +int parser_csi(const char *leader, const long args[], int argcount, const char *intermed, + char command, void *user) +{ + FILE *f = fopen(VTERM_TEST_FILE, "a"); + fprintf(f, "csi %02x", command); + + if (leader && leader[0]) { + fprintf(f, " L="); + for (int i = 0; leader[i]; i++) { + fprintf(f, "%02x", leader[i]); + } + } + + for (int i = 0; i < argcount; i++) { + char sep = i ? ',' : ' '; + + if (args[i] == CSI_ARG_MISSING) { + fprintf(f, "%c*", sep); + } else { + fprintf(f, "%c%ld%s", sep, CSI_ARG(args[i]), CSI_ARG_HAS_MORE(args[i]) ? "+" : ""); + } + } + + if (intermed && intermed[0]) { + fprintf(f, " I="); + for (int i = 0; intermed[i]; i++) { + fprintf(f, "%02x", intermed[i]); + } + } + + fprintf(f, "\n"); + + fclose(f); + + return 1; +} + +int parser_osc(int command, VTermStringFragment frag, void *user) +{ + FILE *f = fopen(VTERM_TEST_FILE, "a"); + fprintf(f, "osc "); + + if (frag.initial) { + if (command == -1) { + fprintf(f, "["); + } else { + fprintf(f, "[%d;", command); + } + } + + printchars(frag.str, frag.len, f); + + if (frag.final) { + fprintf(f, "]"); + } + + fprintf(f, "\n"); + fclose(f); + + return 1; +} + +int parser_dcs(const char *command, size_t commandlen, VTermStringFragment frag, void *user) +{ + FILE *f = fopen(VTERM_TEST_FILE, "a"); + fprintf(f, "dcs "); + + if (frag.initial) { + fprintf(f, "["); + for (size_t i = 0; i < commandlen; i++) { + fprintf(f, "%c", command[i]); + } + } + + printchars(frag.str, frag.len, f); + + if (frag.final) { + fprintf(f, "]"); + } + + fprintf(f, "\n"); + fclose(f); + + return 1; +} + +int parser_apc(VTermStringFragment frag, void *user) +{ + FILE *f = fopen(VTERM_TEST_FILE, "a"); + fprintf(f, "apc "); + + if (frag.initial) { + fprintf(f, "["); + } + + printchars(frag.str, frag.len, f); + + if (frag.final) { + fprintf(f, "]"); + } + + fprintf(f, "\n"); + fclose(f); + + return 1; +} + +int parser_pm(VTermStringFragment frag, void *user) +{ + FILE *f = fopen(VTERM_TEST_FILE, "a"); + fprintf(f, "pm "); + + if (frag.initial) { + fprintf(f, "["); + } + + printchars(frag.str, frag.len, f); + + if (frag.final) { + fprintf(f, "]"); + } + + fprintf(f, "\n"); + fclose(f); + + return 1; +} + +int parser_sos(VTermStringFragment frag, void *user) +{ + FILE *f = fopen(VTERM_TEST_FILE, "a"); + fprintf(f, "sos "); + + if (frag.initial) { + fprintf(f, "["); + } + + printchars(frag.str, frag.len, f); + + if (frag.final) { + fprintf(f, "]"); + } + + fprintf(f, "\n"); + fclose(f); + + return 1; +} + +int selection_set(VTermSelectionMask mask, VTermStringFragment frag, void *user) +{ + FILE *f = fopen(VTERM_TEST_FILE, "a"); + fprintf(f, "selection-set mask=%04X ", mask); + if (frag.initial) { + fprintf(f, "["); + } + printchars(frag.str, frag.len, f); + if (frag.final) { + fprintf(f, "]"); + } + fprintf(f, "\n"); + + fclose(f); + return 1; +} + +int selection_query(VTermSelectionMask mask, void *user) +{ + FILE *f = fopen(VTERM_TEST_FILE, "a"); + fprintf(f, "selection-query mask=%04X\n", mask); + + fclose(f); + return 1; +} + +static void print_schar(FILE *f, schar_T schar) +{ + char buf[MAX_SCHAR_SIZE]; + schar_get(buf, schar); + StrCharInfo ci = utf_ptr2StrCharInfo(buf); + bool did = false; + while (*ci.ptr != 0) { + if (did) { + fprintf(f, ","); + } + + if (ci.chr.len == 1 && ci.chr.value >= 0x80) { + fprintf(f, "??%x", ci.chr.value); + } else { + fprintf(f, "%x", ci.chr.value); + } + did = true; + ci = utf_ptr2StrCharInfo(ci.ptr + ci.chr.len); + } +} + +bool want_state_putglyph; +int state_putglyph(VTermGlyphInfo *info, VTermPos pos, void *user) +{ + if (!want_state_putglyph) { + return 1; + } + + FILE *f = fopen(VTERM_TEST_FILE, "a"); + fprintf(f, "putglyph "); + print_schar(f, info->schar); + fprintf(f, " %d %d,%d", info->width, pos.row, pos.col); + if (info->protected_cell) { + fprintf(f, " prot"); + } + if (info->dwl) { + fprintf(f, " dwl"); + } + if (info->dhl) { + fprintf(f, " dhl-%s", info->dhl == 1 ? "top" : info->dhl == 2 ? "bottom" : "?"); + } + fprintf(f, "\n"); + + fclose(f); + + return 1; +} + +bool want_state_movecursor; +VTermPos state_pos; +int state_movecursor(VTermPos pos, VTermPos oldpos, int visible, void *user) +{ + FILE *f = fopen(VTERM_TEST_FILE, "a"); + state_pos = pos; + + if (want_state_movecursor) { + fprintf(f, "movecursor %d,%d\n", pos.row, pos.col); + } + + fclose(f); + return 1; +} + +bool want_state_scrollrect; +int state_scrollrect(VTermRect rect, int downward, int rightward, void *user) +{ + if (!want_state_scrollrect) { + return 0; + } + + FILE *f = fopen(VTERM_TEST_FILE, "a"); + + fprintf(f, "scrollrect %d..%d,%d..%d => %+d,%+d\n", + rect.start_row, rect.end_row, rect.start_col, rect.end_col, + downward, rightward); + + fclose(f); + return 1; +} + +bool want_state_moverect; +int state_moverect(VTermRect dest, VTermRect src, void *user) +{ + if (!want_state_moverect) { + return 0; + } + + FILE *f = fopen(VTERM_TEST_FILE, "a"); + fprintf(f, "moverect %d..%d,%d..%d -> %d..%d,%d..%d\n", + src.start_row, src.end_row, src.start_col, src.end_col, + dest.start_row, dest.end_row, dest.start_col, dest.end_col); + + fclose(f); + return 1; +} + +void print_color(const VTermColor *col) +{ + FILE *f = fopen(VTERM_TEST_FILE, "a"); + if (VTERM_COLOR_IS_RGB(col)) { + fprintf(f, "rgb(%d,%d,%d", col->rgb.red, col->rgb.green, col->rgb.blue); + } else if (VTERM_COLOR_IS_INDEXED(col)) { + fprintf(f, "idx(%d", col->indexed.idx); + } else { + fprintf(f, "invalid(%d", col->type); + } + if (VTERM_COLOR_IS_DEFAULT_FG(col)) { + fprintf(f, ",is_default_fg"); + } + if (VTERM_COLOR_IS_DEFAULT_BG(col)) { + fprintf(f, ",is_default_bg"); + } + fprintf(f, ")"); + fclose(f); +} + +static VTermValueType vterm_get_prop_type(VTermProp prop) +{ + switch (prop) { + case VTERM_PROP_CURSORVISIBLE: + return VTERM_VALUETYPE_BOOL; + case VTERM_PROP_CURSORBLINK: + return VTERM_VALUETYPE_BOOL; + case VTERM_PROP_ALTSCREEN: + return VTERM_VALUETYPE_BOOL; + case VTERM_PROP_TITLE: + return VTERM_VALUETYPE_STRING; + case VTERM_PROP_ICONNAME: + return VTERM_VALUETYPE_STRING; + case VTERM_PROP_REVERSE: + return VTERM_VALUETYPE_BOOL; + case VTERM_PROP_CURSORSHAPE: + return VTERM_VALUETYPE_INT; + case VTERM_PROP_MOUSE: + return VTERM_VALUETYPE_INT; + case VTERM_PROP_FOCUSREPORT: + return VTERM_VALUETYPE_BOOL; + case VTERM_PROP_THEMEUPDATES: + return VTERM_VALUETYPE_BOOL; + + case VTERM_N_PROPS: + return 0; + } + return 0; // UNREACHABLE +} + +bool want_state_settermprop; +int state_settermprop(VTermProp prop, VTermValue *val, void *user) +{ + if (!want_state_settermprop) { + return 1; + } + + int errcode = 0; + FILE *f = fopen(VTERM_TEST_FILE, "a"); + + VTermValueType type = vterm_get_prop_type(prop); + switch (type) { + case VTERM_VALUETYPE_BOOL: + fprintf(f, "settermprop %d %s\n", prop, val->boolean ? "true" : "false"); + errcode = 1; + goto end; + case VTERM_VALUETYPE_INT: + fprintf(f, "settermprop %d %d\n", prop, val->number); + errcode = 1; + goto end; + case VTERM_VALUETYPE_STRING: + fprintf(f, "settermprop %d %s\"%.*s\"%s\n", prop, + val->string.initial ? "[" : "", (int)val->string.len, val->string.str, + val->string.final ? "]" : ""); + errcode = 0; + goto end; + case VTERM_VALUETYPE_COLOR: + fprintf(f, "settermprop %d ", prop); + print_color(&val->color); + fprintf(f, "\n"); + errcode = 1; + goto end; + case VTERM_N_VALUETYPES: + goto end; + } + +end: + fclose(f); + return errcode; +} + +bool want_state_erase; +int state_erase(VTermRect rect, int selective, void *user) +{ + if (!want_state_erase) { + return 1; + } + + FILE *f = fopen(VTERM_TEST_FILE, "a"); + + fprintf(f, "erase %d..%d,%d..%d%s\n", + rect.start_row, rect.end_row, rect.start_col, rect.end_col, + selective ? " selective" : ""); + + fclose(f); + return 1; +} + +struct { + int bold; + int underline; + int italic; + int blink; + int reverse; + int conceal; + int strike; + int font; + int small; + int baseline; + VTermColor foreground; + VTermColor background; +} state_pen; + +int state_setpenattr(VTermAttr attr, VTermValue *val, void *user) +{ + switch (attr) { + case VTERM_ATTR_BOLD: + state_pen.bold = val->boolean; + break; + case VTERM_ATTR_UNDERLINE: + state_pen.underline = val->number; + break; + case VTERM_ATTR_ITALIC: + state_pen.italic = val->boolean; + break; + case VTERM_ATTR_BLINK: + state_pen.blink = val->boolean; + break; + case VTERM_ATTR_REVERSE: + state_pen.reverse = val->boolean; + break; + case VTERM_ATTR_CONCEAL: + state_pen.conceal = val->boolean; + break; + case VTERM_ATTR_STRIKE: + state_pen.strike = val->boolean; + break; + case VTERM_ATTR_FONT: + state_pen.font = val->number; + break; + case VTERM_ATTR_SMALL: + state_pen.small = val->boolean; + break; + case VTERM_ATTR_BASELINE: + state_pen.baseline = val->number; + break; + case VTERM_ATTR_FOREGROUND: + state_pen.foreground = val->color; + break; + case VTERM_ATTR_BACKGROUND: + state_pen.background = val->color; + break; + + case VTERM_N_ATTRS: + return 0; + default: + break; + } + + return 1; +} + +bool want_state_scrollback; +int state_sb_clear(void *user) +{ + if (!want_state_scrollback) { + return 1; + } + + FILE *f = fopen(VTERM_TEST_FILE, "a"); + fprintf(f, "sb_clear\n"); + fclose(f); + + return 0; +} + +bool want_screen_scrollback; +int screen_sb_pushline(int cols, const VTermScreenCell *cells, void *user) +{ + if (!want_screen_scrollback) { + return 1; + } + + int eol = cols; + while (eol && !cells[eol - 1].schar) { + eol--; + } + + FILE *f = fopen(VTERM_TEST_FILE, "a"); + fprintf(f, "sb_pushline %d =", cols); + for (int c = 0; c < eol; c++) { + fprintf(f, " "); + print_schar(f, cells[c].schar); + } + fprintf(f, "\n"); + + fclose(f); + + return 1; +} + +int screen_sb_popline(int cols, VTermScreenCell *cells, void *user) +{ + if (!want_screen_scrollback) { + return 0; + } + + // All lines of scrollback contain "ABCDE" + for (int col = 0; col < cols; col++) { + if (col < 5) { + cells[col].schar = schar_from_ascii((uint32_t)('A' + col)); + } else { + cells[col].schar = 0; + } + + cells[col].width = 1; + } + + FILE *f = fopen(VTERM_TEST_FILE, "a"); + fprintf(f, "sb_popline %d\n", cols); + fclose(f); + return 1; +} + +int screen_sb_clear(void *user) +{ + if (!want_screen_scrollback) { + return 1; + } + + FILE *f = fopen(VTERM_TEST_FILE, "a"); + fprintf(f, "sb_clear\n"); + fclose(f); + return 0; +} + +void term_output(const char *s, size_t len, void *user) +{ + FILE *f = fopen(VTERM_TEST_FILE, "a"); + fprintf(f, "output "); + for (size_t i = 0; i < len; i++) { + fprintf(f, "%x%s", (unsigned char)s[i], i < len - 1 ? "," : "\n"); + } + fclose(f); +} + +int vterm_state_get_penattr(const VTermState *state, VTermAttr attr, VTermValue *val) +{ + switch (attr) { + case VTERM_ATTR_BOLD: + val->boolean = state->pen.bold; + return 1; + + case VTERM_ATTR_UNDERLINE: + val->number = state->pen.underline; + return 1; + + case VTERM_ATTR_ITALIC: + val->boolean = state->pen.italic; + return 1; + + case VTERM_ATTR_BLINK: + val->boolean = state->pen.blink; + return 1; + + case VTERM_ATTR_REVERSE: + val->boolean = state->pen.reverse; + return 1; + + case VTERM_ATTR_CONCEAL: + val->boolean = state->pen.conceal; + return 1; + + case VTERM_ATTR_STRIKE: + val->boolean = state->pen.strike; + return 1; + + case VTERM_ATTR_FONT: + val->number = state->pen.font; + return 1; + + case VTERM_ATTR_FOREGROUND: + val->color = state->pen.fg; + return 1; + + case VTERM_ATTR_BACKGROUND: + val->color = state->pen.bg; + return 1; + + case VTERM_ATTR_SMALL: + val->boolean = state->pen.small; + return 1; + + case VTERM_ATTR_BASELINE: + val->number = state->pen.baseline; + return 1; + + case VTERM_ATTR_URI: + val->number = state->pen.uri; + return 1; + + case VTERM_N_ATTRS: + return 0; + } + + return 0; +} + +static int attrs_differ(VTermAttrMask attrs, ScreenCell *a, ScreenCell *b) +{ + if ((attrs & VTERM_ATTR_BOLD_MASK) && (a->pen.bold != b->pen.bold)) { + return 1; + } + if ((attrs & VTERM_ATTR_UNDERLINE_MASK) && (a->pen.underline != b->pen.underline)) { + return 1; + } + if ((attrs & VTERM_ATTR_ITALIC_MASK) && (a->pen.italic != b->pen.italic)) { + return 1; + } + if ((attrs & VTERM_ATTR_BLINK_MASK) && (a->pen.blink != b->pen.blink)) { + return 1; + } + if ((attrs & VTERM_ATTR_REVERSE_MASK) && (a->pen.reverse != b->pen.reverse)) { + return 1; + } + if ((attrs & VTERM_ATTR_CONCEAL_MASK) && (a->pen.conceal != b->pen.conceal)) { + return 1; + } + if ((attrs & VTERM_ATTR_STRIKE_MASK) && (a->pen.strike != b->pen.strike)) { + return 1; + } + if ((attrs & VTERM_ATTR_FONT_MASK) && (a->pen.font != b->pen.font)) { + return 1; + } + if ((attrs & VTERM_ATTR_FOREGROUND_MASK) && !vterm_color_is_equal(&a->pen.fg, &b->pen.fg)) { + return 1; + } + if ((attrs & VTERM_ATTR_BACKGROUND_MASK) && !vterm_color_is_equal(&a->pen.bg, &b->pen.bg)) { + return 1; + } + if ((attrs & VTERM_ATTR_SMALL_MASK) && (a->pen.small != b->pen.small)) { + return 1; + } + if ((attrs & VTERM_ATTR_BASELINE_MASK) && (a->pen.baseline != b->pen.baseline)) { + return 1; + } + if ((attrs & VTERM_ATTR_URI_MASK) && (a->pen.uri != b->pen.uri)) { + return 1; + } + + return 0; +} + +int vterm_screen_get_attrs_extent(const VTermScreen *screen, VTermRect *extent, VTermPos pos, + VTermAttrMask attrs) +{ + ScreenCell *target = getcell(screen, pos.row, pos.col); + + // TODO(vterm): bounds check + extent->start_row = pos.row; + extent->end_row = pos.row + 1; + + if (extent->start_col < 0) { + extent->start_col = 0; + } + if (extent->end_col < 0) { + extent->end_col = screen->cols; + } + + int col; + + for (col = pos.col - 1; col >= extent->start_col; col--) { + if (attrs_differ(attrs, target, getcell(screen, pos.row, col))) { + break; + } + } + extent->start_col = col + 1; + + for (col = pos.col + 1; col < extent->end_col; col++) { + if (attrs_differ(attrs, target, getcell(screen, pos.row, col))) { + break; + } + } + extent->end_col = col - 1; + + return 1; +} + +/// Does not NUL-terminate the buffer +size_t vterm_screen_get_text(const VTermScreen *screen, char *buffer, size_t len, + const VTermRect rect) +{ + size_t outpos = 0; + int padding = 0; + +#define PUT(bytes, thislen) \ + if (true) { \ + if (buffer && outpos + thislen <= len) \ + memcpy((char *)buffer + outpos, bytes, thislen); \ + outpos += thislen; \ + } \ + + for (int row = rect.start_row; row < rect.end_row; row++) { + for (int col = rect.start_col; col < rect.end_col; col++) { + ScreenCell *cell = getcell(screen, row, col); + + if (cell->schar == 0) { + // Erased cell, might need a space + padding++; + } else if (cell->schar == (uint32_t)-1) { + // Gap behind a double-width char, do nothing + } else { + while (padding) { + PUT(" ", 1); + padding--; + } + char buf[MAX_SCHAR_SIZE + 1]; + size_t thislen = schar_get(buf, cell->schar); + PUT(buf, thislen); + } + } + + if (row < rect.end_row - 1) { + PUT("\n", 1); + padding = 0; + } + } + + return outpos; +} + +int vterm_screen_is_eol(const VTermScreen *screen, VTermPos pos) +{ + // This cell is EOL if this and every cell to the right is black + for (; pos.col < screen->cols; pos.col++) { + ScreenCell *cell = getcell(screen, pos.row, pos.col); + if (cell->schar != 0) { + return 0; + } + } + + return 1; +} + +void vterm_state_get_cursorpos(const VTermState *state, VTermPos *cursorpos) +{ + *cursorpos = state->pos; +} + +void vterm_state_set_bold_highbright(VTermState *state, int bold_is_highbright) +{ + state->bold_is_highbright = bold_is_highbright; +} + +/// Compares two colours. Returns true if the colors are equal, false otherwise. +int vterm_color_is_equal(const VTermColor *a, const VTermColor *b) +{ + // First make sure that the two colours are of the same type (RGB/Indexed) + if (a->type != b->type) { + return false; + } + + // Depending on the type inspect the corresponding members + if (VTERM_COLOR_IS_INDEXED(a)) { + return a->indexed.idx == b->indexed.idx; + } else if (VTERM_COLOR_IS_RGB(a)) { + return (a->rgb.red == b->rgb.red) + && (a->rgb.green == b->rgb.green) + && (a->rgb.blue == b->rgb.blue); + } + + return 0; +} diff --git a/test/unit/fixtures/vterm_test.h b/test/unit/fixtures/vterm_test.h new file mode 100644 index 0000000000..ef6463af6d --- /dev/null +++ b/test/unit/fixtures/vterm_test.h @@ -0,0 +1,45 @@ +#include <stdbool.h> +#include <stdint.h> + +#include "nvim/macros_defs.h" +#include "nvim/vterm/vterm.h" + +EXTERN VTermPos state_pos; +EXTERN bool want_state_putglyph INIT (=false); +EXTERN bool want_state_movecursor INIT(= false); +EXTERN bool want_state_erase INIT(= false); +EXTERN bool want_state_scrollrect INIT(= false); +EXTERN bool want_state_moverect INIT(= false); +EXTERN bool want_state_settermprop INIT(= false); +EXTERN bool want_state_scrollback INIT(= false); +EXTERN bool want_screen_scrollback INIT(= false); +int parser_text(const char bytes[], size_t len, void *user); +int parser_csi(const char *leader, const long args[], int argcount, const char *intermed, + char command, void *user); +int parser_osc(int command, VTermStringFragment frag, void *user); +int parser_dcs(const char *command, size_t commandlen, VTermStringFragment frag, void *user); +int parser_apc(VTermStringFragment frag, void *user); +int parser_pm(VTermStringFragment frag, void *user); +int parser_sos(VTermStringFragment frag, void *user); +int selection_set(VTermSelectionMask mask, VTermStringFragment frag, void *user); +int selection_query(VTermSelectionMask mask, void *user); +int state_putglyph(VTermGlyphInfo *info, VTermPos pos, void *user); +int state_movecursor(VTermPos pos, VTermPos oldpos, int visible, void *user); +int state_scrollrect(VTermRect rect, int downward, int rightward, void *user); +int state_moverect(VTermRect dest, VTermRect src, void *user); +int state_settermprop(VTermProp prop, VTermValue *val, void *user); +int state_erase(VTermRect rect, int selective, void *user); +int state_setpenattr(VTermAttr attr, VTermValue *val, void *user); +int state_sb_clear(void *user); +void print_color(const VTermColor *col); +int screen_sb_pushline(int cols, const VTermScreenCell *cells, void *user); +int screen_sb_popline(int cols, VTermScreenCell *cells, void *user); +int screen_sb_clear(void *user); +void term_output(const char *s, size_t len, void *user); +int vterm_state_get_penattr(const VTermState *state, VTermAttr attr, VTermValue *val); +int vterm_screen_get_attrs_extent(const VTermScreen *screen, VTermRect *extent, VTermPos pos, VTermAttrMask attrs); +size_t vterm_screen_get_text(const VTermScreen *screen, char *buffer, size_t len, VTermRect rect); +int vterm_screen_is_eol(const VTermScreen *screen, VTermPos pos); +void vterm_state_get_cursorpos(const VTermState *state, VTermPos *cursorpos); +void vterm_state_set_bold_highbright(VTermState *state, int bold_is_highbright); +int vterm_color_is_equal(const VTermColor *a, const VTermColor *b); diff --git a/test/unit/mbyte_spec.lua b/test/unit/mbyte_spec.lua index bdc111de2c..2c52aa9217 100644 --- a/test/unit/mbyte_spec.lua +++ b/test/unit/mbyte_spec.lua @@ -58,11 +58,11 @@ describe('mbyte', function() lib.schar_get(buf, lib.utfc_ptr2schar(to_string(seq), firstc)) local str = ffi.string(buf) if 1 > 2 then -- for debugging - local tabel = {} + local tbl = {} for i = 1, #str do - table.insert(tabel, string.format('0x%02x', string.byte(str, i))) + table.insert(tbl, string.format('0x%02x', string.byte(str, i))) end - print('{ ' .. table.concat(tabel, ', ') .. ' }') + print('{ ' .. table.concat(tbl, ', ') .. ' }') io.stdout:flush() end return { str, firstc[0] } diff --git a/test/unit/optionstr_spec.lua b/test/unit/optionstr_spec.lua index b9c9ceaa85..1f5b42485f 100644 --- a/test/unit/optionstr_spec.lua +++ b/test/unit/optionstr_spec.lua @@ -11,8 +11,8 @@ local check_ff_value = function(ff) end describe('check_ff_value', function() - itp('views empty string as valid', function() - eq(1, check_ff_value('')) + itp('views empty string as invalid', function() + eq(0, check_ff_value('')) end) itp('views "unix", "dos" and "mac" as valid', function() diff --git a/test/unit/strings_spec.lua b/test/unit/strings_spec.lua index 25cdc27b28..2b7a4d6261 100644 --- a/test/unit/strings_spec.lua +++ b/test/unit/strings_spec.lua @@ -1,6 +1,7 @@ local t = require('test.unit.testutil') local itp = t.gen_itp(it) +local child_call_once = t.child_call_once local cimport = t.cimport local eq = t.eq local ffi = t.ffi @@ -8,6 +9,12 @@ local to_cstr = t.to_cstr local strings = cimport('stdlib.h', './src/nvim/strings.h', './src/nvim/memory.h') +local UVARNUM_TYPE + +child_call_once(function() + UVARNUM_TYPE = ffi.typeof('uvarnumber_T') +end) + describe('vim_strsave_escaped()', function() local vim_strsave_escaped = function(s, chars) local res = strings.vim_strsave_escaped(to_cstr(s), to_cstr(chars)) @@ -140,13 +147,22 @@ end) describe('vim_snprintf()', function() local function a(expected, buf, bsize, fmt, ...) - eq(#expected, strings.vim_snprintf(buf, bsize, fmt, ...)) + local args = { ... } + local ctx = string.format('snprintf(buf, %d, "%s"', bsize, fmt) + for _, x in ipairs(args) do + ctx = ctx .. ', ' .. tostring(x) + end + ctx = ctx .. string.format(') = %s', expected) + eq(#expected, strings.vim_snprintf(buf, bsize, fmt, ...), ctx) if bsize > 0 then local actual = ffi.string(buf, math.min(#expected + 1, bsize)) eq(expected:sub(1, bsize - 1) .. '\0', actual) end end + local function uv(n) + return ffi.cast(UVARNUM_TYPE, n) + end local function i(n) return ffi.cast('int', n) end @@ -181,7 +197,7 @@ describe('vim_snprintf()', function() a(' 1234567', buf, bsize, '%9ld', l(1234567)) a('1234567 ', buf, bsize, '%-9ld', l(1234567)) a('deadbeef', buf, bsize, '%x', u(0xdeadbeef)) - a('001100', buf, bsize, '%06b', u(12)) + a('001100', buf, bsize, '%06b', uv(12)) a('one two', buf, bsize, '%s %s', 'one', 'two') a('1.234000', buf, bsize, '%f', 1.234) a('1.234000e+00', buf, bsize, '%e', 1.234) @@ -223,10 +239,10 @@ describe('vim_snprintf()', function() a('three one two', buf, bsize, '%3$s %1$s %2$s', 'one', 'two', 'three') a('1234567', buf, bsize, '%1$d', i(1234567)) a('deadbeef', buf, bsize, '%1$x', u(0xdeadbeef)) - a('001100', buf, bsize, '%2$0*1$b', i(6), u(12)) - a('001100', buf, bsize, '%1$0.*2$b', u(12), i(6)) + a('001100', buf, bsize, '%2$0*1$b', i(6), uv(12)) + a('001100', buf, bsize, '%1$0.*2$b', uv(12), i(6)) a('one two', buf, bsize, '%1$s %2$s', 'one', 'two') - a('001100', buf, bsize, '%06b', u(12)) + a('001100', buf, bsize, '%06b', uv(12)) a('two one', buf, bsize, '%2$s %1$s', 'one', 'two') a('1.234000', buf, bsize, '%1$f', 1.234) a('1.234000e+00', buf, bsize, '%1$e', 1.234) diff --git a/test/unit/vterm_spec.lua b/test/unit/vterm_spec.lua index 4ea5d9c29a..c5293a21cb 100644 --- a/test/unit/vterm_spec.lua +++ b/test/unit/vterm_spec.lua @@ -17,7 +17,9 @@ local bit = require('bit') --- @field VTERM_KEY_NONE integer --- @field VTERM_KEY_TAB integer --- @field VTERM_KEY_UP integer ---- @field VTERM_MAX_CHARS_PER_CELL integer +--- @field VTERM_KEY_BACKSPACE integer +--- @field VTERM_KEY_ESCAPE integer +--- @field VTERM_KEY_DEL integer --- @field VTERM_MOD_ALT integer --- @field VTERM_MOD_CTRL integer --- @field VTERM_MOD_SHIFT integer @@ -29,6 +31,7 @@ local bit = require('bit') --- @field parser_sos function --- @field parser_text function --- @field print_color function +--- @field schar_get fun(any, any):integer --- @field screen_sb_clear function --- @field screen_sb_popline function --- @field screen_sb_pushline function @@ -44,6 +47,8 @@ local bit = require('bit') --- @field state_setpenattr function --- @field state_settermprop function --- @field term_output function +--- @field utf_ptr2char fun(any):integer +--- @field utf_ptr2len fun(any):integer --- @field vterm_input_write function --- @field vterm_keyboard_end_paste function --- @field vterm_keyboard_key function @@ -62,7 +67,6 @@ local bit = require('bit') --- @field vterm_screen_enable_reflow function --- @field vterm_screen_get_attrs_extent function --- @field vterm_screen_get_cell function ---- @field vterm_screen_get_chars fun(any, any, any, any):any --- @field vterm_screen_get_text fun(any, any, any, any):any --- @field vterm_screen_is_eol fun(any, any):any --- @field vterm_screen_reset function @@ -79,7 +83,20 @@ local bit = require('bit') --- @field vterm_state_set_callbacks function --- @field vterm_state_set_selection_callbacks function --- @field vterm_state_set_unrecognised_fallbacks function -local vterm = t.cimport('./src/vterm/vterm.h', './src/vterm/vterm_internal.h') +local vterm = t.cimport( + './src/nvim/grid.h', + './src/nvim/mbyte.h', + './src/nvim/vterm/encoding.h', + './src/nvim/vterm/keyboard.h', + './src/nvim/vterm/mouse.h', + './src/nvim/vterm/parser.h', + './src/nvim/vterm/pen.h', + './src/nvim/vterm/screen.h', + './src/nvim/vterm/state.h', + './src/nvim/vterm/vterm.h', + './src/nvim/vterm/vterm_internal.h', + './test/unit/fixtures/vterm_test.h' +) --- @return string local function read_rm() @@ -298,16 +315,12 @@ local function screen_chars(start_row, start_col, end_row, end_col, expected, sc rect['end_row'] = end_row rect['end_col'] = end_col - local len = vterm.vterm_screen_get_chars(screen, nil, 0, rect) - - local chars = t.ffi.new('uint32_t[?]', len) - vterm.vterm_screen_get_chars(screen, chars, len, rect) + local len = vterm.vterm_screen_get_text(screen, nil, 0, rect) - local actual = '' - for i = 0, tonumber(len) - 1 do - actual = actual .. string.char(chars[i]) - end + local text = t.ffi.new('unsigned char[?]', len) + vterm.vterm_screen_get_text(screen, text, len, rect) + local actual = t.ffi.string(text, len) t.eq(expected, actual) end @@ -345,7 +358,7 @@ local function screen_row(row, expected, screen, end_col) local text = t.ffi.new('unsigned char[?]', len) vterm.vterm_screen_get_text(screen, text, len, rect) - t.eq(expected, t.ffi.string(text)) + t.eq(expected, t.ffi.string(text, len)) end local function screen_cell(row, col, expected, screen) @@ -353,17 +366,23 @@ local function screen_cell(row, col, expected, screen) pos['row'] = row pos['col'] = col - local cell = t.ffi.new('VTermScreenCell') + local cell = t.ffi.new('VTermScreenCell') ---@type any vterm.vterm_screen_get_cell(screen, pos, cell) + local buf = t.ffi.new('unsigned char[32]') + vterm.schar_get(buf, cell.schar) + local actual = '{' - for i = 0, vterm.VTERM_MAX_CHARS_PER_CELL - 1 do - if cell['chars'][i] ~= 0 then - if i > 0 then - actual = actual .. ',' - end - actual = string.format('%s%02x', actual, cell['chars'][i]) + local i = 0 + while buf[i] > 0 do + local char = vterm.utf_ptr2char(buf + i) + local charlen = vterm.utf_ptr2len(buf + i) + if i > 0 then + actual = actual .. ',' end + local invalid = char >= 128 and charlen == 1 + actual = string.format('%s%s%02x', actual, invalid and '?' or '', char) + i = i + charlen end actual = string.format('%s} width=%d attrs={', actual, cell['width']) actual = actual .. (cell['attrs'].bold ~= 0 and 'B' or '') @@ -489,6 +508,18 @@ local function strp_key(input_key) return vterm.VTERM_KEY_ENTER end + if input_key == 'bs' then + return vterm.VTERM_KEY_BACKSPACE + end + + if input_key == 'del' then + return vterm.VTERM_KEY_DEL + end + + if input_key == 'esc' then + return vterm.VTERM_KEY_ESCAPE + end + if input_key == 'f1' then return vterm.VTERM_KEY_FUNCTION_0 + 1 end @@ -958,8 +989,8 @@ describe('vterm', function() -- Spare combining chars get truncated reset(state, nil) - push('e' .. string.rep('\xCC\x81', 10), vt) - expect('putglyph 65,301,301,301,301,301 1 0,0') -- and nothing more + push('e' .. string.rep('\xCC\x81', 20), vt) + expect('putglyph 65,301,301,301,301,301,301,301,301,301,301,301,301,301,301 1 0,0') -- and nothing more reset(state, nil) push('e', vt) @@ -969,6 +1000,34 @@ describe('vterm', function() push('\xCC\x82', vt) expect('putglyph 65,301,302 1 0,0') + -- emoji with ZWJ and variant selectors, as one chunk + reset(state, nil) + push('🏳️🌈🏳️⚧️🏴☠️', vt) + expect([[putglyph 1f3f3,fe0f,200d,1f308 2 0,0 +putglyph 1f3f3,fe0f,200d,26a7,fe0f 2 0,2 +putglyph 1f3f4,200d,2620,fe0f 2 0,4]]) + + -- emoji, one code point at a time + reset(state, nil) + push('🏳', vt) + expect('putglyph 1f3f3 2 0,0') + push('\xef\xb8\x8f', vt) + expect('putglyph 1f3f3,fe0f 2 0,0') + push('\xe2\x80\x8d', vt) + expect('putglyph 1f3f3,fe0f,200d 2 0,0') + push('🌈', vt) + expect('putglyph 1f3f3,fe0f,200d,1f308 2 0,0') + + -- modifier can change width + push('❤', vt) + expect('putglyph 2764 1 0,2') + push('\xef\xb8\x8f', vt) + expect('putglyph 2764,fe0f 2 0,2') + + -- also works batched + push('❤️', vt) + expect('putglyph 2764,fe0f 2 0,4') + -- DECSCA protected reset(state, nil) push('A\x1b[1"qB\x1b[2"qC', vt) @@ -1090,7 +1149,7 @@ describe('vterm', function() push('\x1b[0F', vt) cursor(0, 0, state) - -- Cursor Horizonal Absolute + -- Cursor Horizontal Absolute push('\n', vt) cursor(1, 0, state) push('\x1b[20G', vt) @@ -1664,12 +1723,6 @@ describe('vterm', function() push('#', vt) expect('putglyph 23 1 0,0') - -- Designate G0=UK - reset(state, nil) - push('\x1b(A', vt) - push('#', vt) - expect('putglyph a3 1 0,0') - -- Designate G0=DEC drawing reset(state, nil) push('\x1b(0', vt) @@ -2026,6 +2079,18 @@ describe('vterm', function() mousebtn('u', 1, vt) expect_output('\x1b[<0;301;301m') + -- Button 8 on SGR extended encoding mode + mousebtn('d', 8, vt) + expect_output('\x1b[<128;301;301M') + mousebtn('u', 8, vt) + expect_output('\x1b[<128;301;301m') + + -- Button 9 on SGR extended encoding mode + mousebtn('d', 9, vt) + expect_output('\x1b[<129;301;301M') + mousebtn('u', 9, vt) + expect_output('\x1b[<129;301;301m') + -- DECRQM on SGR extended encoding mode push('\x1b[?1005$p', vt) expect_output('\x1b[?1005;2$y') @@ -2041,6 +2106,18 @@ describe('vterm', function() mousebtn('u', 1, vt) expect_output('\x1b[3;301;301M') + -- Button 8 on rxvt extended encoding mode + mousebtn('d', 8, vt) + expect_output('\x1b[128;301;301M') + mousebtn('u', 8, vt) + expect_output('\x1b[3;301;301M') + + -- Button 9 on rxvt extended encoding mode + mousebtn('d', 9, vt) + expect_output('\x1b[129;301;301M') + mousebtn('u', 9, vt) + expect_output('\x1b[3;301;301M') + -- DECRQM on rxvt extended encoding mode push('\x1b[?1005$p', vt) expect_output('\x1b[?1005;2$y') @@ -2286,65 +2363,83 @@ describe('vterm', function() local vt = init() local state = wantstate(vt) + -- Disambiguate escape codes enabled + push('\x1b[>1u', vt) + -- Unmodified ASCII - inchar(41, vt) - expect('output 29') - inchar(61, vt) - expect('output 3d') + inchar(0x41, vt) + expect_output('A') + inchar(0x61, vt) + expect_output('a') -- Ctrl modifier on ASCII letters - inchar(41, vt, { C = true }) - expect('output 1b,5b,34,31,3b,35,75') - inchar(61, vt, { C = true }) - expect('output 1b,5b,36,31,3b,35,75') + inchar(0x41, vt, { C = true }) + expect_output('\x1b[97;6u') + inchar(0x61, vt, { C = true }) + expect_output('\x1b[97;5u') -- Alt modifier on ASCII letters - inchar(41, vt, { A = true }) - expect('output 1b,29') - inchar(61, vt, { A = true }) - expect('output 1b,3d') + inchar(0x41, vt, { A = true }) + expect_output('\x1b[97;4u') + inchar(0x61, vt, { A = true }) + expect_output('\x1b[97;3u') -- Ctrl-Alt modifier on ASCII letters - inchar(41, vt, { C = true, A = true }) - expect('output 1b,5b,34,31,3b,37,75') - inchar(61, vt, { C = true, A = true }) - expect('output 1b,5b,36,31,3b,37,75') - - -- Special handling of Ctrl-I - inchar(49, vt) - expect('output 31') - inchar(69, vt) - expect('output 45') - inchar(49, vt, { C = true }) - expect('output 1b,5b,34,39,3b,35,75') - inchar(69, vt, { C = true }) - expect('output 1b,5b,36,39,3b,35,75') - inchar(49, vt, { A = true }) - expect('output 1b,31') - inchar(69, vt, { A = true }) - expect('output 1b,45') - inchar(49, vt, { A = true, C = true }) - expect('output 1b,5b,34,39,3b,37,75') - inchar(69, vt, { A = true, C = true }) - expect('output 1b,5b,36,39,3b,37,75') + inchar(0x41, vt, { C = true, A = true }) + expect_output('\x1b[97;8u') + inchar(0x61, vt, { C = true, A = true }) + expect_output('\x1b[97;7u') + + -- Ctrl-I is disambiguated + inchar(0x49, vt) + expect_output('I') + inchar(0x69, vt) + expect_output('i') + inchar(0x49, vt, { C = true }) + expect_output('\x1b[105;6u') + inchar(0x69, vt, { C = true }) + expect_output('\x1b[105;5u') + inchar(0x49, vt, { A = true }) + expect_output('\x1b[105;4u') + inchar(0x69, vt, { A = true }) + expect_output('\x1b[105;3u') + inchar(0x49, vt, { A = true, C = true }) + expect_output('\x1b[105;8u') + inchar(0x69, vt, { A = true, C = true }) + expect_output('\x1b[105;7u') + + -- Ctrl+Digits + for i = 0, 9 do + local c = 0x30 + i + inchar(c, vt) + expect_output(tostring(i)) + inchar(c, vt, { C = true }) + expect_output(string.format('\x1b[%d;5u', c)) + inchar(c, vt, { C = true, S = true }) + expect_output(string.format('\x1b[%d;6u', c)) + inchar(c, vt, { C = true, A = true }) + expect_output(string.format('\x1b[%d;7u', c)) + inchar(c, vt, { C = true, A = true, S = true }) + expect_output(string.format('\x1b[%d;8u', c)) + end -- Special handling of Space - inchar(20, vt) - expect('output 14') - inchar(20, vt, { S = true }) - expect('output 14') - inchar(20, vt, { C = true }) - expect('output 1b,5b,32,30,3b,35,75') - inchar(20, vt, { C = true, S = true }) - expect('output 1b,5b,32,30,3b,35,75') - inchar(20, vt, { A = true }) - expect('output 1b,14') - inchar(20, vt, { S = true, A = true }) - expect('output 1b,14') - inchar(20, vt, { C = true, A = true }) - expect('output 1b,5b,32,30,3b,37,75') - inchar(20, vt, { S = true, C = true, A = true }) - expect('output 1b,5b,32,30,3b,37,75') + inchar(0x20, vt) + expect_output(' ') + inchar(0x20, vt, { S = true }) + expect_output('\x1b[32;2u') + inchar(0x20, vt, { C = true }) + expect_output('\x1b[32;5u') + inchar(0x20, vt, { C = true, S = true }) + expect_output('\x1b[32;6u') + inchar(0x20, vt, { A = true }) + expect_output('\x1b[32;3u') + inchar(0x20, vt, { S = true, A = true }) + expect_output('\x1b[32;4u') + inchar(0x20, vt, { C = true, A = true }) + expect_output('\x1b[32;7u') + inchar(0x20, vt, { S = true, C = true, A = true }) + expect_output('\x1b[32;8u') -- Cursor keys in reset (cursor) mode inkey('up', vt) @@ -2375,21 +2470,65 @@ describe('vterm', function() inkey('up', vt, { C = true }) expect_output('\x1b[1;5A') - -- Shift-Tab should be different + -- Tab inkey('tab', vt) expect_output('\x09') inkey('tab', vt, { S = true }) - expect_output('\x1b[Z') + expect_output('\x1b[9;2u') inkey('tab', vt, { C = true }) expect_output('\x1b[9;5u') inkey('tab', vt, { A = true }) - expect_output('\x1b\x09') + expect_output('\x1b[9;3u') inkey('tab', vt, { C = true, A = true }) expect_output('\x1b[9;7u') + -- Backspace + inkey('bs', vt) + expect_output('\x7f') + inkey('bs', vt, { S = true }) + expect_output('\x1b[127;2u') + inkey('bs', vt, { C = true }) + expect_output('\x1b[127;5u') + inkey('bs', vt, { A = true }) + expect_output('\x1b[127;3u') + inkey('bs', vt, { C = true, A = true }) + expect_output('\x1b[127;7u') + + -- DEL + inkey('del', vt) + expect_output('\x1b[3~') + inkey('del', vt, { S = true }) + expect_output('\x1b[3;2~') + inkey('del', vt, { C = true }) + expect_output('\x1b[3;5~') + inkey('del', vt, { A = true }) + expect_output('\x1b[3;3~') + inkey('del', vt, { C = true, A = true }) + expect_output('\x1b[3;7~') + + -- ESC + inkey('esc', vt) + expect_output('\x1b[27;1u') + inkey('esc', vt, { S = true }) + expect_output('\x1b[27;2u') + inkey('esc', vt, { C = true }) + expect_output('\x1b[27;5u') + inkey('esc', vt, { A = true }) + expect_output('\x1b[27;3u') + inkey('esc', vt, { C = true, A = true }) + expect_output('\x1b[27;7u') + -- Enter in linefeed mode inkey('enter', vt) expect_output('\x0d') + inkey('enter', vt, { S = true }) + expect_output('\x1b[13;2u') + inkey('enter', vt, { C = true }) + expect_output('\x1b[13;5u') + inkey('enter', vt, { A = true }) + expect_output('\x1b[13;3u') + inkey('enter', vt, { C = true, A = true }) + expect_output('\x1b[13;7u') -- Enter in newline mode push('\x1b[20h', vt) @@ -2410,7 +2549,7 @@ describe('vterm', function() -- Keypad in DECKPNM inkey('kp0', vt) - expect_output('0') + expect_output('\x1b[57399;1u') -- Keypad in DECKPAM push('\x1b=', vt) @@ -2440,6 +2579,77 @@ describe('vterm', function() expect_output('\x1b[I') vterm.vterm_state_focus_out(state) expect_output('\x1b[O') + + -- Disambiguate escape codes disabled + push('\x1b[<u', vt) + + -- Unmodified ASCII + inchar(0x41, vt) + expect_output('A') + inchar(0x61, vt) + expect_output('a') + + -- Ctrl modifier on ASCII letters + inchar(0x41, vt, { C = true }) + expect_output('\x01') + inchar(0x61, vt, { C = true }) + expect_output('\x01') + + -- Alt modifier on ASCII letters + inchar(0x41, vt, { A = true }) + expect_output('\x1bA') + inchar(0x61, vt, { A = true }) + expect_output('\x1ba') + + -- Ctrl-Alt modifier on ASCII letters + inchar(0x41, vt, { C = true, A = true }) + expect_output('\x1b\x01') + inchar(0x61, vt, { C = true, A = true }) + expect_output('\x1b\x01') + + -- Ctrl-I is ambiguous + inchar(0x49, vt) + expect_output('I') + inchar(0x69, vt) + expect_output('i') + inchar(0x49, vt, { C = true }) + expect_output('\x09') + inchar(0x69, vt, { C = true }) + expect_output('\x09') + inchar(0x49, vt, { A = true }) + expect_output('\x1bI') + inchar(0x69, vt, { A = true }) + expect_output('\x1bi') + inchar(0x49, vt, { A = true, C = true }) + expect_output('\x1b\x09') + inchar(0x69, vt, { A = true, C = true }) + expect_output('\x1b\x09') + + -- Ctrl+Digits + inchar(0x30, vt, { C = true }) + expect_output('0') + inchar(0x31, vt, { C = true }) + expect_output('1') + inchar(0x32, vt, { C = true }) + expect_output('\x00') + inchar(0x33, vt, { C = true }) + expect_output('\x1b') + inchar(0x34, vt, { C = true }) + expect_output('\x1c') + inchar(0x35, vt, { C = true }) + expect_output('\x1d') + inchar(0x36, vt, { C = true }) + expect_output('\x1e') + inchar(0x37, vt, { C = true }) + expect_output('\x1f') + inchar(0x38, vt, { C = true }) + expect_output('\x7f') + inchar(0x39, vt, { C = true }) + expect_output('9') + + -- Ctrl+/ + inchar(0x2F, vt, { C = true }) + expect_output('\x1f') end) itp('26state_query', function() @@ -3042,7 +3252,7 @@ describe('vterm', function() screen_cell( 0, 0, - '{65,301,302,303,304,305} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)', + '{65,301,302,303,304,305,306,307,308,309,30a} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)', screen ) @@ -3059,15 +3269,25 @@ describe('vterm', function() screen_cell( 0, 0, - '{65,301,301,301,301,301} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)', + '{65,301,301,301,301,301,301,301,301,301,301,301,301,301,301} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)', screen ) - -- Outputing CJK doublewidth in 80th column should wraparound to next line and not crash" + -- Outputting CJK doublewidth in 80th column should wraparound to next line and not crash" reset(nil, screen) push('\x1b[80G\xEF\xBC\x90', vt) screen_cell(0, 79, '{} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)', screen) screen_cell(1, 0, '{ff10} width=2 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)', screen) + + -- Outputting emoji with ZWJ and variant selectors + reset(nil, screen) + push('🏳️🌈🏳️⚧️🏴☠️', vt) + + -- stylua: ignore start + screen_cell(0, 0, '{1f3f3,fe0f,200d,1f308} width=2 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)', screen) + screen_cell(0, 2, '{1f3f3,fe0f,200d,26a7,fe0f} width=2 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)', screen) + screen_cell(0, 4, '{1f3f4,200d,2620,fe0f} width=2 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)', screen) + -- stylua: ignore end end) pending('62screen_damage', function() end) @@ -3121,7 +3341,7 @@ describe('vterm', function() screen = wantscreen(vt, { b = true }) resize(20, 80, vt) expect( - 'sb_pushline 80 = 54 6F 70\nsb_pushline 80 =\nsb_pushline 80 =\nsb_pushline 80 =\nsb_pushline 80 =' + 'sb_pushline 80 = 54 6f 70\nsb_pushline 80 =\nsb_pushline 80 =\nsb_pushline 80 =\nsb_pushline 80 =' ) -- TODO(dundargoc): fix or remove -- screen_row( 0 , "",screen) |