diff options
50 files changed, 876 insertions, 415 deletions
diff --git a/runtime/doc/api.txt b/runtime/doc/api.txt index 9cb8f72348..cb3b2a3f77 100644 --- a/runtime/doc/api.txt +++ b/runtime/doc/api.txt @@ -3494,9 +3494,9 @@ nvim_create_autocmd({event}, {opts}) *nvim_create_autocmd()* • event: (string) name of the triggered event |autocmd-events| • group: (number|nil) autocommand group id, if any - • match: (string) expanded value of <amatch> - • buf: (number) expanded value of <abuf> - • file: (string) expanded value of <afile> + • file: (string) <afile> (not expanded to a full path) + • match: (string) <amatch> (expanded to a full path) + • buf: (number) <abuf> • data: (any) arbitrary data passed from |nvim_exec_autocmds()| *event-data* • command (string) optional: Vim command to execute on event. diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt index 304e63fd95..f2942b959b 100644 --- a/runtime/doc/builtin.txt +++ b/runtime/doc/builtin.txt @@ -2997,7 +2997,7 @@ getbufline({buf}, {lnum} [, {end}]) *getbufline()* Parameters: ~ • {buf} (`integer|string`) • {lnum} (`integer`) - • {end_} (`integer?`) + • {end} (`integer?`) Return: ~ (`any`) @@ -3635,7 +3635,7 @@ getline({lnum} [, {end}]) *getline()* Parameters: ~ • {lnum} (`integer|string`) - • {end_} (`nil|false?`) + • {end} (`nil|false?`) Return: ~ (`string`) @@ -6292,7 +6292,7 @@ matchbufline({buf}, {pat}, {lnum}, {end}, [, {dict}]) *matchbufline()* • {buf} (`string|integer`) • {pat} (`string`) • {lnum} (`string|integer`) - • {end_} (`string|integer`) + • {end} (`string|integer`) • {dict} (`table?`) Return: ~ @@ -7786,7 +7786,7 @@ reltime({start}, {end}) Parameters: ~ • {start} (`any?`) - • {end_} (`any?`) + • {end} (`any?`) Return: ~ (`any`) @@ -7847,7 +7847,7 @@ remove({list}, {idx}, {end}) Parameters: ~ • {list} (`any[]`) • {idx} (`integer`) - • {end_} (`integer?`) + • {end} (`integer?`) Return: ~ (`any`) @@ -7869,7 +7869,7 @@ remove({blob}, {idx}, {end}) Parameters: ~ • {blob} (`any`) • {idx} (`integer`) - • {end_} (`integer?`) + • {end} (`integer?`) Return: ~ (`any`) @@ -8500,7 +8500,7 @@ searchpair({start}, {middle}, {end} [, {flags} [, {skip} [, {stopline} [, {timeo Parameters: ~ • {start} (`string`) • {middle} (`string`) - • {end_} (`string`) + • {end} (`string`) • {flags} (`string?`) • {skip} (`string|function?`) • {stopline} (`integer?`) @@ -8524,7 +8524,7 @@ searchpairpos({start}, {middle}, {end} [, {flags} [, {skip} [, {stopline} [, {ti Parameters: ~ • {start} (`string`) • {middle} (`string`) - • {end_} (`string`) + • {end} (`string`) • {flags} (`string?`) • {skip} (`string|function?`) • {stopline} (`integer?`) @@ -9840,7 +9840,7 @@ slice({expr}, {start} [, {end}]) *slice()* Parameters: ~ • {expr} (`any`) • {start} (`integer`) - • {end_} (`integer?`) + • {end} (`integer?`) Return: ~ (`any`) diff --git a/runtime/doc/cmdline.txt b/runtime/doc/cmdline.txt index 7967e2ce1a..f6a3e95b35 100644 --- a/runtime/doc/cmdline.txt +++ b/runtime/doc/cmdline.txt @@ -41,15 +41,19 @@ thus you cannot edit beyond that. The command-lines that you enter are remembered in a history table. You can recall them with the up and down cursor keys. There are actually five history tables: + - one for ':' commands - one for search strings - one for expressions - one for input lines, typed for the |input()| function. - one for debug mode commands + These are completely separate. Each history can only be accessed when entering the same type of line. Use the 'history' option to set the number of lines that are remembered. + Notes: + - When you enter a command-line that is exactly the same as an older one, the old one is removed (to avoid repeated commands moving older commands out of the history). diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt index 2654d7f14f..1607f3492c 100644 --- a/runtime/doc/lsp.txt +++ b/runtime/doc/lsp.txt @@ -872,7 +872,9 @@ start({config}, {opts}) *vim.lsp.start()* (`fun(client: vim.lsp.Client, config: vim.lsp.ClientConfig): boolean`) Predicate used to decide if a client should be re-used. Used on all running clients. The default implementation - re-uses a client if name and root_dir matches. + re-uses a client if it has the same name and if the given + workspace folders (or root_dir) are all included in the + client's workspace folders. • {bufnr}? (`integer`) Buffer handle to attach to if starting or re-using a client (0 for current). • {attach}? (`boolean`) Whether to attach the client to a diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt index 48fa595a53..80808abd51 100644 --- a/runtime/doc/lua.txt +++ b/runtime/doc/lua.txt @@ -811,11 +811,14 @@ vim.json.decode({str}, {opts}) *vim.json.decode()* Return: ~ (`any`) -vim.json.encode({obj}) *vim.json.encode()* +vim.json.encode({obj}, {opts}) *vim.json.encode()* Encodes (or "packs") Lua object {obj} as JSON in a Lua string. Parameters: ~ - • {obj} (`any`) + • {obj} (`any`) + • {opts} (`table<string,any>?`) Options table with keys: + • escape_slash: (boolean) (default false) When true, escapes + `/` character in JSON strings Return: ~ (`string`) diff --git a/runtime/doc/message.txt b/runtime/doc/message.txt index ac151811c2..d6f5aeb354 100644 --- a/runtime/doc/message.txt +++ b/runtime/doc/message.txt @@ -27,7 +27,7 @@ depends on the 'shortmess' option. Clear messages, keeping only the {count} most recent ones. -The number of remembered messages is fixed at 200. +The number of remembered messages is determined by the 'messagesopt' option. *g<* The "g<" command can be used to see the last page of previous command output. @@ -789,6 +789,7 @@ If you accidentally hit <Enter> or <Space> and you want to see the displayed text then use |g<|. This only works when 'more' is set. To reduce the number of hit-enter prompts: +- Set 'messagesopt'. - Set 'cmdheight' to 2 or higher. - Add flags to 'shortmess'. - Reset 'showcmd' and/or 'ruler'. diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index 58fe2e02e8..3ce73135ba 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -25,6 +25,7 @@ EXPERIMENTS OPTIONS • 'jumpoptions' flag "unload" has been renamed to "clean". +• The `msghistory` option has been removed in favor of 'messagesopt'. ============================================================================== BREAKING CHANGES *news-breaking* @@ -58,8 +59,9 @@ DEFAULTS • |]d-default| and |[d-default| accept a count. • |[D-default| and |]D-default| jump to the first and last diagnostic in the current buffer, respectively. -• 'number', 'relativenumber', and 'signcolumn' are disabled in |terminal| - buffers. See |terminal-config| for an example of changing these defaults. +• 'number', 'relativenumber', 'signcolumn', and 'foldcolumn' are disabled in + |terminal| buffers. See |terminal-config| for an example of changing these defaults. +• |vim.json.encode()| no longer escapes the forward slash symbol by default DIAGNOSTICS @@ -169,6 +171,7 @@ The following new features were added. API • |nvim__ns_set()| can set properties for a namespace +• |vim.json.encode()| has an option to enable forward slash escaping DEFAULTS @@ -246,7 +249,7 @@ LUA OPTIONS • 'completeopt' flag "fuzzy" enables |fuzzy-matching| during |ins-completion|. -• 'msghistory' controls maximum number of messages to remember. +• 'messagesopt' configures |:messages| and |hit-enter| prompt. • 'tabclose' controls which tab page to focus when closing a tab page. PERFORMANCE @@ -280,6 +283,8 @@ TREESITTER • |LanguageTree:node_for_range()| gets anonymous and named nodes for a range • |vim.treesitter.get_node()| now takes an option `include_anonymous`, default false, which allows it to return anonymous nodes as well as named nodes. +• |treesitter-directive-trim!| can trim all whitespace (not just empty lines) + from both sides of a node. TUI diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index 9782846be5..0db7b0f03c 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -3197,7 +3197,7 @@ A jump table for the options with a short description can be found at |Q_op|. global A history of ":" commands, and a history of previous search patterns is remembered. This option decides how many entries may be stored in - each of these histories (see |cmdline-editing| and 'msghistory' for + each of these histories (see |cmdline-editing| and 'messagesopt' for the number of messages to remember). The maximum value is 10000. @@ -4045,6 +4045,25 @@ A jump table for the options with a short description can be found at |Q_op|. generated from a list of items, e.g., the Buffers menu. Changing this option has no direct effect, the menu must be refreshed first. + *'messagesopt'* *'mopt'* +'messagesopt' 'mopt' string (default "hit-enter,history:500") + global + Option settings when outputting messages. It can consist of the + following items. Items must be separated by a comma. + + hit-enter Use |hit-enter| prompt when the message is longer than + 'cmdheight' size. + + wait:{n} Ignored when "hit-enter" is present. Instead of using + |hit-enter| prompt, will simply wait for {n} + milliseconds so the user has a chance to read the + message, use 0 to disable sleep (but then the user may + miss an important message). + + history:{n} Determines how many entries are remembered in the + |:messages| history. The maximum value is 10000. + Setting it to zero clears the message history. + *'mkspellmem'* *'msm'* 'mkspellmem' 'msm' string (default "460000,2000,500") global @@ -4290,13 +4309,6 @@ A jump table for the options with a short description can be found at |Q_op|. Defines the maximum time in msec between two mouse clicks for the second click to be recognized as a multi click. - *'msghistory'* *'mhi'* -'msghistory' 'mhi' number (default 500) - global - Determines how many entries are remembered in the |:messages| history. - The maximum value is 10000. - Setting it to zero clears the message history. - *'nrformats'* *'nf'* 'nrformats' 'nf' string (default "bin,hex") local to buffer diff --git a/runtime/doc/terminal.txt b/runtime/doc/terminal.txt index 27586c38a7..5a1421f430 100644 --- a/runtime/doc/terminal.txt +++ b/runtime/doc/terminal.txt @@ -111,6 +111,7 @@ global configuration. - 'number' is disabled - 'relativenumber' is disabled - 'signcolumn' is set to "no" +- 'foldcolumn' is set to "0" You can change the defaults with a TermOpen autocommand: >vim au TermOpen * setlocal list diff --git a/runtime/doc/treesitter.txt b/runtime/doc/treesitter.txt index 5fc6429f7a..877d90a3b7 100644 --- a/runtime/doc/treesitter.txt +++ b/runtime/doc/treesitter.txt @@ -245,15 +245,32 @@ The following directives are built in: (#gsub! @_node ".*%.(.*)" "%1") < `trim!` *treesitter-directive-trim!* - Trim blank lines from the end of the node. This will set a new - `metadata[capture_id].range`. + Trims whitespace from the node. Sets a new + `metadata[capture_id].range`. Takes a capture ID and, optionally, four + integers to customize trimming behavior (`1` meaning trim, `0` meaning + don't trim). When only given a capture ID, trims blank lines (lines + that contain only whitespace, or are empty) from the end of the node + (for backwards compatibility). Can trim all whitespace from both sides + of the node if parameters are given. + Examples: >query + ; only trim blank lines from the end of the node + ; (equivalent to (#trim! @fold 0 0 1 0)) + (#trim! @fold) + + ; trim blank lines from both sides of the node + (#trim! @fold 1 0 1 0) + + ; trim all whitespace around the node + (#trim! @fold 1 1 1 1) +< Parameters: ~ {capture_id} + {trim_start_linewise} + {trim_start_charwise} + {trim_end_linewise} (default `1` if only given {capture_id}) + {trim_end_charwise} - Example: >query - (#trim! @fold) -< Further directives can be added via |vim.treesitter.query.add_directive()|. Use |vim.treesitter.query.list_directives()| to list all available directives. diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index 8d2d672ce1..7021e1d3e2 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -189,6 +189,7 @@ nvim_terminal: - 'nonumber' - 'norelativenumber' - 'signcolumn' set to "no" + - 'foldcolumn' set to "0" - 'winhighlight' uses |hl-StatusLineTerm| and |hl-StatusLineTermNC| in place of |hl-StatusLine| and |hl-StatusLineNC| diff --git a/runtime/filetype.lua b/runtime/filetype.lua index 797033da06..730991a00c 100644 --- a/runtime/filetype.lua +++ b/runtime/filetype.lua @@ -13,8 +13,8 @@ vim.api.nvim_create_autocmd({ 'BufRead', 'BufNewFile', 'StdinReadPost' }, { end local ft, on_detect = vim.filetype.match({ -- The unexpanded file name is needed here. #27914 - -- Neither args.file nor args.match are guaranteed to be unexpanded. - filename = vim.fn.bufname(args.buf), + -- However, bufname() can't be used, as it doesn't work with :doautocmd. #31306 + filename = args.file, buf = args.buf, }) if not ft then diff --git a/runtime/lua/vim/_defaults.lua b/runtime/lua/vim/_defaults.lua index f891c3baa4..0b8a54e957 100644 --- a/runtime/lua/vim/_defaults.lua +++ b/runtime/lua/vim/_defaults.lua @@ -495,6 +495,7 @@ do vim.wo[0][0].number = false vim.wo[0][0].relativenumber = false vim.wo[0][0].signcolumn = 'no' + vim.wo[0][0].foldcolumn = '0' -- This is gross. Proper list options support when? local winhl = vim.o.winhighlight diff --git a/runtime/lua/vim/_inspector.lua b/runtime/lua/vim/_inspector.lua index f4e41a31e5..520c437906 100644 --- a/runtime/lua/vim/_inspector.lua +++ b/runtime/lua/vim/_inspector.lua @@ -191,7 +191,12 @@ function vim.show_pos(bufnr, row, col, filter) append('Treesitter', 'Title') nl() for _, capture in ipairs(items.treesitter) do - item(capture, capture.lang) + item( + capture, + capture.metadata.priority + and string.format('%s priority: %d', capture.lang, capture.metadata.priority) + or capture.lang + ) end nl() end diff --git a/runtime/lua/vim/_meta/api.lua b/runtime/lua/vim/_meta/api.lua index d74ee11b46..b2385197bd 100644 --- a/runtime/lua/vim/_meta/api.lua +++ b/runtime/lua/vim/_meta/api.lua @@ -963,9 +963,9 @@ function vim.api.nvim_create_augroup(name, opts) end --- - id: (number) autocommand id --- - event: (string) name of the triggered event `autocmd-events` --- - group: (number|nil) autocommand group id, if any ---- - match: (string) expanded value of [<amatch>] ---- - buf: (number) expanded value of [<abuf>] ---- - file: (string) expanded value of [<afile>] +--- - file: (string) [<afile>] (not expanded to a full path) +--- - match: (string) [<amatch>] (expanded to a full path) +--- - buf: (number) [<abuf>] --- - data: (any) arbitrary data passed from [nvim_exec_autocmds()] [event-data]() --- - command (string) optional: Vim command to execute on event. Cannot be used with --- {callback} diff --git a/runtime/lua/vim/_meta/json.lua b/runtime/lua/vim/_meta/json.lua index 07d89aafc8..1a7e87db9c 100644 --- a/runtime/lua/vim/_meta/json.lua +++ b/runtime/lua/vim/_meta/json.lua @@ -35,5 +35,8 @@ function vim.json.decode(str, opts) end --- Encodes (or "packs") Lua object {obj} as JSON in a Lua string. ---@param obj any +---@param opts? table<string,any> Options table with keys: +--- - escape_slash: (boolean) (default false) When true, escapes `/` +--- character in JSON strings ---@return string -function vim.json.encode(obj) end +function vim.json.encode(obj, opts) end diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua index 01d5235d94..59d270e640 100644 --- a/runtime/lua/vim/_meta/options.lua +++ b/runtime/lua/vim/_meta/options.lua @@ -3016,7 +3016,7 @@ vim.go.hid = vim.go.hidden --- A history of ":" commands, and a history of previous search patterns --- is remembered. This option decides how many entries may be stored in ---- each of these histories (see `cmdline-editing` and 'msghistory' for +--- each of these histories (see `cmdline-editing` and 'messagesopt' for --- the number of messages to remember). --- The maximum value is 10000. --- @@ -4084,6 +4084,28 @@ vim.o.mis = vim.o.menuitems vim.go.menuitems = vim.o.menuitems vim.go.mis = vim.go.menuitems +--- Option settings when outputting messages. It can consist of the +--- following items. Items must be separated by a comma. +--- +--- hit-enter Use `hit-enter` prompt when the message is longer than +--- 'cmdheight' size. +--- +--- wait:{n} Ignored when "hit-enter" is present. Instead of using +--- `hit-enter` prompt, will simply wait for {n} +--- milliseconds so the user has a chance to read the +--- message, use 0 to disable sleep (but then the user may +--- miss an important message). +--- +--- history:{n} Determines how many entries are remembered in the +--- `:messages` history. The maximum value is 10000. +--- Setting it to zero clears the message history. +--- +--- @type string +vim.o.messagesopt = "hit-enter,history:500" +vim.o.mopt = vim.o.messagesopt +vim.go.messagesopt = vim.o.messagesopt +vim.go.mopt = vim.go.messagesopt + --- Parameters for `:mkspell`. This tunes when to start compressing the --- word tree. Compression can be slow when there are many words, but --- it's needed to avoid running out of memory. The amount of memory used @@ -4379,16 +4401,6 @@ vim.o.mouset = vim.o.mousetime vim.go.mousetime = vim.o.mousetime vim.go.mouset = vim.go.mousetime ---- Determines how many entries are remembered in the `:messages` history. ---- The maximum value is 10000. ---- Setting it to zero clears the message history. ---- ---- @type integer -vim.o.msghistory = 500 -vim.o.mhi = vim.o.msghistory -vim.go.msghistory = vim.o.msghistory -vim.go.mhi = vim.go.msghistory - --- This defines what bases Vim will consider for numbers when using the --- CTRL-A and CTRL-X commands for adding to and subtracting from a number --- respectively; see `CTRL-A` for more info on these commands. diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 4717d7995a..e1946816da 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -114,6 +114,22 @@ function lsp._unsupported_method(method) return msg end +---@private +---@param workspace_folders string|lsp.WorkspaceFolder[]? +---@return lsp.WorkspaceFolder[]? +function lsp._get_workspace_folders(workspace_folders) + if type(workspace_folders) == 'table' then + return workspace_folders + elseif type(workspace_folders) == 'string' then + return { + { + uri = vim.uri_from_fname(workspace_folders), + name = workspace_folders, + }, + } + end +end + local wait_result_reason = { [-1] = 'timeout', [-2] = 'interrupted', [-3] = 'error' } local format_line_ending = { @@ -196,19 +212,24 @@ local function reuse_client_default(client, config) return false end - if config.root_dir then - local root = vim.uri_from_fname(config.root_dir) - for _, dir in ipairs(client.workspace_folders or {}) do - -- note: do not need to check client.root_dir since that should be client.workspace_folders[1] - if root == dir.uri then - return true + local config_folders = lsp._get_workspace_folders(config.workspace_folders or config.root_dir) + or {} + local config_folders_included = 0 + + if not next(config_folders) then + return false + end + + for _, config_folder in ipairs(config_folders) do + for _, client_folder in ipairs(client.workspace_folders) do + if config_folder.uri == client_folder.uri then + config_folders_included = config_folders_included + 1 + break end end end - -- TODO(lewis6991): also check config.workspace_folders - - return false + return config_folders_included == #config_folders end --- Reset defaults set by `set_defaults`. @@ -311,9 +332,10 @@ end --- @inlinedoc --- --- Predicate used to decide if a client should be re-used. Used on all ---- running clients. The default implementation re-uses a client if name and ---- root_dir matches. ---- @field reuse_client? (fun(client: vim.lsp.Client, config: vim.lsp.ClientConfig): boolean) +--- running clients. The default implementation re-uses a client if it has the +--- same name and if the given workspace folders (or root_dir) are all included +--- in the client's workspace folders. +--- @field reuse_client? fun(client: vim.lsp.Client, config: vim.lsp.ClientConfig): boolean --- --- Buffer handle to attach to if starting or re-using a client (0 for current). --- @field bufnr? integer @@ -1257,44 +1279,6 @@ function lsp.with(handler, override_config) end end ---- Helper function to use when implementing a handler. ---- This will check that all of the keys in the user configuration ---- are valid keys and make sense to include for this handler. ---- ---- Will error on invalid keys (i.e. keys that do not exist in the options) ---- @param name string ---- @param options table<string,any> ---- @param user_config table<string,any> -function lsp._with_extend(name, options, user_config) - user_config = user_config or {} - - local resulting_config = {} --- @type table<string,any> - for k, v in pairs(user_config) do - if options[k] == nil then - error( - debug.traceback( - string.format( - 'Invalid option for `%s`: %s. Valid options are:\n%s', - name, - k, - vim.inspect(vim.tbl_keys(options)) - ) - ) - ) - end - - resulting_config[k] = v - end - - for k, v in pairs(options) do - if resulting_config[k] == nil then - resulting_config[k] = v - end - end - - return resulting_config -end - --- Registry for client side commands. --- This is an extension point for plugins to handle custom commands which are --- not part of the core language server protocol specification. diff --git a/runtime/lua/vim/lsp/_folding_range.lua b/runtime/lua/vim/lsp/_folding_range.lua index 6a445017a3..2f1767aaf5 100644 --- a/runtime/lua/vim/lsp/_folding_range.lua +++ b/runtime/lua/vim/lsp/_folding_range.lua @@ -271,7 +271,9 @@ local function setup(bufnr) buffer = bufnr, callback = function(args) local client = assert(vim.lsp.get_client_by_id(args.data.client_id)) - request(bufnr, client) + if client:supports_method(vim.lsp.protocol.Methods.textDocument_foldingRange, bufnr) then + request(bufnr, client) + end end, }) api.nvim_create_autocmd('LspNotify', { diff --git a/runtime/lua/vim/lsp/client.lua b/runtime/lua/vim/lsp/client.lua index a83d75bf75..7eb023da39 100644 --- a/runtime/lua/vim/lsp/client.lua +++ b/runtime/lua/vim/lsp/client.lua @@ -365,21 +365,6 @@ local function get_name(id, config) return tostring(id) end ---- @param workspace_folders string|lsp.WorkspaceFolder[]? ---- @return lsp.WorkspaceFolder[]? -local function get_workspace_folders(workspace_folders) - if type(workspace_folders) == 'table' then - return workspace_folders - elseif type(workspace_folders) == 'string' then - return { - { - uri = vim.uri_from_fname(workspace_folders), - name = workspace_folders, - }, - } - end -end - --- @generic T --- @param x elem_or_list<T>? --- @return T[] @@ -417,7 +402,7 @@ function Client.create(config) flags = config.flags or {}, get_language_id = config.get_language_id or default_get_language_id, capabilities = config.capabilities or lsp.protocol.make_client_capabilities(), - workspace_folders = get_workspace_folders(config.workspace_folders or config.root_dir), + workspace_folders = lsp._get_workspace_folders(config.workspace_folders or config.root_dir), root_dir = config.root_dir, _before_init_cb = config.before_init, _on_init_cbs = ensure_list(config.on_init), @@ -1174,7 +1159,7 @@ function Client:_add_workspace_folder(dir) end end - local wf = assert(get_workspace_folders(dir)) + local wf = assert(lsp._get_workspace_folders(dir)) self:notify(ms.workspace_didChangeWorkspaceFolders, { event = { added = wf, removed = {} }, @@ -1189,7 +1174,7 @@ end --- Remove a directory to the workspace folders. --- @param dir string? function Client:_remove_workspace_folder(dir) - local wf = assert(get_workspace_folders(dir)) + local wf = assert(lsp._get_workspace_folders(dir)) self:notify(ms.workspace_didChangeWorkspaceFolders, { event = { added = {}, removed = wf }, diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index ced8aa5745..5bf436f2ca 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -2162,6 +2162,11 @@ function M._refresh(method, opts) local first = vim.fn.line('w0', window) local last = vim.fn.line('w$', window) for _, client in ipairs(clients) do + for rid, req in pairs(client.requests) do + if req.method == method and req.type == 'pending' and req.bufnr == bufnr then + client:cancel_request(rid) + end + end client:request(method, { textDocument = textDocument, range = make_line_range_params(bufnr, first - 1, last - 1, client.offset_encoding), diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 1677e8d364..dbe3d54c2f 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -572,13 +572,17 @@ local directive_handlers = { metadata[id].text = text:gsub(pattern, replacement) end, - -- Trim blank lines from end of the node - -- Example: (#trim! @fold) - -- TODO(clason): generalize to arbitrary whitespace removal + -- Trim whitespace from both sides of the node + -- Example: (#trim! @fold 1 1 1 1) ['trim!'] = function(match, _, bufnr, pred, metadata) local capture_id = pred[2] assert(type(capture_id) == 'number') + local trim_start_lines = pred[3] == '1' + local trim_start_cols = pred[4] == '1' + local trim_end_lines = pred[5] == '1' or not pred[3] -- default true for backwards compatibility + local trim_end_cols = pred[6] == '1' + local nodes = match[capture_id] if not nodes or #nodes == 0 then return @@ -588,20 +592,45 @@ local directive_handlers = { local start_row, start_col, end_row, end_col = node:range() - -- Don't trim if region ends in middle of a line - if end_col ~= 0 then - return + local node_text = vim.split(vim.treesitter.get_node_text(node, bufnr), '\n') + if end_col == 0 then + -- get_node_text() will ignore the last line if the node ends at column 0 + node_text[#node_text + 1] = '' end - while end_row >= start_row do - -- As we only care when end_col == 0, always inspect one line above end_row. - local end_line = api.nvim_buf_get_lines(bufnr, end_row - 1, end_row, true)[1] + local end_idx = #node_text + local start_idx = 1 - if end_line ~= '' then - break + if trim_end_lines then + while end_idx > 0 and node_text[end_idx]:find('^%s*$') do + end_idx = end_idx - 1 + end_row = end_row - 1 + -- set the end position to the last column of the next line, or 0 if we just trimmed the + -- last line + end_col = end_idx > 0 and #node_text[end_idx] or 0 end + end + if trim_end_cols then + if end_idx == 0 then + end_row = start_row + end_col = start_col + else + local whitespace_start = node_text[end_idx]:find('(%s*)$') + end_col = (whitespace_start - 1) + (end_idx == 1 and start_col or 0) + end + end - end_row = end_row - 1 + if trim_start_lines then + while start_idx <= end_idx and node_text[start_idx]:find('^%s*$') do + start_idx = start_idx + 1 + start_row = start_row + 1 + start_col = 0 + end + end + if trim_start_cols and node_text[start_idx] then + local _, whitespace_end = node_text[start_idx]:find('^(%s*)') + whitespace_end = whitespace_end or 0 + start_col = (start_idx == 1 and start_col or 0) + whitespace_end end -- If this produces an invalid range, we just skip it. diff --git a/runtime/optwin.vim b/runtime/optwin.vim index 48a4bd2816..923f55f8fa 100644 --- a/runtime/optwin.vim +++ b/runtime/optwin.vim @@ -626,8 +626,8 @@ call <SID>AddOption("terse", gettext("add 's' flag in 'shortmess' (don't show se call <SID>BinOptionG("terse", &terse) call <SID>AddOption("shortmess", gettext("list of flags to make messages shorter")) call <SID>OptionG("shm", &shm) -call <SID>AddOption("msghistory", gettext("how many messages are remembered")) -call append("$", " \tset mhi=" . &mhi) +call <SID>AddOption("messagesopt", gettext("Option settings when outputting messages")) +call <SID>OptionG("mopt", &mopt) call <SID>AddOption("showcmd", gettext("show (partial) command keys in location given by 'showcmdloc'")) let &sc = s:old_sc call <SID>BinOptionG("sc", &sc) diff --git a/scripts/gen_eval_files.lua b/scripts/gen_eval_files.lua index 498bf6b26f..de9df5054f 100755 --- a/scripts/gen_eval_files.lua +++ b/scripts/gen_eval_files.lua @@ -134,6 +134,15 @@ local API_TYPES = { void = '', } +--- @param s string +--- @return string +local function luaescape(s) + if LUA_KEYWORDS[s] then + return s .. '_' + end + return s +end + --- @param x string --- @param sep? string --- @return string[] @@ -208,7 +217,7 @@ local function render_fun_sig(f, params) --- @param v [string,string] --- @return string function(v) - return v[1] + return luaescape(v[1]) end, params ), @@ -224,7 +233,6 @@ local function render_fun_sig(f, params) end --- Uniquify names ---- Fix any names that are lua keywords --- @param params [string,string,string][] --- @return [string,string,string][] local function process_params(params) @@ -232,9 +240,6 @@ local function process_params(params) local sfx = 1 for _, p in ipairs(params) do - if LUA_KEYWORDS[p[1]] then - p[1] = p[1] .. '_' - end if seen[p[1]] then p[1] = p[1] .. sfx sfx = sfx + 1 @@ -389,10 +394,10 @@ local function render_api_meta(_f, fun, write) local param_names = {} --- @type string[] local params = process_params(fun.params) for _, p in ipairs(params) do - param_names[#param_names + 1] = p[1] - local pdesc = p[3] + local pname, ptype, pdesc = luaescape(p[1]), p[2], p[3] + param_names[#param_names + 1] = pname if pdesc then - local s = '--- @param ' .. p[1] .. ' ' .. p[2] .. ' ' + local s = '--- @param ' .. pname .. ' ' .. ptype .. ' ' local pdesc_a = split(vim.trim(norm_text(pdesc))) write(s .. pdesc_a[1]) for i = 2, #pdesc_a do @@ -402,7 +407,7 @@ local function render_api_meta(_f, fun, write) write('--- ' .. pdesc_a[i]) end else - write('--- @param ' .. p[1] .. ' ' .. p[2]) + write('--- @param ' .. pname .. ' ' .. ptype) end end @@ -494,7 +499,7 @@ local function render_eval_meta(f, fun, write) local req_args = type(fun.args) == 'table' and fun.args[1] or fun.args or 0 for i, param in ipairs(params) do - local pname, ptype = param[1], param[2] + local pname, ptype = luaescape(param[1]), param[2] local optional = (pname ~= '...' and i > req_args) and '?' or '' write(fmt('--- @param %s%s %s', pname, optional, ptype)) end diff --git a/src/cjson/lua_cjson.c b/src/cjson/lua_cjson.c index 254355e5a2..e4bd0bcf6b 100644 --- a/src/cjson/lua_cjson.c +++ b/src/cjson/lua_cjson.c @@ -173,6 +173,16 @@ typedef struct { } json_config_t; typedef struct { + const char **char2escape[256]; +} json_encode_options_t; + +typedef struct { + json_config_t *cfg; + json_encode_options_t *options; + strbuf_t *json; +} json_encode_t; + +typedef struct { /* convert null in json objects to lua nil instead of vim.NIL */ bool luanil_object; /* convert null in json arrays to lua nil instead of vim.NIL */ @@ -209,7 +219,7 @@ static const char *char2escape[256] = { "\\u0018", "\\u0019", "\\u001a", "\\u001b", "\\u001c", "\\u001d", "\\u001e", "\\u001f", NULL, NULL, "\\\"", NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, "\\/", + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, @@ -555,11 +565,11 @@ static void json_create_config(lua_State *l) /* ===== ENCODING ===== */ -static void json_encode_exception(lua_State *l, json_config_t *cfg, strbuf_t *json, int lindex, +static void json_encode_exception(lua_State *l, json_encode_t *ctx, int lindex, const char *reason) { - if (!cfg->encode_keep_buffer) - strbuf_free(json); + if (!ctx->cfg->encode_keep_buffer) + strbuf_free(ctx->json); luaL_error(l, "Cannot serialise %s: %s", lua_typename(l, lua_type(l, lindex)), reason); } @@ -570,12 +580,13 @@ static void json_encode_exception(lua_State *l, json_config_t *cfg, strbuf_t *js * - String (Lua stack index) * * Returns nothing. Doesn't remove string from Lua stack */ -static void json_append_string(lua_State *l, strbuf_t *json, int lindex) +static void json_append_string(lua_State *l, json_encode_t *ctx, int lindex) { const char *escstr; unsigned i; const char *str; size_t len; + strbuf_t *json = ctx->json; str = lua_tolstring(l, lindex, &len); @@ -587,7 +598,7 @@ static void json_append_string(lua_State *l, strbuf_t *json, int lindex) strbuf_append_char_unsafe(json, '\"'); for (i = 0; i < len; i++) { - escstr = char2escape[(unsigned char)str[i]]; + escstr = (*ctx->options->char2escape)[(unsigned char)str[i]]; if (escstr) strbuf_append_string(json, escstr); else @@ -600,11 +611,12 @@ static void json_append_string(lua_State *l, strbuf_t *json, int lindex) * -1 object (not a pure array) * >=0 elements in array */ -static int lua_array_length(lua_State *l, json_config_t *cfg, strbuf_t *json) +static int lua_array_length(lua_State *l, json_encode_t *ctx) { double k; int max; int items; + json_config_t *cfg = ctx->cfg; max = 0; items = 0; @@ -635,7 +647,7 @@ static int lua_array_length(lua_State *l, json_config_t *cfg, strbuf_t *json) max > items * cfg->encode_sparse_ratio && max > cfg->encode_sparse_safe) { if (!cfg->encode_sparse_convert) - json_encode_exception(l, cfg, json, -1, "excessively sparse array"); + json_encode_exception(l, ctx, -1, "excessively sparse array"); return -1; } @@ -666,17 +678,18 @@ static void json_check_encode_depth(lua_State *l, json_config_t *cfg, current_depth); } -static void json_append_data(lua_State *l, json_config_t *cfg, - int current_depth, strbuf_t *json); +static void json_append_data(lua_State *l, json_encode_t *cfg, + int current_depth); /* json_append_array args: * - lua_State * - JSON strbuf * - Size of passwd Lua array (top of stack) */ -static void json_append_array(lua_State *l, json_config_t *cfg, int current_depth, - strbuf_t *json, int array_length) +static void json_append_array(lua_State *l, json_encode_t *ctx, int current_depth, + int array_length) { int comma, i; + strbuf_t *json = ctx->json; strbuf_append_char(json, '['); @@ -688,23 +701,25 @@ static void json_append_array(lua_State *l, json_config_t *cfg, int current_dept comma = 1; lua_rawgeti(l, -1, i); - json_append_data(l, cfg, current_depth, json); + json_append_data(l, ctx, current_depth); lua_pop(l, 1); } strbuf_append_char(json, ']'); } -static void json_append_number(lua_State *l, json_config_t *cfg, - strbuf_t *json, int lindex) +static void json_append_number(lua_State *l, json_encode_t *ctx, + int lindex) { double num = lua_tonumber(l, lindex); int len; + json_config_t *cfg = ctx->cfg; + strbuf_t *json = ctx->json; if (cfg->encode_invalid_numbers == 0) { /* Prevent encoding invalid numbers */ if (isinf(num) || isnan(num)) - json_encode_exception(l, cfg, json, lindex, + json_encode_exception(l, ctx, lindex, "must not be NaN or Infinity"); } else if (cfg->encode_invalid_numbers == 1) { /* Encode NaN/Infinity separately to ensure Javascript compatible @@ -733,10 +748,11 @@ static void json_append_number(lua_State *l, json_config_t *cfg, strbuf_extend_length(json, len); } -static void json_append_object(lua_State *l, json_config_t *cfg, - int current_depth, strbuf_t *json) +static void json_append_object(lua_State *l, json_encode_t *ctx, + int current_depth) { int comma, keytype; + strbuf_t *json = ctx->json; /* Object */ strbuf_append_char(json, '{'); @@ -754,19 +770,19 @@ static void json_append_object(lua_State *l, json_config_t *cfg, keytype = lua_type(l, -2); if (keytype == LUA_TNUMBER) { strbuf_append_char(json, '"'); - json_append_number(l, cfg, json, -2); + json_append_number(l, ctx, -2); strbuf_append_mem(json, "\":", 2); } else if (keytype == LUA_TSTRING) { - json_append_string(l, json, -2); + json_append_string(l, ctx, -2); strbuf_append_char(json, ':'); } else { - json_encode_exception(l, cfg, json, -2, + json_encode_exception(l, ctx, -2, "table key must be a number or string"); /* never returns */ } /* table, key, value */ - json_append_data(l, cfg, current_depth, json); + json_append_data(l, ctx, current_depth); lua_pop(l, 1); /* table, key */ } @@ -775,20 +791,22 @@ static void json_append_object(lua_State *l, json_config_t *cfg, } /* Serialise Lua data into JSON string. */ -static void json_append_data(lua_State *l, json_config_t *cfg, - int current_depth, strbuf_t *json) +static void json_append_data(lua_State *l, json_encode_t *ctx, + int current_depth) { int len; int as_array = 0; int as_empty_dict = 0; int has_metatable; + json_config_t *cfg = ctx->cfg; + strbuf_t *json = ctx->json; switch (lua_type(l, -1)) { case LUA_TSTRING: - json_append_string(l, json, -1); + json_append_string(l, ctx, -1); break; case LUA_TNUMBER: - json_append_number(l, cfg, json, -1); + json_append_number(l, ctx, -1); break; case LUA_TBOOLEAN: if (lua_toboolean(l, -1)) @@ -818,12 +836,12 @@ static void json_append_data(lua_State *l, json_config_t *cfg, if (as_array) { len = lua_objlen(l, -1); - json_append_array(l, cfg, current_depth, json, len); + json_append_array(l, ctx, current_depth, len); } else { - len = lua_array_length(l, cfg, json); + len = lua_array_length(l, ctx); if (len > 0 || (len == 0 && !cfg->encode_empty_table_as_object && !as_empty_dict)) { - json_append_array(l, cfg, current_depth, json, len); + json_append_array(l, ctx, current_depth, len); } else { if (has_metatable) { lua_getmetatable(l, -1); @@ -833,11 +851,11 @@ static void json_append_data(lua_State *l, json_config_t *cfg, as_array = lua_rawequal(l, -1, -2); lua_pop(l, 2); /* pop pointer + metatable */ if (as_array) { - json_append_array(l, cfg, current_depth, json, 0); + json_append_array(l, ctx, current_depth, 0); break; } } - json_append_object(l, cfg, current_depth, json); + json_append_object(l, ctx, current_depth); } } break; @@ -846,7 +864,7 @@ static void json_append_data(lua_State *l, json_config_t *cfg, break; case LUA_TLIGHTUSERDATA: if (lua_touserdata(l, -1) == &json_array) { - json_append_array(l, cfg, current_depth, json, 0); + json_append_array(l, ctx, current_depth, 0); } break; case LUA_TUSERDATA: @@ -862,7 +880,7 @@ static void json_append_data(lua_State *l, json_config_t *cfg, default: /* Remaining types (LUA_TFUNCTION, LUA_TUSERDATA, LUA_TTHREAD, * and LUA_TLIGHTUSERDATA) cannot be serialised */ - json_encode_exception(l, cfg, json, -1, "type not supported"); + json_encode_exception(l, ctx, -1, "type not supported"); /* never returns */ } } @@ -870,12 +888,44 @@ static void json_append_data(lua_State *l, json_config_t *cfg, static int json_encode(lua_State *l) { json_config_t *cfg = json_fetch_config(l); + json_encode_options_t options = { .char2escape = { char2escape } }; + json_encode_t ctx = { .options = &options, .cfg = cfg }; strbuf_t local_encode_buf; strbuf_t *encode_buf; char *json; int len; + const char *customChar2escape[256]; - luaL_argcheck(l, lua_gettop(l) == 1, 1, "expected 1 argument"); + switch (lua_gettop(l)) { + case 1: + break; + case 2: + luaL_checktype(l, 2, LUA_TTABLE); + lua_getfield(l, 2, "escape_slash"); + + /* We only handle the escape_slash option for now */ + if (lua_isnil(l, -1)) { + lua_pop(l, 2); + break; + } + + luaL_checktype(l, -1, LUA_TBOOLEAN); + + int escape_slash = lua_toboolean(l, -1); + + if (escape_slash) { + /* This can be optimised by adding a new hard-coded escape table for this case, + * but this path will rarely if ever be used, so let's just memcpy.*/ + memcpy(customChar2escape, char2escape, sizeof(char2escape)); + customChar2escape['/'] = "\\/"; + *ctx.options->char2escape = customChar2escape; + } + + lua_pop(l, 2); + break; + default: + return luaL_error (l, "expected 1 or 2 arguments"); + } if (!cfg->encode_keep_buffer) { /* Use private buffer */ @@ -887,7 +937,8 @@ static int json_encode(lua_State *l) strbuf_reset(encode_buf); } - json_append_data(l, cfg, 0, encode_buf); + ctx.json = encode_buf; + json_append_data(l, &ctx, 0); json = strbuf_string(encode_buf, &len); lua_pushlstring(l, json, len); diff --git a/src/nvim/api/autocmd.c b/src/nvim/api/autocmd.c index db87500d08..45e2de69e0 100644 --- a/src/nvim/api/autocmd.c +++ b/src/nvim/api/autocmd.c @@ -386,9 +386,9 @@ cleanup: /// - id: (number) autocommand id /// - event: (string) name of the triggered event |autocmd-events| /// - group: (number|nil) autocommand group id, if any -/// - match: (string) expanded value of [<amatch>] -/// - buf: (number) expanded value of [<abuf>] -/// - file: (string) expanded value of [<afile>] +/// - file: (string) [<afile>] (not expanded to a full path) +/// - match: (string) [<amatch>] (expanded to a full path) +/// - buf: (number) [<abuf>] /// - data: (any) arbitrary data passed from [nvim_exec_autocmds()] [event-data]() /// - command (string) optional: Vim command to execute on event. Cannot be used with /// {callback} diff --git a/src/nvim/autocmd.c b/src/nvim/autocmd.c index c08ef7a4c1..118a50e15d 100644 --- a/src/nvim/autocmd.c +++ b/src/nvim/autocmd.c @@ -1666,7 +1666,9 @@ bool apply_autocmds_group(event_T event, char *fname, char *fname_io, bool force } else { autocmd_fname = fname_io; } + char *afile_orig = NULL; ///< Unexpanded <afile> if (autocmd_fname != NULL) { + afile_orig = xstrdup(autocmd_fname); // Allocate MAXPATHL for when eval_vars() resolves the fullpath. autocmd_fname = xstrnsave(autocmd_fname, MAXPATHL); } @@ -1798,6 +1800,7 @@ bool apply_autocmds_group(event_T event, char *fname, char *fname_io, bool force // save vector size, to avoid an endless loop when more patterns // are added when executing autocommands .ausize = kv_size(autocmds[(int)event]), + .afile_orig = afile_orig, .fname = fname, .sfname = sfname, .tail = tail, @@ -1865,6 +1868,7 @@ bool apply_autocmds_group(event_T event, char *fname, char *fname_io, bool force autocmd_nested = save_autocmd_nested; xfree(SOURCING_NAME); estack_pop(); + xfree(afile_orig); xfree(autocmd_fname); autocmd_fname = save_autocmd_fname; autocmd_fname_full = save_autocmd_fname_full; @@ -2029,8 +2033,8 @@ static bool call_autocmd_callback(const AutoCmd *ac, const AutoPatCmd *apc) MAXSIZE_TEMP_DICT(data, 7); PUT_C(data, "id", INTEGER_OBJ(ac->id)); PUT_C(data, "event", CSTR_AS_OBJ(event_nr2name(apc->event))); + PUT_C(data, "file", CSTR_AS_OBJ(apc->afile_orig)); PUT_C(data, "match", CSTR_AS_OBJ(autocmd_match)); - PUT_C(data, "file", CSTR_AS_OBJ(autocmd_fname)); PUT_C(data, "buf", INTEGER_OBJ(autocmd_bufnr)); if (apc->data) { diff --git a/src/nvim/autocmd_defs.h b/src/nvim/autocmd_defs.h index 490782b209..cba947e85f 100644 --- a/src/nvim/autocmd_defs.h +++ b/src/nvim/autocmd_defs.h @@ -52,6 +52,7 @@ struct AutoPatCmd_S { AutoPat *lastpat; ///< Last matched AutoPat size_t auidx; ///< Current autocmd index to execute size_t ausize; ///< Saved AutoCmd vector size + char *afile_orig; ///< Unexpanded <afile> char *fname; ///< Fname to match with char *sfname; ///< Sfname to match with char *tail; ///< Tail of fname diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 248f419807..f5e11a188f 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -604,7 +604,7 @@ static int insert_execute(VimState *state, int key) || (ins_compl_enter_selects() && (s->c == CAR || s->c == K_KENTER || s->c == NL))) && stop_arrow() == OK) { - ins_compl_delete(); + ins_compl_delete(false); ins_compl_insert(false); } } diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 9968f32de1..b7c83ea1ac 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -6101,12 +6101,18 @@ static void ex_sleep(exarg_T *eap) default: semsg(_(e_invarg2), eap->arg); return; } - do_sleep(len); + do_sleep(len, false); } /// Sleep for "msec" milliseconds, but return early on CTRL-C. -void do_sleep(int64_t msec) +/// +/// @param hide_cursor hide the cursor if true +void do_sleep(int64_t msec, bool hide_cursor) { + if (hide_cursor) { + ui_busy_start(); + } + ui_flush(); // flush before waiting LOOP_PROCESS_EVENTS_UNTIL(&main_loop, main_loop.events, msec, got_int); @@ -6115,6 +6121,10 @@ void do_sleep(int64_t msec) if (got_int) { vpeekc(); } + + if (hide_cursor) { + ui_busy_stop(); + } } /// ":winsize" command (obsolete). diff --git a/src/nvim/generators/gen_options.lua b/src/nvim/generators/gen_options.lua index 779b31e7a0..c79683dc00 100644 --- a/src/nvim/generators/gen_options.lua +++ b/src/nvim/generators/gen_options.lua @@ -47,7 +47,9 @@ end --- @param s string --- @return string local lowercase_to_titlecase = function(s) - return s:sub(1, 1):upper() .. s:sub(2) + return table.concat(vim.tbl_map(function(word) --- @param word string + return word:sub(1, 1):upper() .. word:sub(2) + end, vim.split(s, '[-_]'))) end -- Generate options enum file @@ -177,7 +179,7 @@ for _, option in ipairs(options_meta) do vars_w( (' kOpt%sFlag%s = 0x%02x,'):format( opt_name, - lowercase_to_titlecase(flag_name), + lowercase_to_titlecase(flag_name:gsub(':$', '')), enum_values[flag_name] ) ) diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c index 93d081153c..b18c4ead41 100644 --- a/src/nvim/insexpand.c +++ b/src/nvim/insexpand.c @@ -951,14 +951,14 @@ static void ins_compl_longest_match(compl_T *match) compl_leader = xstrdup(match->cp_str); bool had_match = (curwin->w_cursor.col > compl_col); - ins_compl_delete(); + ins_compl_delete(false); ins_bytes(compl_leader + get_compl_len()); ins_redraw(false); // When the match isn't there (to avoid matching itself) remove it // again after redrawing. if (!had_match) { - ins_compl_delete(); + ins_compl_delete(false); } compl_used_match = false; @@ -985,14 +985,14 @@ static void ins_compl_longest_match(compl_T *match) // Leader was shortened, need to change the inserted text. *p = NUL; bool had_match = (curwin->w_cursor.col > compl_col); - ins_compl_delete(); + ins_compl_delete(false); ins_bytes(compl_leader + get_compl_len()); ins_redraw(false); // When the match isn't there (to avoid matching itself) remove it // again after redrawing. if (!had_match) { - ins_compl_delete(); + ins_compl_delete(false); } } @@ -1811,9 +1811,8 @@ static bool ins_compl_need_restart(void) static void ins_compl_new_leader(void) { ins_compl_del_pum(); - ins_compl_delete(); + ins_compl_delete(true); ins_bytes(compl_leader + get_compl_len()); - restore_orig_extmarks(); compl_used_match = false; if (compl_started) { @@ -2137,7 +2136,7 @@ static bool ins_compl_stop(const int c, const int prev_mode, bool retval) // CTRL-E means completion is Ended, go back to the typed text. // but only do this, if the Popup is still visible if (c == Ctrl_E) { - ins_compl_delete(); + ins_compl_delete(false); char *p = NULL; if (compl_leader != NULL) { p = compl_leader; @@ -3610,11 +3609,24 @@ static void ins_compl_update_shown_match(void) } /// Delete the old text being completed. -void ins_compl_delete(void) +void ins_compl_delete(bool new_leader) { + // Avoid deleting text that will be reinserted when changing leader. This + // allows marks present on the original text to shrink/grow appropriately. + int orig_col = 0; + if (new_leader) { + char *orig = compl_orig_text; + char *leader = ins_compl_leader(); + while (*orig != NUL && utf_ptr2char(orig) == utf_ptr2char(leader)) { + leader += utf_ptr2len(leader); + orig += utf_ptr2len(orig); + } + orig_col = (int)(orig - compl_orig_text); + } + // In insert mode: Delete the typed part. // In replace mode: Put the old characters back, if any. - int col = compl_col + (compl_status_adding() ? compl_length : 0); + int col = compl_col + (compl_status_adding() ? compl_length : orig_col); if ((int)curwin->w_cursor.col > col) { if (stop_arrow() == FAIL) { return; @@ -3858,7 +3870,7 @@ static int ins_compl_next(bool allow_get_expansion, int count, bool insert_match if (allow_get_expansion && insert_match && (!compl_get_longest || compl_used_match)) { // Delete old text to be replaced - ins_compl_delete(); + ins_compl_delete(false); } // When finding the longest common text we stick at the original text, @@ -3911,7 +3923,7 @@ static int ins_compl_next(bool allow_get_expansion, int count, bool insert_match // Delete old text to be replaced, since we're still searching and // don't want to match ourselves! - ins_compl_delete(); + ins_compl_delete(false); } // Enter will select a match when the match wasn't inserted and the popup diff --git a/src/nvim/message.c b/src/nvim/message.c index 0b1156a6bd..a32a06edca 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -26,6 +26,7 @@ #include "nvim/event/loop.h" #include "nvim/event/multiqueue.h" #include "nvim/ex_cmds_defs.h" +#include "nvim/ex_docmd.h" #include "nvim/ex_eval.h" #include "nvim/fileio.h" #include "nvim/garray.h" @@ -94,6 +95,16 @@ static char *confirm_msg_tail; // tail of confirm_msg MessageHistoryEntry *first_msg_hist = NULL; MessageHistoryEntry *last_msg_hist = NULL; static int msg_hist_len = 0; +static int msg_hist_max = 500; // The default max value is 500 + +// args in 'messagesopt' option +#define MESSAGES_OPT_HIT_ENTER "hit-enter" +#define MESSAGES_OPT_WAIT "wait:" +#define MESSAGES_OPT_HISTORY "history:" + +// The default is "hit-enter,history:500" +static int msg_flags = kOptMoptFlagHitEnter | kOptMoptFlagHistory; +static int msg_wait = 0; static FILE *verbose_fd = NULL; static bool verbose_did_open = false; @@ -1038,14 +1049,69 @@ int delete_first_msg(void) return OK; } -void check_msg_hist(void) +static void check_msg_hist(void) { // Don't let the message history get too big - while (msg_hist_len > 0 && msg_hist_len > p_mhi) { + while (msg_hist_len > 0 && msg_hist_len > msg_hist_max) { (void)delete_first_msg(); } } +int messagesopt_changed(void) +{ + int messages_flags_new = 0; + int messages_wait_new = 0; + int messages_history_new = 0; + + char *p = p_meo; + while (*p != NUL) { + if (strnequal(p, S_LEN(MESSAGES_OPT_HIT_ENTER))) { + p += STRLEN_LITERAL(MESSAGES_OPT_HIT_ENTER); + messages_flags_new |= kOptMoptFlagHitEnter; + } else if (strnequal(p, S_LEN(MESSAGES_OPT_WAIT)) + && ascii_isdigit(p[STRLEN_LITERAL(MESSAGES_OPT_WAIT)])) { + p += STRLEN_LITERAL(MESSAGES_OPT_WAIT); + messages_wait_new = getdigits_int(&p, false, INT_MAX); + messages_flags_new |= kOptMoptFlagWait; + } else if (strnequal(p, S_LEN(MESSAGES_OPT_HISTORY)) + && ascii_isdigit(p[STRLEN_LITERAL(MESSAGES_OPT_HISTORY)])) { + p += STRLEN_LITERAL(MESSAGES_OPT_HISTORY); + messages_history_new = getdigits_int(&p, false, INT_MAX); + messages_flags_new |= kOptMoptFlagHistory; + } + + if (*p != ',' && *p != NUL) { + return FAIL; + } + if (*p == ',') { + p++; + } + } + + // Either "wait" or "hit-enter" is required + if (!(messages_flags_new & (kOptMoptFlagHitEnter | kOptMoptFlagWait))) { + return FAIL; + } + + // "history" must be set + if (!(messages_flags_new & kOptMoptFlagHistory)) { + return FAIL; + } + + // "history" must be <= 10000 + if (messages_history_new > 10000) { + return FAIL; + } + + msg_flags = messages_flags_new; + msg_wait = messages_wait_new; + + msg_hist_max = messages_history_new; + check_msg_hist(); + + return OK; +} + /// :messages command implementation void ex_messages(exarg_T *eap) FUNC_ATTR_NONNULL_ALL @@ -1209,83 +1275,88 @@ void wait_return(int redraw) cmdline_row = Rows - 1; } - hit_return_msg(true); - - do { - // Remember "got_int", if it is set vgetc() probably returns a - // CTRL-C, but we need to loop then. - had_got_int = got_int; - - // Don't do mappings here, we put the character back in the - // typeahead buffer. - no_mapping++; - allow_keys++; - - // Temporarily disable Recording. If Recording is active, the - // character will be recorded later, since it will be added to the - // typebuf after the loop - const int save_reg_recording = reg_recording; - save_scriptout = scriptout; - reg_recording = 0; - scriptout = NULL; - c = safe_vgetc(); - if (had_got_int && !global_busy) { - got_int = false; - } - no_mapping--; - allow_keys--; - reg_recording = save_reg_recording; - scriptout = save_scriptout; - - // Allow scrolling back in the messages. - // Also accept scroll-down commands when messages fill the screen, - // to avoid that typing one 'j' too many makes the messages - // disappear. - if (p_more) { - if (c == 'b' || c == 'k' || c == 'u' || c == 'g' - || c == K_UP || c == K_PAGEUP) { - if (msg_scrolled > Rows) { - // scroll back to show older messages - do_more_prompt(c); - } else { - msg_didout = false; - c = K_IGNORE; - msg_col = 0; - } - if (quit_more) { - c = CAR; // just pretend CR was hit - quit_more = false; - got_int = false; - } else if (c != K_IGNORE) { + if (msg_flags & kOptMoptFlagHitEnter) { + hit_return_msg(true); + + do { + // Remember "got_int", if it is set vgetc() probably returns a + // CTRL-C, but we need to loop then. + had_got_int = got_int; + + // Don't do mappings here, we put the character back in the + // typeahead buffer. + no_mapping++; + allow_keys++; + + // Temporarily disable Recording. If Recording is active, the + // character will be recorded later, since it will be added to the + // typebuf after the loop + const int save_reg_recording = reg_recording; + save_scriptout = scriptout; + reg_recording = 0; + scriptout = NULL; + c = safe_vgetc(); + if (had_got_int && !global_busy) { + got_int = false; + } + no_mapping--; + allow_keys--; + reg_recording = save_reg_recording; + scriptout = save_scriptout; + + // Allow scrolling back in the messages. + // Also accept scroll-down commands when messages fill the screen, + // to avoid that typing one 'j' too many makes the messages + // disappear. + if (p_more) { + if (c == 'b' || c == 'k' || c == 'u' || c == 'g' + || c == K_UP || c == K_PAGEUP) { + if (msg_scrolled > Rows) { + // scroll back to show older messages + do_more_prompt(c); + } else { + msg_didout = false; + c = K_IGNORE; + msg_col = 0; + } + if (quit_more) { + c = CAR; // just pretend CR was hit + quit_more = false; + got_int = false; + } else if (c != K_IGNORE) { + c = K_IGNORE; + hit_return_msg(false); + } + } else if (msg_scrolled > Rows - 2 + && (c == 'j' || c == 'd' || c == 'f' + || c == K_DOWN || c == K_PAGEDOWN)) { c = K_IGNORE; - hit_return_msg(false); } - } else if (msg_scrolled > Rows - 2 - && (c == 'j' || c == 'd' || c == 'f' - || c == K_DOWN || c == K_PAGEDOWN)) { - c = K_IGNORE; } + } while ((had_got_int && c == Ctrl_C) + || c == K_IGNORE + || c == K_LEFTDRAG || c == K_LEFTRELEASE + || c == K_MIDDLEDRAG || c == K_MIDDLERELEASE + || c == K_RIGHTDRAG || c == K_RIGHTRELEASE + || c == K_MOUSELEFT || c == K_MOUSERIGHT + || c == K_MOUSEDOWN || c == K_MOUSEUP + || c == K_MOUSEMOVE); + os_breakcheck(); + + // Avoid that the mouse-up event causes visual mode to start. + if (c == K_LEFTMOUSE || c == K_MIDDLEMOUSE || c == K_RIGHTMOUSE + || c == K_X1MOUSE || c == K_X2MOUSE) { + jump_to_mouse(MOUSE_SETPOS, NULL, 0); + } else if (vim_strchr("\r\n ", c) == NULL && c != Ctrl_C) { + // Put the character back in the typeahead buffer. Don't use the + // stuff buffer, because lmaps wouldn't work. + ins_char_typebuf(vgetc_char, vgetc_mod_mask, true); + do_redraw = true; // need a redraw even though there is typeahead } - } while ((had_got_int && c == Ctrl_C) - || c == K_IGNORE - || c == K_LEFTDRAG || c == K_LEFTRELEASE - || c == K_MIDDLEDRAG || c == K_MIDDLERELEASE - || c == K_RIGHTDRAG || c == K_RIGHTRELEASE - || c == K_MOUSELEFT || c == K_MOUSERIGHT - || c == K_MOUSEDOWN || c == K_MOUSEUP - || c == K_MOUSEMOVE); - os_breakcheck(); - - // Avoid that the mouse-up event causes visual mode to start. - if (c == K_LEFTMOUSE || c == K_MIDDLEMOUSE || c == K_RIGHTMOUSE - || c == K_X1MOUSE || c == K_X2MOUSE) { - jump_to_mouse(MOUSE_SETPOS, NULL, 0); - } else if (vim_strchr("\r\n ", c) == NULL && c != Ctrl_C) { - // Put the character back in the typeahead buffer. Don't use the - // stuff buffer, because lmaps wouldn't work. - ins_char_typebuf(vgetc_char, vgetc_mod_mask, true); - do_redraw = true; // need a redraw even though there is - // typeahead + } else { + c = CAR; + // Wait to allow the user to verify the output. + do_sleep(msg_wait, true); } } redir_off = false; diff --git a/src/nvim/normal.c b/src/nvim/normal.c index ba84380529..92a6068c5a 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -812,7 +812,7 @@ static void normal_get_additional_char(NormalState *s) // There is a busy wait here when typing "f<C-\>" and then // something different from CTRL-N. Can't be avoided. while ((s->c = vpeekc()) <= 0 && towait > 0) { - do_sleep(towait > 50 ? 50 : towait); + do_sleep(towait > 50 ? 50 : towait, false); towait -= 50; } if (s->c > 0) { @@ -5561,7 +5561,7 @@ static void nv_g_cmd(cmdarg_T *cap) // "gs": Goto sleep. case 's': - do_sleep(cap->count1 * 1000); + do_sleep(cap->count1 * 1000, false); break; // "ga": Display the ascii value of the character under the diff --git a/src/nvim/option.c b/src/nvim/option.c index fd0ac375a6..e261f06b42 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -2199,13 +2199,6 @@ static const char *did_set_modified(optset_T *args) return NULL; } -/// Process the updated 'msghistory' option value. -static const char *did_set_msghistory(optset_T *args FUNC_ATTR_UNUSED) -{ - check_msg_hist(); - return NULL; -} - /// Process the updated 'number' or 'relativenumber' option value. static const char *did_set_number_relativenumber(optset_T *args) { @@ -2886,13 +2879,6 @@ static const char *validate_num_option(OptIndex opt_idx, OptInt *newval, char *e return e_invarg; } break; - case kOptMsghistory: - if (value < 0) { - return e_positive; - } else if (value > 10000) { - return e_invarg; - } - break; case kOptPyxversion: if (value == 0) { *newval = 3; diff --git a/src/nvim/option_vars.h b/src/nvim/option_vars.h index 3bb2035e7c..aca876bddb 100644 --- a/src/nvim/option_vars.h +++ b/src/nvim/option_vars.h @@ -448,6 +448,7 @@ EXTERN OptInt p_mfd; ///< 'maxfuncdepth' EXTERN OptInt p_mmd; ///< 'maxmapdepth' EXTERN OptInt p_mmp; ///< 'maxmempattern' EXTERN OptInt p_mis; ///< 'menuitems' +EXTERN char *p_meo; ///< 'messagesopt' EXTERN char *p_msm; ///< 'mkspellmem' EXTERN int p_ml; ///< 'modeline' EXTERN int p_mle; ///< 'modelineexpr' @@ -464,7 +465,6 @@ EXTERN OptInt p_mousescroll_vert INIT( = MOUSESCROLL_VERT_DFLT); EXTERN OptInt p_mousescroll_hor INIT( = MOUSESCROLL_HOR_DFLT); EXTERN OptInt p_mouset; ///< 'mousetime' EXTERN int p_more; ///< 'more' -EXTERN OptInt p_mhi; ///< 'msghistory' EXTERN char *p_nf; ///< 'nrformats' EXTERN char *p_opfunc; ///< 'operatorfunc' EXTERN char *p_para; ///< 'paragraphs' diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 97fe09f376..a5d5a45b59 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -4094,7 +4094,7 @@ return { desc = [=[ A history of ":" commands, and a history of previous search patterns is remembered. This option decides how many entries may be stored in - each of these histories (see |cmdline-editing| and 'msghistory' for + each of these histories (see |cmdline-editing| and 'messagesopt' for the number of messages to remember). The maximum value is 10000. ]=], @@ -5449,6 +5449,38 @@ return { varname = 'p_mis', }, { + abbreviation = 'mopt', + cb = 'did_set_messagesopt', + defaults = { if_true = 'hit-enter,history:500' }, + values = { 'hit-enter', 'wait:', 'history:' }, + flags = true, + deny_duplicates = true, + desc = [=[ + Option settings when outputting messages. It can consist of the + following items. Items must be separated by a comma. + + hit-enter Use |hit-enter| prompt when the message is longer than + 'cmdheight' size. + + wait:{n} Ignored when "hit-enter" is present. Instead of using + |hit-enter| prompt, will simply wait for {n} + milliseconds so the user has a chance to read the + message, use 0 to disable sleep (but then the user may + miss an important message). + + history:{n} Determines how many entries are remembered in the + |:messages| history. The maximum value is 10000. + Setting it to zero clears the message history. + ]=], + expand_cb = 'expand_set_messagesopt', + full_name = 'messagesopt', + list = 'onecommacolon', + scope = { 'global' }, + short_desc = N_('options for outputting messages'), + type = 'string', + varname = 'p_meo', + }, + { abbreviation = 'msm', cb = 'did_set_mkspellmem', defaults = { if_true = '460000,2000,500' }, @@ -5893,21 +5925,6 @@ return { varname = 'p_mouset', }, { - abbreviation = 'mhi', - cb = 'did_set_msghistory', - defaults = { if_true = 500 }, - desc = [=[ - Determines how many entries are remembered in the |:messages| history. - The maximum value is 10000. - Setting it to zero clears the message history. - ]=], - full_name = 'msghistory', - scope = { 'global' }, - short_desc = N_('how many messages are remembered'), - type = 'number', - varname = 'p_mhi', - }, - { abbreviation = 'nf', cb = 'did_set_nrformats', defaults = { if_true = 'bin,hex' }, diff --git a/src/nvim/optionstr.c b/src/nvim/optionstr.c index 918443db9f..75b6585553 100644 --- a/src/nvim/optionstr.c +++ b/src/nvim/optionstr.c @@ -1682,6 +1682,24 @@ const char *did_set_matchpairs(optset_T *args) return NULL; } +/// Process the updated 'messagesopt' option value. +const char *did_set_messagesopt(optset_T *args FUNC_ATTR_UNUSED) +{ + if (messagesopt_changed() == FAIL) { + return e_invarg; + } + return NULL; +} + +int expand_set_messagesopt(optexpand_T *args, int *numMatches, char ***matches) +{ + return expand_set_opt_string(args, + opt_mopt_values, + ARRAY_SIZE(opt_mopt_values) - 1, + numMatches, + matches); +} + /// The 'mkspellmem' option is changed. const char *did_set_mkspellmem(optset_T *args FUNC_ATTR_UNUSED) { diff --git a/test/functional/api/autocmd_spec.lua b/test/functional/api/autocmd_spec.lua index 3f9883f43f..bb6456f45b 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() diff --git a/test/functional/editor/completion_spec.lua b/test/functional/editor/completion_spec.lua index eb23de2007..c20d925713 100644 --- a/test/functional/editor/completion_spec.lua +++ b/test/functional/editor/completion_spec.lua @@ -1265,7 +1265,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 @@ -1312,5 +1312,18 @@ describe('completion', function() {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) end) diff --git a/test/functional/legacy/messages_spec.lua b/test/functional/legacy/messages_spec.lua index bc58551a34..063bd05618 100644 --- a/test/functional/legacy/messages_spec.lua +++ b/test/functional/legacy/messages_spec.lua @@ -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:100,history:500') + feed(":echo 'foo' | echo 'bar' | echo 'baz'\n") + screen:expect({ + grid = [[ + | + {1:~ }| + {3: }| + foo | + bar | + baz | + ]], + timeout = 100, + }) + screen:expect([[ + ^ | + {1:~ }|*4 + | + ]]) + end) end) diff --git a/test/functional/lua/filetype_spec.lua b/test/functional/lua/filetype_spec.lua index 6a4f8d70f3..b75ff75b05 100644 --- a/test/functional/lua/filetype_spec.lua +++ b/test/functional/lua/filetype_spec.lua @@ -208,7 +208,7 @@ describe('filetype.lua', function() eq('gitconfig', api.nvim_get_option_value('filetype', {})) end) - pending('works with :doautocmd BufRead #31306', function() + it('works with :doautocmd BufRead #31306', function() clear({ args = { '--clean' } }) eq('', api.nvim_get_option_value('filetype', {})) command('doautocmd BufRead README.md') diff --git a/test/functional/lua/json_spec.lua b/test/functional/lua/json_spec.lua index a6e814d739..8df42fcaa1 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('dumps strings with & without escaped 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/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/treesitter/parser_spec.lua b/test/functional/treesitter/parser_spec.lua index 2f8d204d36..2f80cee226 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,7 @@ 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 describe('treesitter parser API', function() before_each(function() @@ -644,6 +646,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 +764,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() 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/old/testdir/gen_opt_test.vim b/test/old/testdir/gen_opt_test.vim index be5a7e6ee4..74aea93ee9 100644 --- a/test/old/testdir/gen_opt_test.vim +++ b/test/old/testdir/gen_opt_test.vim @@ -117,7 +117,6 @@ 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]], @@ -264,6 +263,11 @@ 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', 'hit-enter']], \ 'mkspellmem': [['10000,100,12'], ['', 'xxx', '10000,100']], \ 'mouse': [['', 'n', 'v', 'i', 'c', 'h', 'a', 'r', 'nvi'], \ ['xxx', 'n,v,i']], diff --git a/test/old/testdir/test_cmdline.vim b/test/old/testdir/test_cmdline.vim index 2f34ecb414..cbce0e908d 100644 --- a/test/old/testdir/test_cmdline.vim +++ b/test/old/testdir/test_cmdline.vim @@ -4181,30 +4181,4 @@ func Test_cd_bslash_completion_windows() let &shellslash = save_shellslash endfunc -func Test_msghistory() - " After setting 'msghistory' to 2 and outputting a message 4 times with - " :echomsg, is the number of output lines of :messages 2? - set msghistory=2 - echomsg 'foo' - echomsg 'bar' - echomsg 'baz' - echomsg 'foobar' - call assert_equal(['baz', 'foobar'], GetMessages()) - - " When the number of messages is 10 and 'msghistory' is changed to 5, is the - " number of output lines of :messages 5? - set msghistory=10 - for num in range(1, 10) - echomsg num - endfor - set msghistory=5 - call assert_equal(5, len(GetMessages())) - - " Check empty list - set msghistory=0 - call assert_true(empty(GetMessages())) - - set msghistory& -endfunc - " vim: shiftwidth=2 sts=2 expandtab diff --git a/test/old/testdir/test_messages.vim b/test/old/testdir/test_messages.vim index ac5184645f..d79bc0fae7 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_options.vim b/test/old/testdir/test_options.vim index 3773775564..d090186b62 100644 --- a/test/old/testdir/test_options.vim +++ b/test/old/testdir/test_options.vim @@ -644,6 +644,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 " @@ -743,7 +747,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 +760,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:') @@ -2517,6 +2519,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'], |