diff options
44 files changed, 345 insertions, 308 deletions
@@ -14,7 +14,7 @@ else TOUCH := touch RM := rm -rf CMAKE := $(shell (command -v cmake3 || echo cmake)) - CMAKE_GENERATOR ?= $(shell (command -v ninja > /dev/null 2>&1 && echo "Ninja") || echo "Unix Makefiles") + CMAKE_GENERATOR ?= "$(shell (command -v ninja > /dev/null 2>&1 && echo "Ninja") || echo "Unix Makefiles")" define rmdir rm -rf $1 endef diff --git a/runtime/autoload/provider/clipboard.vim b/runtime/autoload/provider/clipboard.vim index 82e0953196..58d3d4550f 100644 --- a/runtime/autoload/provider/clipboard.vim +++ b/runtime/autoload/provider/clipboard.vim @@ -140,6 +140,18 @@ function! provider#clipboard#Executable() abort let s:copy['*'] = s:copy['+'] let s:paste['*'] = s:paste['+'] return 'win32yank' + elseif executable('putclip') && executable('getclip') + let s:copy['+'] = ['putclip'] + let s:paste['+'] = ['getclip'] + let s:copy['*'] = s:copy['+'] + let s:paste['*'] = s:paste['+'] + return 'putclip' + elseif executable('clip') && executable('powershell') + let s:copy['+'] = ['clip'] + let s:paste['+'] = ['powershell', '-NoProfile', '-NoLogo', '-Command', 'Get-Clipboard'] + let s:copy['*'] = s:copy['+'] + let s:paste['*'] = s:paste['+'] + return 'clip' elseif executable('termux-clipboard-set') let s:copy['+'] = ['termux-clipboard-set'] let s:paste['+'] = ['termux-clipboard-get'] diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt index f1d8cc8526..cd8abc2351 100644 --- a/runtime/doc/builtin.txt +++ b/runtime/doc/builtin.txt @@ -4361,6 +4361,7 @@ maparg({name} [, {mode} [, {abbr} [, {dict}]]]) *maparg()* "lhsrawalt" The {lhs} of the mapping as raw bytes, alternate form, only present when it differs from "lhsraw" "rhs" The {rhs} of the mapping as typed. + "callback" Lua function, if RHS was defined as such. "silent" 1 for a |:map-silent| mapping, else 0. "noremap" 1 if the {rhs} of the mapping is not remappable. "script" 1 if mapping was defined with <script>. diff --git a/runtime/doc/develop.txt b/runtime/doc/develop.txt index d7837dc2fe..41bb90299b 100644 --- a/runtime/doc/develop.txt +++ b/runtime/doc/develop.txt @@ -138,15 +138,15 @@ DOCUMENTATION *dev-doc* - Write docstrings (as opposed to inline comments) with present tense ("Gets"), not imperative ("Get"). This tends to reduce ambiguity and improve clarity by describing "What" instead of "How". > - GOOD: + ✅ OK: /// Gets a highlight definition. - BAD: + ❌ NO: /// Get a highlight definition. - Avoid starting docstrings with "The" or "A" unless needed to avoid ambiguity. This is a visual aid and reduces noise. > - GOOD: + ✅ OK: /// @param dirname Path fragment before `pend` - BAD: + ❌ NO: /// @param dirname The path fragment before `pend` - Vim differences: - Do not prefix help tags with "nvim-". Use |vim_diff.txt| to catalog @@ -329,13 +329,20 @@ Where possible, these patterns apply to _both_ Lua and the API: - When accepting a buffer id, etc., 0 means "current buffer", nil means "all buffers". Likewise for window id, tabpage id, etc. - Examples: |vim.lsp.codelens.clear()| |vim.diagnostic.enable()| -- Any function signature that accepts a callback function should define the - callback as the LAST parameter, if possible. This improves readability of - calls by placing the less "noisy" arguments near the start. > - GOOD: - filter(table, opts, function() … end) - BAD: - filter(function() … end, table, opts) +- Any function signature that accepts a callback (example: |table.foreach()|) + should place it as the LAST parameter (after opts), if possible (or ALWAYS + for "continuation callbacks"—functions called exactly once). + - Improves readability by placing the less "noisy" arguments near the start. + - Consistent with luv. + - Useful for future async lib which transforms functions of the form + `function(<args>, cb(<ret)>))` => `function(<args>) -> <ret>`. + - Example: >lua + -- ✅ OK: + filter(…, opts, function() … end) + -- ❌ NO: + filter(function() … end, …, opts) + -- ❌ NO: + filter(…, function() … end, opts) - "Enable" ("toggle") interface and behavior: - `enable(…, nil)` and `enable(…, {buf=nil})` are synonyms and control the the "global" enablement of a feature. @@ -566,10 +573,10 @@ a good name: it's idiomatic and unambiguous. If the package is named "neovim", it confuses users, and complicates documentation and discussions. Examples of API-client package names: -- GOOD: nvim-racket -- GOOD: pynvim -- BAD: python-client -- BAD: neovim_ +- ✅ OK: nvim-racket +- ✅ OK: pynvim +- ❌ NO: python-client +- ❌ NO: neovim_ API client implementation guidelines ~ diff --git a/runtime/doc/health.txt b/runtime/doc/health.txt index e879f11351..cb70961b55 100644 --- a/runtime/doc/health.txt +++ b/runtime/doc/health.txt @@ -7,10 +7,10 @@ Type |gO| to see the table of contents. ============================================================================== -Checkhealth *health* +Checkhealth *vim.health* *health* -health.vim is a minimal framework to help users troubleshoot configuration and +vim.health is a minimal framework to help users troubleshoot configuration and any other environment conditions that a plugin might care about. Nvim ships with healthchecks for configuration, performance, python support, ruby support, clipboard support, and more. @@ -49,7 +49,7 @@ Commands *health-commands* :checkhealth vim* < -Create a healthcheck *health-dev* *vim.health* +Create a healthcheck *health-dev* Healthchecks are functions that check the user environment, configuration, or any other prerequisites that a plugin cares about. Nvim ships with diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt index 7f5ae06030..a8b5825e63 100644 --- a/runtime/doc/lsp.txt +++ b/runtime/doc/lsp.txt @@ -1639,8 +1639,7 @@ Lua module: vim.lsp.completion *lsp-completion* Fields: ~ • {autotrigger}? (`boolean`) Whether to trigger completion automatically. Default: false - • {convert}? (`fun(item: lsp.CompletionItem): table`) An optional - function used to customize the transformation of an + • {convert}? (`fun(item: lsp.CompletionItem): table`) Transforms an LSP CompletionItem to |complete-items|. @@ -2312,14 +2311,14 @@ connect({host_or_path}, {port}) *vim.lsp.rpc.connect()* Create a LSP RPC client factory that connects to either: • a named pipe (windows) • a domain socket (unix) - • a host and port via TCP + • a host and port via TCP (host must be IP address) Return a function that can be passed to the `cmd` field for |vim.lsp.start_client()| or |vim.lsp.start()|. Parameters: ~ - • {host_or_path} (`string`) host to connect to or path to a pipe/domain - socket + • {host_or_path} (`string`) host (IP address) to connect to or path to + a pipe/domain socket • {port} (`integer?`) TCP port to connect to. If absent the first argument must be a pipe diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt index f071d67030..0a7c53a482 100644 --- a/runtime/doc/lua.txt +++ b/runtime/doc/lua.txt @@ -352,16 +352,14 @@ Example: >vim < *lua-table-ambiguous* Lua tables are used as both dictionaries and lists, so it is impossible to -determine whether empty table is meant to be empty list or empty dictionary. -Additionally Lua does not have integer numbers. To distinguish between these -cases there is the following agreement: +decide whether empty table is a list or a dict. Also Lua does not have integer +numbers. To disambiguate these cases, we define: *lua-list* -0. Empty table is empty list. -1. Table with N consecutive integer indices starting from 1 and ending with - N is considered a list. See also |list-iterator|. +0. Empty table is a list. Use |vim.empty_dict()| to represent empty dict. +1. Table with N consecutive (no `nil` values, aka "holes") integer keys 1…N is + a list. See also |list-iterator|. *lua-dict* -2. Table with string keys, none of which contains NUL byte, is considered to - be a dictionary. +2. Table with string keys, none of which contains NUL byte, is a dict. 3. Table with string keys, at least one of which contains NUL byte, is also considered to be a dictionary, but this time it is converted to a |msgpack-special-map|. @@ -3839,10 +3837,12 @@ argument into an *Iter* object with methods (such as |Iter:filter()| and chained to create iterator "pipelines": the output of each pipeline stage is input to the next stage. The first stage depends on the type passed to `vim.iter()`: -• List tables (arrays, |lua-list|) yield only the value of each element. - • Holes (nil values) are allowed. +• Lists or arrays (|lua-list|) yield only the value of each element. + • Holes (nil values) are allowed (but discarded). + • Use pairs() to treat array/list tables as dicts (preserve holes and + non-contiguous integer keys): `vim.iter(pairs(…))`. • Use |Iter:enumerate()| to also pass the index to the next stage. - • Or initialize with ipairs(): `vim.iter(ipairs(…))`. + • Or initialize with ipairs(): `vim.iter(ipairs(…))`. • Non-list tables (|lua-dict|) yield both the key and value of each element. • Function |iterator|s yield all values returned by the underlying function. • Tables with a |__call()| metamethod are treated as function iterators. diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index b7e1e0c84f..dacb27e320 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -53,21 +53,14 @@ EDITOR documented and skips help buffers if run from a non-help buffer, otherwise it moves to another help buffer. -VIM SCRIPT - -• |v:msgpack_types| has the type "binary" removed. |msgpackparse()| no longer - treats BIN, STR and FIXSTR as separate types. Any of these is returned as a - string if possible, or a |blob| if the value contained embedded NUL:s. - EVENTS • TODO LSP -• Add convert field in |vim.lsp.completion.BufferOpts| of - |vim.lsp.completion.enable()| an optional function used to customize the - transformation of an Lsp CompletionItem to |complete-items|. +• |vim.lsp.completion.enable()| gained the `convert` callback which enables + customizing the transformation of an LSP CompletionItem to |complete-items|. • |vim.lsp.diagnostic.from()| can be used to convert a list of |vim.Diagnostic| objects into their LSP diagnostic representation. @@ -89,12 +82,22 @@ PLUGINS TREESITTER -• TODO +• |Query:iter_matches()| correctly returns all matching nodes in a match + instead of only the last node. This means that the returned table maps + capture IDs to a list of nodes that need to be iterated over. For + backwards compatibility, an option `all=false` (only return the last + matching node) is provided that will be removed in a future release. TUI • TODO +VIMSCRIPT + +• |v:msgpack_types| has the type "binary" removed. |msgpackparse()| no longer + treats BIN, STR and FIXSTR as separate types. Any of these is returned as a + string if possible, or a |blob| if the value contained embedded NUL:s. + ============================================================================== NEW FEATURES *news-features* @@ -159,7 +162,8 @@ PLUGINS STARTUP -• TODO +• Nvim will fail if the |--listen| or |$NVIM_LISTEN_ADDRESS| address is + invalid, instead of silently skipping an invalid address. TERMINAL diff --git a/runtime/doc/provider.txt b/runtime/doc/provider.txt index a39f4bc5d7..f1b0daee76 100644 --- a/runtime/doc/provider.txt +++ b/runtime/doc/provider.txt @@ -194,6 +194,8 @@ registers. Nvim looks for these clipboard tools, in order of priority: - lemonade (for SSH) https://github.com/pocke/lemonade - doitclient (for SSH) https://www.chiark.greenend.org.uk/~sgtatham/doit/ - win32yank (Windows) + - putclip, getclip (Windows) https://cygwin.com/packages/summary/cygutils.html + - clip, powershell (Windows) https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/clip - termux (via termux-clipboard-set, termux-clipboard-set) - tmux (if $TMUX is set) @@ -248,8 +250,8 @@ For Windows WSL, try this g:clipboard definition: \ '*': 'clip.exe', \ }, \ 'paste': { - \ '+': 'powershell.exe -c [Console]::Out.Write($(Get-Clipboard -Raw).tostring().replace("`r", ""))', - \ '*': 'powershell.exe -c [Console]::Out.Write($(Get-Clipboard -Raw).tostring().replace("`r", ""))', + \ '+': 'powershell.exe -NoLogo -NoProfile -c [Console]::Out.Write($(Get-Clipboard -Raw).tostring().replace("`r", ""))', + \ '*': 'powershell.exe -NoLogo -NoProfile -c [Console]::Out.Write($(Get-Clipboard -Raw).tostring().replace("`r", ""))', \ }, \ 'cache_enabled': 0, \ } diff --git a/runtime/doc/treesitter.txt b/runtime/doc/treesitter.txt index d9b71c4b5b..fea469e63b 100644 --- a/runtime/doc/treesitter.txt +++ b/runtime/doc/treesitter.txt @@ -1036,9 +1036,8 @@ add_directive({name}, {handler}, {opts}) the same name • {all}? (`boolean`) Use the correct implementation of the match table where capture IDs map to a list of nodes - instead of a single node. Defaults to false (for backward - compatibility). This option will eventually become the - default and removed. + instead of a single node. Defaults to true. This option + will be removed in a future release. *vim.treesitter.query.add_predicate()* add_predicate({name}, {handler}, {opts}) @@ -1049,14 +1048,13 @@ add_predicate({name}, {handler}, {opts}) • {handler} (`fun(match: table<integer,TSNode[]>, pattern: integer, source: integer|string, predicate: any[], metadata: vim.treesitter.query.TSMetadata)`) • see |vim.treesitter.query.add_directive()| for argument meanings - • {opts} (`table`) A table with the following fields: + • {opts} (`table?`) A table with the following fields: • {force}? (`boolean`) Override an existing predicate of the same name • {all}? (`boolean`) Use the correct implementation of the match table where capture IDs map to a list of nodes - instead of a single node. Defaults to false (for backward - compatibility). This option will eventually become the - default and removed. + instead of a single node. Defaults to true. This option + will be removed in a future release. edit({lang}) *vim.treesitter.query.edit()* Opens a live editor to query the buffer you started from. @@ -1216,14 +1214,8 @@ Query:iter_matches({node}, {source}, {start}, {stop}, {opts}) indices to a list of nodes, and metadata from any directives processing the match. - WARNING: Set `all=true` to ensure all matching nodes in a match are - returned, otherwise only the last node in a match is returned, breaking - captures involving quantifiers such as `(comment)+ @comment`. The default - option `all=false` is only provided for backward compatibility and will be - removed after Nvim 0.10. - Example: >lua - for pattern, match, metadata in cquery:iter_matches(tree:root(), bufnr, 0, -1, { all = true }) do + for pattern, match, metadata in cquery:iter_matches(tree:root(), bufnr, 0, -1) do for id, nodes in pairs(match) do local name = query.captures[id] for _, node in ipairs(nodes) do @@ -1248,12 +1240,11 @@ Query:iter_matches({node}, {source}, {start}, {stop}, {opts}) start depth for each match. This is used to prevent traversing too deep into a tree. • match_limit (integer) Set the maximum number of - in-progress matches (Default: 256). - • all (boolean) When set, the returned match table maps - capture IDs to a list of nodes. Older versions of - iter_matches incorrectly mapped capture IDs to a single - node, which is incorrect behavior. This option will - eventually become the default and removed. + in-progress matches (Default: 256). all (boolean) When + `false` (default `true`), the returned table maps capture + IDs to a single (last) node instead of the full list of + matching nodes. This option is only for backward + compatibility and will be removed in a future release. Return: ~ (`fun(): integer, table<integer, TSNode[]>, vim.treesitter.query.TSMetadata`) diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua index c7c8362bfb..7b5570cc99 100644 --- a/runtime/lua/vim/_editor.lua +++ b/runtime/lua/vim/_editor.lua @@ -1,17 +1,19 @@ -- Nvim-Lua stdlib: the `vim` module (:help lua-stdlib) -- --- Lua code lives in one of three places: --- 1. runtime/lua/vim/ (the runtime): For "nice to have" features, e.g. the --- `inspect` and `lpeg` modules. --- 2. runtime/lua/vim/shared.lua: pure Lua functions which always --- are available. Used in the test runner, as well as worker threads --- and processes launched from Nvim. --- 3. runtime/lua/vim/_editor.lua: Code which directly interacts with --- the Nvim editor state. Only available in the main thread. +-- Lua code lives in one of four places: +-- 1. Plugins! Not everything needs to live on "vim.*". Plugins are the correct model for +-- non-essential features which the user may want to disable or replace with a third-party +-- plugin. Examples: "editorconfig", "comment". +-- - "opt-out": runtime/plugin/*.lua +-- - "opt-in": runtime/pack/dist/opt/ +-- 2. runtime/lua/vim/ (the runtime): Lazy-loaded modules. Examples: `inspect`, `lpeg`. +-- 3. runtime/lua/vim/shared.lua: pure Lua functions which always are available. Used in the test +-- runner, as well as worker threads and processes launched from Nvim. +-- 4. runtime/lua/vim/_editor.lua: Eager-loaded code which directly interacts with the Nvim +-- editor state. Only available in the main thread. -- --- Guideline: "If in doubt, put it in the runtime". --- --- Most functions should live directly in `vim.`, not in submodules. +-- The top level "vim.*" namespace is for fundamental Lua and editor features. Use submodules for +-- everything else (but avoid excessive "nesting"), or plugins (see above). -- -- Compatibility with Vim's `if_lua` is explicitly a non-goal. -- @@ -19,9 +21,7 @@ -- - https://github.com/luafun/luafun -- - https://github.com/rxi/lume -- - http://leafo.net/lapis/reference/utilities.html --- - https://github.com/torch/paths -- - https://github.com/bakpakin/Fennel (pretty print, repl) --- - https://github.com/howl-editor/howl/tree/master/lib/howl/util -- These are for loading runtime modules lazily since they aren't available in -- the nvim binary as specified in executor.c diff --git a/runtime/lua/vim/_meta/api_keysets_extra.lua b/runtime/lua/vim/_meta/api_keysets_extra.lua index 8faf5f49aa..81bce50746 100644 --- a/runtime/lua/vim/_meta/api_keysets_extra.lua +++ b/runtime/lua/vim/_meta/api_keysets_extra.lua @@ -23,7 +23,7 @@ error('Cannot require a meta file') --- @field conceal? boolean --- @field spell? boolean --- @field ui_watched? boolean ---- @field url? boolean +--- @field url? string --- @field hl_mode? string --- --- @field virt_text? [string, string][] diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua index 05b9fc2bbf..10b09333a8 100644 --- a/runtime/lua/vim/_meta/vimfn.lua +++ b/runtime/lua/vim/_meta/vimfn.lua @@ -5262,6 +5262,7 @@ function vim.fn.map(expr1, expr2) end --- "lhsrawalt" The {lhs} of the mapping as raw bytes, alternate --- form, only present when it differs from "lhsraw" --- "rhs" The {rhs} of the mapping as typed. +--- "callback" Lua function, if RHS was defined as such. --- "silent" 1 for a |:map-silent| mapping, else 0. --- "noremap" 1 if the {rhs} of the mapping is not remappable. --- "script" 1 if mapping was defined with <script>. diff --git a/runtime/lua/vim/health.lua b/runtime/lua/vim/health.lua index 236f9da752..d183c82516 100644 --- a/runtime/lua/vim/health.lua +++ b/runtime/lua/vim/health.lua @@ -1,6 +1,6 @@ --- @brief ---<pre>help ---- health.vim is a minimal framework to help users troubleshoot configuration and +--- vim.health is a minimal framework to help users troubleshoot configuration and --- any other environment conditions that a plugin might care about. Nvim ships --- with healthchecks for configuration, performance, python support, ruby --- support, clipboard support, and more. @@ -39,7 +39,7 @@ --- :checkhealth vim* --- < --- ---- Create a healthcheck *health-dev* *vim.health* +--- Create a healthcheck *health-dev* --- --- Healthchecks are functions that check the user environment, configuration, or --- any other prerequisites that a plugin cares about. Nvim ships with diff --git a/runtime/lua/vim/iter.lua b/runtime/lua/vim/iter.lua index 6bddf0bc5e..4bbcaf16db 100644 --- a/runtime/lua/vim/iter.lua +++ b/runtime/lua/vim/iter.lua @@ -6,10 +6,12 @@ --- of each pipeline stage is input to the next stage. The first stage depends on the type passed to --- `vim.iter()`: --- ---- - List tables (arrays, |lua-list|) yield only the value of each element. ---- - Holes (nil values) are allowed. +--- - Lists or arrays (|lua-list|) yield only the value of each element. +--- - Holes (nil values) are allowed (but discarded). +--- - Use pairs() to treat array/list tables as dicts (preserve holes and non-contiguous integer +--- keys): `vim.iter(pairs(…))`. --- - Use |Iter:enumerate()| to also pass the index to the next stage. ---- - Or initialize with ipairs(): `vim.iter(ipairs(…))`. +--- - Or initialize with ipairs(): `vim.iter(ipairs(…))`. --- - Non-list tables (|lua-dict|) yield both the key and value of each element. --- - Function |iterator|s yield all values returned by the underlying function. --- - Tables with a |__call()| metamethod are treated as function iterators. @@ -1034,7 +1036,7 @@ function Iter.new(src, ...) if type(k) ~= 'number' or k <= 0 or math.floor(k) ~= k then return Iter.new(pairs(src)) end - t[#t + 1] = v + t[#t + 1] = v -- Coerce to list-like table. end return ArrayIter.new(t) end diff --git a/runtime/lua/vim/lsp/completion.lua b/runtime/lua/vim/lsp/completion.lua index 89d6d0e8b9..71ea2df100 100644 --- a/runtime/lua/vim/lsp/completion.lua +++ b/runtime/lua/vim/lsp/completion.lua @@ -597,7 +597,7 @@ end --- @class vim.lsp.completion.BufferOpts --- @field autotrigger? boolean Whether to trigger completion automatically. Default: false ---- @field convert? fun(item: lsp.CompletionItem): table An optional function used to customize the transformation of an LSP CompletionItem to |complete-items|. +--- @field convert? fun(item: lsp.CompletionItem): table Transforms an LSP CompletionItem to |complete-items|. ---@param client_id integer ---@param bufnr integer diff --git a/runtime/lua/vim/lsp/rpc.lua b/runtime/lua/vim/lsp/rpc.lua index 7acb67b25e..a9c6723094 100644 --- a/runtime/lua/vim/lsp/rpc.lua +++ b/runtime/lua/vim/lsp/rpc.lua @@ -627,12 +627,12 @@ end --- --- - a named pipe (windows) --- - a domain socket (unix) ---- - a host and port via TCP +--- - a host and port via TCP (host must be IP address) --- --- Return a function that can be passed to the `cmd` field for --- |vim.lsp.start_client()| or |vim.lsp.start()|. --- ----@param host_or_path string host to connect to or path to a pipe/domain socket +---@param host_or_path string host (IP address) to connect to or path to a pipe/domain socket ---@param port integer? TCP port to connect to. If absent the first argument must be a pipe ---@return fun(dispatchers: vim.lsp.rpc.Dispatchers): vim.lsp.rpc.PublicClient function M.connect(host_or_path, port) diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua index 27590eff5d..7375beaf9d 100644 --- a/runtime/lua/vim/treesitter/_fold.lua +++ b/runtime/lua/vim/treesitter/_fold.lua @@ -131,9 +131,7 @@ local function compute_folds_levels(bufnr, info, srow, erow, parse_injections) -- Collect folds starting from srow - 1, because we should first subtract the folds that end at -- srow - 1 from the level of srow - 1 to get accurate level of srow. - for _, match, metadata in - query:iter_matches(tree:root(), bufnr, math.max(srow - 1, 0), erow, { all = true }) - do + for _, match, metadata in query:iter_matches(tree:root(), bufnr, math.max(srow - 1, 0), erow) do for id, nodes in pairs(match) do if query.captures[id] == 'fold' then local range = ts.get_range(nodes[1], bufnr, metadata[id]) diff --git a/runtime/lua/vim/treesitter/_query_linter.lua b/runtime/lua/vim/treesitter/_query_linter.lua index 8654b89c9b..ea1ae5416a 100644 --- a/runtime/lua/vim/treesitter/_query_linter.lua +++ b/runtime/lua/vim/treesitter/_query_linter.lua @@ -176,7 +176,7 @@ function M.lint(buf, opts) parser:parse() parser:for_each_tree(function(tree, ltree) if ltree:lang() == 'query' then - for _, match, _ in query:iter_matches(tree:root(), buf, 0, -1, { all = true }) do + for _, match, _ in query:iter_matches(tree:root(), buf, 0, -1) do local lang_context = { lang = lang, parser_info = parser_info, diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index d43a0a5d0b..cc9ffeaa29 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -833,13 +833,7 @@ function LanguageTree:_get_injections() local start_line, _, end_line, _ = root_node:range() for pattern, match, metadata in - self._injection_query:iter_matches( - root_node, - self._source, - start_line, - end_line + 1, - { all = true } - ) + self._injection_query:iter_matches(root_node, self._source, start_line, end_line + 1) do local lang, combined, ranges = self:_get_injection(match, metadata) if lang then diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index 8e21353154..bd0574b8b7 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -620,8 +620,8 @@ local directive_handlers = { --- @field force? boolean --- --- Use the correct implementation of the match table where capture IDs map to ---- a list of nodes instead of a single node. Defaults to false (for backward ---- compatibility). This option will eventually become the default and removed. +--- a list of nodes instead of a single node. Defaults to true. This option will +--- be removed in a future release. --- @field all? boolean --- Adds a new predicate to be used in queries @@ -629,7 +629,7 @@ local directive_handlers = { ---@param name string Name of the predicate, without leading # ---@param handler fun(match: table<integer,TSNode[]>, pattern: integer, source: integer|string, predicate: any[], metadata: vim.treesitter.query.TSMetadata) --- - see |vim.treesitter.query.add_directive()| for argument meanings ----@param opts vim.treesitter.query.add_predicate.Opts +---@param opts? vim.treesitter.query.add_predicate.Opts function M.add_predicate(name, handler, opts) -- Backward compatibility: old signature had "force" as boolean argument if type(opts) == 'boolean' then @@ -642,7 +642,7 @@ function M.add_predicate(name, handler, opts) error(string.format('Overriding existing predicate %s', name)) end - if opts.all then + if opts.all ~= false then predicate_handlers[name] = handler else --- @param match table<integer, TSNode[]> @@ -894,16 +894,10 @@ end --- index of the pattern in the query, a table mapping capture indices to a list --- of nodes, and metadata from any directives processing the match. --- ---- WARNING: Set `all=true` to ensure all matching nodes in a match are ---- returned, otherwise only the last node in a match is returned, breaking captures ---- involving quantifiers such as `(comment)+ @comment`. The default option ---- `all=false` is only provided for backward compatibility and will be removed ---- after Nvim 0.10. ---- --- Example: --- --- ```lua ---- for pattern, match, metadata in cquery:iter_matches(tree:root(), bufnr, 0, -1, { all = true }) do +--- for pattern, match, metadata in cquery:iter_matches(tree:root(), bufnr, 0, -1) do --- for id, nodes in pairs(match) do --- local name = query.captures[id] --- for _, node in ipairs(nodes) do @@ -925,9 +919,9 @@ end --- - max_start_depth (integer) if non-zero, sets the maximum start depth --- for each match. This is used to prevent traversing too deep into a tree. --- - match_limit (integer) Set the maximum number of in-progress matches (Default: 256). ---- - all (boolean) When set, the returned match table maps capture IDs to a list of nodes. ---- Older versions of iter_matches incorrectly mapped capture IDs to a single node, which is ---- incorrect behavior. This option will eventually become the default and removed. +--- - all (boolean) When `false` (default `true`), the returned table maps capture IDs to a single +--- (last) node instead of the full list of matching nodes. This option is only for backward +--- compatibility and will be removed in a future release. --- ---@return (fun(): integer, table<integer, TSNode[]>, vim.treesitter.query.TSMetadata): pattern id, match, metadata function Query:iter_matches(node, source, start, stop, opts) @@ -960,10 +954,10 @@ function Query:iter_matches(node, source, start, stop, opts) local captures = match:captures() - if not opts.all then + if opts.all == false then -- Convert the match table into the old buggy version for backward - -- compatibility. This is slow. Plugin authors, if you're reading this, set the "all" - -- option! + -- compatibility. This is slow, but we only do it when the caller explicitly opted into it by + -- setting `all` to `false`. local old_match = {} ---@type table<integer, TSNode> for k, v in pairs(captures or {}) do old_match[k] = v[#v] diff --git a/runtime/lua/vim/ui.lua b/runtime/lua/vim/ui.lua index b831a4f23e..2d3b828ea1 100644 --- a/runtime/lua/vim/ui.lua +++ b/runtime/lua/vim/ui.lua @@ -170,7 +170,7 @@ end --- Returns all URLs at cursor, if any. --- @return string[] function M._get_urls() - local urls = {} + local urls = {} ---@type string[] local bufnr = vim.api.nvim_get_current_buf() local cursor = vim.api.nvim_win_get_cursor(0) @@ -183,7 +183,7 @@ function M._get_urls() }) for _, v in ipairs(extmarks) do local details = v[4] - if details.url then + if details and details.url then urls[#urls + 1] = details.url end end @@ -195,15 +195,16 @@ function M._get_urls() local lang = ltree:lang() local query = vim.treesitter.query.get(lang, 'highlights') if query then - local tree = ltree:tree_for_range(range) - for _, match, metadata in query:iter_matches(tree:root(), bufnr, row, row + 1, { all = true }) do + local tree = assert(ltree:tree_for_range(range)) + for _, match, metadata in query:iter_matches(tree:root(), bufnr, row, row + 1) do for id, nodes in pairs(match) do for _, node in ipairs(nodes) do if vim.treesitter.node_contains(node, range) then local url = metadata[id] and metadata[id].url if url and match[url] then for _, n in ipairs(match[url]) do - urls[#urls + 1] = vim.treesitter.get_node_text(n, bufnr, metadata[url]) + urls[#urls + 1] = + vim.treesitter.get_node_text(n, bufnr, { metadata = metadata[url] }) end end end diff --git a/runtime/syntax/sudoers.vim b/runtime/syntax/sudoers.vim index bf2d337d9e..4cdf598be0 100644 --- a/runtime/syntax/sudoers.vim +++ b/runtime/syntax/sudoers.vim @@ -2,9 +2,10 @@ " Language: sudoers(5) configuration files " Maintainer: Eisuke Kawashima ( e.kawaschima+vim AT gmail.com ) " Previous Maintainer: Nikolai Weibull <now@bitwi.se> -" Latest Revision: 2021 Mar 15 +" Latest Revision: 2024 Sep 02 " Recent Changes: Support for #include and #includedir. " Added many new options (Samuel D. Leslie) +" Update allowed Tag_Spec Runas_Spec syntax items if exists("b:current_syntax") finish @@ -22,7 +23,7 @@ syn match sudoersUserSpec '^' nextgroup=@sudoersUserInSpec skipwhite syn match sudoersSpecEquals contained '=' nextgroup=@sudoersCmndSpecList skipwhite -syn cluster sudoersCmndSpecList contains=sudoersUserRunasBegin,sudoersPASSWD,@sudoersCmndInSpec +syn cluster sudoersCmndSpecList contains=sudoersUserRunasBegin,sudoersTagSpec,@sudoersCmndInSpec syn keyword sudoersTodo contained TODO FIXME XXX NOTE @@ -92,10 +93,11 @@ syn cluster sudoersUserList contains=sudoersUserListComma,sudoersUserLis syn match sudoersUserSpecComma contained ',' nextgroup=@sudoersUserInSpec skipwhite skipnl syn cluster sudoersUserSpec contains=sudoersUserSpecComma,@sudoersHostInSpec -syn match sudoersUserRunasBegin contained '(' nextgroup=@sudoersUserInRunas skipwhite skipnl +syn match sudoersUserRunasBegin contained '(' nextgroup=@sudoersUserInRunas,sudoersUserRunasColon skipwhite skipnl syn match sudoersUserRunasComma contained ',' nextgroup=@sudoersUserInRunas skipwhite skipnl -syn match sudoersUserRunasEnd contained ')' nextgroup=sudoersPASSWD,@sudoersCmndInSpec skipwhite skipnl -syn cluster sudoersUserRunas contains=sudoersUserRunasComma,@sudoersUserInRunas,sudoersUserRunasEnd +syn match sudoersUserRunasColon contained ':' nextgroup=@sudoersUserInRunas skipwhite skipnl +syn match sudoersUserRunasEnd contained ')' nextgroup=sudoersTagSpec,@sudoersCmndInSpec skipwhite skipnl +syn cluster sudoersUserRunas contains=sudoersUserRunasComma,sudoersUserRunasColon,@sudoersUserInRunas,sudoersUserRunasEnd syn match sudoersHostAliasEquals contained '=' nextgroup=@sudoersHostInList skipwhite skipnl @@ -291,7 +293,7 @@ syn region sudoersStringValue contained start=+"+ skip=+\\"+ end=+"+ nextgroup syn match sudoersListValue contained '[^[:space:],:=\\]*\%(\\[[:space:],:=\\][^[:space:],:=\\]*\)*' nextgroup=sudoersParameterListComma skipwhite skipnl syn region sudoersListValue contained start=+"+ skip=+\\"+ end=+"+ nextgroup=sudoersParameterListComma skipwhite skipnl -syn match sudoersPASSWD contained '\%(NO\)\=PASSWD:' nextgroup=@sudoersCmndInSpec skipwhite +syn match sudoersTagSpec contained '\%(NO\)\=\%(EXEC\|FOLLOW\|LOG_\%(INPUT\|OUTPUT\)\|MAIL\|INTERCEPT\|PASSWD\|SETENV\):' nextgroup=sudoersTagSpec,@sudoersCmndInSpec skipwhite hi def link sudoersSpecEquals Operator hi def link sudoersTodo Todo @@ -345,6 +347,7 @@ hi def link sudoersUserListColon Delimiter hi def link sudoersUserSpecComma Delimiter hi def link sudoersUserRunasBegin Delimiter hi def link sudoersUserRunasComma Delimiter +hi def link sudoersUserRunasColon Delimiter hi def link sudoersUserRunasEnd Delimiter hi def link sudoersHostAliasEquals Operator hi def link sudoersHostListComma Delimiter @@ -381,7 +384,7 @@ hi def link sudoersListParameterEquals Operator hi def link sudoersIntegerValue Number hi def link sudoersStringValue String hi def link sudoersListValue String -hi def link sudoersPASSWD Special +hi def link sudoersTagSpec Special hi def link sudoersInclude Statement let b:current_syntax = "sudoers" diff --git a/runtime/syntax/vim.vim b/runtime/syntax/vim.vim index f3d9c53f0b..89577ffaae 100644 --- a/runtime/syntax/vim.vim +++ b/runtime/syntax/vim.vim @@ -177,10 +177,11 @@ syn match vimNumber '\%(^\|\A\)\zs#\x\{6}' skipwhite nextgroup=vimGlobal,vimSub syn case match " All vimCommands are contained by vimIsCommand. {{{2 -syn cluster vimCmdList contains=vimAbb,vimAddress,vimAutoCmd,vimAugroup,vimBehave,vimCall,vimCatch,vimConst,vimDef,@vimEcho,vimEnddef,vimEndfunction,vimExecute,vimIsCommand,vimExtCmd,vimFor,vimFunction,vimGlobal,vimHighlight,vimLet,vimLoadkeymap,vimMap,vimMark,vimMatch,vimNotFunc,vimNorm,vimSet,vimSleep,vimSyntax,vimThrow,vimUnlet,vimUnmap,vimUserCmd,vimMenu,vimMenutranslate,@vim9CmdList +syn cluster vimCmdList contains=vimAbb,vimAddress,vimAutoCmd,vimAugroup,vimBehave,vimCall,vimCatch,vimConst,vimDef,vimDelcommand,@vimEcho,vimEnddef,vimEndfunction,vimExecute,vimIsCommand,vimExtCmd,vimFor,vimFunction,vimGlobal,vimHighlight,vimLet,vimLoadkeymap,vimMap,vimMark,vimMatch,vimNotFunc,vimNorm,vimSet,vimSleep,vimSyntax,vimThrow,vimUnlet,vimUnmap,vimUserCmd,vimMenu,vimMenutranslate,@vim9CmdList syn cluster vim9CmdList contains=vim9Const,vim9Final,vim9For,vim9Var syn match vimCmdSep "[:|]\+" skipwhite nextgroup=@vimCmdList,vimSubst1 syn match vimIsCommand "\<\%(\h\w*\|[23]mat\%[ch]\)\>" contains=vimCommand +syn match vimBang contained "!" syn match vimVar contained "\<\h[a-zA-Z0-9#_]*\>" syn match vimVar "\<[bwglstav]:\h[a-zA-Z0-9#_]*\>" syn match vimVar "\s\zs&\%([lg]:\)\=\a\+\>" @@ -353,33 +354,46 @@ syn match vimSpecFileMod "\(:[phtre]\)\+" contained " User-Specified Commands: {{{2 " ======================= syn cluster vimUserCmdList contains=@vimCmdList,vimCmplxRepeat,@vimComment,vimCtrlChar,vimEscapeBrace,vimFunc,vimNotation,vimNumber,vimOper,vimRegister,vimSpecFile,vimString,vimSubst,vimSubstRep,vimSubstRange -syn keyword vimUserCommand contained com[mand] -syn match vimUserCmdName contained "\<\u\w*\>" nextgroup=vimUserCmdBlock skipwhite -syn match vimUserCmd "\<com\%[mand]!\=\>.*$" contains=vimUserAttrb,vimUserAttrbError,vimUserCommand,@vimUserCmdList,vimComFilter,vimCmdBlock,vimUserCmdName -syn match vimUserAttrbError contained "-\a\+\ze\s" -syn match vimUserAttrb contained "-nargs=[01*?+]" contains=vimUserAttrbKey,vimOper -syn match vimUserAttrb contained "-complete=" contains=vimUserAttrbKey,vimOper nextgroup=vimUserAttrbCmplt,vimUserCmdError -syn match vimUserAttrb contained "-range\(=%\|=\d\+\)\=" contains=vimNumber,vimOper,vimUserAttrbKey -syn match vimUserAttrb contained "-count\(=\d\+\)\=" contains=vimNumber,vimOper,vimUserAttrbKey -syn match vimUserAttrb contained "-bang\>" contains=vimOper,vimUserAttrbKey -syn match vimUserAttrb contained "-bar\>" contains=vimOper,vimUserAttrbKey -syn match vimUserAttrb contained "-buffer\>" contains=vimOper,vimUserAttrbKey -syn match vimUserAttrb contained "-register\>" contains=vimOper,vimUserAttrbKey +syn keyword vimUserCmdKey contained com[mand] +syn match vimUserCmdName contained "\<\u[[:alnum:]]*\>" skipwhite nextgroup=vimUserCmdBlock +syn match vimUserCmd "\<com\%[mand]\>!\=.*$" contains=vimUserCmdKey,vimBang,vimUserCmdAttr,vimUserCmdAttrError,vimUserCmdName,@vimUserCmdList,vimComFilter +syn match vimUserCmdAttrError contained "-\a\+\ze\%(\s\|=\)" +syn match vimUserCmdAttr contained "-addr=" contains=vimUserCmdAttrKey nextgroup=vimUserCmdAttrAddr +syn match vimUserCmdAttr contained "-bang\>" contains=vimUserCmdAttrKey +syn match vimUserCmdAttr contained "-bar\>" contains=vimUserCmdAttrKey +syn match vimUserCmdAttr contained "-buffer\>" contains=vimUserCmdAttrKey +syn match vimUserCmdAttr contained "-complete=" contains=vimUserCmdAttrKey nextgroup=vimUserCmdAttrCmplt,vimUserCmdError +syn match vimUserCmdAttr contained "-count\>" contains=vimUserCmdAttrKey +syn match vimUserCmdAttr contained "-count=" contains=vimUserCmdAttrKey nextgroup=vimNumber +syn match vimUserCmdAttr contained "-keepscript\>" contains=vimUserCmdAttrKey +syn match vimUserCmdAttr contained "-nargs=" contains=vimUserCmdAttrKey nextgroup=vimUserCmdAttrNargs +syn match vimUserCmdAttr contained "-range\>" contains=vimUserCmdAttrKey +syn match vimUserCmdAttr contained "-range=" contains=vimUserCmdAttrKey nextgroup=vimNumber,vimUserCmdAttrRange +syn match vimUserCmdAttr contained "-register\>" contains=vimUserCmdAttrKey + +syn match vimUserCmdAttrNargs contained "[01*?+]" +syn match vimUserCmdAttrRange contained "%" + if !exists("g:vimsyn_noerror") && !exists("g:vimsyn_nousercmderror") syn match vimUserCmdError contained "\S\+\>" endif -syn case ignore -syn keyword vimUserAttrbKey contained bar ban[g] cou[nt] ra[nge] com[plete] n[args] re[gister] -" GEN_SYN_VIM: vimUserAttrbCmplt, START_STR='syn keyword vimUserAttrbCmplt contained', END_STR='' -syn keyword vimUserAttrbCmplt contained arglist augroup behave breakpoint buffer color command compiler cscope diff_buffer dir dir_in_path environment event expression file file_in_path filetype function help highlight history keymap locale mapclear mapping menu messages option packadd runtime scriptnames shellcmd sign syntax syntime tag tag_listfiles user var -syn keyword vimUserAttrbCmplt contained custom customlist nextgroup=vimUserAttrbCmpltFunc,vimUserCmdError -syn match vimUserAttrbCmpltFunc contained ",\%([sS]:\|<[sS][iI][dD]>\)\=\%(\h\w*\%([.#]\h\w*\)\+\|\h\w*\)"hs=s+1 nextgroup=vimUserCmdError +syn case ignore +syn keyword vimUserCmdAttrKey contained a[ddr] ban[g] bar bu[ffer] com[plete] cou[nt] k[eepscript] n[args] ra[nge] re[gister] +" GEN_SYN_VIM: vimUserCmdAttrCmplt, START_STR='syn keyword vimUserCmdAttrCmplt contained', END_STR='' +syn keyword vimUserCmdAttrCmplt contained arglist augroup behave breakpoint buffer color command compiler cscope diff_buffer dir dir_in_path environment event expression file file_in_path filetype function help highlight history keymap locale mapclear mapping menu messages option packadd runtime scriptnames shellcmd sign syntax syntime tag tag_listfiles user var +syn keyword vimUserCmdAttrCmplt contained custom customlist nextgroup=vimUserCmdAttrCmpltFunc,vimUserCmdError +syn match vimUserCmdAttrCmpltFunc contained ",\%([sS]:\|<[sS][iI][dD]>\)\=\%(\h\w*\%([.#]\h\w*\)\+\|\h\w*\)"hs=s+1 nextgroup=vimUserCmdError +" GEN_SYN_VIM: vimUserCmdAttrAddr, START_STR='syn keyword vimUserCmdAttrAddr contained', END_STR='' +syn keyword vimUserCmdAttrAddr contained arguments arg buffers buf lines line loaded_buffers load other quickfix qf tabs tab windows win +syn match vimUserCmdAttrAddr contained "?" syn case match -syn match vimUserAttrbCmplt contained "custom,\u\w*" syn region vimUserCmdBlock contained matchgroup=vimSep start="{" end="}" contains=@vimDefBodyList +syn match vimDelcommand "\<delc\%[ommand]\>" skipwhite nextgroup=vimDelcommandAttr +syn match vimDelcommandAttr contained "-buffer\>" + " Lower Priority Comments: after some vim commands... {{{2 " ======================= if get(g:, "vimsyn_comment_strings", 1) @@ -1162,6 +1176,8 @@ if !exists("skip_vim_syntax_inits") hi def link vimDefComment vim9Comment hi def link vimDefKey vimCommand hi def link vimDefParam vimVar + hi def link vimDelcommand vimCommand + hi def link vimDelcommandAttr vimUserCmdAttr hi def link vimEcho vimCommand hi def link vimEchohlNone vimGroup hi def link vimEchohl vimCommand @@ -1319,13 +1335,15 @@ if !exists("skip_vim_syntax_inits") hi def link vimUnlet vimCommand hi def link vimUnletBang vimBang hi def link vimUnmap vimMap - hi def link vimUserAttrbCmpltFunc Special - hi def link vimUserAttrbCmplt vimSpecial - hi def link vimUserAttrbKey vimOption - hi def link vimUserAttrb vimSpecial - hi def link vimUserAttrbError Error + hi def link vimUserCmdAttrAddr vimSpecial + hi def link vimUserCmdAttrCmplt vimSpecial + hi def link vimUserCmdAttrNargs vimSpecial + hi def link vimUserCmdAttrRange vimSpecial + hi def link vimUserCmdAttrKey vimUserCmdAttr + hi def link vimUserCmdAttr Special + hi def link vimUserCmdAttrError Error hi def link vimUserCmdError Error - hi def link vimUserCommand vimCommand + hi def link vimUserCmdKey vimCommand hi def link vimUserFunc Normal hi def link vimVar Identifier hi def link vimWarn WarningMsg diff --git a/scripts/gen_vimdoc.lua b/scripts/gen_vimdoc.lua index dc384c12f5..aa09bc7dc7 100755 --- a/scripts/gen_vimdoc.lua +++ b/scripts/gen_vimdoc.lua @@ -373,8 +373,8 @@ local config = { section_fmt = function(_name) return 'Checkhealth' end, - helptag_fmt = function(name) - return name:lower() + helptag_fmt = function() + return 'vim.health* *health' -- HACK end, }, } diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c index a99d97acb8..5dc373acdc 100644 --- a/src/nvim/api/ui.c +++ b/src/nvim/api/ui.c @@ -93,15 +93,15 @@ void remote_ui_free_all_mem(void) } #endif -/// Wait until ui has connected on stdio channel if only_stdio -/// is true, otherwise any channel. +/// Wait until UI has connected. +/// +/// @param only_stdio UI is expected to connect on stdio. void remote_ui_wait_for_attach(bool only_stdio) { if (only_stdio) { Channel *channel = find_channel(CHAN_STDIO); if (!channel) { - // this function should only be called in --embed mode, stdio channel - // can be assumed. + // `only_stdio` implies --embed mode, thus stdio channel can be assumed. abort(); } diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index 5087c2d36d..cbab735489 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -6442,6 +6442,7 @@ M.funcs = { "lhsrawalt" The {lhs} of the mapping as raw bytes, alternate form, only present when it differs from "lhsraw" "rhs" The {rhs} of the mapping as typed. + "callback" Lua function, if RHS was defined as such. "silent" 1 for a |:map-silent| mapping, else 0. "noremap" 1 if the {rhs} of the mapping is not remappable. "script" 1 if mapping was defined with <script>. diff --git a/src/nvim/main.c b/src/nvim/main.c index c507a201b0..a45ee81c81 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -332,12 +332,6 @@ int main(int argc, char **argv) #endif bool use_builtin_ui = (has_term && !headless_mode && !embedded_mode && !silent_mode); - // don't bind the server yet, if we are using builtin ui. - // This will be done when nvim server has been forked from the ui process - if (!use_builtin_ui) { - server_init(params.listen_addr); - } - if (params.remote) { remote_request(¶ms, params.remote, params.server_addr, argc, argv, use_builtin_ui); @@ -355,11 +349,19 @@ int main(int argc, char **argv) ui_client_channel_id = rv; } + // NORETURN: Start builtin UI client. if (ui_client_channel_id) { time_finish(); ui_client_run(remote_ui); // NORETURN } assert(!ui_client_channel_id && !use_builtin_ui); + // Nvim server... + + int listen_rv = server_init(params.listen_addr); + if (listen_rv != 0) { + mainerr("Failed to --listen", listen_rv < 0 + ? os_strerror(listen_rv) : (listen_rv == 1 ? "empty address" : NULL)); + } TIME_MSG("expanding arguments"); @@ -1434,9 +1436,9 @@ scripterror: // On Windows expand "~\" or "~/" prefix in file names to profile directory. #ifdef MSWIN if (*p == '~' && (p[1] == '\\' || p[1] == '/')) { - size_t size = strlen(os_get_homedir()) + strlen(p); + size_t size = strlen(os_homedir()) + strlen(p); char *tilde_expanded = xmalloc(size); - snprintf(tilde_expanded, size, "%s%s", os_get_homedir(), p + 1); + snprintf(tilde_expanded, size, "%s%s", os_homedir(), p + 1); xfree(p); p = tilde_expanded; } diff --git a/src/nvim/msgpack_rpc/server.c b/src/nvim/msgpack_rpc/server.c index 24bd343a34..ae34829181 100644 --- a/src/nvim/msgpack_rpc/server.c +++ b/src/nvim/msgpack_rpc/server.c @@ -28,27 +28,32 @@ static garray_T watchers = GA_EMPTY_INIT_VALUE; #endif /// Initializes the module -bool server_init(const char *listen_addr) +/// +/// @returns 0: success, 1: validation error, 2: already listening, -errno: failed to bind/listen. +int server_init(const char *listen_addr) { + bool must_free = false; ga_init(&watchers, sizeof(SocketWatcher *), 1); // $NVIM_LISTEN_ADDRESS (deprecated) - if (!listen_addr && os_env_exists(ENV_LISTEN)) { + if ((!listen_addr || listen_addr[0] == '\0') && os_env_exists(ENV_LISTEN)) { listen_addr = os_getenv(ENV_LISTEN); } - int rv = listen_addr ? server_start(listen_addr) : 1; - if (0 != rv) { + if (!listen_addr || listen_addr[0] == '\0') { listen_addr = server_address_new(NULL); - if (!listen_addr) { - return false; - } - rv = server_start(listen_addr); - xfree((char *)listen_addr); + must_free = true; + } + + if (!listen_addr) { + abort(); // Cannot happen. } + int rv = server_start(listen_addr); + if (os_env_exists(ENV_LISTEN)) { - // Unset $NVIM_LISTEN_ADDRESS, it's a liability hereafter. + // Unset $NVIM_LISTEN_ADDRESS, it's a liability hereafter. It is "input only", it must not be + // leaked to child jobs or :terminal. os_unsetenv(ENV_LISTEN); } @@ -57,7 +62,11 @@ bool server_init(const char *listen_addr) ELOG("test log message"); } - return rv == 0; + if (must_free) { + xfree((char *)listen_addr); + } + + return rv; } /// Teardown a single server diff --git a/src/nvim/os/env.c b/src/nvim/os/env.c index a4d5c02b5b..8dfedd073e 100644 --- a/src/nvim/os/env.c +++ b/src/nvim/os/env.c @@ -395,7 +395,21 @@ void os_get_hostname(char *hostname, size_t size) #endif } -/// To get the "real" home directory: +/// The "real" home directory as determined by `init_homedir`. +static char *homedir = NULL; +static char *os_uv_homedir(void); + +/// Gets the "real", resolved user home directory as determined by `init_homedir`. +const char *os_homedir(void) +{ + if (!homedir) { + emsg("os_homedir failed: homedir not initialized"); + return NULL; + } + return homedir; +} + +/// Sets `homedir` to the "real", resolved user home directory, as follows: /// 1. get value of $HOME /// 2. if $HOME is not set, try the following /// For Windows: @@ -409,20 +423,6 @@ void os_get_hostname(char *hostname, size_t size) /// This also works with mounts and links. /// Don't do this for Windows, it will change the "current dir" for a drive. /// 3. fall back to current working directory as a last resort -static char *homedir = NULL; -static char *os_uv_homedir(void); - -/// Public accessor for the cached "real", resolved user home directory. See -/// comment on `homedir`. -const char *os_get_homedir(void) -{ - if (!homedir) { - emsg("os_get_homedir failed: homedir not initialized"); - return NULL; - } - return homedir; -} - void init_homedir(void) { // In case we are called a second time. diff --git a/src/nvim/os/input.c b/src/nvim/os/input.c index c4eb2803f6..8affc58591 100644 --- a/src/nvim/os/input.c +++ b/src/nvim/os/input.c @@ -514,7 +514,7 @@ static void process_ctrl_c(void) size_t available = input_available(); ssize_t i; - for (i = (ssize_t)available - 1; i >= 0; i--) { + for (i = (ssize_t)available - 1; i >= 0; i--) { // Reverse-search input for Ctrl_C. uint8_t c = (uint8_t)input_read_pos[i]; if (c == Ctrl_C || (c == 'C' && i >= 3 diff --git a/test/functional/core/fileio_spec.lua b/test/functional/core/fileio_spec.lua index 1c4f42eb43..073041fced 100644 --- a/test/functional/core/fileio_spec.lua +++ b/test/functional/core/fileio_spec.lua @@ -325,7 +325,7 @@ describe('tmpdir', function() before_each(function() -- Fake /tmp dir so that we can mess it up. - os_tmpdir = vim.uv.fs_mkdtemp(vim.fs.dirname(t.tmpname()) .. '/nvim_XXXXXXXXXX') + os_tmpdir = vim.uv.fs_mkdtemp(vim.fs.dirname(t.tmpname(false)) .. '/nvim_XXXXXXXXXX') end) after_each(function() diff --git a/test/functional/ex_cmds/profile_spec.lua b/test/functional/ex_cmds/profile_spec.lua index 57e5c6b2dc..365583948b 100644 --- a/test/functional/ex_cmds/profile_spec.lua +++ b/test/functional/ex_cmds/profile_spec.lua @@ -6,17 +6,11 @@ require('os') local eval = n.eval local command = n.command local eq, neq = t.eq, t.neq -local tempfile = t.tmpname() +local tempfile = t.tmpname(false) local source = n.source local matches = t.matches local read_file = t.read_file --- tmpname() also creates the file on POSIX systems. Remove it again. --- We just need the name, ignoring any race conditions. -if uv.fs_stat(tempfile).uid then - os.remove(tempfile) -end - local function assert_file_exists(filepath) neq(nil, uv.fs_stat(filepath).uid) end @@ -31,6 +25,7 @@ describe(':profile', function() after_each(function() n.expect_exit(command, 'qall!') if uv.fs_stat(tempfile).uid ~= nil then + -- Delete the tempfile. We just need the name, ignoring any race conditions. os.remove(tempfile) end end) diff --git a/test/functional/lua/loader_spec.lua b/test/functional/lua/loader_spec.lua index 7d71e33ced..06403e856c 100644 --- a/test/functional/lua/loader_spec.lua +++ b/test/functional/lua/loader_spec.lua @@ -74,8 +74,7 @@ describe('vim.loader', function() vim.loader.enable() ]] - local tmp = t.tmpname() - assert(os.remove(tmp)) + local tmp = t.tmpname(false) assert(t.mkdir(tmp)) assert(t.mkdir(tmp .. '/%')) local tmp1 = tmp .. '/%/x' diff --git a/test/functional/lua/vim_spec.lua b/test/functional/lua/vim_spec.lua index a70f35e8e3..df68020d8e 100644 --- a/test/functional/lua/vim_spec.lua +++ b/test/functional/lua/vim_spec.lua @@ -145,10 +145,9 @@ describe('lua stdlib', function() -- "0.10" or "0.10-dev+xxx" local curstr = ('%s.%s%s'):format(curver.major, curver.minor, prerel or '') eq( - dedent( - [[ - foo.bar() is deprecated. Run ":checkhealth vim.deprecated" for more information]] - ):format(curstr), + ([[foo.bar() is deprecated. Run ":checkhealth vim.deprecated" for more information]]):format( + curstr + ), exec_lua('return vim.deprecate(...)', 'foo.bar()', 'zub.wooo{ok=yay}', curstr) ) -- Same message as above; skipped this time. @@ -178,8 +177,7 @@ describe('lua stdlib', function() it('plugin=nil, to be deleted in the next major version (1.0)', function() eq( - dedent [[ - foo.baz() is deprecated. Run ":checkhealth vim.deprecated" for more information]], + [[foo.baz() is deprecated. Run ":checkhealth vim.deprecated" for more information]], exec_lua [[ return vim.deprecate('foo.baz()', nil, '1.0') ]] ) end) diff --git a/test/functional/lua/watch_spec.lua b/test/functional/lua/watch_spec.lua index 3d2dda716e..ab6b1416aa 100644 --- a/test/functional/lua/watch_spec.lua +++ b/test/functional/lua/watch_spec.lua @@ -12,7 +12,6 @@ local skip = t.skip -- events which can happen with some backends on some platforms local function touch(path) local tmp = t.tmpname() - io.open(tmp, 'w'):close() assert(vim.uv.fs_rename(tmp, path)) end @@ -42,7 +41,7 @@ describe('vim._watch', function() ) end - local root_dir = vim.uv.fs_mkdtemp(vim.fs.dirname(t.tmpname()) .. '/nvim_XXXXXXXXXX') + local root_dir = vim.uv.fs_mkdtemp(vim.fs.dirname(t.tmpname(false)) .. '/nvim_XXXXXXXXXX') local expected_events = 0 diff --git a/test/functional/options/defaults_spec.lua b/test/functional/options/defaults_spec.lua index f61139d92d..0faced5149 100644 --- a/test/functional/options/defaults_spec.lua +++ b/test/functional/options/defaults_spec.lua @@ -247,6 +247,7 @@ describe('startup defaults', function() } }) eq('Xtest-logpath', eval('$NVIM_LOG_FILE')) end) + it('defaults to stdpath("log")/log if empty', function() eq(true, mkdir(xdgdir) and mkdir(xdgstatedir)) clear({ @@ -257,6 +258,7 @@ describe('startup defaults', function() }) eq(xdgstatedir .. '/log', string.gsub(eval('$NVIM_LOG_FILE'), '\\', '/')) end) + it('defaults to stdpath("log")/log if invalid', function() eq(true, mkdir(xdgdir) and mkdir(xdgstatedir)) clear({ @@ -266,6 +268,8 @@ describe('startup defaults', function() }, }) eq(xdgstatedir .. '/log', string.gsub(eval('$NVIM_LOG_FILE'), '\\', '/')) + -- Avoid "failed to open $NVIM_LOG_FILE" noise in test output. + expect_exit(command, 'qall!') end) end) end) @@ -339,9 +343,11 @@ describe('XDG defaults', function() local state_dir = is_os('win') and 'nvim-data' or 'nvim' local root_path = is_os('win') and 'C:' or '' - describe('with too long XDG variables', function() + describe('with too long XDG vars', function() before_each(function() clear({ + -- Ensure valid --listen address despite broken XDG vars (else Nvim won't start). + args = { '--listen', is_os('win') and '' or t.tmpname(false) }, args_rm = { 'runtimepath' }, env = { NVIM_LOG_FILE = testlog, @@ -361,6 +367,9 @@ describe('XDG defaults', function() it('are correctly set', function() if not is_os('win') then + -- Broken XDG vars cause serverstart() to fail (except on Windows, where servernames are not + -- informed by $XDG_STATE_HOME). + t.matches('Failed to start server: no such file or directory', t.pcall_err(fn.serverstart)) assert_log('Failed to start server: no such file or directory: /X/X/X', testlog, 10) end @@ -522,9 +531,11 @@ describe('XDG defaults', function() end) end) - describe('with XDG variables that can be expanded', function() + describe('with expandable XDG vars', function() before_each(function() clear({ + -- Ensure valid --listen address despite broken XDG vars (else Nvim won't start). + args = { '--listen', is_os('win') and '' or t.tmpname(false) }, args_rm = { 'runtimepath' }, env = { NVIM_LOG_FILE = testlog, @@ -544,6 +555,9 @@ describe('XDG defaults', function() it('are not expanded', function() if not is_os('win') then + -- Broken XDG vars cause serverstart() to fail (except on Windows, where servernames are not + -- informed by $XDG_STATE_HOME). + t.matches('Failed to start server: no such file or directory', t.pcall_err(fn.serverstart)) assert_log( 'Failed to start server: no such file or directory: %$XDG_RUNTIME_DIR%/', testlog, @@ -895,7 +909,7 @@ describe('stdpath()', function() assert_alive() -- Check for crash. #8393 end) - it('reacts to $NVIM_APPNAME', function() + it('supports $NVIM_APPNAME', function() local appname = 'NVIM_APPNAME_TEST' .. ('_'):rep(106) clear({ env = { NVIM_APPNAME = appname, NVIM_LOG_FILE = testlog } }) eq(appname, fn.fnamemodify(fn.stdpath('config'), ':t')) @@ -916,7 +930,7 @@ describe('stdpath()', function() local function test_appname(testAppname, expected_exitcode) local lua_code = string.format( [[ - local child = vim.fn.jobstart({ vim.v.progpath, '--clean', '--headless', '+qall!' }, { env = { NVIM_APPNAME = %q } }) + local child = vim.fn.jobstart({ vim.v.progpath, '--clean', '--headless', '--listen', 'x', '+qall!' }, { env = { NVIM_APPNAME = %q } }) return vim.fn.jobwait({ child }, %d)[1] ]], alter_slashes(testAppname), @@ -935,9 +949,6 @@ describe('stdpath()', function() -- Valid appnames: test_appname('a/b', 0) test_appname('a/b\\c', 0) - if not is_os('win') then - assert_log('Failed to start server: no such file or directory:', testlog) - end end) describe('returns a String', function() diff --git a/test/functional/plugin/lsp/completion_spec.lua b/test/functional/plugin/lsp/completion_spec.lua index 16d64fc95d..d3796082fb 100644 --- a/test/functional/plugin/lsp/completion_spec.lua +++ b/test/functional/plugin/lsp/completion_spec.lua @@ -724,7 +724,7 @@ describe('vim.lsp.completion: protocol', function() end) end) - it('custom word/abbar format', function() + it('enable(…,{convert=fn}) custom word/abbr format', function() create_server({ isIncomplete = false, items = { diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua index 06e286f872..ff042424f9 100644 --- a/test/functional/plugin/lsp_spec.lua +++ b/test/functional/plugin/lsp_spec.lua @@ -800,8 +800,7 @@ describe('LSP', function() eq(table.remove(expected_handlers), { err, result, ctx }, 'expected handler') if ctx.method == 'start' then local tmpfile_old = tmpname() - local tmpfile_new = tmpname() - os.remove(tmpfile_new) + local tmpfile_new = tmpname(false) exec_lua(function(oldname, newname) _G.BUFFER = vim.api.nvim_get_current_buf() vim.api.nvim_buf_set_name(_G.BUFFER, oldname) @@ -2370,8 +2369,7 @@ describe('LSP', function() end) it('Supports file creation with CreateFile payload', function() - local tmpfile = tmpname() - os.remove(tmpfile) -- Should not exist, only interested in a tmpname + local tmpfile = tmpname(false) local uri = exec_lua('return vim.uri_from_fname(...)', tmpfile) local edit = { documentChanges = { @@ -2388,9 +2386,7 @@ describe('LSP', function() it( 'Supports file creation in folder that needs to be created with CreateFile payload', function() - local tmpfile = tmpname() - os.remove(tmpfile) -- Should not exist, only interested in a tmpname - tmpfile = tmpfile .. '/dummy/x/' + local tmpfile = tmpname(false) .. '/dummy/x/' local uri = exec_lua('return vim.uri_from_fname(...)', tmpfile) local edit = { documentChanges = { @@ -2468,8 +2464,7 @@ describe('LSP', function() end) it('DeleteFile fails if file does not exist and ignoreIfNotExists is false', function() - local tmpfile = tmpname() - os.remove(tmpfile) + local tmpfile = tmpname(false) local uri = exec_lua('return vim.uri_from_fname(...)', tmpfile) local edit = { documentChanges = { @@ -2493,8 +2488,7 @@ describe('LSP', function() it('Can rename an existing file', function() local old = tmpname() write_file(old, 'Test content') - local new = tmpname() - os.remove(new) -- only reserve the name, file must not exist for the test scenario + local new = tmpname(false) local lines = exec_lua(function(old0, new0) local old_bufnr = vim.fn.bufadd(old0) vim.fn.bufload(old_bufnr) @@ -2514,10 +2508,8 @@ describe('LSP', function() it('Can rename a directory', function() -- only reserve the name, file must not exist for the test scenario - local old_dir = tmpname() - local new_dir = tmpname() - os.remove(old_dir) - os.remove(new_dir) + local old_dir = tmpname(false) + local new_dir = tmpname(false) n.mkdir_p(old_dir) @@ -2542,10 +2534,8 @@ describe('LSP', function() end) it('Does not touch buffers that do not match path prefix', function() - local old = tmpname() - local new = tmpname() - os.remove(old) - os.remove(new) + local old = tmpname(false) + local new = tmpname(false) n.mkdir_p(old) eq( @@ -2604,8 +2594,7 @@ describe('LSP', function() it('Maintains undo information for loaded buffer', function() local old = tmpname() write_file(old, 'line') - local new = tmpname() - os.remove(new) + local new = tmpname(false) local undo_kept = exec_lua(function(old0, new0) vim.opt.undofile = true @@ -2629,8 +2618,7 @@ describe('LSP', function() it('Maintains undo information for unloaded buffer', function() local old = tmpname() write_file(old, 'line') - local new = tmpname() - os.remove(new) + local new = tmpname(false) local undo_kept = exec_lua(function(old0, new0) vim.opt.undofile = true @@ -2651,8 +2639,7 @@ describe('LSP', function() it('Does not rename file when it conflicts with a buffer without file', function() local old = tmpname() write_file(old, 'Old File') - local new = tmpname() - os.remove(new) + local new = tmpname(false) local lines = exec_lua(function(old0, new0) local old_buf = vim.fn.bufadd(old0) @@ -5023,13 +5010,7 @@ describe('LSP', function() end) it('can connect to lsp server via pipe or domain_socket', function() - local tmpfile --- @type string - if is_os('win') then - tmpfile = '\\\\.\\\\pipe\\pipe.test' - else - tmpfile = tmpname() - os.remove(tmpfile) - end + local tmpfile = is_os('win') and '\\\\.\\\\pipe\\pipe.test' or tmpname(false) local result = exec_lua(function(SOCK) local uv = vim.uv local server = assert(uv.new_pipe(false)) @@ -5135,9 +5116,7 @@ describe('LSP', function() describe('#dynamic vim.lsp._dynamic', function() it('supports dynamic registration', function() - ---@type string - local root_dir = tmpname() - os.remove(root_dir) + local root_dir = tmpname(false) mkdir(root_dir) local tmpfile = root_dir .. '/dynamic.foo' local file = io.open(tmpfile, 'w') @@ -5264,8 +5243,7 @@ describe('LSP', function() ) end - local root_dir = tmpname() - os.remove(root_dir) + local root_dir = tmpname(false) mkdir(root_dir) exec_lua(create_server_definition) diff --git a/test/functional/plugin/man_spec.lua b/test/functional/plugin/man_spec.lua index c0256d1c63..057748f51d 100644 --- a/test/functional/plugin/man_spec.lua +++ b/test/functional/plugin/man_spec.lua @@ -8,7 +8,6 @@ local exec_lua = n.exec_lua local fn = n.fn local nvim_prog = n.nvim_prog local matches = t.matches -local write_file = t.write_file local tmpname = t.tmpname local eq = t.eq local pesc = vim.pesc @@ -226,7 +225,6 @@ describe(':Man', function() local actual_file = tmpname() -- actual_file must be an absolute path to an existent file for us to test against it matches('^/.+', actual_file) - write_file(actual_file, '') local args = { nvim_prog, '--headless', '+:Man ' .. actual_file, '+q' } matches( ('Error detected while processing command line:\r\n' .. 'man.lua: "no manual entry for %s"'):format( diff --git a/test/functional/treesitter/parser_spec.lua b/test/functional/treesitter/parser_spec.lua index 46e6a6002a..4aa8beebae 100644 --- a/test/functional/treesitter/parser_spec.lua +++ b/test/functional/treesitter/parser_spec.lua @@ -591,8 +591,7 @@ print() vim.treesitter.query.parse('c', '((number_literal) @number (#set! "key" "value"))') local parser = vim.treesitter.get_parser(0, 'c') - local _, _, metadata = - query:iter_matches(parser:parse()[1]:root(), 0, 0, -1, { all = true })() + local _, _, metadata = query:iter_matches(parser:parse()[1]:root(), 0, 0, -1)() return metadata.key end) @@ -612,8 +611,7 @@ print() ) local parser = vim.treesitter.get_parser(0, 'c') - local _, _, metadata = - query:iter_matches(parser:parse()[1]:root(), 0, 0, -1, { all = true })() + local _, _, metadata = query:iter_matches(parser:parse()[1]:root(), 0, 0, -1)() local _, nested_tbl = next(metadata) return nested_tbl.key end) @@ -633,8 +631,7 @@ print() ) local parser = vim.treesitter.get_parser(0, 'c') - local _, _, metadata = - query:iter_matches(parser:parse()[1]:root(), 0, 0, -1, { all = true })() + local _, _, metadata = query:iter_matches(parser:parse()[1]:root(), 0, 0, -1)() local _, nested_tbl = next(metadata) return nested_tbl end) diff --git a/test/functional/treesitter/query_spec.lua b/test/functional/treesitter/query_spec.lua index 00e8071738..d8338c1335 100644 --- a/test/functional/treesitter/query_spec.lua +++ b/test/functional/treesitter/query_spec.lua @@ -138,7 +138,7 @@ void ui_refresh(void) local parser = vim.treesitter.get_parser(0, 'c') local tree = parser:parse()[1] local res = {} - for pattern, match in cquery:iter_matches(tree:root(), 0, 7, 14, { all = true }) do + for pattern, match in cquery:iter_matches(tree:root(), 0, 7, 14) do -- can't transmit node over RPC. just check the name and range local mrepr = {} for cid, nodes in pairs(match) do @@ -211,7 +211,7 @@ void ui_refresh(void) local parser = vim.treesitter.get_parser(0, 'c') local tree = parser:parse()[1] local res = {} - for pattern, match in cquery:iter_matches(tree:root(), 0, 7, 14, { all = true }) do + for pattern, match in cquery:iter_matches(tree:root(), 0, 7, 14) do -- can't transmit node over RPC. just check the name and range local mrepr = {} for cid, nodes in pairs(match) do @@ -260,7 +260,7 @@ void ui_refresh(void) local parser = vim.treesitter.get_parser(0, 'c') local tree = parser:parse()[1] local res = {} - for pattern, match in cquery:iter_matches(tree:root(), 0, 7, 14, { all = true }) do + for pattern, match in cquery:iter_matches(tree:root(), 0, 7, 14) do -- can't transmit node over RPC. just check the name and range local mrepr = {} for cid, nodes in pairs(match) do @@ -307,7 +307,7 @@ void ui_refresh(void) local parser = vim.treesitter.get_parser(0, 'c') local tree = parser:parse()[1] local res = {} - for pattern, match in cquery:iter_matches(tree:root(), 0, 0, -1, { all = true }) do + for pattern, match in cquery:iter_matches(tree:root(), 0, 0, -1) do -- can't transmit node over RPC. just check the name and range local mrepr = {} for cid, nodes in pairs(match) do @@ -418,7 +418,7 @@ void ui_refresh(void) local parser = vim.treesitter.get_parser(0, 'c') local tree = parser:parse()[1] local res = {} - for pattern, match in cquery:iter_matches(tree:root(), 0, 0, -1, { all = true }) do + for pattern, match in cquery:iter_matches(tree:root(), 0, 0, -1) do -- can't transmit node over RPC. just check the name and range local mrepr = {} for cid, nodes in pairs(match) do @@ -464,11 +464,7 @@ void ui_refresh(void) local parser = vim.treesitter.get_parser(0, 'c') - -- Time bomb: update this in 0.12 - if vim.fn.has('nvim-0.12') == 1 then - return 'Update this test to remove this message and { all = true } from add_predicate' - end - query.add_predicate('is-main?', is_main, { all = true }) + query.add_predicate('is-main?', is_main) local query0 = query.parse('c', custom_query0) @@ -496,7 +492,7 @@ void ui_refresh(void) local parser = vim.treesitter.get_parser(0, 'c') - query.add_predicate('is-main?', is_main, true) + query.add_predicate('is-main?', is_main, { all = false, force = true }) local query0 = query.parse('c', custom_query0) @@ -650,7 +646,7 @@ void ui_refresh(void) local parser = vim.treesitter.get_parser(0, 'c') local tree = parser:parse()[1] local res = {} - for pattern, match in cquery:iter_matches(tree:root(), 0, 7, 14) do + for pattern, match in cquery:iter_matches(tree:root(), 0, 7, 14, { all = false }) do local mrepr = {} for cid, node in pairs(match) do table.insert(mrepr, { '@' .. cquery.captures[cid], node:type(), node:range() }) diff --git a/test/functional/vimscript/server_spec.lua b/test/functional/vimscript/server_spec.lua index 4b0dc087f6..2dabe1afc1 100644 --- a/test/functional/vimscript/server_spec.lua +++ b/test/functional/vimscript/server_spec.lua @@ -4,7 +4,6 @@ local n = require('test.functional.testnvim')() local assert_log = t.assert_log local eq, neq, eval = t.eq, t.neq, n.eval local clear, fn, api = n.clear, n.fn, n.api -local ok = t.ok local matches = t.matches local pcall_err = t.pcall_err local check_close = n.check_close @@ -49,15 +48,6 @@ describe('server', function() eq('', eval('$NVIM_LISTEN_ADDRESS')) end) - it('sets new v:servername if $NVIM_LISTEN_ADDRESS is invalid', function() - clear({ env = { NVIM_LISTEN_ADDRESS = '.' } }) - -- Cleared on startup. - eq('', eval('$NVIM_LISTEN_ADDRESS')) - local servers = fn.serverlist() - eq(1, #servers) - ok(string.len(servers[1]) > 4) -- "~/.local/state/nvim…/…" or "\\.\pipe\…" - end) - it('sets v:servername at startup or if all servers were stopped', function() clear() local initial_server = api.nvim_get_vvar('servername') @@ -89,20 +79,26 @@ describe('server', function() end) it('serverstop() returns false for invalid input', function() - clear { env = { - NVIM_LOG_FILE = testlog, - NVIM_LISTEN_ADDRESS = '.', - } } + clear { + args_rm = { '--listen' }, + env = { + NVIM_LOG_FILE = testlog, + NVIM_LISTEN_ADDRESS = '', + }, + } eq(0, eval("serverstop('')")) eq(0, eval("serverstop('bogus-socket-name')")) assert_log('Not listening on bogus%-socket%-name', testlog, 10) end) it('parses endpoints', function() - clear { env = { - NVIM_LOG_FILE = testlog, - NVIM_LISTEN_ADDRESS = '.', - } } + clear { + args_rm = { '--listen' }, + env = { + NVIM_LOG_FILE = testlog, + NVIM_LISTEN_ADDRESS = '', + }, + } clear_serverlist() eq({}, fn.serverlist()) @@ -178,18 +174,40 @@ end) describe('startup --listen', function() it('validates', function() clear() - local cmd = { unpack(n.nvim_argv) } - table.insert(cmd, '--listen') - matches('nvim.*: Argument missing after: "%-%-listen"', fn.system(cmd)) - cmd = { unpack(n.nvim_argv) } - table.insert(cmd, '--listen2') - matches('nvim.*: Garbage after option argument: "%-%-listen2"', fn.system(cmd)) + -- Tests args with and without "--headless". + local function _test(args, expected) + -- XXX: clear v:shell_error, sigh... + fn.system({ n.nvim_prog, '-es', '+qall!' }) + assert(0 == eval('v:shell_error')) + local cmd = vim.list_extend({ unpack(n.nvim_argv) }, vim.list_extend({ '--headless' }, args)) + local output = fn.system(cmd) + assert(0 ~= eval('v:shell_error')) + -- TODO(justinmk): output not properly captured on Windows? + if is_os('win') then + return + end + matches(expected, output) + matches(expected, fn.system(vim.list_extend({ unpack(n.nvim_argv) }, args))) + end + + _test({ '--listen' }, 'nvim.*: Argument missing after: "%-%-listen"') + _test({ '--listen2' }, 'nvim.*: Garbage after option argument: "%-%-listen2"') + _test({ '--listen', n.eval('v:servername') }, 'nvim.*: Failed to %-%-listen: ".* already .*"') + _test({ '--listen', '/' }, 'nvim.*: Failed to %-%-listen: ".*"') + _test( + { '--listen', 'https://example.com' }, + ('nvim.*: Failed to %%-%%-listen: "%s"'):format( + (is_os('mac') or is_os('win')) and 'unknown node or service' + or 'service not available for socket type' + ) + ) end) it('sets v:servername, overrides $NVIM_LISTEN_ADDRESS', function() local addr = (is_os('win') and [[\\.\pipe\Xtest-listen-pipe]] or './Xtest-listen-pipe') clear({ env = { NVIM_LISTEN_ADDRESS = './Xtest-env-pipe' }, args = { '--listen', addr } }) + eq('', eval('$NVIM_LISTEN_ADDRESS')) -- Cleared on startup. eq(addr, api.nvim_get_vvar('servername')) -- Address without slashes is a "name" which is appended to a generated path. #8519 diff --git a/test/testutil.lua b/test/testutil.lua index 439f13cf49..01eaf25406 100644 --- a/test/testutil.lua +++ b/test/testutil.lua @@ -402,18 +402,27 @@ end local tmpname_id = 0 local tmpdir = tmpdir_get() ---- Creates a new temporary file for use by tests. -function M.tmpname() +--- Generates a unique filepath for use by tests, in a test-specific "…/Xtest_tmpdir/T42.7" +--- directory (which is cleaned up by the test runner), and writes the file unless `create=false`. +--- +---@param create? boolean (default true) Write the file. +function M.tmpname(create) if tmpdir_is_local(tmpdir) then -- Cannot control os.tmpname() dir, so hack our own tmpname() impl. tmpname_id = tmpname_id + 1 -- "…/Xtest_tmpdir/T42.7" local fname = ('%s/%s.%d'):format(tmpdir, (_G._nvim_test_id or 'nvim-test'), tmpname_id) - io.open(fname, 'w'):close() + if create ~= false then + io.open(fname, 'w'):close() + end return fname end local fname = os.tmpname() + if create == false then + os.remove(fname) + end + if M.is_os('win') and fname:sub(1, 2) == '\\s' then -- In Windows tmpname() returns a filename starting with -- special sequence \s, prepend $TEMP path |