aboutsummaryrefslogtreecommitdiff
path: root/runtime/lua/vim
diff options
context:
space:
mode:
Diffstat (limited to 'runtime/lua/vim')
-rw-r--r--runtime/lua/vim/_editor.lua34
-rw-r--r--runtime/lua/vim/_init_packages.lua6
-rw-r--r--runtime/lua/vim/_inspector.lua35
-rw-r--r--runtime/lua/vim/_meta/api.lua106
-rw-r--r--runtime/lua/vim/_meta/api_keysets_extra.lua2
-rw-r--r--runtime/lua/vim/_meta/builtin.lua8
-rw-r--r--runtime/lua/vim/_meta/lpeg.lua2
-rw-r--r--runtime/lua/vim/_meta/options.lua20
-rw-r--r--runtime/lua/vim/_meta/regex.lua1
-rw-r--r--runtime/lua/vim/_meta/vimfn.lua57
-rw-r--r--runtime/lua/vim/_options.lua77
-rw-r--r--runtime/lua/vim/_watch.lua311
-rw-r--r--runtime/lua/vim/diagnostic.lua470
-rw-r--r--runtime/lua/vim/filetype.lua32
-rw-r--r--runtime/lua/vim/fs.lua72
-rw-r--r--runtime/lua/vim/iter.lua102
-rw-r--r--runtime/lua/vim/loader.lua74
-rw-r--r--runtime/lua/vim/lsp.lua331
-rw-r--r--runtime/lua/vim/lsp/_changetracking.lua12
-rw-r--r--runtime/lua/vim/lsp/_dynamic.lua2
-rw-r--r--runtime/lua/vim/lsp/_watchfiles.lua17
-rw-r--r--runtime/lua/vim/lsp/buf.lua266
-rw-r--r--runtime/lua/vim/lsp/client.lua180
-rw-r--r--runtime/lua/vim/lsp/codelens.lua6
-rw-r--r--runtime/lua/vim/lsp/diagnostic.lua8
-rw-r--r--runtime/lua/vim/lsp/handlers.lua76
-rw-r--r--runtime/lua/vim/lsp/health.lua40
-rw-r--r--runtime/lua/vim/lsp/inlay_hint.lua24
-rw-r--r--runtime/lua/vim/lsp/log.lua26
-rw-r--r--runtime/lua/vim/lsp/protocol.lua54
-rw-r--r--runtime/lua/vim/lsp/rpc.lua49
-rw-r--r--runtime/lua/vim/lsp/semantic_tokens.lua28
-rw-r--r--runtime/lua/vim/lsp/util.lua170
-rw-r--r--runtime/lua/vim/secure.lua25
-rw-r--r--runtime/lua/vim/shared.lua67
-rw-r--r--runtime/lua/vim/snippet.lua4
-rw-r--r--runtime/lua/vim/treesitter.lua84
-rw-r--r--runtime/lua/vim/treesitter/_meta.lua7
-rw-r--r--runtime/lua/vim/treesitter/_query_linter.lua14
-rw-r--r--runtime/lua/vim/treesitter/dev.lua69
-rw-r--r--runtime/lua/vim/treesitter/highlighter.lua146
-rw-r--r--runtime/lua/vim/treesitter/language.lua30
-rw-r--r--runtime/lua/vim/treesitter/languagetree.lua66
-rw-r--r--runtime/lua/vim/treesitter/query.lua94
-rw-r--r--runtime/lua/vim/version.lua65
45 files changed, 1868 insertions, 1501 deletions
diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua
index 4e39abb2be..f527fc194c 100644
--- a/runtime/lua/vim/_editor.lua
+++ b/runtime/lua/vim/_editor.lua
@@ -74,7 +74,6 @@ vim.log = {
--- Examples:
---
--- ```lua
----
--- local on_exit = function(obj)
--- print(obj.code)
--- print(obj.signal)
@@ -190,6 +189,7 @@ function vim._os_proc_children(ppid)
return children
end
+--- @nodoc
--- @class vim.inspect.Opts
--- @field depth? integer
--- @field newline? string
@@ -274,6 +274,7 @@ do
for _, line in ipairs(lines) do
nchars = nchars + line:len()
end
+ --- @type integer, integer
local row, col = unpack(vim.api.nvim_win_get_cursor(0))
local bufline = vim.api.nvim_buf_get_lines(0, row - 1, row, true)[1]
local firstline = lines[1]
@@ -354,8 +355,11 @@ end
-- vim.fn.{func}(...)
---@nodoc
vim.fn = setmetatable({}, {
+ --- @param t table<string,function>
+ --- @param key string
+ --- @return function
__index = function(t, key)
- local _fn
+ local _fn --- @type function
if vim.api[key] ~= nil then
_fn = function()
error(string.format('Tried to call API function with vim.fn: use vim.api.%s instead', key))
@@ -454,7 +458,7 @@ vim.cmd = setmetatable({}, {
end,
})
---- @class vim.var_accessor
+--- @class (private) vim.var_accessor
--- @field [string] any
--- @field [integer] vim.var_accessor
@@ -497,7 +501,7 @@ end
---@param bufnr integer Buffer number, or 0 for current buffer
---@param pos1 integer[]|string Start of region as a (line, column) tuple or |getpos()|-compatible string
---@param pos2 integer[]|string End of region as a (line, column) tuple or |getpos()|-compatible string
----@param regtype string \|setreg()|-style selection type
+---@param regtype string [setreg()]-style selection type
---@param inclusive boolean Controls whether the ending column is inclusive (see also 'selection').
---@return table region Dict of the form `{linenr = {startcol,endcol}}`. `endcol` is exclusive, and
---whole lines are returned as `{startcol,endcol} = {0,-1}`.
@@ -619,7 +623,7 @@ function vim.notify(msg, level, opts) -- luacheck: no unused args
end
do
- local notified = {}
+ local notified = {} --- @type table<string,true>
--- Displays a notification only one time.
---
@@ -640,7 +644,7 @@ do
end
end
-local on_key_cbs = {}
+local on_key_cbs = {} --- @type table<integer,function>
--- Adds Lua function {fn} with namespace id {ns_id} as a listener to every,
--- yes every, input key.
@@ -710,6 +714,7 @@ end
--- 2. Can we get it to return things from global namespace even with `print(` in front.
---
--- @param pat string
+--- @return any[], integer
function vim._expand_pat(pat, env)
env = env or _G
@@ -742,7 +747,7 @@ function vim._expand_pat(pat, env)
if type(final_env) ~= 'table' then
return {}, 0
end
- local key
+ local key --- @type any
-- Normally, we just have a string
-- Just attempt to get the string directly from the environment
@@ -784,7 +789,8 @@ function vim._expand_pat(pat, env)
end
end
- local keys = {}
+ local keys = {} --- @type table<string,true>
+ --- @param obj table<any,any>
local function insert_keys(obj)
for k, _ in pairs(obj) do
if type(k) == 'string' and string.sub(k, 1, string.len(match_part)) == match_part then
@@ -812,6 +818,7 @@ function vim._expand_pat(pat, env)
end
--- @param lua_string string
+--- @return (string|string[])[], integer
vim._expand_pat_get_parts = function(lua_string)
local parts = {}
@@ -869,6 +876,7 @@ vim._expand_pat_get_parts = function(lua_string)
end
end
+ --- @param val any[]
parts = vim.tbl_filter(function(val)
return #val > 0
end, parts)
@@ -879,7 +887,7 @@ end
do
-- Ideally we should just call complete() inside omnifunc, though there are
-- some bugs, so fake the two-step dance for now.
- local matches
+ local matches --- @type any[]
--- Omnifunc for completing Lua values from the runtime Lua interpreter,
--- similar to the builtin completion for the `:lua` command.
@@ -899,12 +907,6 @@ do
end
end
----@private
-function vim.pretty_print(...)
- vim.deprecate('vim.pretty_print()', 'vim.print()', '0.10')
- return vim.print(...)
-end
-
--- "Pretty prints" the given arguments and returns them unmodified.
---
--- Example:
@@ -1048,7 +1050,7 @@ function vim.deprecate(name, alternative, version, plugin, backtrace)
-- e.g., when planned to be removed in version = '0.12' (soft-deprecated since 0.10-dev),
-- show warnings since 0.11, including 0.11-dev (hard_deprecated_since = 0.11-dev).
if plugin == 'Nvim' then
- local current_version = vim.version() ---@type Version
+ local current_version = vim.version() ---@type vim.Version
local removal_version = assert(vim.version.parse(version))
local is_hard_deprecated ---@type boolean
diff --git a/runtime/lua/vim/_init_packages.lua b/runtime/lua/vim/_init_packages.lua
index 71ac3bcfcd..d0bb91114e 100644
--- a/runtime/lua/vim/_init_packages.lua
+++ b/runtime/lua/vim/_init_packages.lua
@@ -1,5 +1,5 @@
-local pathtrails = {}
-vim._so_trails = {}
+local pathtrails = {} --- @type table<string,true> ta
+vim._so_trails = {} --- @type string[]
for s in (package.cpath .. ';'):gmatch('[^;]*;') do
s = s:sub(1, -2) -- Strip trailing semicolon
-- Find out path patterns. pathtrail should contain something like
@@ -65,6 +65,7 @@ vim._submodules = {
-- These are for loading runtime modules in the vim namespace lazily.
setmetatable(vim, {
+ --- @param t table<any,any>
__index = function(t, key)
if vim._submodules[key] then
t[key] = require('vim.' .. key)
@@ -73,6 +74,7 @@ setmetatable(vim, {
require('vim._inspector')
return t[key]
elseif vim.startswith(key, 'uri_') then
+ --- @type any?
local val = require('vim.uri')[key]
if val ~= nil then
-- Expose all `vim.uri` functions on the `vim` module.
diff --git a/runtime/lua/vim/_inspector.lua b/runtime/lua/vim/_inspector.lua
index 9a073c32c4..afbd6211cd 100644
--- a/runtime/lua/vim/_inspector.lua
+++ b/runtime/lua/vim/_inspector.lua
@@ -1,8 +1,21 @@
----@class InspectorFilter
----@field syntax boolean include syntax based highlight groups (defaults to true)
----@field treesitter boolean include treesitter based highlight groups (defaults to true)
----@field extmarks boolean|"all" include extmarks. When `all`, then extmarks without a `hl_group` will also be included (defaults to true)
----@field semantic_tokens boolean include semantic token highlights (defaults to true)
+--- @class vim._inspector.Filter
+--- @inlinedoc
+---
+--- Include syntax based highlight groups.
+--- (default: `true`)
+--- @field syntax boolean
+---
+--- Include treesitter based highlight groups.
+--- (default: `true`)
+--- @field treesitter boolean
+---
+--- Include extmarks. When `all`, then extmarks without a `hl_group` will also be included.
+--- (default: true)
+--- @field extmarks boolean|"all"
+---
+--- Include semantic token highlights.
+--- (default: true)
+--- @field semantic_tokens boolean
local defaults = {
syntax = true,
treesitter = true,
@@ -12,16 +25,12 @@ local defaults = {
---Get all the items at a given buffer position.
---
----Can also be pretty-printed with `:Inspect!`. *:Inspect!*
+---Can also be pretty-printed with `:Inspect!`. [:Inspect!]()
---
---@param bufnr? integer defaults to the current buffer
---@param row? integer row to inspect, 0-based. Defaults to the row of the current cursor
---@param col? integer col to inspect, 0-based. Defaults to the col of the current cursor
----@param filter? InspectorFilter (table) a table with key-value pairs to filter the items
---- - syntax (boolean): include syntax based highlight groups (defaults to true)
---- - treesitter (boolean): include treesitter based highlight groups (defaults to true)
---- - extmarks (boolean|"all"): include extmarks. When `all`, then extmarks without a `hl_group` will also be included (defaults to true)
---- - semantic_tokens (boolean): include semantic tokens (defaults to true)
+---@param filter? vim._inspector.Filter Table with key-value pairs to filter the items
---@return {treesitter:table,syntax:table,extmarks:table,semantic_tokens:table,buffer:integer,col:integer,row:integer} (table) a table with the following key-value pairs. Items are in "traversal order":
--- - treesitter: a list of treesitter captures
--- - syntax: a list of syntax groups
@@ -134,12 +143,12 @@ end
---Show all the items at a given buffer position.
---
----Can also be shown with `:Inspect`. *:Inspect*
+---Can also be shown with `:Inspect`. [:Inspect]()
---
---@param bufnr? integer defaults to the current buffer
---@param row? integer row to inspect, 0-based. Defaults to the row of the current cursor
---@param col? integer col to inspect, 0-based. Defaults to the col of the current cursor
----@param filter? InspectorFilter (table) see |vim.inspect_pos()|
+---@param filter? vim._inspector.Filter
function vim.show_pos(bufnr, row, col, filter)
local items = vim.inspect_pos(bufnr, row, col, filter)
diff --git a/runtime/lua/vim/_meta/api.lua b/runtime/lua/vim/_meta/api.lua
index d2f624fd97..ed8128769d 100644
--- a/runtime/lua/vim/_meta/api.lua
+++ b/runtime/lua/vim/_meta/api.lua
@@ -530,7 +530,7 @@ function vim.api.nvim_buf_line_count(buffer) end
--- EOL of a line, continue the highlight for the rest of the
--- screen line (just like for diff and cursorline highlight).
--- • virt_text : virtual text to link to this mark. A list of
---- [text, highlight] tuples, each representing a text chunk
+--- `[text, highlight]` tuples, each representing a text chunk
--- with specified highlight. `highlight` element can either be
--- a single highlight group, or an array of multiple highlight
--- groups that will be stacked (highest priority last). A
@@ -563,8 +563,8 @@ function vim.api.nvim_buf_line_count(buffer) end
--- for "inline" virt_text.
--- • virt_lines : virtual lines to add next to this mark This
--- should be an array over lines, where each line in turn is an
---- array over [text, highlight] tuples. In general, buffer and
---- window options do not affect the display of the text. In
+--- array over `[text, highlight]` tuples. In general, buffer
+--- and window options do not affect the display of the text. In
--- particular 'wrap' and 'linebreak' options do not take
--- effect, so the number of extra screen lines will always
--- match the size of the array. However the 'tabstop' buffer
@@ -690,9 +690,9 @@ function vim.api.nvim_buf_set_option(buffer, name, value) end
--- Indexing is zero-based. Row indices are end-inclusive, and column indices
--- are end-exclusive.
---
---- To insert text at a given `(row, column)` location, use `start_row =
---- end_row = row` and `start_col = end_col = col`. To delete the text in a
---- range, use `replacement = {}`.
+--- To insert text at a given `(row, column)` location, use
+--- `start_row = end_row = row` and `start_col = end_col = col`. To delete the
+--- text in a range, use `replacement = {}`.
---
--- Prefer `nvim_buf_set_lines()` if you are only adding or deleting entire
--- lines.
@@ -903,9 +903,9 @@ function vim.api.nvim_create_augroup(name, opts) end
--- • 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>`
+--- • match: (string) expanded value of <amatch>
+--- • buf: (number) expanded value of <abuf>
+--- • file: (string) expanded value of <afile>
--- • data: (any) arbitrary data passed from
--- `nvim_exec_autocmds()`
--- • command (string) optional: Vim command to execute on event.
@@ -960,22 +960,21 @@ function vim.api.nvim_create_namespace(name) end
--- argument that contains the following keys:
--- • name: (string) Command name
--- • args: (string) The args passed to the command, if any
---- `<args>`
+--- <args>
--- • fargs: (table) The args split by unescaped whitespace
---- (when more than one argument is allowed), if any
---- `<f-args>`
+--- (when more than one argument is allowed), if any <f-args>
--- • nargs: (string) Number of arguments `:command-nargs`
--- • bang: (boolean) "true" if the command was executed with a
---- ! modifier `<bang>`
+--- ! modifier <bang>
--- • line1: (number) The starting line of the command range
---- `<line1>`
+--- <line1>
--- • line2: (number) The final line of the command range
---- `<line2>`
+--- <line2>
--- • range: (number) The number of items in the command range:
---- 0, 1, or 2 `<range>`
---- • count: (number) Any count supplied `<count>`
---- • reg: (string) The optional register, if specified `<reg>`
---- • mods: (string) Command modifiers, if any `<mods>`
+--- 0, 1, or 2 <range>
+--- • count: (number) Any count supplied <count>
+--- • reg: (string) The optional register, if specified <reg>
+--- • mods: (string) Command modifiers, if any <mods>
--- • smods: (table) Command modifiers in a structured format.
--- Has the same structure as the "mods" key of
--- `nvim_parse_cmd()`.
@@ -1049,9 +1048,9 @@ function vim.api.nvim_del_var(name) end
--- Echo a message.
---
---- @param chunks any[] A list of [text, hl_group] arrays, each representing a text
---- chunk with specified highlight. `hl_group` element can be
---- omitted for no highlight.
+--- @param chunks any[] A list of `[text, hl_group]` arrays, each representing a
+--- text chunk with specified highlight. `hl_group` element can
+--- be omitted for no highlight.
--- @param history boolean if true, add to `message-history`.
--- @param opts vim.api.keyset.echo_opts Optional parameters.
--- • verbose: Message was printed as a result of 'verbose' option
@@ -1134,7 +1133,7 @@ function vim.api.nvim_exec2(src, opts) end
--- • buffer (integer) optional: buffer number `autocmd-buflocal`.
--- Cannot be used with {pattern}.
--- • modeline (bool) optional: defaults to true. Process the
---- modeline after the autocommands `<nomodeline>`.
+--- modeline after the autocommands <nomodeline>.
--- • data (any): arbitrary data to send to the autocommand
--- callback. See `nvim_create_autocmd()` for details.
function vim.api.nvim_exec_autocmds(event, opts) end
@@ -1541,7 +1540,7 @@ function vim.api.nvim_notify(msg, log_level, opts) end
--- be to the pty master end. For instance, a carriage return is
--- sent as a "\r", not as a "\n". `textlock` applies. It is
--- possible to call `nvim_chan_send()` directly in the callback
---- however. ["input", term, bufnr, data]
+--- however. `["input", term, bufnr, data]`
--- • force_crlf: (boolean, default true) Convert "\n" to "\r\n".
--- @return integer
function vim.api.nvim_open_term(buffer, opts) end
@@ -1625,9 +1624,9 @@ function vim.api.nvim_open_term(buffer, opts) end
--- • width: Window width (in character cells). Minimum of 1.
--- • height: Window height (in character cells). Minimum of 1.
--- • bufpos: Places float relative to buffer text (only when
---- relative="win"). Takes a tuple of zero-indexed [line,
---- column]. `row` and `col` if given are applied relative to
---- this position, else they default to:
+--- relative="win"). Takes a tuple of zero-indexed
+--- `[line, column]`. `row` and `col` if given are applied
+--- relative to this position, else they default to:
--- • `row=1` and `col=0` if `anchor` is "NW" or "NE"
--- • `row=0` and `col=0` if `anchor` is "SW" or "SE" (thus
--- like a tooltip near the buffer text).
@@ -1719,9 +1718,9 @@ function vim.api.nvim_open_term(buffer, opts) end
--- • footer_pos: Footer position. Must be set with `footer`
--- option. Value can be one of "left", "center", or "right".
--- Default is `"left"`.
---- • noautocmd: If true then no buffer-related autocommand
---- events such as `BufEnter`, `BufLeave` or `BufWinEnter` may
---- fire from calling this function.
+--- • noautocmd: If true then autocommands triggered from
+--- setting the `buffer` to display are blocked (e.g:
+--- `BufEnter`, `BufLeave`, `BufWinEnter`).
--- • fixed: If true when anchor is NW or SW, the float window
--- would be kept fixed even if the window would be truncated.
--- • hide: If true the floating window will be hidden.
@@ -1756,8 +1755,8 @@ function vim.api.nvim_parse_cmd(str, opts) end
--- operator/space, though also yielding an error).
--- • "l" when needing to start parsing with lvalues for ":let"
--- or ":for". Common flag sets:
---- • "m" to parse like for ":echo".
---- • "E" to parse like for "<C-r>=".
+--- • "m" to parse like for `":echo"`.
+--- • "E" to parse like for `"<C-r>="`.
--- • empty string for ":call".
--- • "lm" to parse for ":let".
--- @param highlight boolean If true, return value will also include "highlight" key
@@ -1887,15 +1886,32 @@ function vim.api.nvim_set_current_win(window) end
---
--- @param ns_id integer Namespace id from `nvim_create_namespace()`
--- @param opts vim.api.keyset.set_decoration_provider Table of callbacks:
---- • on_start: called first on each screen redraw ["start", tick]
+--- • on_start: called first on each screen redraw
+--- ```
+--- ["start", tick]
+--- ```
+---
--- • on_buf: called for each buffer being redrawn (before window
---- callbacks) ["buf", bufnr, tick]
+--- callbacks)
+--- ```
+--- ["buf", bufnr, tick]
+--- ```
+---
--- • on_win: called when starting to redraw a specific window.
---- ["win", winid, bufnr, topline, botline]
+--- ```
+--- ["win", winid, bufnr, topline, botline]
+--- ```
+---
--- • on_line: called for each buffer line being redrawn. (The
---- interaction with fold lines is subject to change) ["line",
---- winid, bufnr, row]
---- • on_end: called at the end of a redraw cycle ["end", tick]
+--- interaction with fold lines is subject to change)
+--- ```
+--- ["line", winid, bufnr, row]
+--- ```
+---
+--- • on_end: called at the end of a redraw cycle
+--- ```
+--- ["end", tick]
+--- ```
function vim.api.nvim_set_decoration_provider(ns_id, opts) end
--- Sets a highlight group.
@@ -1955,7 +1971,7 @@ function vim.api.nvim_set_hl_ns_fast(ns_id) end
--- To set a buffer-local mapping, use `nvim_buf_set_keymap()`.
---
--- Unlike `:map`, leading/trailing whitespace is accepted as part of the
---- {lhs} or {rhs}. Empty {rhs} is `<Nop>`. `keycodes` are replaced as usual.
+--- {lhs} or {rhs}. Empty {rhs} is <Nop>. `keycodes` are replaced as usual.
---
--- Example:
---
@@ -1977,7 +1993,7 @@ function vim.api.nvim_set_hl_ns_fast(ns_id) end
--- @param lhs string Left-hand-side `{lhs}` of the mapping.
--- @param rhs string Right-hand-side `{rhs}` of the mapping.
--- @param opts vim.api.keyset.keymap Optional parameters map: Accepts all `:map-arguments` as keys
---- except `<buffer>`, values are booleans (default false). Also:
+--- except <buffer>, values are booleans (default false). Also:
--- • "noremap" disables `recursive_mapping`, like `:noremap`
--- • "desc" human-readable description.
--- • "callback" Lua function called in place of {rhs}.
@@ -2118,7 +2134,7 @@ function vim.api.nvim_win_get_buf(window) end
--- `relative` is empty for normal windows.
---
--- @param window integer Window handle, or 0 for current window
---- @return vim.api.keyset.float_config
+--- @return vim.api.keyset.win_config
function vim.api.nvim_win_get_config(window) end
--- Gets the (1,0)-indexed, buffer-relative cursor position for a given window
@@ -2207,11 +2223,11 @@ function vim.api.nvim_win_remove_ns(window, ns_id) end
--- @param buffer integer Buffer handle
function vim.api.nvim_win_set_buf(window, buffer) end
---- Configures window layout. Currently only for floating and external windows
---- (including changing a split window to those layouts).
+--- Configures window layout. Cannot be used to move the last window in a
+--- tabpage to a different one.
---
---- When reconfiguring a floating window, absent option keys will not be
---- changed. `row`/`col` and `relative` must be reconfigured together.
+--- When reconfiguring a window, absent option keys will not be changed.
+--- `row`/`col` and `relative` must be reconfigured together.
---
--- @param window integer Window handle, or 0 for current window
--- @param config vim.api.keyset.win_config Map defining the window configuration, see `nvim_open_win()`
diff --git a/runtime/lua/vim/_meta/api_keysets_extra.lua b/runtime/lua/vim/_meta/api_keysets_extra.lua
index 8e34ee534c..d61dd2c02f 100644
--- a/runtime/lua/vim/_meta/api_keysets_extra.lua
+++ b/runtime/lua/vim/_meta/api_keysets_extra.lua
@@ -129,7 +129,7 @@ error('Cannot require a meta file')
--- @field last_set_chan integer
--- @field type 'string'|'boolean'|'number'
--- @field default string|boolean|integer
---- @field allow_duplicates boolean
+--- @field allows_duplicates boolean
--- @class vim.api.keyset.parse_cmd.mods
--- @field filter { force: boolean, pattern: string }
diff --git a/runtime/lua/vim/_meta/builtin.lua b/runtime/lua/vim/_meta/builtin.lua
index 472162ecc1..ef9821fa32 100644
--- a/runtime/lua/vim/_meta/builtin.lua
+++ b/runtime/lua/vim/_meta/builtin.lua
@@ -61,6 +61,7 @@ error('Cannot require a meta file')
---
--- </pre>
+---@nodoc
---@class vim.NIL
---@type vim.NIL
@@ -90,9 +91,8 @@ function vim.empty_dict() end
--- This function also works in a fast callback |lua-loop-callbacks|.
--- @param channel integer
--- @param method string
---- @param args? any[]
--- @param ...? any
-function vim.rpcnotify(channel, method, args, ...) end
+function vim.rpcnotify(channel, method, ...) end
--- Sends a request to {channel} to invoke {method} via |RPC| and blocks until
--- a response is received.
@@ -101,9 +101,8 @@ function vim.rpcnotify(channel, method, args, ...) end
--- special value
--- @param channel integer
--- @param method string
---- @param args? any[]
--- @param ...? any
-function vim.rpcrequest(channel, method, args, ...) end
+function vim.rpcrequest(channel, method, ...) end
--- Compares strings case-insensitively.
--- @param a string
@@ -216,7 +215,6 @@ function vim.schedule(fn) end
--- Examples:
---
--- ```lua
----
--- ---
--- -- Wait for 100 ms, allowing other events to process
--- vim.wait(100, function() end)
diff --git a/runtime/lua/vim/_meta/lpeg.lua b/runtime/lua/vim/_meta/lpeg.lua
index 202c99f18c..1ce40f3340 100644
--- a/runtime/lua/vim/_meta/lpeg.lua
+++ b/runtime/lua/vim/_meta/lpeg.lua
@@ -21,6 +21,7 @@ error('Cannot require a meta file')
vim.lpeg = {}
+--- @nodoc
--- @class vim.lpeg.Pattern
--- @operator unm: vim.lpeg.Pattern
--- @operator add(vim.lpeg.Pattern): vim.lpeg.Pattern
@@ -167,6 +168,7 @@ function vim.lpeg.S(string) end
--- @return vim.lpeg.Pattern
function vim.lpeg.V(v) end
+--- @nodoc
--- @class vim.lpeg.Locale
--- @field alnum userdata
--- @field alpha userdata
diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua
index 5a9215fa9e..ac366197cc 100644
--- a/runtime/lua/vim/_meta/options.lua
+++ b/runtime/lua/vim/_meta/options.lua
@@ -5195,9 +5195,6 @@ vim.wo.scr = vim.wo.scroll
--- Minimum is 1, maximum is 100000.
--- Only in `terminal` buffers.
---
---- Note: Lines that are not visible and kept in scrollback are not
---- reflown when the terminal buffer is resized horizontally.
----
--- @type integer
vim.o.scrollback = -1
vim.o.scbk = vim.o.scrollback
@@ -5799,7 +5796,8 @@ vim.bo.sw = vim.bo.shiftwidth
--- items, for instance "scanning tags"
--- q do not show "recording @a" when recording a macro *shm-q*
--- F don't give the file info when editing a file, like *shm-F*
---- `:silent` was used for the command
+--- `:silent` was used for the command; note that this also
+--- affects messages from 'autoread' reloading
--- S do not show search count message when searching, e.g. *shm-S*
--- "[1/5]"
---
@@ -6745,6 +6743,8 @@ vim.bo.swf = vim.bo.swapfile
--- "split" when both are present.
--- uselast If included, jump to the previously used window when
--- jumping to errors with `quickfix` commands.
+--- If a window has 'winfixbuf' enabled, 'switchbuf' is currently not
+--- applied to the split window.
---
--- @type string
vim.o.switchbuf = "uselast"
@@ -7873,6 +7873,18 @@ vim.o.wi = vim.o.window
vim.go.window = vim.o.window
vim.go.wi = vim.go.window
+--- If enabled, the window and the buffer it is displaying are paired.
+--- For example, attempting to change the buffer with `:edit` will fail.
+--- Other commands which change a window's buffer such as `:cnext` will
+--- also skip any window with 'winfixbuf' enabled. However if an Ex
+--- command has a "!" modifier, it can force switching buffers.
+---
+--- @type boolean
+vim.o.winfixbuf = false
+vim.o.wfb = vim.o.winfixbuf
+vim.wo.winfixbuf = vim.o.winfixbuf
+vim.wo.wfb = vim.wo.winfixbuf
+
--- Keep the window height when windows are opened or closed and
--- 'equalalways' is set. Also for `CTRL-W_=`. Set by default for the
--- `preview-window` and `quickfix-window`.
diff --git a/runtime/lua/vim/_meta/regex.lua b/runtime/lua/vim/_meta/regex.lua
index ab403b97e7..595ad96319 100644
--- a/runtime/lua/vim/_meta/regex.lua
+++ b/runtime/lua/vim/_meta/regex.lua
@@ -12,6 +12,7 @@
--- @return vim.regex
function vim.regex(re) end
+--- @nodoc
--- @class vim.regex
local regex = {} -- luacheck: no unused
diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua
index be89c7dd01..da251f89ad 100644
--- a/runtime/lua/vim/_meta/vimfn.lua
+++ b/runtime/lua/vim/_meta/vimfn.lua
@@ -3185,7 +3185,7 @@ function vim.fn.getjumplist(winnr, tabnr) end
--- <To get lines from another buffer see |getbufline()| and
--- |getbufoneline()|
---
---- @param lnum integer
+--- @param lnum integer|string
--- @param end_? nil|false
--- @return string
function vim.fn.getline(lnum, end_) end
@@ -3525,43 +3525,49 @@ function vim.fn.getreg(regname, list) end
--- @return table
function vim.fn.getreginfo(regname) end
---- Returns the list of strings from {pos1} to {pos2} in current
+--- Returns the list of strings from {pos1} to {pos2} from a
--- buffer.
---
--- {pos1} and {pos2} must both be |List|s with four numbers.
---- See |getpos()| for the format of the list.
+--- See |getpos()| for the format of the list. It's possible
+--- to specify positions from a different buffer, but please
+--- note the limitations at |getregion-notes|.
---
--- The optional argument {opts} is a Dict and supports the
--- following items:
---
---- type Specify the selection type
+--- type Specify the region's selection type
--- (default: "v"):
--- "v" for |charwise| mode
--- "V" for |linewise| mode
--- "<CTRL-V>" for |blockwise-visual| mode
---
--- exclusive If |TRUE|, use exclusive selection
---- for the end position 'selection'.
+--- for the end position
+--- (default: follow 'selection')
---
--- You can get the last selection type by |visualmode()|.
--- If Visual mode is active, use |mode()| to get the Visual mode
--- (e.g., in a |:vmap|).
---- This function uses the line and column number from the
---- specified position.
---- It is useful to get text starting and ending in different
---- columns, such as |charwise-visual| selection.
+--- This function is useful to get text starting and ending in
+--- different columns, such as a |charwise-visual| selection.
---
+--- *getregion-notes*
--- Note that:
--- - Order of {pos1} and {pos2} doesn't matter, it will always
--- return content from the upper left position to the lower
--- right position.
---- - If 'virtualedit' is enabled and selection is past the end of
---- line, resulting lines are filled with blanks.
---- - If the selection starts or ends in the middle of a multibyte
---- character, it is not included but its selected part is
---- substituted with spaces.
---- - If {pos1} or {pos2} is not current in the buffer, an empty
+--- - If 'virtualedit' is enabled and the region is past the end
+--- of the lines, resulting lines are padded with spaces.
+--- - If the region is blockwise and it starts or ends in the
+--- middle of a multi-cell character, it is not included but
+--- its selected part is substituted with spaces.
+--- - If {pos1} and {pos2} are not in the same buffer, an empty
--- list is returned.
+--- - {pos1} and {pos2} must belong to a |bufloaded()| buffer.
+--- - It is evaluated in current window context, which makes a
+--- difference if the buffer is displayed in a window with
+--- different 'virtualedit' or 'list' values.
---
--- Examples: >
--- :xnoremap <CR>
@@ -4246,7 +4252,7 @@ function vim.fn.id(expr) end
--- |getline()|.
--- When {lnum} is invalid -1 is returned.
---
---- @param lnum integer
+--- @param lnum integer|string
--- @return integer
function vim.fn.indent(lnum) end
@@ -6514,6 +6520,9 @@ function vim.fn.prevnonblank(lnum) end
--- echo printf("%1$*2$.*3$f", 1.4142135, 6, 2)
--- < 1.41
---
+--- You will get an overflow error |E1510|, when the field-width
+--- or precision will result in a string longer than 6400 chars.
+---
--- *E1500*
--- You cannot mix positional and non-positional arguments: >vim
--- echo printf("%s%1$s", "One", "Two")
@@ -7253,6 +7262,7 @@ function vim.fn.screenstring(row, col) end
--- When a match has been found its line number is returned.
--- If there is no match a 0 is returned and the cursor doesn't
--- move. No error message is given.
+--- To get the matched string, use |matchbufline()|.
---
--- {flags} is a String, which can contain these character flags:
--- 'b' search Backward instead of forward
@@ -10585,17 +10595,16 @@ function vim.fn.win_move_statusline(nr, offset) end
--- [1, 1], unless there is a tabline, then it is [2, 1].
--- {nr} can be the window number or the |window-ID|. Use zero
--- for the current window.
---- Returns [0, 0] if the window cannot be found in the current
---- tabpage.
+--- Returns [0, 0] if the window cannot be found.
---
--- @param nr integer
--- @return any
function vim.fn.win_screenpos(nr) end
---- Move the window {nr} to a new split of the window {target}.
---- This is similar to moving to {target}, creating a new window
---- using |:split| but having the same contents as window {nr}, and
---- then closing {nr}.
+--- Temporarily switch to window {target}, then move window {nr}
+--- to a new split adjacent to {target}.
+--- Unlike commands such as |:split|, no new windows are created
+--- (the |window-ID| of window {nr} is unchanged after the move).
---
--- Both {nr} and {target} can be window numbers or |window-ID|s.
--- Both must be in the current tab page.
@@ -10718,7 +10727,9 @@ function vim.fn.winline() end
--- # the number of the last accessed window (where
--- |CTRL-W_p| goes to). If there is no previous
--- window or it is in another tab page 0 is
---- returned.
+--- returned. May refer to the current window in
+--- some cases (e.g. when evaluating 'statusline'
+--- expressions).
--- {N}j the number of the Nth window below the
--- current window (where |CTRL-W_j| goes to).
--- {N}k the number of the Nth window above the current
diff --git a/runtime/lua/vim/_options.lua b/runtime/lua/vim/_options.lua
index f1fed50c6d..13ad6cc58f 100644
--- a/runtime/lua/vim/_options.lua
+++ b/runtime/lua/vim/_options.lua
@@ -105,6 +105,10 @@ local key_value_options = {
winhl = true,
}
+--- @nodoc
+--- @class vim._option.Info : vim.api.keyset.get_option_info
+--- @field metatype 'boolean'|'string'|'number'|'map'|'array'|'set'
+
--- Convert a vimoption_T style dictionary to the correct OptionType associated with it.
---@return string
local function get_option_metatype(name, info)
@@ -123,8 +127,10 @@ local function get_option_metatype(name, info)
end
--- @param name string
+--- @return vim._option.Info
local function get_options_info(name)
local info = api.nvim_get_option_info2(name, {})
+ --- @cast info vim._option.Info
info.metatype = get_option_metatype(name, info)
return info
end
@@ -139,7 +145,6 @@ end
--- vim.env.FOO = 'bar'
--- print(vim.env.TERM)
--- ```
----@param var string
vim.env = setmetatable({}, {
__index = function(_, k)
local v = vim.fn.getenv(k)
@@ -271,10 +276,10 @@ vim.go = setmetatable({}, {
})
--- Get or set buffer-scoped |options| for the buffer with number {bufnr}.
---- Like `:set` and `:setlocal`. If [{bufnr}] is omitted then the current
---- buffer is used. Invalid {bufnr} or key is an error.
+--- If {bufnr} is omitted then the current buffer is used.
+--- Invalid {bufnr} or key is an error.
---
---- Note: this is equivalent to both `:set` and `:setlocal`.
+--- Note: this is equivalent to `:setlocal` for |global-local| options and `:set` otherwise.
---
--- Example:
---
@@ -287,9 +292,9 @@ vim.go = setmetatable({}, {
vim.bo = new_buf_opt_accessor()
--- Get or set window-scoped |options| for the window with handle {winid} and
---- buffer with number {bufnr}. Like `:setlocal` if {bufnr} is provided, like
---- `:set` otherwise. If [{winid}] is omitted then the current window is
---- used. Invalid {winid}, {bufnr} or key is an error.
+--- buffer with number {bufnr}. Like `:setlocal` if setting a |global-local| option
+--- or if {bufnr} is provided, like `:set` otherwise. If {winid} is omitted then
+--- the current window is used. Invalid {winid}, {bufnr} or key is an error.
---
--- Note: only {bufnr} with value `0` (the current buffer in the window) is
--- supported.
@@ -311,9 +316,15 @@ vim.wo = new_win_opt_accessor()
--- For information on how to use, see :help vim.opt
--- Preserves the order and does not mutate the original list
+--- @generic T
+--- @param t T[]
+--- @return T[]
local function remove_duplicate_values(t)
+ --- @type table, table<any,true>
local result, seen = {}, {}
- for _, v in ipairs(t) do
+ for _, v in
+ ipairs(t --[[@as any[] ]])
+ do
if not seen[v] then
table.insert(result, v)
end
@@ -324,8 +335,11 @@ local function remove_duplicate_values(t)
return result
end
--- Check whether the OptionTypes is allowed for vim.opt
--- If it does not match, throw an error which indicates which option causes the error.
+--- Check whether the OptionTypes is allowed for vim.opt
+--- If it does not match, throw an error which indicates which option causes the error.
+--- @param name any
+--- @param value any
+--- @param types string[]
local function assert_valid_value(name, value, types)
local type_of_value = type(value)
for _, valid_type in ipairs(types) do
@@ -352,6 +366,8 @@ local function tbl_merge(left, right)
return vim.tbl_extend('force', left, right)
end
+--- @param t table<any,any>
+--- @param value any|any[]
local function tbl_remove(t, value)
if type(value) == 'string' then
t[value] = nil
@@ -380,6 +396,8 @@ local to_vim_value = {
number = passthrough,
string = passthrough,
+ --- @param info vim._option.Info
+ --- @param value string|table<string,true>
set = function(info, value)
if type(value) == 'string' then
return value
@@ -407,6 +425,8 @@ local to_vim_value = {
end
end,
+ --- @param info vim._option.Info
+ --- @param value string|string[]
array = function(info, value)
if type(value) == 'string' then
return value
@@ -417,6 +437,7 @@ local to_vim_value = {
return table.concat(value, ',')
end,
+ --- @param value string|table<string,string>
map = function(_, value)
if type(value) == 'string' then
return value
@@ -466,7 +487,8 @@ local to_lua_value = {
end
-- Handles unescaped commas in a list.
- if string.find(value, ',,,') then
+ if value:find(',,,') then
+ --- @type string, string
local left, right = unpack(vim.split(value, ',,,'))
local result = {}
@@ -479,8 +501,9 @@ local to_lua_value = {
return result
end
- if string.find(value, ',^,,', 1, true) then
- local left, right = unpack(vim.split(value, ',^,,', true))
+ if value:find(',^,,', 1, true) then
+ --- @type string, string
+ local left, right = unpack(vim.split(value, ',^,,', { plain = true }))
local result = {}
vim.list_extend(result, vim.split(left, ','))
@@ -508,22 +531,20 @@ local to_lua_value = {
assert(info.flaglist, 'That is the only one I know how to handle')
+ local result = {} --- @type table<string,true>
+
if info.flaglist and info.commalist then
local split_value = vim.split(value, ',')
- local result = {}
for _, v in ipairs(split_value) do
result[v] = true
end
-
- return result
else
- local result = {}
for i = 1, #value do
result[value:sub(i, i)] = true
end
-
- return result
end
+
+ return result
end,
map = function(info, raw_value)
@@ -533,10 +554,11 @@ local to_lua_value = {
assert(info.commalist, 'Only commas are supported currently')
- local result = {}
+ local result = {} --- @type table<string,string>
local comma_split = vim.split(raw_value, ',')
for _, key_value_str in ipairs(comma_split) do
+ --- @type string, string
local key, value = unpack(vim.split(key_value_str, ':'))
key = vim.trim(key)
@@ -582,14 +604,21 @@ local function prepend_value(info, current, new)
end
local add_methods = {
+ --- @param left integer
+ --- @param right integer
number = function(left, right)
return left + right
end,
+ --- @param left string
+ --- @param right string
string = function(left, right)
return left .. right
end,
+ --- @param left string[]
+ --- @param right string[]
+ --- @return string[]
array = function(left, right)
for _, v in ipairs(right) do
table.insert(left, v)
@@ -610,6 +639,8 @@ local function add_value(info, current, new)
)
end
+--- @param t table<any,any>
+--- @param val any
local function remove_one_item(t, val)
if vim.tbl_islist(t) then
local remove_index = nil
@@ -628,6 +659,8 @@ local function remove_one_item(t, val)
end
local remove_methods = {
+ --- @param left integer
+ --- @param right integer
number = function(left, right)
return left - right
end,
@@ -636,6 +669,9 @@ local remove_methods = {
error('Subtraction not supported for strings.')
end,
+ --- @param left string[]
+ --- @param right string[]
+ --- @return string[]
array = function(left, right)
if type(right) == 'string' then
remove_one_item(left, right)
@@ -797,6 +833,7 @@ end
--- `vim.opt_global`.
--- </pre>
+--- @nodoc
--- @class vim.Option
local Option = {} -- luacheck: no unused
diff --git a/runtime/lua/vim/_watch.lua b/runtime/lua/vim/_watch.lua
index 092781826f..23c810099e 100644
--- a/runtime/lua/vim/_watch.lua
+++ b/runtime/lua/vim/_watch.lua
@@ -1,45 +1,61 @@
-local M = {}
local uv = vim.uv
----@enum vim._watch.FileChangeType
-local FileChangeType = {
+local M = {}
+
+--- @enum vim._watch.FileChangeType
+--- Types of events watchers will emit.
+M.FileChangeType = {
Created = 1,
Changed = 2,
Deleted = 3,
}
---- Enumeration describing the types of events watchers will emit.
-M.FileChangeType = vim.tbl_add_reverse_lookup(FileChangeType)
-
---- Joins filepath elements by static '/' separator
+--- @class vim._watch.Opts
---
----@param ... (string) The path elements.
----@return string
-local function filepath_join(...)
- return table.concat({ ... }, '/')
-end
-
---- Stops and closes a libuv |uv_fs_event_t| or |uv_fs_poll_t| handle
+--- @field debounce? integer ms
+---
+--- An |lpeg| pattern. Only changes to files whose full paths match the pattern
+--- will be reported. Only matches against non-directoriess, all directories will
+--- be watched for new potentially-matching files. exclude_pattern can be used to
+--- filter out directories. When nil, matches any file name.
+--- @field include_pattern? vim.lpeg.Pattern
---
----@param handle (uv.uv_fs_event_t|uv.uv_fs_poll_t) The handle to stop
-local function stop(handle)
- local _, stop_err = handle:stop()
- assert(not stop_err, stop_err)
- local is_closing, close_err = handle:is_closing()
- assert(not close_err, close_err)
- if not is_closing then
- handle:close()
+--- An |lpeg| pattern. Only changes to files and directories whose full path does
+--- not match the pattern will be reported. Matches against both files and
+--- directories. When nil, matches nothing.
+--- @field exclude_pattern? vim.lpeg.Pattern
+
+--- @alias vim._watch.Callback fun(path: string, change_type: vim._watch.FileChangeType)
+
+--- @class vim._watch.watch.Opts : vim._watch.Opts
+--- @field uvflags? uv.fs_event_start.flags
+
+--- @param path string
+--- @param opts? vim._watch.Opts
+local function skip(path, opts)
+ if not opts then
+ return false
+ end
+
+ if opts.include_pattern and opts.include_pattern:match(path) == nil then
+ return true
+ end
+
+ if opts.exclude_pattern and opts.exclude_pattern:match(path) ~= nil then
+ return true
end
+
+ return false
end
--- Initializes and starts a |uv_fs_event_t|
---
----@param path (string) The path to watch
----@param opts (table|nil) Additional options
---- - uvflags (table|nil)
---- Same flags as accepted by |uv.fs_event_start()|
----@param callback (function) The function called when new events
----@return (function) Stops the watcher
+--- @param path string The path to watch
+--- @param opts vim._watch.watch.Opts? Additional options:
+--- - uvflags (table|nil)
+--- Same flags as accepted by |uv.fs_event_start()|
+--- @param callback vim._watch.Callback Callback for new events
+--- @return fun() cancel Stops the watcher
function M.watch(path, opts, callback)
vim.validate({
path = { path, 'string', false },
@@ -47,111 +63,120 @@ function M.watch(path, opts, callback)
callback = { callback, 'function', false },
})
+ opts = opts or {}
+
path = vim.fs.normalize(path)
local uvflags = opts and opts.uvflags or {}
- local handle, new_err = vim.uv.new_fs_event()
- assert(not new_err, new_err)
- handle = assert(handle)
+ local handle = assert(uv.new_fs_event())
+
local _, start_err = handle:start(path, uvflags, function(err, filename, events)
assert(not err, err)
local fullpath = path
if filename then
- filename = filename:gsub('\\', '/')
- fullpath = filepath_join(fullpath, filename)
+ fullpath = vim.fs.normalize(vim.fs.joinpath(fullpath, filename))
end
- local change_type = events.change and M.FileChangeType.Changed or 0
+
+ if skip(fullpath, opts) then
+ return
+ end
+
+ --- @type vim._watch.FileChangeType
+ local change_type
if events.rename then
- local _, staterr, staterrname = vim.uv.fs_stat(fullpath)
+ local _, staterr, staterrname = uv.fs_stat(fullpath)
if staterrname == 'ENOENT' then
change_type = M.FileChangeType.Deleted
else
assert(not staterr, staterr)
change_type = M.FileChangeType.Created
end
+ elseif events.change then
+ change_type = M.FileChangeType.Changed
end
callback(fullpath, change_type)
end)
+
assert(not start_err, start_err)
+
return function()
- stop(handle)
+ local _, stop_err = handle:stop()
+ assert(not stop_err, stop_err)
+ local is_closing, close_err = handle:is_closing()
+ assert(not close_err, close_err)
+ if not is_closing then
+ handle:close()
+ end
end
end
---- @class watch.PollOpts
---- @field debounce? integer
---- @field include_pattern? vim.lpeg.Pattern
---- @field exclude_pattern? vim.lpeg.Pattern
+--- Initializes and starts a |uv_fs_event_t| recursively watching every directory underneath the
+--- directory at path.
+---
+--- @param path string The path to watch. Must refer to a directory.
+--- @param opts vim._watch.Opts? Additional options
+--- @param callback vim._watch.Callback Callback for new events
+--- @return fun() cancel Stops the watcher
+function M.watchdirs(path, opts, callback)
+ vim.validate({
+ path = { path, 'string', false },
+ opts = { opts, 'table', true },
+ callback = { callback, 'function', false },
+ })
----@param path string
----@param opts watch.PollOpts
----@param callback function Called on new events
----@return function cancel stops the watcher
-local function recurse_watch(path, opts, callback)
opts = opts or {}
local debounce = opts.debounce or 500
- local uvflags = {}
+
---@type table<string, uv.uv_fs_event_t> handle by fullpath
local handles = {}
local timer = assert(uv.new_timer())
- ---@type table[]
- local changesets = {}
+ --- Map of file path to boolean indicating if the file has been changed
+ --- at some point within the debounce cycle.
+ --- @type table<string, boolean>
+ local filechanges = {}
- local function is_included(filepath)
- return opts.include_pattern and opts.include_pattern:match(filepath)
- end
- local function is_excluded(filepath)
- return opts.exclude_pattern and opts.exclude_pattern:match(filepath)
- end
-
- local process_changes = function()
- assert(false, "Replaced later. I'm only here as forward reference")
- end
+ local process_changes --- @type fun()
+ --- @param filepath string
+ --- @return uv.fs_event_start.callback
local function create_on_change(filepath)
return function(err, filename, events)
assert(not err, err)
local fullpath = vim.fs.joinpath(filepath, filename)
- if is_included(fullpath) and not is_excluded(filepath) then
- table.insert(changesets, {
- fullpath = fullpath,
- events = events,
- })
- timer:start(debounce, 0, process_changes)
+ if skip(fullpath, opts) then
+ return
end
+
+ if not filechanges[fullpath] then
+ filechanges[fullpath] = events.change or false
+ end
+ timer:start(debounce, 0, process_changes)
end
end
process_changes = function()
- ---@type table<string, table[]>
- local filechanges = vim.defaulttable()
- for i, change in ipairs(changesets) do
- changesets[i] = nil
- if is_included(change.fullpath) and not is_excluded(change.fullpath) then
- table.insert(filechanges[change.fullpath], change.events)
- end
- end
- for fullpath, events_list in pairs(filechanges) do
+ -- Since the callback is debounced it may have also been deleted later on
+ -- so we always need to check the existence of the file:
+ -- stat succeeds, changed=true -> Changed
+ -- stat succeeds, changed=false -> Created
+ -- stat fails -> Removed
+ for fullpath, changed in pairs(filechanges) do
uv.fs_stat(fullpath, function(_, stat)
---@type vim._watch.FileChangeType
local change_type
if stat then
- change_type = FileChangeType.Created
- for _, event in ipairs(events_list) do
- if event.change then
- change_type = FileChangeType.Changed
- end
- end
+ change_type = changed and M.FileChangeType.Changed or M.FileChangeType.Created
if stat.type == 'directory' then
local handle = handles[fullpath]
if not handle then
handle = assert(uv.new_fs_event())
handles[fullpath] = handle
- handle:start(fullpath, uvflags, create_on_change(fullpath))
+ handle:start(fullpath, {}, create_on_change(fullpath))
end
end
else
+ change_type = M.FileChangeType.Deleted
local handle = handles[fullpath]
if handle then
if not handle:is_closing() then
@@ -159,15 +184,16 @@ local function recurse_watch(path, opts, callback)
end
handles[fullpath] = nil
end
- change_type = FileChangeType.Deleted
end
callback(fullpath, change_type)
end)
end
+ filechanges = {}
end
+
local root_handle = assert(uv.new_fs_event())
handles[path] = root_handle
- root_handle:start(path, uvflags, create_on_change(path))
+ root_handle:start(path, {}, create_on_change(path))
--- "640K ought to be enough for anyone"
--- Who has folders this deep?
@@ -175,12 +201,13 @@ local function recurse_watch(path, opts, callback)
for name, type in vim.fs.dir(path, { depth = max_depth }) do
local filepath = vim.fs.joinpath(path, name)
- if type == 'directory' and not is_excluded(filepath) then
+ if type == 'directory' and not skip(filepath, opts) then
local handle = assert(uv.new_fs_event())
handles[filepath] = handle
- handle:start(filepath, uvflags, create_on_change(filepath))
+ handle:start(filepath, {}, create_on_change(filepath))
end
end
+
local function cancel()
for fullpath, handle in pairs(handles) do
if not handle:is_closing() then
@@ -191,34 +218,102 @@ local function recurse_watch(path, opts, callback)
timer:stop()
timer:close()
end
+
return cancel
end
---- Initializes and starts a |uv_fs_poll_t| recursively watching every file underneath the
---- directory at path.
----
----@param path (string) The path to watch. Must refer to a directory.
----@param opts (table|nil) Additional options
---- - debounce (number|nil)
---- Time events are debounced in ms. Defaults to 500
---- - include_pattern (LPeg pattern|nil)
---- An |lpeg| pattern. Only changes to files whose full paths match the pattern
---- will be reported. Only matches against non-directoriess, all directories will
---- be watched for new potentially-matching files. exclude_pattern can be used to
---- filter out directories. When nil, matches any file name.
---- - exclude_pattern (LPeg pattern|nil)
---- An |lpeg| pattern. Only changes to files and directories whose full path does
---- not match the pattern will be reported. Matches against both files and
---- directories. When nil, matches nothing.
----@param callback (function) The function called when new events
----@return function Stops the watcher
-function M.poll(path, opts, callback)
- vim.validate({
- path = { path, 'string', false },
- opts = { opts, 'table', true },
- callback = { callback, 'function', false },
+--- @param data string
+--- @param opts vim._watch.Opts?
+--- @param callback vim._watch.Callback
+local function fswatch_output_handler(data, opts, callback)
+ local d = vim.split(data, '%s+')
+
+ -- only consider the last reported event
+ local fullpath, event = d[1], d[#d]
+
+ if skip(fullpath, opts) then
+ return
+ end
+
+ --- @type integer
+ local change_type
+
+ if event == 'Created' then
+ change_type = M.FileChangeType.Created
+ elseif event == 'Removed' then
+ change_type = M.FileChangeType.Deleted
+ elseif event == 'Updated' then
+ change_type = M.FileChangeType.Changed
+ elseif event == 'Renamed' then
+ local _, staterr, staterrname = uv.fs_stat(fullpath)
+ if staterrname == 'ENOENT' then
+ change_type = M.FileChangeType.Deleted
+ else
+ assert(not staterr, staterr)
+ change_type = M.FileChangeType.Created
+ end
+ end
+
+ if change_type then
+ callback(fullpath, change_type)
+ end
+end
+
+--- @param path string The path to watch. Must refer to a directory.
+--- @param opts vim._watch.Opts?
+--- @param callback vim._watch.Callback Callback for new events
+--- @return fun() cancel Stops the watcher
+function M.fswatch(path, opts, callback)
+ -- debounce isn't the same as latency but close enough
+ local latency = 0.5 -- seconds
+ if opts and opts.debounce then
+ latency = opts.debounce / 1000
+ end
+
+ local obj = vim.system({
+ 'fswatch',
+ '--event=Created',
+ '--event=Removed',
+ '--event=Updated',
+ '--event=Renamed',
+ '--event-flags',
+ '--recursive',
+ '--latency=' .. tostring(latency),
+ '--exclude',
+ '/.git/',
+ path,
+ }, {
+ stderr = function(err, data)
+ if err then
+ error(err)
+ end
+
+ if data and #vim.trim(data) > 0 then
+ vim.schedule(function()
+ if vim.fn.has('linux') == 1 and vim.startswith(data, 'Event queue overflow') then
+ data = 'inotify(7) limit reached, see :h fswatch-limitations for more info.'
+ end
+
+ vim.notify('fswatch: ' .. data, vim.log.levels.ERROR)
+ end)
+ end
+ end,
+ stdout = function(err, data)
+ if err then
+ error(err)
+ end
+
+ for line in vim.gsplit(data or '', '\n', { plain = true, trimempty = true }) do
+ fswatch_output_handler(line, opts, callback)
+ end
+ end,
+ -- --latency is locale dependent but tostring() isn't and will always have '.' as decimal point.
+ env = { LC_NUMERIC = 'C' },
})
- return recurse_watch(path, opts, callback)
+
+ return function()
+ obj:kill(2)
+ end
end
return M
diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua
index 49165c4db9..d5075d7d3d 100644
--- a/runtime/lua/vim/diagnostic.lua
+++ b/runtime/lua/vim/diagnostic.lua
@@ -2,29 +2,84 @@ local api, if_nil = vim.api, vim.F.if_nil
local M = {}
+--- [diagnostic-structure]()
+---
+--- Diagnostics use the same indexing as the rest of the Nvim API (i.e. 0-based
+--- rows and columns). |api-indexing|
--- @class vim.Diagnostic
+---
+--- Buffer number
--- @field bufnr? integer
---- @field lnum integer 0-indexed
---- @field end_lnum? integer 0-indexed
---- @field col integer 0-indexed
---- @field end_col? integer 0-indexed
+---
+--- The starting line of the diagnostic (0-indexed)
+--- @field lnum integer
+---
+--- The final line of the diagnostic (0-indexed)
+--- @field end_lnum? integer
+---
+--- The starting column of the diagnostic (0-indexed)
+--- @field col integer
+---
+--- The final column of the diagnostic (0-indexed)
+--- @field end_col? integer
+---
+--- The severity of the diagnostic |vim.diagnostic.severity|
--- @field severity? vim.diagnostic.Severity
+---
+--- The diagnostic text
--- @field message string
+---
+--- The source of the diagnostic
--- @field source? string
+---
+--- The diagnostic code
--- @field code? string|integer
+---
--- @field _tags? { deprecated: boolean, unnecessary: boolean}
+---
+--- Arbitrary data plugins or users can add
--- @field user_data? any arbitrary data plugins can add
+---
--- @field namespace? integer
+--- Each of the configuration options below accepts one of the following:
+--- - `false`: Disable this feature
+--- - `true`: Enable this feature, use default settings.
+--- - `table`: Enable this feature with overrides. Use an empty table to use default values.
+--- - `function`: Function with signature (namespace, bufnr) that returns any of the above.
--- @class vim.diagnostic.Opts
---- @field float? boolean|vim.diagnostic.Opts.Float
+---
+--- Use underline for diagnostics.
+--- (default: `true`)
+--- @field underline? boolean|vim.diagnostic.Opts.Underline|fun(namespace: integer, bufnr:integer): vim.diagnostic.Opts.Underline
+---
+--- Use virtual text for diagnostics. If multiple diagnostics are set for a
+--- namespace, one prefix per diagnostic + the last diagnostic message are
+--- shown.
+--- (default: `true`)
+--- @field virtual_text? boolean|vim.diagnostic.Opts.VirtualText|fun(namespace: integer, bufnr:integer): vim.diagnostic.Opts.VirtualText
+---
+--- Use signs for diagnostics |diagnostic-signs|.
+--- (default: `true`)
+--- @field signs? boolean|vim.diagnostic.Opts.Signs|fun(namespace: integer, bufnr:integer): vim.diagnostic.Opts.Signs
+---
+--- Options for floating windows. See |vim.diagnostic.Opts.Float|.
+--- @field float? boolean|vim.diagnostic.Opts.Float|fun(namespace: integer, bufnr:integer): vim.diagnostic.Opts.Float
+---
+--- Update diagnostics in Insert mode
+--- (if `false`, diagnostics are updated on |InsertLeave|)
+--- (default: `false`)
--- @field update_in_insert? boolean
---- @field underline? boolean|vim.diagnostic.Opts.Underline
---- @field virtual_text? boolean|vim.diagnostic.Opts.VirtualText
---- @field signs? boolean|vim.diagnostic.Opts.Signs
+---
+--- Sort diagnostics by severity. This affects the order in which signs and
+--- virtual text are displayed. When true, higher severities are displayed
+--- before lower severities (e.g. ERROR is displayed before WARN).
+--- Options:
+--- - {reverse}? (boolean) Reverse sort order
+--- (default: `false)
--- @field severity_sort? boolean|{reverse?:boolean}
---- @class vim.diagnostic.OptsResolved
+--- @class (private) vim.diagnostic.OptsResolved
--- @field float vim.diagnostic.Opts.Float
--- @field update_in_insert boolean
--- @field underline vim.diagnostic.Opts.Underline
@@ -33,42 +88,156 @@ local M = {}
--- @field severity_sort {reverse?:boolean}
--- @class vim.diagnostic.Opts.Float
+---
+--- Buffer number to show diagnostics from.
+--- (default: current buffer)
--- @field bufnr? integer
+---
+--- Limit diagnostics to the given namespace
--- @field namespace? integer
+---
+--- Show diagnostics from the whole buffer (`buffer"`, the current cursor line
+--- (`line`), or the current cursor position (`cursor`). Shorthand versions
+--- are also accepted (`c` for `cursor`, `l` for `line`, `b` for `buffer`).
+--- (default: `line`)
--- @field scope? 'line'|'buffer'|'cursor'|'c'|'l'|'b'
+---
+--- If {scope} is "line" or "cursor", use this position rather than the cursor
+--- position. If a number, interpreted as a line number; otherwise, a
+--- (row, col) tuple.
--- @field pos? integer|{[1]:integer,[2]:integer}
+---
+--- Sort diagnostics by severity.
+--- Overrides the setting from |vim.diagnostic.config()|.
+--- (default: `false`)
--- @field severity_sort? boolean|{reverse?:boolean}
+---
+--- See |diagnostic-severity|.
+--- Overrides the setting from |vim.diagnostic.config()|.
--- @field severity? vim.diagnostic.SeverityFilter
+---
+--- String to use as the header for the floating window. If a table, it is
+--- interpreted as a `[text, hl_group]` tuple.
+--- Overrides the setting from |vim.diagnostic.config()|.
--- @field header? string|{[1]:string,[2]:any}
---- @field source? boolean|string
+---
+--- Include the diagnostic source in the message.
+--- Use "if_many" to only show sources if there is more than one source of
+--- diagnostics in the buffer. Otherwise, any truthy value means to always show
+--- the diagnostic source.
+--- Overrides the setting from |vim.diagnostic.config()|.
+--- @field source? boolean|'if_many'
+---
+--- A function that takes a diagnostic as input and returns a string.
+--- The return value is the text used to display the diagnostic.
+--- Overrides the setting from |vim.diagnostic.config()|.
--- @field format? fun(diagnostic:vim.Diagnostic): string
+---
+--- Prefix each diagnostic in the floating window:
+--- - If a `function`, {i} is the index of the diagnostic being evaluated and
+--- {total} is the total number of diagnostics displayed in the window. The
+--- function should return a `string` which is prepended to each diagnostic
+--- in the window as well as an (optional) highlight group which will be
+--- used to highlight the prefix.
+--- - If a `table`, it is interpreted as a `[text, hl_group]` tuple as
+--- in |nvim_echo()|
+--- - If a `string`, it is prepended to each diagnostic in the window with no
+--- highlight.
+--- Overrides the setting from |vim.diagnostic.config()|.
--- @field prefix? string|table|(fun(diagnostic:vim.Diagnostic,i:integer,total:integer): string, string)
+---
+--- Same as {prefix}, but appends the text to the diagnostic instead of
+--- prepending it.
+--- Overrides the setting from |vim.diagnostic.config()|.
--- @field suffix? string|table|(fun(diagnostic:vim.Diagnostic,i:integer,total:integer): string, string)
+---
--- @field focus_id? string
--- @class vim.diagnostic.Opts.Underline
+---
+--- Only underline diagnostics matching the given
+--- severity |diagnostic-severity|.
--- @field severity? vim.diagnostic.SeverityFilter
--- @class vim.diagnostic.Opts.VirtualText
+---
+--- Only show virtual text for diagnostics matching the given
+--- severity |diagnostic-severity|
--- @field severity? vim.diagnostic.SeverityFilter
---- @field source? boolean|string
---- @field prefix? string|function
---- @field suffix? string|function
+---
+--- Include the diagnostic source in virtual text. Use `'if_many'` to only
+--- show sources if there is more than one diagnostic source in the buffer.
+--- Otherwise, any truthy value means to always show the diagnostic source.
+--- @field source? boolean|"if_many"
+---
+--- Amount of empty spaces inserted at the beginning of the virtual text.
--- @field spacing? integer
---- @field format? function
+---
+--- Prepend diagnostic message with prefix. If a `function`, {i} is the index
+--- of the diagnostic being evaluated, and {total} is the total number of
+--- diagnostics for the line. This can be used to render diagnostic symbols
+--- or error codes.
+--- @field prefix? string|(fun(diagnostic:vim.Diagnostic,i:integer,total:integer): string)
+---
+--- Append diagnostic message with suffix.
+--- This can be used to render an LSP diagnostic error code.
+--- @field suffix? string|(fun(diagnostic:vim.Diagnostic): string)
+---
+--- The return value is the text used to display the diagnostic. Example:
+--- ```lua
+--- function(diagnostic)
+--- if diagnostic.severity == vim.diagnostic.severity.ERROR then
+--- return string.format("E: %s", diagnostic.message)
+--- end
+--- return diagnostic.message
+--- end
+--- ```
+--- @field format? fun(diagnostic:vim.Diagnostic): string
+---
+--- See |nvim_buf_set_extmark()|.
--- @field hl_mode? 'replace'|'combine'|'blend'
+---
+--- See |nvim_buf_set_extmark()|.
--- @field virt_text? {[1]:string,[2]:any}[]
+---
+--- See |nvim_buf_set_extmark()|.
--- @field virt_text_pos? 'eol'|'overlay'|'right_align'|'inline'
+---
+--- See |nvim_buf_set_extmark()|.
--- @field virt_text_win_col? integer
+---
+--- See |nvim_buf_set_extmark()|.
--- @field virt_text_hide? boolean
--- @class vim.diagnostic.Opts.Signs
+---
+--- Only show virtual text for diagnostics matching the given
+--- severity |diagnostic-severity|
--- @field severity? vim.diagnostic.SeverityFilter
+---
+--- Base priority to use for signs. When {severity_sort} is used, the priority
+--- of a sign is adjusted based on its severity.
+--- Otherwise, all signs use the same priority.
+--- (default: `10`)
--- @field priority? integer
+---
+--- A table mapping |diagnostic-severity| to the sign text to display in the
+--- sign column. The default is to use `"E"`, `"W"`, `"I"`, and `"H"` for errors,
+--- warnings, information, and hints, respectively. Example:
+--- ```lua
+--- vim.diagnostic.config({
+--- signs = { text = { [vim.diagnostic.severity.ERROR] = 'E', ... } }
+--- })
+--- ```
--- @field text? table<vim.diagnostic.Severity,string>
+---
+--- A table mapping |diagnostic-severity| to the highlight group used for the
+--- line number where the sign is placed.
--- @field numhl? table<vim.diagnostic.Severity,string>
+---
+--- A table mapping |diagnostic-severity| to the highlight group used for the
+--- whole line the sign is placed in.
--- @field linehl? table<vim.diagnostic.Severity,string>
---- @field texthl? table<vim.diagnostic.Severity,string>
--- @nodoc
--- @enum vim.diagnostic.Severity
@@ -104,7 +273,7 @@ local global_diagnostic_options = {
severity_sort = false,
}
---- @class vim.diagnostic.Handler
+--- @class (private) vim.diagnostic.Handler
--- @field show? fun(namespace: integer, bufnr: integer, diagnostics: vim.Diagnostic[], opts?: vim.diagnostic.OptsResolved)
--- @field hide? fun(namespace:integer, bufnr:integer)
@@ -154,7 +323,7 @@ do
})
end
---- @class vim.diagnostic._extmark
+--- @class (private) vim.diagnostic._extmark
--- @field [1] integer id
--- @field [2] integer start
--- @field [3] integer end
@@ -726,83 +895,11 @@ end
---
--- then virtual text will not be enabled for those diagnostics.
---
----@note Each of the configuration options below accepts one of the following:
---- - `false`: Disable this feature
---- - `true`: Enable this feature, use default settings.
---- - `table`: Enable this feature with overrides. Use an empty table to use default values.
---- - `function`: Function with signature (namespace, bufnr) that returns any of the above.
----
----@param opts vim.diagnostic.Opts? (table?) When omitted or "nil", retrieve the current
---- configuration. Otherwise, a configuration table with the following keys:
---- - underline: (default true) Use underline for diagnostics. Options:
---- * severity: Only underline diagnostics matching the given
---- severity |diagnostic-severity|
---- - virtual_text: (default true) Use virtual text for diagnostics. If multiple diagnostics
---- are set for a namespace, one prefix per diagnostic + the last diagnostic
---- message are shown. In addition to the options listed below, the
---- "virt_text" options of |nvim_buf_set_extmark()| may also be used here
---- (e.g. "virt_text_pos" and "hl_mode").
---- Options:
---- * severity: Only show virtual text for diagnostics matching the given
---- severity |diagnostic-severity|
---- * source: (boolean or string) Include the diagnostic source in virtual
---- text. Use "if_many" to only show sources if there is more than
---- one diagnostic source in the buffer. Otherwise, any truthy value
---- means to always show the diagnostic source.
---- * spacing: (number) Amount of empty spaces inserted at the beginning
---- of the virtual text.
---- * prefix: (string or function) prepend diagnostic message with prefix.
---- If a function, it must have the signature (diagnostic, i, total)
---- -> string, where {diagnostic} is of type |diagnostic-structure|,
---- {i} is the index of the diagnostic being evaluated, and {total}
---- is the total number of diagnostics for the line. This can be
---- used to render diagnostic symbols or error codes.
---- * suffix: (string or function) Append diagnostic message with suffix.
---- If a function, it must have the signature (diagnostic) ->
---- string, where {diagnostic} is of type |diagnostic-structure|.
---- This can be used to render an LSP diagnostic error code.
---- * format: (function) A function that takes a diagnostic as input and
---- returns a string. The return value is the text used to display
---- the diagnostic. Example:
---- ```lua
---- function(diagnostic)
---- if diagnostic.severity == vim.diagnostic.severity.ERROR then
---- return string.format("E: %s", diagnostic.message)
---- end
---- return diagnostic.message
---- end
---- ```
---- - signs: (default true) Use signs for diagnostics |diagnostic-signs|. Options:
---- * severity: Only show signs for diagnostics matching the given
---- severity |diagnostic-severity|
---- * priority: (number, default 10) Base priority to use for signs. When
---- {severity_sort} is used, the priority of a sign is adjusted based on
---- its severity. Otherwise, all signs use the same priority.
---- * text: (table) A table mapping |diagnostic-severity| to the sign text
---- to display in the sign column. The default is to use "E", "W", "I", and "H"
---- for errors, warnings, information, and hints, respectively. Example:
---- ```lua
---- vim.diagnostic.config({
---- signs = { text = { [vim.diagnostic.severity.ERROR] = 'E', ... } }
---- })
---- ```
---- * numhl: (table) A table mapping |diagnostic-severity| to the highlight
---- group used for the line number where the sign is placed.
---- * linehl: (table) A table mapping |diagnostic-severity| to the highlight group
---- used for the whole line the sign is placed in.
---- - float: Options for floating windows. See |vim.diagnostic.open_float()|.
---- - update_in_insert: (default false) Update diagnostics in Insert mode (if false,
---- diagnostics are updated on InsertLeave)
---- - severity_sort: (default false) Sort diagnostics by severity. This affects the order in
---- which signs and virtual text are displayed. When true, higher severities
---- are displayed before lower severities (e.g. ERROR is displayed before WARN).
---- Options:
---- * reverse: (boolean) Reverse sort order
----
----@param namespace integer? Update the options for the given namespace. When omitted, update the
---- global diagnostic options.
----
----@return vim.diagnostic.Opts? (table?) table of current diagnostic config if `opts` is omitted.
+---@param opts vim.diagnostic.Opts? When omitted or `nil`, retrieve the current
+--- configuration. Otherwise, a configuration table (see |vim.diagnostic.Opts|).
+---@param namespace integer? Update the options for the given namespace.
+--- When omitted, update the global diagnostic options.
+---@return vim.diagnostic.Opts? : Current diagnostic config if {opts} is omitted.
function M.config(opts, namespace)
vim.validate({
opts = { opts, 't', true },
@@ -847,8 +944,8 @@ end
---
---@param namespace integer The diagnostic namespace
---@param bufnr integer Buffer number
----@param diagnostics vim.Diagnostic[] A list of diagnostic items |diagnostic-structure|
----@param opts? vim.diagnostic.Opts (table) Display options to pass to |vim.diagnostic.show()|
+---@param diagnostics vim.Diagnostic[]
+---@param opts? vim.diagnostic.Opts Display options to pass to |vim.diagnostic.show()|
function M.set(namespace, bufnr, diagnostics, opts)
vim.validate({
namespace = { namespace, 'n' },
@@ -882,7 +979,7 @@ end
--- Get namespace metadata.
---
---@param namespace integer Diagnostic namespace
----@return vim.diagnostic.NS (table) Namespace metadata
+---@return vim.diagnostic.NS : Namespace metadata
function M.get_namespace(namespace)
vim.validate({ namespace = { namespace, 'n' } })
if not all_namespaces[namespace] then
@@ -907,22 +1004,21 @@ end
--- Get current diagnostic namespaces.
---
----@return table<integer,vim.diagnostic.NS> A list of active diagnostic namespaces |vim.diagnostic|.
+---@return table<integer,vim.diagnostic.NS> : List of active diagnostic namespaces |vim.diagnostic|.
function M.get_namespaces()
return vim.deepcopy(all_namespaces, true)
end
--- Get current diagnostics.
---
---- Modifying diagnostics in the returned table has no effect. To set diagnostics in a buffer, use |vim.diagnostic.set()|.
+--- Modifying diagnostics in the returned table has no effect.
+--- To set diagnostics in a buffer, use |vim.diagnostic.set()|.
---
---@param bufnr integer? Buffer number to get diagnostics from. Use 0 for
---- current buffer or nil for all buffers.
----@param opts? vim.diagnostic.GetOpts (table) A table with the following keys:
---- - namespace: (number) Limit diagnostics to the given namespace.
---- - lnum: (number) Limit diagnostics to the given line number.
---- - severity: See |diagnostic-severity|.
----@return vim.Diagnostic[] table A list of diagnostic items |diagnostic-structure|. Keys `bufnr`, `end_lnum`, `end_col`, and `severity` are guaranteed to be present.
+--- current buffer or nil for all buffers.
+---@param opts? vim.diagnostic.GetOpts
+---@return vim.Diagnostic[] : Fields `bufnr`, `end_lnum`, `end_col`, and `severity`
+--- are guaranteed to be present.
function M.get(bufnr, opts)
vim.validate({
bufnr = { bufnr, 'n', true },
@@ -936,11 +1032,9 @@ end
---
---@param bufnr? integer Buffer number to get diagnostics from. Use 0 for
--- current buffer or nil for all buffers.
----@param opts? table A table with the following keys:
---- - namespace: (number) Limit diagnostics to the given namespace.
---- - lnum: (number) Limit diagnostics to the given line number.
---- - severity: See |diagnostic-severity|.
----@return table A table with actually present severity values as keys (see |diagnostic-severity|) and integer counts as values.
+---@param opts? vim.diagnostic.GetOpts
+---@return table : Table with actually present severity values as keys
+--- (see |diagnostic-severity|) and integer counts as values.
function M.count(bufnr, opts)
vim.validate({
bufnr = { bufnr, 'n', true },
@@ -958,8 +1052,8 @@ end
--- Get the previous diagnostic closest to the cursor position.
---
----@param opts? vim.diagnostic.GotoOpts (table) See |vim.diagnostic.goto_next()|
----@return vim.Diagnostic? Previous diagnostic
+---@param opts? vim.diagnostic.GotoOpts
+---@return vim.Diagnostic? : Previous diagnostic
function M.get_prev(opts)
opts = opts or {}
@@ -972,9 +1066,9 @@ end
--- Return the position of the previous diagnostic in the current buffer.
---
----@param opts? vim.diagnostic.GotoOpts (table) See |vim.diagnostic.goto_next()|
----@return table|false: Previous diagnostic position as a (row, col) tuple or false if there is no
---- prior diagnostic
+---@param opts? vim.diagnostic.GotoOpts
+---@return table|false: Previous diagnostic position as a `(row, col)` tuple
+--- or `false` if there is no prior diagnostic.
function M.get_prev_pos(opts)
local prev = M.get_prev(opts)
if not prev then
@@ -985,14 +1079,14 @@ function M.get_prev_pos(opts)
end
--- Move to the previous diagnostic in the current buffer.
----@param opts? vim.diagnostic.GotoOpts (table) See |vim.diagnostic.goto_next()|
+---@param opts? vim.diagnostic.GotoOpts
function M.goto_prev(opts)
return diagnostic_move_pos(opts, M.get_prev_pos(opts))
end
--- Get the next diagnostic closest to the cursor position.
---
----@param opts? vim.diagnostic.GotoOpts (table) See |vim.diagnostic.goto_next()|
+---@param opts? vim.diagnostic.GotoOpts
---@return vim.Diagnostic? : Next diagnostic
function M.get_next(opts)
opts = opts or {}
@@ -1006,8 +1100,8 @@ end
--- Return the position of the next diagnostic in the current buffer.
---
----@param opts? vim.diagnostic.GotoOpts (table) See |vim.diagnostic.goto_next()|
----@return table|false : Next diagnostic position as a (row, col) tuple or false if no next
+---@param opts? vim.diagnostic.GotoOpts
+---@return table|false : Next diagnostic position as a `(row, col)` tuple or false if no next
--- diagnostic.
function M.get_next_pos(opts)
local next = M.get_next(opts)
@@ -1018,31 +1112,47 @@ function M.get_next_pos(opts)
return { next.lnum, next.col }
end
+--- A table with the following keys:
--- @class vim.diagnostic.GetOpts
+---
+--- Limit diagnostics to the given namespace.
--- @field namespace? integer
+---
+--- Limit diagnostics to the given line number.
--- @field lnum? integer
+---
+--- See |diagnostic-severity|.
--- @field severity? vim.diagnostic.SeverityFilter
+--- Configuration table with the following keys:
--- @class vim.diagnostic.GotoOpts : vim.diagnostic.GetOpts
+---
+--- Cursor position as a `(row, col)` tuple.
+--- See |nvim_win_get_cursor()|.
+--- (default: current cursor position)
--- @field cursor_position? {[1]:integer,[2]:integer}
+---
+--- Whether to loop around file or not. Similar to 'wrapscan'.
+--- (default: `true`)
--- @field wrap? boolean
+---
+--- See |diagnostic-severity|.
+--- @field severity vim.diagnostic.Severity
+---
+--- If `true`, call |vim.diagnostic.open_float()| after moving.
+--- If a table, pass the table as the {opts} parameter to |vim.diagnostic.open_float()|.
+--- Unless overridden, the float will show diagnostics at the new cursor
+--- position (as if "cursor" were passed to the "scope" option).
+--- (default: `true`)
--- @field float? boolean|vim.diagnostic.Opts.Float
+---
+--- Window ID
+--- (default: `0`)
--- @field win_id? integer
--- Move to the next diagnostic.
---
----@param opts? vim.diagnostic.GotoOpts (table) Configuration table with the following keys:
---- - namespace: (integer) Only consider diagnostics from the given namespace.
---- - cursor_position: (cursor position) Cursor position as a (row, col) tuple.
---- See |nvim_win_get_cursor()|. Defaults to the current cursor position.
---- - wrap: (boolean, default true) Whether to loop around file or not. Similar to 'wrapscan'.
---- - severity: See |diagnostic-severity|.
---- - float: (boolean or table, default true) If "true", call |vim.diagnostic.open_float()|
---- after moving. If a table, pass the table as the {opts} parameter
---- to |vim.diagnostic.open_float()|. Unless overridden, the float will show
---- diagnostics at the new cursor position (as if "cursor" were passed to
---- the "scope" option).
---- - win_id: (number, default 0) Window ID
+---@param opts? vim.diagnostic.GotoOpts
function M.goto_next(opts)
diagnostic_move_pos(opts, M.get_next_pos(opts))
end
@@ -1094,7 +1204,7 @@ M.handlers.signs = {
-- Handle legacy diagnostic sign definitions
-- These were deprecated in 0.10 and will be removed in 0.12
- if opts.signs and not opts.signs.text and not opts.signs.numhl and not opts.signs.texthl then
+ if opts.signs and not opts.signs.text and not opts.signs.numhl then
for _, v in ipairs({ 'Error', 'Warn', 'Info', 'Hint' }) do
local name = string.format('DiagnosticSign%s', v)
local sign = vim.fn.sign_getdefined(name)[1]
@@ -1429,7 +1539,7 @@ end
--- without saving them or to display only a subset of
--- diagnostics. May not be used when {namespace}
--- or {bufnr} is nil.
----@param opts? vim.diagnostic.Opts (table) Display options. See |vim.diagnostic.config()|.
+---@param opts? vim.diagnostic.Opts Display options.
function M.show(namespace, bufnr, diagnostics, opts)
vim.validate({
namespace = { namespace, 'n', true },
@@ -1505,46 +1615,7 @@ end
--- Show diagnostics in a floating window.
---
----@param opts vim.diagnostic.Opts.Float? (table?) Configuration table with the same keys
---- as |vim.lsp.util.open_floating_preview()| in addition to the following:
---- - bufnr: (number) Buffer number to show diagnostics from.
---- Defaults to the current buffer.
---- - namespace: (number) Limit diagnostics to the given namespace
---- - scope: (string, default "line") Show diagnostics from the whole buffer ("buffer"),
---- the current cursor line ("line"), or the current cursor position ("cursor").
---- Shorthand versions are also accepted ("c" for "cursor", "l" for "line", "b"
---- for "buffer").
---- - pos: (number or table) If {scope} is "line" or "cursor", use this position rather
---- than the cursor position. If a number, interpreted as a line number;
---- otherwise, a (row, col) tuple.
---- - severity_sort: (default false) Sort diagnostics by severity. Overrides the setting
---- from |vim.diagnostic.config()|.
---- - severity: See |diagnostic-severity|. Overrides the setting
---- from |vim.diagnostic.config()|.
---- - header: (string or table) String to use as the header for the floating window. If a
---- table, it is interpreted as a [text, hl_group] tuple. Overrides the setting
---- from |vim.diagnostic.config()|.
---- - source: (boolean or string) Include the diagnostic source in the message.
---- Use "if_many" to only show sources if there is more than one source of
---- diagnostics in the buffer. Otherwise, any truthy value means to always show
---- the diagnostic source. Overrides the setting from |vim.diagnostic.config()|.
---- - format: (function) A function that takes a diagnostic as input and returns a
---- string. The return value is the text used to display the diagnostic.
---- Overrides the setting from |vim.diagnostic.config()|.
---- - prefix: (function, string, or table) Prefix each diagnostic in the floating
---- window. If a function, it must have the signature (diagnostic, i,
---- total) -> (string, string), where {i} is the index of the diagnostic
---- being evaluated and {total} is the total number of diagnostics
---- displayed in the window. The function should return a string which
---- is prepended to each diagnostic in the window as well as an
---- (optional) highlight group which will be used to highlight the
---- prefix. If {prefix} is a table, it is interpreted as a [text,
---- hl_group] tuple as in |nvim_echo()|; otherwise, if {prefix} is a
---- string, it is prepended to each diagnostic in the window with no
---- highlight.
---- Overrides the setting from |vim.diagnostic.config()|.
---- - suffix: Same as {prefix}, but appends the text to the diagnostic instead of
---- prepending it. Overrides the setting from |vim.diagnostic.config()|.
+---@param opts vim.diagnostic.Opts.Float?
---@return integer? float_bufnr
---@return integer? win_id
function M.open_float(opts, ...)
@@ -1789,38 +1860,54 @@ function M.reset(namespace, bufnr)
end
end
+--- Configuration table with the following keys:
--- @class vim.diagnostic.setqflist.Opts
+--- @inlinedoc
+---
+--- Only add diagnostics from the given namespace.
--- @field namespace? integer
+---
+--- Open quickfix list after setting.
+--- (default: `true`)
--- @field open? boolean
+---
+--- Title of quickfix list. Defaults to "Diagnostics".
--- @field title? string
+---
+--- See |diagnostic-severity|.
--- @field severity? vim.diagnostic.Severity
--- Add all diagnostics to the quickfix list.
---
----@param opts? vim.diagnostic.setqflist.Opts (table) Configuration table with the following keys:
---- - namespace: (number) Only add diagnostics from the given namespace.
---- - open: (boolean, default true) Open quickfix list after setting.
---- - title: (string) Title of quickfix list. Defaults to "Diagnostics".
---- - severity: See |diagnostic-severity|.
+---@param opts? vim.diagnostic.setqflist.Opts
function M.setqflist(opts)
set_list(false, opts)
end
+---Configuration table with the following keys:
--- @class vim.diagnostic.setloclist.Opts
+--- @inlinedoc
+---
+--- Only add diagnostics from the given namespace.
--- @field namespace? integer
+---
+--- Window number to set location list for.
+--- (default: `0`)
+--- @field winnr? integer
+---
+--- Open the location list after setting.
+--- (default: `true`)
--- @field open? boolean
+---
+--- Title of the location list. Defaults to "Diagnostics".
--- @field title? string
+---
+--- See |diagnostic-severity|.
--- @field severity? vim.diagnostic.Severity
---- @field winnr? integer
--- Add buffer diagnostics to the location list.
---
----@param opts? vim.diagnostic.setloclist.Opts (table) Configuration table with the following keys:
---- - namespace: (number) Only add diagnostics from the given namespace.
---- - winnr: (number, default 0) Window number to set location list for.
---- - open: (boolean, default true) Open the location list after setting.
---- - title: (string) Title of the location list. Defaults to "Diagnostics".
---- - severity: See |diagnostic-severity|.
+---@param opts? vim.diagnostic.setloclist.Opts
function M.setloclist(opts)
set_list(true, opts)
end
@@ -1900,8 +1987,7 @@ end
--- WARNING filename:27:3: Variable 'foo' does not exist
--- ```
---
---- This can be parsed into a diagnostic |diagnostic-structure|
---- with:
+--- This can be parsed into |vim.Diagnostic| structure with:
---
--- ```lua
--- local s = "WARNING filename:27:3: Variable 'foo' does not exist"
@@ -1912,14 +1998,14 @@ end
---
---@param str string String to parse diagnostics from.
---@param pat string Lua pattern with capture groups.
----@param groups string[] List of fields in a |diagnostic-structure| to
+---@param groups string[] List of fields in a |vim.Diagnostic| structure to
--- associate with captures from {pat}.
---@param severity_map table A table mapping the severity field from {groups}
--- with an item from |vim.diagnostic.severity|.
---@param defaults table? Table of default values for any fields not listed in {groups}.
--- When omitted, numeric values default to 0 and "severity" defaults to
--- ERROR.
----@return vim.Diagnostic?: |diagnostic-structure| or `nil` if {pat} fails to match {str}.
+---@return vim.Diagnostic?: |vim.Diagnostic| structure or `nil` if {pat} fails to match {str}.
function M.match(str, pat, groups, severity_map, defaults)
vim.validate({
str = { str, 's' },
@@ -1967,8 +2053,8 @@ local errlist_type_map = {
--- Convert a list of diagnostics to a list of quickfix items that can be
--- passed to |setqflist()| or |setloclist()|.
---
----@param diagnostics vim.Diagnostic[] List of diagnostics |diagnostic-structure|.
----@return table[] of quickfix list items |setqflist-what|
+---@param diagnostics vim.Diagnostic[]
+---@return table[] : Quickfix list items |setqflist-what|
function M.toqflist(diagnostics)
vim.validate({
diagnostics = {
@@ -2008,7 +2094,7 @@ end
--- Convert a list of quickfix items to a list of diagnostics.
---
---@param list table[] List of quickfix items from |getqflist()| or |getloclist()|.
----@return vim.Diagnostic[] array of |diagnostic-structure|
+---@return vim.Diagnostic[]
function M.fromqflist(list)
vim.validate({
list = {
diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua
index e7971d8916..fba76f93b2 100644
--- a/runtime/lua/vim/filetype.lua
+++ b/runtime/lua/vim/filetype.lua
@@ -2082,6 +2082,7 @@ local function normalize_path(path, as_pattern)
end
--- @class vim.filetype.add.filetypes
+--- @inlinedoc
--- @field pattern? vim.filetype.mapping
--- @field extension? vim.filetype.mapping
--- @field filename? vim.filetype.mapping
@@ -2170,7 +2171,7 @@ end
--- }
--- ```
---
----@param filetypes vim.filetype.add.filetypes (table) A table containing new filetype maps (see example).
+---@param filetypes vim.filetype.add.filetypes A table containing new filetype maps (see example).
function M.add(filetypes)
for k, v in pairs(filetypes.extension or {}) do
extension[k] = v
@@ -2272,8 +2273,23 @@ local function match_pattern(name, path, tail, pat)
end
--- @class vim.filetype.match.args
+--- @inlinedoc
+---
+--- Buffer number to use for matching. Mutually exclusive with {contents}
--- @field buf? integer
+---
+--- Filename to use for matching. When {buf} is given,
+--- defaults to the filename of the given buffer number. The
+--- file need not actually exist in the filesystem. When used
+--- without {buf} only the name of the file is used for
+--- filetype matching. This may result in failure to detect
+--- the filetype in cases where the filename alone is not
+--- enough to disambiguate the filetype.
--- @field filename? string
+---
+--- An array of lines representing file contents to use for
+--- matching. Can be used with {filename}. Mutually exclusive
+--- with {buf}.
--- @field contents? string[]
--- Perform filetype detection.
@@ -2305,20 +2321,8 @@ end
--- vim.filetype.match({ contents = {'#!/usr/bin/env bash'} })
--- ```
---
----@param args vim.filetype.match.args (table) Table specifying which matching strategy to use.
+---@param args vim.filetype.match.args Table specifying which matching strategy to use.
--- Accepted keys are:
---- * buf (number): Buffer number to use for matching. Mutually exclusive with
---- {contents}
---- * filename (string): Filename to use for matching. When {buf} is given,
---- defaults to the filename of the given buffer number. The
---- file need not actually exist in the filesystem. When used
---- without {buf} only the name of the file is used for
---- filetype matching. This may result in failure to detect
---- the filetype in cases where the filename alone is not
---- enough to disambiguate the filetype.
---- * contents (table): An array of lines representing file contents to use for
---- matching. Can be used with {filename}. Mutually exclusive
---- with {buf}.
---@return string|nil # If a match was found, the matched filetype.
---@return function|nil # A function that modifies buffer state when called (for example, to set some
--- filetype specific buffer variables). The function accepts a buffer number as
diff --git a/runtime/lua/vim/fs.lua b/runtime/lua/vim/fs.lua
index 0af5fc4f30..f9fe122f01 100644
--- a/runtime/lua/vim/fs.lua
+++ b/runtime/lua/vim/fs.lua
@@ -39,8 +39,9 @@ end
--- Return the parent directory of the given path
---
----@param file (string) Path
----@return string|nil Parent directory of {file}
+---@generic T : string|nil
+---@param file T Path
+---@return T Parent directory of {file}
function M.dirname(file)
if file == nil then
return nil
@@ -53,6 +54,7 @@ function M.dirname(file)
elseif file == '/' or file:match('^/[^/]+$') then
return '/'
end
+ ---@type string
local dir = file:match('[/\\]$') and file:sub(1, #file - 1) or file:match('^([/\\]?.+)[/\\]')
if iswin and dir:match('^%w:$') then
return dir .. '/'
@@ -62,8 +64,9 @@ end
--- Return the basename of the given path
---
----@param file string Path
----@return string|nil Basename of {file}
+---@generic T : string|nil
+---@param file T Path
+---@return T Basename of {file}
function M.basename(file)
if file == nil then
return nil
@@ -147,12 +150,30 @@ function M.dir(path, opts)
end)
end
---- @class vim.fs.find.opts
---- @field path string
---- @field upward boolean
---- @field stop string
---- @field type string
---- @field limit number
+--- @class vim.fs.find.Opts
+--- @inlinedoc
+---
+--- Path to begin searching from. If
+--- omitted, the |current-directory| is used.
+--- @field path? string
+---
+--- Search upward through parent directories.
+--- Otherwise, search through child directories (recursively).
+--- (default: `false`)
+--- @field upward? boolean
+---
+--- Stop searching when this directory is reached.
+--- The directory itself is not searched.
+--- @field stop? string
+---
+--- Find only items of the given type.
+--- If omitted, all items that match {names} are included.
+--- @field type? string
+---
+--- Stop the search after finding this many matches.
+--- Use `math.huge` to place no limit on the number of matches.
+--- (default: `1`)
+--- @field limit? number
--- Find files or directories (or other items as specified by `opts.type`) in the given path.
---
@@ -194,23 +215,10 @@ end
--- - path: full path of the current item
--- The function should return `true` if the given item is considered a match.
---
----@param opts (table) Optional keyword arguments:
---- - path (string): Path to begin searching from. If
---- omitted, the |current-directory| is used.
---- - upward (boolean, default false): If true, search
---- upward through parent directories. Otherwise,
---- search through child directories
---- (recursively).
---- - stop (string): Stop searching when this directory is
---- reached. The directory itself is not searched.
---- - type (string): Find only items of the given type.
---- If omitted, all items that match {names} are included.
---- - limit (number, default 1): Stop the search after
---- finding this many matches. Use `math.huge` to
---- place no limit on the number of matches.
+---@param opts vim.fs.find.Opts Optional keyword arguments:
---@return (string[]) # Normalized paths |vim.fs.normalize()| of all matching items
function M.find(names, opts)
- opts = opts or {} --[[@as vim.fs.find.opts]]
+ opts = opts or {}
vim.validate({
names = { names, { 's', 't', 'f' } },
path = { opts.path, 's', true },
@@ -224,7 +232,7 @@ function M.find(names, opts)
names = { names }
end
- local path = opts.path or vim.uv.cwd()
+ local path = opts.path or assert(vim.uv.cwd())
local stop = opts.stop
local limit = opts.limit or 1
@@ -318,6 +326,13 @@ function M.find(names, opts)
return matches
end
+--- @class vim.fs.normalize.Opts
+--- @inlinedoc
+---
+--- Expand environment variables.
+--- (default: `true`)
+--- @field expand_env boolean
+
--- Normalize a path to a standard format. A tilde (~) character at the
--- beginning of the path is expanded to the user's home directory and any
--- backslash (\) characters are converted to forward slashes (/). Environment
@@ -337,9 +352,8 @@ end
--- ```
---
---@param path (string) Path to normalize
----@param opts table|nil Options:
---- - expand_env: boolean Expand environment variables (default: true)
----@return (string) Normalized path
+---@param opts? vim.fs.normalize.Opts
+---@return (string) : Normalized path
function M.normalize(path, opts)
opts = opts or {}
diff --git a/runtime/lua/vim/iter.lua b/runtime/lua/vim/iter.lua
index 798428014d..a37b7f7858 100644
--- a/runtime/lua/vim/iter.lua
+++ b/runtime/lua/vim/iter.lua
@@ -1,7 +1,7 @@
--- @brief
---
---- \*vim.iter()\* is an interface for |iterable|s: it wraps a table or function argument into an
---- \*Iter\* object with methods (such as |Iter:filter()| and |Iter:map()|) that transform the
+--- [vim.iter()]() is an interface for [iterable]s: it wraps a table or function argument into an
+--- [Iter]() object with methods (such as [Iter:filter()] and [Iter:map()]) that transform the
--- underlying source data. These methods can be 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()`:
@@ -64,11 +64,16 @@
--- In addition to the |vim.iter()| function, the |vim.iter| module provides
--- convenience functions like |vim.iter.filter()| and |vim.iter.totable()|.
+--- LuaLS is bad at generics which this module mostly deals with
+--- @diagnostic disable:no-unknown
+
+---@nodoc
---@class IterMod
---@operator call:Iter
local M = {}
+---@nodoc
---@class Iter
local Iter = {}
Iter.__index = Iter
@@ -77,6 +82,7 @@ Iter.__call = function(self)
end
--- Special case implementations for iterators on list tables.
+---@nodoc
---@class ListIter : Iter
---@field _table table Underlying table data
---@field _head number Index to the front of a table iterator
@@ -186,7 +192,7 @@ end
--- in the pipeline and returns false or nil if the
--- current iterator element should be removed.
---@return Iter
-function Iter.filter(self, f)
+function Iter:filter(f)
return self:map(function(...)
if f(...) then
return ...
@@ -195,7 +201,7 @@ function Iter.filter(self, f)
end
---@private
-function ListIter.filter(self, f)
+function ListIter:filter(f)
local inc = self._head < self._tail and 1 or -1
local n = self._head
for i = self._head, self._tail - inc, inc do
@@ -228,12 +234,13 @@ end
---@param depth? number Depth to which |list-iterator| should be flattened
--- (defaults to 1)
---@return Iter
-function Iter.flatten(self, depth) -- luacheck: no unused args
+---@diagnostic disable-next-line:unused-local
+function Iter:flatten(depth) -- luacheck: no unused args
error('flatten() requires a list-like table')
end
---@private
-function ListIter.flatten(self, depth)
+function ListIter:flatten(depth)
depth = depth or 1
local inc = self._head < self._tail and 1 or -1
local target = {}
@@ -279,7 +286,7 @@ end
--- in the next pipeline stage. Nil return values
--- are filtered from the output.
---@return Iter
-function Iter.map(self, f)
+function Iter:map(f)
-- Implementation note: the reader may be forgiven for observing that this
-- function appears excessively convoluted. The problem to solve is that each
-- stage of the iterator pipeline can return any number of values, and the
@@ -323,7 +330,7 @@ function Iter.map(self, f)
end
---@private
-function ListIter.map(self, f)
+function ListIter:map(f)
local inc = self._head < self._tail and 1 or -1
local n = self._head
for i = self._head, self._tail - inc, inc do
@@ -344,7 +351,7 @@ end
---@param f fun(...) Function to execute for each item in the pipeline.
--- Takes all of the values returned by the previous stage
--- in the pipeline as arguments.
-function Iter.each(self, f)
+function Iter:each(f)
local function fn(...)
if select(1, ...) ~= nil then
f(...)
@@ -356,7 +363,7 @@ function Iter.each(self, f)
end
---@private
-function ListIter.each(self, f)
+function ListIter:each(f)
local inc = self._head < self._tail and 1 or -1
for i = self._head, self._tail - inc, inc do
f(unpack(self._table[i]))
@@ -389,7 +396,7 @@ end
---
---
---@return table
-function Iter.totable(self)
+function Iter:totable()
local t = {}
while true do
@@ -404,7 +411,7 @@ function Iter.totable(self)
end
---@private
-function ListIter.totable(self)
+function ListIter:totable()
if self.next ~= ListIter.next or self._head >= self._tail then
return Iter.totable(self)
end
@@ -442,7 +449,7 @@ end
---
--- @param delim string Delimiter
--- @return string
-function Iter.join(self, delim)
+function Iter:join(delim)
return table.concat(self:totable(), delim)
end
@@ -467,7 +474,7 @@ end
---@param init A Initial value of the accumulator.
---@param f fun(acc:A, ...):A Accumulation function.
---@return A
-function Iter.fold(self, init, f)
+function Iter:fold(init, f)
local acc = init
--- Use a closure to handle var args returned from iterator
@@ -484,7 +491,7 @@ function Iter.fold(self, init, f)
end
---@private
-function ListIter.fold(self, init, f)
+function ListIter:fold(init, f)
local acc = init
local inc = self._head < self._tail and 1 or -1
for i = self._head, self._tail - inc, inc do
@@ -510,14 +517,13 @@ end
--- ```
---
---@return any
----@diagnostic disable-next-line: unused-local
-function Iter.next(self) -- luacheck: no unused args
+function Iter:next()
-- This function is provided by the source iterator in Iter.new. This definition exists only for
-- the docstring
end
---@private
-function ListIter.next(self)
+function ListIter:next()
if self._head ~= self._tail then
local v = self._table[self._head]
local inc = self._head < self._tail and 1 or -1
@@ -539,12 +545,12 @@ end
--- ```
---
---@return Iter
-function Iter.rev(self) -- luacheck: no unused args
+function Iter:rev()
error('rev() requires a list-like table')
end
---@private
-function ListIter.rev(self)
+function ListIter:rev()
local inc = self._head < self._tail and 1 or -1
self._head, self._tail = self._tail - inc, self._head - inc
return self
@@ -567,13 +573,12 @@ end
--- ```
---
---@return any
----@diagnostic disable-next-line: unused-local
-function Iter.peek(self) -- luacheck: no unused args
+function Iter:peek()
error('peek() requires a list-like table')
end
---@private
-function ListIter.peek(self)
+function ListIter:peek()
if self._head ~= self._tail then
return self._table[self._head]
end
@@ -602,7 +607,7 @@ end
--- ```
---@param f any
---@return any
-function Iter.find(self, f)
+function Iter:find(f)
if type(f) ~= 'function' then
local val = f
f = function(v)
@@ -649,12 +654,12 @@ end
---@param f any
---@return any
---@diagnostic disable-next-line: unused-local
-function Iter.rfind(self, f) -- luacheck: no unused args
+function Iter:rfind(f) -- luacheck: no unused args
error('rfind() requires a list-like table')
end
---@private
-function ListIter.rfind(self, f)
+function ListIter:rfind(f)
if type(f) ~= 'function' then
local val = f
f = function(v)
@@ -689,7 +694,7 @@ end
---
---@param n integer
---@return Iter
-function Iter.take(self, n)
+function Iter:take(n)
local next = self.next
local i = 0
self.next = function()
@@ -702,7 +707,7 @@ function Iter.take(self, n)
end
---@private
-function ListIter.take(self, n)
+function ListIter:take(n)
local inc = self._head < self._tail and 1 or -1
self._tail = math.min(self._tail, self._head + n * inc)
return self
@@ -721,13 +726,12 @@ end
--- ```
---
---@return any
----@diagnostic disable-next-line: unused-local
-function Iter.nextback(self) -- luacheck: no unused args
+function Iter:nextback()
error('nextback() requires a list-like table')
end
--- @nodoc
-function ListIter.nextback(self)
+function ListIter:nextback()
if self._head ~= self._tail then
local inc = self._head < self._tail and 1 or -1
self._tail = self._tail - inc
@@ -752,13 +756,12 @@ end
--- ```
---
---@return any
----@diagnostic disable-next-line: unused-local
-function Iter.peekback(self) -- luacheck: no unused args
+function Iter:peekback()
error('peekback() requires a list-like table')
end
---@nodoc
-function ListIter.peekback(self)
+function ListIter:peekback()
if self._head ~= self._tail then
local inc = self._head < self._tail and 1 or -1
return self._table[self._tail - inc]
@@ -779,7 +782,7 @@ end
---
---@param n number Number of values to skip.
---@return Iter
-function Iter.skip(self, n)
+function Iter:skip(n)
for _ = 1, n do
local _ = self:next()
end
@@ -787,7 +790,7 @@ function Iter.skip(self, n)
end
---@private
-function ListIter.skip(self, n)
+function ListIter:skip(n)
local inc = self._head < self._tail and n or -n
self._head = self._head + inc
if (inc > 0 and self._head > self._tail) or (inc < 0 and self._head < self._tail) then
@@ -811,12 +814,12 @@ end
---@param n number Number of values to skip.
---@return Iter
---@diagnostic disable-next-line: unused-local
-function Iter.skipback(self, n) -- luacheck: no unused args
+function Iter:skipback(n) -- luacheck: no unused args
error('skipback() requires a list-like table')
end
---@private
-function ListIter.skipback(self, n)
+function ListIter:skipback(n)
local inc = self._head < self._tail and n or -n
self._tail = self._tail - inc
if (inc > 0 and self._head > self._tail) or (inc < 0 and self._head < self._tail) then
@@ -841,7 +844,7 @@ end
---
---@param n number The index of the value to return.
---@return any
-function Iter.nth(self, n)
+function Iter:nth(n)
if n > 0 then
return self:skip(n - 1):next()
end
@@ -863,7 +866,7 @@ end
---
---@param n number The index of the value to return.
---@return any
-function Iter.nthback(self, n)
+function Iter:nthback(n)
if n > 0 then
return self:skipback(n - 1):nextback()
end
@@ -877,12 +880,12 @@ end
---@param last number
---@return Iter
---@diagnostic disable-next-line: unused-local
-function Iter.slice(self, first, last) -- luacheck: no unused args
+function Iter:slice(first, last) -- luacheck: no unused args
error('slice() requires a list-like table')
end
---@private
-function ListIter.slice(self, first, last)
+function ListIter:slice(first, last)
return self:skip(math.max(0, first - 1)):skipback(math.max(0, self._tail - last - 1))
end
@@ -891,7 +894,7 @@ end
---@param pred fun(...):boolean Predicate function. Takes all values returned from the previous
--- stage in the pipeline as arguments and returns true if the
--- predicate matches.
-function Iter.any(self, pred)
+function Iter:any(pred)
local any = false
--- Use a closure to handle var args returned from iterator
@@ -915,7 +918,7 @@ end
---@param pred fun(...):boolean Predicate function. Takes all values returned from the previous
--- stage in the pipeline as arguments and returns true if the
--- predicate matches.
-function Iter.all(self, pred)
+function Iter:all(pred)
local all = true
local function fn(...)
@@ -950,7 +953,7 @@ end
--- ```
---
---@return any
-function Iter.last(self)
+function Iter:last()
local last = self:next()
local cur = self:next()
while cur do
@@ -961,7 +964,7 @@ function Iter.last(self)
end
---@private
-function ListIter.last(self)
+function ListIter:last()
local inc = self._head < self._tail and 1 or -1
local v = self._table[self._tail - inc]
self._head = self._tail
@@ -997,7 +1000,7 @@ end
--- ```
---
---@return Iter
-function Iter.enumerate(self)
+function Iter:enumerate()
local i = 0
return self:map(function(...)
i = i + 1
@@ -1006,7 +1009,7 @@ function Iter.enumerate(self)
end
---@private
-function ListIter.enumerate(self)
+function ListIter:enumerate()
local inc = self._head < self._tail and 1 or -1
for i = self._head, self._tail - inc, inc do
local v = self._table[i]
@@ -1137,9 +1140,8 @@ function M.map(f, src, ...)
return Iter.new(src, ...):map(f):totable()
end
----@type IterMod
return setmetatable(M, {
__call = function(_, ...)
return Iter.new(...)
end,
-})
+}) --[[@as IterMod]]
diff --git a/runtime/lua/vim/loader.lua b/runtime/lua/vim/loader.lua
index 5f3da55544..d3d8948654 100644
--- a/runtime/lua/vim/loader.lua
+++ b/runtime/lua/vim/loader.lua
@@ -7,19 +7,40 @@ local loaders = package.loaders
local M = {}
----@alias CacheHash {mtime: {nsec: integer, sec: integer}, size: integer, type?: uv.aliases.fs_stat_types}
+---@alias CacheHash {mtime: {nsec: integer, sec: integer}, size: integer, type?: string}
---@alias CacheEntry {hash:CacheHash, chunk:string}
----@class ModuleFindOpts
----@field all? boolean Search for all matches (defaults to `false`)
----@field rtp? boolean Search for modname in the runtime path (defaults to `true`)
----@field patterns? string[] Patterns to use (defaults to `{"/init.lua", ".lua"}`)
----@field paths? string[] Extra paths to search for modname
-
----@class ModuleInfo
----@field modpath string Path of the module
----@field modname string Name of the module
----@field stat? uv.uv_fs_t File stat of the module path
+--- @class vim.loader.find.Opts
+--- @inlinedoc
+---
+--- Search for modname in the runtime path.
+--- (default: `true`)
+--- @field rtp? boolean
+---
+--- Extra paths to search for modname
+--- (default: `{}`)
+--- @field paths? string[]
+---
+--- List of patterns to use when searching for modules.
+--- A pattern is a string added to the basename of the Lua module being searched.
+--- (default: `{"/init.lua", ".lua"}`)
+--- @field patterns? string[]
+---
+--- Search for all matches.
+--- (default: `false`)
+--- @field all? boolean
+
+--- @class vim.loader.ModuleInfo
+--- @inlinedoc
+---
+--- Path of the module
+--- @field modpath string
+---
+--- Name of the module
+--- @field modname string
+---
+--- The fs_stat of the module path. Won't be returned for `modname="*"`
+--- @field stat? uv.uv_fs_t
---@alias LoaderStats table<string, {total:number, time:number, [string]:number?}?>
@@ -29,14 +50,14 @@ M.path = vim.fn.stdpath('cache') .. '/luac'
---@nodoc
M.enabled = false
----@class Loader
----@field _rtp string[]
----@field _rtp_pure string[]
----@field _rtp_key string
----@field _hashes? table<string, CacheHash>
+---@class (private) Loader
+---@field private _rtp string[]
+---@field private _rtp_pure string[]
+---@field private _rtp_key string
+---@field private _hashes? table<string, CacheHash>
local Loader = {
VERSION = 4,
- ---@type table<string, table<string,ModuleInfo>>
+ ---@type table<string, table<string,vim.loader.ModuleInfo>>
_indexed = {},
---@type table<string, string[]>
_topmods = {},
@@ -270,17 +291,8 @@ end
--- Finds Lua modules for the given module name.
---@param modname string Module name, or `"*"` to find the top-level modules instead
----@param opts? ModuleFindOpts (table) Options for finding a module:
---- - rtp: (boolean) Search for modname in the runtime path (defaults to `true`)
---- - paths: (string[]) Extra paths to search for modname (defaults to `{}`)
---- - patterns: (string[]) List of patterns to use when searching for modules.
---- A pattern is a string added to the basename of the Lua module being searched.
---- (defaults to `{"/init.lua", ".lua"}`)
---- - all: (boolean) Return all matches instead of just the first one (defaults to `false`)
----@return ModuleInfo[] (table) A list of results with the following properties:
---- - modpath: (string) the path to the module
---- - modname: (string) the name of the module
---- - stat: (table|nil) the fs_stat of the module path. Won't be returned for `modname="*"`
+---@param opts? vim.loader.find.Opts Options for finding a module:
+---@return vim.loader.ModuleInfo[]
function M.find(modname, opts)
opts = opts or {}
@@ -306,7 +318,7 @@ function M.find(modname, opts)
patterns[p] = '/lua/' .. basename .. pattern
end
- ---@type ModuleInfo[]
+ ---@type vim.loader.ModuleInfo[]
local results = {}
-- Only continue if we haven't found anything yet or we want to find all
@@ -472,12 +484,12 @@ function Loader.track(stat, f)
end
end
----@class ProfileOpts
+---@class (private) vim.loader._profile.Opts
---@field loaders? boolean Add profiling to the loaders
--- Debug function that wraps all loaders and tracks stats
---@private
----@param opts ProfileOpts?
+---@param opts vim.loader._profile.Opts?
function M._profile(opts)
Loader.get_rtp = Loader.track('get_rtp', Loader.get_rtp)
Loader.read = Loader.track('read', Loader.read)
diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua
index 19497e40dc..d5c376ba44 100644
--- a/runtime/lua/vim/lsp.lua
+++ b/runtime/lua/vim/lsp.lua
@@ -108,12 +108,12 @@ function lsp._buf_get_line_ending(bufnr)
end
-- Tracks all clients created via lsp.start_client
-local active_clients = {} --- @type table<integer,lsp.Client>
+local active_clients = {} --- @type table<integer,vim.lsp.Client>
local all_buffer_active_clients = {} --- @type table<integer,table<integer,true>>
-local uninitialized_clients = {} --- @type table<integer,lsp.Client>
+local uninitialized_clients = {} --- @type table<integer,vim.lsp.Client>
---@param bufnr? integer
----@param fn fun(client: lsp.Client, client_id: integer, bufnr: integer)
+---@param fn fun(client: vim.lsp.Client, client_id: integer, bufnr: integer)
local function for_each_buffer_client(bufnr, fn, restrict_client_ids)
validate({
fn = { fn, 'f' },
@@ -146,9 +146,10 @@ end
local client_errors_base = table.maxn(lsp.rpc.client_errors)
local client_errors_offset = 0
-local function new_error_index()
+local function client_error(name)
client_errors_offset = client_errors_offset + 1
- return client_errors_base + client_errors_offset
+ local index = client_errors_base + client_errors_offset
+ return { [name] = index, [index] = name }
end
--- Error codes to be used with `on_error` from |vim.lsp.start_client|.
@@ -158,12 +159,10 @@ end
lsp.client_errors = tbl_extend(
'error',
lsp.rpc.client_errors,
- vim.tbl_add_reverse_lookup({
- BEFORE_INIT_CALLBACK_ERROR = new_error_index(),
- ON_INIT_CALLBACK_ERROR = new_error_index(),
- ON_ATTACH_ERROR = new_error_index(),
- ON_EXIT_CALLBACK_ERROR = new_error_index(),
- })
+ client_error('BEFORE_INIT_CALLBACK_ERROR'),
+ client_error('ON_INIT_CALLBACK_ERROR'),
+ client_error('ON_ATTACH_ERROR'),
+ client_error('ON_EXIT_CALLBACK_ERROR')
)
---@private
@@ -200,105 +199,15 @@ local function once(fn)
end
end
--- FIXME: DOC: Shouldn't need to use a dummy function
---
---- LSP client object. You can get an active client object via
---- |vim.lsp.get_client_by_id()| or |vim.lsp.get_clients()|.
----
---- - Methods:
---- - request(method, params, [handler], bufnr)
---- Sends a request to the server.
---- This is a thin wrapper around {client.rpc.request} with some additional
---- checking.
---- If {handler} is not specified, If one is not found there, then an error will occur.
---- Returns: {status}, {[client_id]}. {status} is a boolean indicating if
---- the notification was successful. If it is `false`, then it will always
---- be `false` (the client has shutdown).
---- If {status} is `true`, the function returns {request_id} as the second
---- result. You can use this with `client.cancel_request(request_id)`
---- to cancel the request.
----
---- - request_sync(method, params, timeout_ms, bufnr)
---- Sends a request to the server and synchronously waits for the response.
---- This is a wrapper around {client.request}
---- Returns: { err=err, result=result }, a dictionary, where `err` and `result` come from
---- the |lsp-handler|. On timeout, cancel or error, returns `(nil, err)` where `err` is a
---- string describing the failure reason. If the request was unsuccessful returns `nil`.
----
---- - notify(method, params)
---- Sends a notification to an LSP server.
---- Returns: a boolean to indicate if the notification was successful. If
---- it is false, then it will always be false (the client has shutdown).
----
---- - cancel_request(id)
---- Cancels a request with a given request id.
---- Returns: same as `notify()`.
----
---- - stop([force])
---- Stops a client, optionally with force.
---- By default, it will just ask the server to shutdown without force.
---- If you request to stop a client which has previously been requested to
---- shutdown, it will automatically escalate and force shutdown.
----
---- - is_stopped()
---- Checks whether a client is stopped.
---- Returns: true if the client is fully stopped.
----
---- - on_attach(client, bufnr)
---- Runs the on_attach function from the client's config if it was defined.
---- Useful for buffer-local setup.
----
---- - supports_method(method, [opts]): boolean
---- Checks if a client supports a given method.
---- Always returns true for unknown off-spec methods.
---- [opts] is a optional `{bufnr?: integer}` table.
---- Some language server capabilities can be file specific.
----
---- - Members
---- - {id} (number): The id allocated to the client.
----
---- - {name} (string): If a name is specified on creation, that will be
---- used. Otherwise it is just the client id. This is used for
---- logs and messages.
----
---- - {rpc} (table): RPC client object, for low level interaction with the
---- client. See |vim.lsp.rpc.start()|.
----
---- - {offset_encoding} (string): The encoding used for communicating
---- with the server. You can modify this in the `config`'s `on_init` method
---- before text is sent to the server.
----
---- - {handlers} (table): The handlers used by the client as described in |lsp-handler|.
+--- @class vim.lsp.start.Opts
+--- @inlinedoc
---
---- - {commands} (table): Table of command name to function which is called if
---- any LSP action (code action, code lenses, ...) triggers the command.
---- Client commands take precedence over the global command registry.
+--- 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: table): boolean
---
---- - {requests} (table): The current pending requests in flight
---- to the server. Entries are key-value pairs with the key
---- being the request ID while the value is a table with `type`,
---- `bufnr`, and `method` key-value pairs. `type` is either "pending"
---- for an active request, or "cancel" for a cancel request. It will
---- be "complete" ephemerally while executing |LspRequest| autocmds
---- when replies are received from the server.
----
---- - {config} (table): Reference of the table that was passed by the user
---- to |vim.lsp.start_client()|.
----
---- - {server_capabilities} (table): Response from the server sent on
---- `initialize` describing the server's capabilities.
----
---- - {progress} A ring buffer (|vim.ringbuf()|) containing progress messages
---- sent by the server.
----
---- - {settings} Map with language server specific settings.
---- See {config} in |vim.lsp.start_client()|
----
---- - {flags} A table with flags for the client. See {config} in |vim.lsp.start_client()|
-lsp.client = nil
-
---- @class lsp.StartOpts
---- @field reuse_client fun(client: lsp.Client, config: table): boolean
+--- Buffer handle to attach to if starting or re-using a client (0 for current).
--- @field bufnr integer
--- Create a new LSP client and start a language server or reuses an already
@@ -337,17 +246,9 @@ lsp.client = nil
--- Either use |:au|, |nvim_create_autocmd()| or put the call in a
--- `ftplugin/<filetype_name>.lua` (See |ftplugin-name|)
---
----@param config lsp.ClientConfig Same configuration as documented in |vim.lsp.start_client()|
----@param opts lsp.StartOpts? Optional keyword arguments:
---- - reuse_client (fun(client: client, config: table): 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.
---- - bufnr (number)
---- Buffer handle to attach to if starting or re-using a
---- client (0 for current).
----@return integer? client_id
+--- @param config vim.lsp.ClientConfig Configuration for the server.
+--- @param opts vim.lsp.start.Opts? Optional keyword arguments
+--- @return integer? client_id
function lsp.start(config, opts)
opts = opts or {}
local reuse_client = opts.reuse_client
@@ -428,7 +329,7 @@ local function is_empty_or_default(bufnr, option)
end
---@private
----@param client lsp.Client
+---@param client vim.lsp.Client
---@param bufnr integer
function lsp._set_defaults(client, bufnr)
if
@@ -482,7 +383,7 @@ local function reset_defaults(bufnr)
end)
end
---- @param client lsp.Client
+--- @param client vim.lsp.Client
local function on_client_init(client)
local id = client.id
uninitialized_clients[id] = nil
@@ -552,121 +453,9 @@ local function on_client_exit(code, signal, client_id)
end)
end
--- FIXME: DOC: Currently all methods on the `vim.lsp.client` object are
--- documented twice: Here, and on the methods themselves (e.g.
--- `client.request()`). This is a workaround for the vimdoc generator script
--- not handling method names correctly. If you change the documentation on
--- either, please make sure to update the other as well.
---
--- Starts and initializes a client with the given configuration.
----
---- Field `cmd` in {config} is required.
----
----@param config (lsp.ClientConfig) Configuration for the server:
---- - cmd: (string[]|fun(dispatchers: table):table) command string[] that launches the language
---- server (treated as in |jobstart()|, must be absolute or on `$PATH`, shell constructs like
---- "~" are not expanded), or function that creates an RPC client. Function receives
---- a `dispatchers` table and returns a table with member functions `request`, `notify`,
---- `is_closing` and `terminate`.
---- See |vim.lsp.rpc.request()|, |vim.lsp.rpc.notify()|.
---- For TCP there is a builtin RPC client factory: |vim.lsp.rpc.connect()|
----
---- - cmd_cwd: (string, default=|getcwd()|) Directory to launch
---- the `cmd` process. Not related to `root_dir`.
----
---- - cmd_env: (table) Environment flags to pass to the LSP on
---- spawn. Must be specified using a table.
---- Non-string values are coerced to string.
---- Example:
---- ```
---- { PORT = 8080; HOST = "0.0.0.0"; }
---- ```
----
---- - detached: (boolean, default true) Daemonize the server process so that it runs in a
---- separate process group from Nvim. Nvim will shutdown the process on exit, but if Nvim fails to
---- exit cleanly this could leave behind orphaned server processes.
----
---- - workspace_folders: (table) List of workspace folders passed to the
---- language server. For backwards compatibility rootUri and rootPath will be
---- derived from the first workspace folder in this list. See `workspaceFolders` in
---- the LSP spec.
----
---- - capabilities: Map overriding the default capabilities defined by
---- \|vim.lsp.protocol.make_client_capabilities()|, passed to the language
---- server on initialization. Hint: use make_client_capabilities() and modify
---- its result.
----
---- - Note: To send an empty dictionary use |vim.empty_dict()|, else it will be encoded as an
---- array.
----
---- - handlers: Map of language server method names to |lsp-handler|
----
---- - settings: Map with language server specific settings. These are
---- returned to the language server if requested via `workspace/configuration`.
---- Keys are case-sensitive.
----
---- - commands: table Table that maps string of clientside commands to user-defined functions.
---- Commands passed to start_client take precedence over the global command registry. Each key
---- must be a unique command name, and the value is a function which is called if any LSP action
---- (code action, code lenses, ...) triggers the command.
----
---- - init_options Values to pass in the initialization request
---- as `initializationOptions`. See `initialize` in the LSP spec.
----
---- - name: (string, default=client-id) Name in log messages.
----
---- - get_language_id: function(bufnr, filetype) -> language ID as string.
---- Defaults to the filetype.
----
---- - offset_encoding: (default="utf-16") One of "utf-8", "utf-16",
---- or "utf-32" which is the encoding that the LSP server expects. Client does
---- not verify this is correct.
----
---- - on_error: Callback with parameters (code, ...), invoked
---- when the client operation throws an error. `code` is a number describing
---- the error. Other arguments may be passed depending on the error kind. See
---- `vim.lsp.rpc.client_errors` for possible errors.
---- Use `vim.lsp.rpc.client_errors[code]` to get human-friendly name.
----
---- - before_init: Callback with parameters (initialize_params, config)
---- invoked before the LSP "initialize" phase, where `params` contains the
---- parameters being sent to the server and `config` is the config that was
---- passed to |vim.lsp.start_client()|. You can use this to modify parameters before
---- they are sent.
----
---- - on_init: Callback (client, initialize_result) invoked after LSP
---- "initialize", where `result` is a table of `capabilities` and anything else
---- the server may send. For example, clangd sends
---- `initialize_result.offsetEncoding` if `capabilities.offsetEncoding` was
---- sent to it. You can only modify the `client.offset_encoding` here before
---- any notifications are sent.
----
---- - on_exit Callback (code, signal, client_id) invoked on client
---- exit.
---- - code: exit code of the process
---- - signal: number describing the signal used to terminate (if any)
---- - client_id: client handle
----
---- - on_attach: Callback (client, bufnr) invoked when client
---- attaches to a buffer.
----
---- - trace: ("off" | "messages" | "verbose" | nil) passed directly to the language
---- server in the initialize request. Invalid/empty values will default to "off"
----
---- - flags: A table with flags for the client. The current (experimental) flags are:
---- - allow_incremental_sync (bool, default true): Allow using incremental sync for buffer edits
---- - debounce_text_changes (number, default 150): Debounce didChange
---- notifications to the server by the given number in milliseconds. No debounce
---- occurs if nil
---- - exit_timeout (number|boolean, default false): Milliseconds to wait for server to
---- exit cleanly after sending the "shutdown" request before sending kill -15.
---- If set to false, nvim exits immediately after sending the "shutdown" request to the server.
----
---- - root_dir: (string) Directory where the LSP
---- server will base its workspaceFolders, rootUri, and rootPath
---- on initialization.
----
----@return integer|nil client_id. |vim.lsp.get_client_by_id()| Note: client may not be
+--- @param config vim.lsp.ClientConfig Configuration for the server.
+--- @return integer|nil client_id |vim.lsp.get_client_by_id()| Note: client may not be
--- fully initialized. Use `on_init` to do any actions once
--- the client has been initialized.
function lsp.start_client(config)
@@ -927,7 +716,7 @@ end
---
---@param client_id integer client id
---
----@return (nil|lsp.Client) client rpc object
+---@return (nil|vim.lsp.Client) client rpc object
function lsp.get_client_by_id(client_id)
return active_clients[client_id] or uninitialized_clients[client_id]
end
@@ -943,7 +732,7 @@ end
--- Stops a client(s).
---
---- You can also use the `stop()` function on a |vim.lsp.client| object.
+--- You can also use the `stop()` function on a |vim.lsp.Client| object.
--- To stop all clients:
---
--- ```lua
@@ -953,7 +742,7 @@ end
--- By default asks the server to shutdown, unless stop was requested
--- already for this client, then force-shutdown is attempted.
---
----@param client_id integer|table id or |vim.lsp.client| object, or list thereof
+---@param client_id integer|vim.lsp.Client id or |vim.lsp.Client| object, or list thereof
---@param force boolean|nil shutdown forcefully
function lsp.stop_client(client_id, force)
local ids = type(client_id) == 'table' and client_id or { client_id }
@@ -968,28 +757,32 @@ function lsp.stop_client(client_id, force)
end
end
----@class vim.lsp.get_clients.filter
----@field id integer|nil Match clients by id
----@field bufnr integer|nil match clients attached to the given buffer
----@field name string|nil match clients by name
----@field method string|nil match client by supported method name
+--- Key-value pairs used to filter the returned clients.
+--- @class vim.lsp.get_clients.Filter
+--- @inlinedoc
+---
+--- Only return clients with the given id
+--- @field id? integer
+---
+--- Only return clients attached to this buffer
+--- @field bufnr? integer
+---
+--- Only return clients with the given name
+--- @field name? string
+---
+--- Only return clients supporting the given method
+--- @field method? string
--- Get active clients.
---
----@param filter vim.lsp.get_clients.filter|nil (table|nil) A table with
---- key-value pairs used to filter the returned clients.
---- The available keys are:
---- - id (number): Only return clients with the given id
---- - bufnr (number): Only return clients attached to this buffer
---- - name (string): Only return clients with the given name
---- - method (string): Only return clients supporting the given method
----@return lsp.Client[]: List of |vim.lsp.client| objects
+---@param filter? vim.lsp.get_clients.Filter
+---@return vim.lsp.Client[]: List of |vim.lsp.Client| objects
function lsp.get_clients(filter)
validate({ filter = { filter, 't', true } })
filter = filter or {}
- local clients = {} --- @type lsp.Client[]
+ local clients = {} --- @type vim.lsp.Client[]
local t = filter.bufnr and (all_buffer_active_clients[resolve_bufnr(filter.bufnr)] or {})
or active_clients
@@ -1167,16 +960,15 @@ end
---
--- Calls |vim.lsp.buf_request_all()| but blocks Nvim while awaiting the result.
--- Parameters are the same as |vim.lsp.buf_request_all()| but the result is
---- different. Waits a maximum of {timeout_ms} (default 1000) ms.
----
----@param bufnr (integer) Buffer handle, or 0 for current.
----@param method (string) LSP method name
----@param params (table|nil) Parameters to send to the server
----@param timeout_ms (integer|nil) Maximum time in milliseconds to wait for a
---- result. Defaults to 1000
----
----@return table<integer, {err: lsp.ResponseError, result: any}>|nil (table) result Map of client_id:request_result.
----@return string|nil err On timeout, cancel, or error, `err` is a string describing the failure reason, and `result` is nil.
+--- different. Waits a maximum of {timeout_ms}.
+---
+---@param bufnr integer Buffer handle, or 0 for current.
+---@param method string LSP method name
+---@param params table? Parameters to send to the server
+---@param timeout_ms integer? Maximum time in milliseconds to wait for a result.
+--- (default: `1000`)
+---@return table<integer, {err: lsp.ResponseError, result: any}>? result Map of client_id:request_result.
+---@return string? err On timeout, cancel, or error, `err` is a string describing the failure reason, and `result` is nil.
function lsp.buf_request_sync(bufnr, method, params, timeout_ms)
local request_results
@@ -1233,15 +1025,20 @@ function lsp.omnifunc(findstart, base)
return vim.lsp._completion.omnifunc(findstart, base)
end
+--- @class vim.lsp.formatexpr.Opts
+--- @inlinedoc
+---
+--- The timeout period for the formatting request.
+--- (default: 500ms).
+--- @field timeout_ms integer
+
--- Provides an interface between the built-in client and a `formatexpr` function.
---
--- Currently only supports a single client. This can be set via
--- `setlocal formatexpr=v:lua.vim.lsp.formatexpr()` but will typically or in `on_attach`
--- via `vim.bo[bufnr].formatexpr = 'v:lua.vim.lsp.formatexpr(#{timeout_ms:250})'`.
---
----@param opts table options for customizing the formatting expression which takes the
---- following optional keys:
---- * timeout_ms (default 500ms). The timeout period for the formatting request.
+---@param opts? vim.lsp.formatexpr.Opts
function lsp.formatexpr(opts)
opts = opts or {}
local timeout_ms = opts.timeout_ms or 500
@@ -1313,14 +1110,14 @@ function lsp.client_is_stopped(client_id)
end
--- Gets a map of client_id:client pairs for the given buffer, where each value
---- is a |vim.lsp.client| object.
+--- is a |vim.lsp.Client| object.
---
---@param bufnr (integer|nil): Buffer handle, or 0 for current
---@return table result is table of (client_id, client) pairs
---@deprecated Use |vim.lsp.get_clients()| instead.
function lsp.buf_get_clients(bufnr)
vim.deprecate('vim.lsp.buf_get_clients()', 'vim.lsp.get_clients()', '0.12')
- local result = {} --- @type table<integer,lsp.Client>
+ local result = {} --- @type table<integer,vim.lsp.Client>
for _, client in ipairs(lsp.get_clients({ bufnr = resolve_bufnr(bufnr) })) do
result[client.id] = client
end
diff --git a/runtime/lua/vim/lsp/_changetracking.lua b/runtime/lua/vim/lsp/_changetracking.lua
index 3ecdec1659..b2be53269f 100644
--- a/runtime/lua/vim/lsp/_changetracking.lua
+++ b/runtime/lua/vim/lsp/_changetracking.lua
@@ -61,7 +61,7 @@ local state_by_group = setmetatable({}, {
end,
})
----@param client lsp.Client
+---@param client vim.lsp.Client
---@return vim.lsp.CTGroup
local function get_group(client)
local allow_inc_sync = vim.F.if_nil(client.flags.allow_incremental_sync, true) --- @type boolean
@@ -127,7 +127,7 @@ local function incremental_changes(state, encoding, bufnr, firstline, lastline,
return incremental_change
end
----@param client lsp.Client
+---@param client vim.lsp.Client
---@param bufnr integer
function M.init(client, bufnr)
assert(client.offset_encoding, 'lsp client must have an offset_encoding')
@@ -165,7 +165,7 @@ function M.init(client, bufnr)
end
end
---- @param client lsp.Client
+--- @param client vim.lsp.Client
--- @param bufnr integer
--- @param name string
--- @return string
@@ -189,7 +189,7 @@ local function reset_timer(buf_state)
end
end
---- @param client lsp.Client
+--- @param client vim.lsp.Client
--- @param bufnr integer
function M.reset_buf(client, bufnr)
M.flush(client, bufnr)
@@ -207,7 +207,7 @@ function M.reset_buf(client, bufnr)
end
end
---- @param client lsp.Client
+--- @param client vim.lsp.Client
function M.reset(client)
local state = state_by_group[get_group(client)]
if not state then
@@ -350,7 +350,7 @@ function M.send_changes(bufnr, firstline, lastline, new_lastline)
end
--- Flushes any outstanding change notification.
----@param client lsp.Client
+---@param client vim.lsp.Client
---@param bufnr? integer
function M.flush(client, bufnr)
local group = get_group(client)
diff --git a/runtime/lua/vim/lsp/_dynamic.lua b/runtime/lua/vim/lsp/_dynamic.lua
index 9c2af979fa..819b03a63a 100644
--- a/runtime/lua/vim/lsp/_dynamic.lua
+++ b/runtime/lua/vim/lsp/_dynamic.lua
@@ -58,7 +58,7 @@ end
--- @param method string
--- @param opts? {bufnr: integer?}
--- @return lsp.Registration? (table|nil) the registration if found
---- @private
+--- @package
function M:get(method, opts)
opts = opts or {}
opts.bufnr = opts.bufnr or vim.api.nvim_get_current_buf()
diff --git a/runtime/lua/vim/lsp/_watchfiles.lua b/runtime/lua/vim/lsp/_watchfiles.lua
index 6ca60b78cd..49328fbe9b 100644
--- a/runtime/lua/vim/lsp/_watchfiles.lua
+++ b/runtime/lua/vim/lsp/_watchfiles.lua
@@ -7,7 +7,13 @@ local lpeg = vim.lpeg
local M = {}
-M._watchfunc = (vim.fn.has('win32') == 1 or vim.fn.has('mac') == 1) and watch.watch or watch.poll
+if vim.fn.has('win32') == 1 or vim.fn.has('mac') == 1 then
+ M._watchfunc = watch.watch
+elseif vim.fn.executable('fswatch') == 1 then
+ M._watchfunc = watch.fswatch
+else
+ M._watchfunc = watch.watchdirs
+end
---@type table<integer, table<string, function[]>> client id -> registration id -> cancel function
local cancels = vim.defaulttable()
@@ -163,4 +169,13 @@ function M.unregister(unreg, ctx)
end
end
+--- @param client_id integer
+function M.cancel(client_id)
+ for _, reg_cancels in pairs(cancels[client_id]) do
+ for _, cancel in pairs(reg_cancels) do
+ cancel()
+ end
+ end
+end
+
return M
diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua
index d2e92de083..50121f30b2 100644
--- a/runtime/lua/vim/lsp/buf.lua
+++ b/runtime/lua/vim/lsp/buf.lua
@@ -11,7 +11,7 @@ local M = {}
---
---@param method (string) LSP method name
---@param params (table|nil) Parameters to send to the server
----@param handler (function|nil) See |lsp-handler|. Follows |lsp-handler-resolution|
+---@param handler lsp.Handler? See |lsp-handler|. Follows |lsp-handler-resolution|
---
---@return table<integer, integer> client_request_ids Map of client-id:request-id pairs
---for all successful requests.
@@ -28,16 +28,6 @@ local function request(method, params, handler)
return vim.lsp.buf_request(0, method, params, handler)
end
---- Checks whether the language servers attached to the current buffer are
---- ready.
----
----@return boolean if server responds.
----@deprecated
-function M.server_ready()
- vim.deprecate('vim.lsp.buf.server_ready()', nil, '0.10')
- return not not vim.lsp.buf_notify(0, 'window/progress', {})
-end
-
--- Displays hover information about the symbol under the cursor in a floating
--- window. Calling the function twice will jump into the floating window.
function M.hover()
@@ -57,35 +47,57 @@ local function request_with_options(name, params, options)
request(name, params, req_handler)
end
---- Jumps to the declaration of the symbol under the cursor.
----@note Many servers do not implement this method. Generally, see |vim.lsp.buf.definition()| instead.
+--- @class vim.lsp.ListOpts
+---
+--- list-handler replacing the default handler.
+--- Called for any non-empty result.
+--- This table can be used with |setqflist()| or |setloclist()|. E.g.:
+--- ```lua
+--- local function on_list(options)
+--- vim.fn.setqflist({}, ' ', options)
+--- vim.cmd.cfirst()
+--- end
---
----@param options table|nil additional options
---- - reuse_win: (boolean) Jump to existing window if buffer is already open.
---- - on_list: (function) |lsp-on-list-handler| replacing the default handler.
---- Called for any non-empty result.
+--- vim.lsp.buf.definition({ on_list = on_list })
+--- vim.lsp.buf.references(nil, { on_list = on_list })
+--- ```
+---
+--- If you prefer loclist do something like this:
+--- ```lua
+--- local function on_list(options)
+--- vim.fn.setloclist(0, {}, ' ', options)
+--- vim.cmd.lopen()
+--- end
+--- ```
+--- @field on_list? fun(t: vim.lsp.LocationOpts.OnList)
+
+--- @class vim.lsp.LocationOpts.OnList
+--- @field items table[] Structured like |setqflist-what|
+--- @field title? string Title for the list.
+--- @field context? table `ctx` from |lsp-handler|
+
+--- @class vim.lsp.LocationOpts: vim.lsp.ListOpts
+---
+--- Jump to existing window if buffer is already open.
+--- @field reuse_win? boolean
+
+--- Jumps to the declaration of the symbol under the cursor.
+--- @note Many servers do not implement this method. Generally, see |vim.lsp.buf.definition()| instead.
+--- @param options? vim.lsp.LocationOpts
function M.declaration(options)
local params = util.make_position_params()
request_with_options(ms.textDocument_declaration, params, options)
end
--- Jumps to the definition of the symbol under the cursor.
----
----@param options table|nil additional options
---- - reuse_win: (boolean) Jump to existing window if buffer is already open.
---- - on_list: (function) |lsp-on-list-handler| replacing the default handler.
---- Called for any non-empty result.
+--- @param options? vim.lsp.LocationOpts
function M.definition(options)
local params = util.make_position_params()
request_with_options(ms.textDocument_definition, params, options)
end
--- Jumps to the definition of the type of the symbol under the cursor.
----
----@param options table|nil additional options
---- - reuse_win: (boolean) Jump to existing window if buffer is already open.
---- - on_list: (function) |lsp-on-list-handler| replacing the default handler.
---- Called for any non-empty result.
+--- @param options? vim.lsp.LocationOpts
function M.type_definition(options)
local params = util.make_position_params()
request_with_options(ms.textDocument_typeDefinition, params, options)
@@ -93,10 +105,7 @@ end
--- Lists all the implementations for the symbol under the cursor in the
--- quickfix window.
----
----@param options table|nil additional options
---- - on_list: (function) |lsp-on-list-handler| replacing the default handler.
---- Called for any non-empty result.
+--- @param options? vim.lsp.LocationOpts
function M.implementation(options)
local params = util.make_position_params()
request_with_options(ms.textDocument_implementation, params, options)
@@ -156,45 +165,55 @@ local function range_from_selection(bufnr, mode)
}
end
+--- @class vim.lsp.buf.format.Opts
+--- @inlinedoc
+---
+--- Can be used to specify FormattingOptions. Some unspecified options will be
+--- automatically derived from the current Nvim options.
+--- See https://microsoft.github.io/language-server-protocol/specification/#formattingOptions
+--- @field formatting_options? table
+---
+--- Time in milliseconds to block for formatting requests. No effect if async=true.
+--- (default: `1000`)
+--- @field timeout_ms? integer
+---
+--- Restrict formatting to the clients attached to the given buffer.
+--- (default: current buffer)
+--- @field bufnr? integer
+---
+--- Predicate used to filter clients. Receives a client as argument and must
+--- return a boolean. Clients matching the predicate are included. Example:
+--- ```lua
+--- -- Never request typescript-language-server for formatting
+--- vim.lsp.buf.format {
+--- filter = function(client) return client.name ~= "tsserver" end
+--- }
+--- ```
+--- @field filter? fun(client: vim.lsp.Client): boolean?
+---
+--- If true the method won't block.
+--- Editing the buffer while formatting asynchronous can lead to unexpected
+--- changes.
+--- (Default: false)
+--- @field async? boolean
+---
+--- Restrict formatting to the client with ID (client.id) matching this field.
+--- @field id? integer
+---
+--- Restrict formatting to the client with name (client.name) matching this field.
+--- @field name? string
+---
+--- Range to format.
+--- Table must contain `start` and `end` keys with {row,col} tuples using
+--- (1,0) indexing.
+--- (Default: current selection in visual mode, `nil` in other modes,
+--- formatting the full buffer)
+--- @field range? {start:integer[],end:integer[]}
+
--- Formats a buffer using the attached (and optionally filtered) language
--- server clients.
---
---- @param options table|nil Optional table which holds the following optional fields:
---- - formatting_options (table|nil):
---- Can be used to specify FormattingOptions. Some unspecified options will be
---- automatically derived from the current Nvim options.
---- See https://microsoft.github.io/language-server-protocol/specification/#formattingOptions
---- - timeout_ms (integer|nil, default 1000):
---- Time in milliseconds to block for formatting requests. No effect if async=true
---- - bufnr (number|nil):
---- Restrict formatting to the clients attached to the given buffer, defaults to the current
---- buffer (0).
----
---- - filter (function|nil):
---- Predicate used to filter clients. Receives a client as argument and must return a
---- boolean. Clients matching the predicate are included. Example:
---- ```lua
---- -- Never request typescript-language-server for formatting
---- vim.lsp.buf.format {
---- filter = function(client) return client.name ~= "tsserver" end
---- }
---- ```
----
---- - async boolean|nil
---- If true the method won't block. Defaults to false.
---- Editing the buffer while formatting asynchronous can lead to unexpected
---- changes.
----
---- - id (number|nil):
---- Restrict formatting to the client with ID (client.id) matching this field.
---- - name (string|nil):
---- Restrict formatting to the client with name (client.name) matching this field.
----
---- - range (table|nil) Range to format.
---- Table must contain `start` and `end` keys with {row,col} tuples using
---- (1,0) indexing.
---- Defaults to current selection in visual mode
---- Defaults to `nil` in other modes, formatting the full buffer
+--- @param options? vim.lsp.buf.format.Opts
function M.format(options)
options = options or {}
local bufnr = options.bufnr or api.nvim_get_current_buf()
@@ -219,6 +238,9 @@ function M.format(options)
vim.notify('[LSP] Format request failed, no matching language servers.')
end
+ --- @param client vim.lsp.Client
+ --- @param params lsp.DocumentFormattingParams
+ --- @return lsp.DocumentFormattingParams
local function set_range(client, params)
if range then
local range_params =
@@ -229,8 +251,7 @@ function M.format(options)
end
if options.async then
- local do_format
- do_format = function(idx, client)
+ local function do_format(idx, client)
if not client then
return
end
@@ -256,17 +277,25 @@ function M.format(options)
end
end
+--- @class vim.lsp.buf.rename.Opts
+--- @inlinedoc
+---
+--- Predicate used to filter clients. Receives a client as argument and
+--- must return a boolean. Clients matching the predicate are included.
+--- @field filter? fun(client: vim.lsp.Client): boolean?
+---
+--- Restrict clients used for rename to ones where client.name matches
+--- this field.
+--- @field name? string
+---
+--- (default: current buffer)
+--- @field bufnr? integer
+
--- Renames all references to the symbol under the cursor.
---
---@param new_name string|nil If not provided, the user will be prompted for a new
--- name using |vim.ui.input()|.
----@param options table|nil additional options
---- - filter (function|nil):
---- Predicate used to filter clients. Receives a client as argument and
---- must return a boolean. Clients matching the predicate are included.
---- - name (string|nil):
---- Restrict clients used for rename to ones where client.name matches
---- this field.
+---@param options? vim.lsp.buf.rename.Opts Additional options:
function M.rename(new_name, options)
options = options or {}
local bufnr = options.bufnr or api.nvim_get_current_buf()
@@ -386,8 +415,7 @@ end
---
---@param context (table|nil) Context for the request
---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_references
----@param options table|nil additional options
---- - on_list: (function) handler for list results. See |lsp-on-list-handler|
+---@param options? vim.lsp.ListOpts
function M.references(context, options)
validate({ context = { context, 't', true } })
local params = util.make_position_params()
@@ -398,14 +426,13 @@ function M.references(context, options)
end
--- Lists all symbols in the current buffer in the quickfix window.
----
----@param options table|nil additional options
---- - on_list: (function) handler for list results. See |lsp-on-list-handler|
+--- @param options? vim.lsp.ListOpts
function M.document_symbol(options)
local params = { textDocument = util.make_text_document_params() }
request_with_options(ms.textDocument_documentSymbol, params, options)
end
+--- @param call_hierarchy_items lsp.CallHierarchyItem[]?
local function pick_call_hierarchy_item(call_hierarchy_items)
if not call_hierarchy_items then
return
@@ -425,8 +452,10 @@ local function pick_call_hierarchy_item(call_hierarchy_items)
return choice
end
+--- @param method string
local function call_hierarchy(method)
local params = util.make_position_params()
+ --- @param result lsp.CallHierarchyItem[]?
request(ms.textDocument_prepareCallHierarchy, params, function(err, result, ctx)
if err then
vim.notify(err.message, vim.log.levels.WARN)
@@ -545,9 +574,8 @@ end
--- call, the user is prompted to enter a string on the command line. An empty
--- string means no filtering is done.
---
----@param query string|nil optional
----@param options table|nil additional options
---- - on_list: (function) handler for list results. See |lsp-on-list-handler|
+--- @param query string? optional
+--- @param options? vim.lsp.ListOpts
function M.workspace_symbol(query, options)
query = query or npcall(vim.fn.input, 'Query: ')
if query == nil then
@@ -582,16 +610,36 @@ function M.clear_references()
util.buf_clear_references()
end
+---@nodoc
---@class vim.lsp.CodeActionResultEntry
---@field error? lsp.ResponseError
---@field result? (lsp.Command|lsp.CodeAction)[]
---@field ctx lsp.HandlerContext
----@class vim.lsp.buf.code_action.opts
----@field context? lsp.CodeActionContext
----@field filter? fun(x: lsp.CodeAction|lsp.Command):boolean
----@field apply? boolean
----@field range? {start: integer[], end: integer[]}
+--- @class vim.lsp.buf.code_action.Opts
+--- @inlinedoc
+---
+--- Corresponds to `CodeActionContext` of the LSP specification:
+--- - {diagnostics}? (`table`) LSP `Diagnostic[]`. Inferred from the current
+--- position if not provided.
+--- - {only}? (`table`) List of LSP `CodeActionKind`s used to filter the code actions.
+--- Most language servers support values like `refactor`
+--- or `quickfix`.
+--- - {triggerKind}? (`integer`) The reason why code actions were requested.
+--- @field context? lsp.CodeActionContext
+---
+--- Predicate taking an `CodeAction` and returning a boolean.
+--- @field filter? fun(x: lsp.CodeAction|lsp.Command):boolean
+---
+--- When set to `true`, and there is just one remaining action
+--- (after filtering), the action is applied without user query.
+--- @field apply? boolean
+---
+--- Range for which code actions should be requested.
+--- If in visual mode this defaults to the active selection.
+--- Table must contain `start` and `end` keys with {row,col} tuples
+--- using mark-like indexing. See |api-indexing|
+--- @field range? {start: integer[], end: integer[]}
--- This is not public because the main extension point is
--- vim.ui.select which can be overridden independently.
@@ -602,7 +650,7 @@ end
--- need to be able to link a `CodeAction|Command` to the right client for
--- `codeAction/resolve`
---@param results table<integer, vim.lsp.CodeActionResultEntry>
----@param opts? vim.lsp.buf.code_action.opts
+---@param opts? vim.lsp.buf.code_action.Opts
local function on_code_action_results(results, opts)
---@param a lsp.Command|lsp.CodeAction
local function action_filter(a)
@@ -647,14 +695,15 @@ local function on_code_action_results(results, opts)
end
---@param action lsp.Command|lsp.CodeAction
- ---@param client lsp.Client
+ ---@param client vim.lsp.Client
---@param ctx lsp.HandlerContext
local function apply_action(action, client, ctx)
if action.edit then
util.apply_workspace_edit(action.edit, client.offset_encoding)
end
- if action.command then
- local command = type(action.command) == 'table' and action.command or action
+ local a_cmd = action.command
+ if a_cmd then
+ local command = type(a_cmd) == 'table' and a_cmd or action
client:_exec_cmd(command, ctx)
end
end
@@ -676,7 +725,6 @@ local function on_code_action_results(results, opts)
-- command: string
-- arguments?: any[]
--
- ---@type lsp.Client
local client = assert(vim.lsp.get_client_by_id(choice.ctx.client_id))
local action = choice.action
local bufnr = assert(choice.ctx.bufnr, 'Must have buffer number')
@@ -726,29 +774,7 @@ end
--- Selects a code action available at the current
--- cursor position.
---
----@param options table|nil Optional table which holds the following optional fields:
---- - context: (table|nil)
---- Corresponds to `CodeActionContext` of the LSP specification:
---- - diagnostics (table|nil):
---- LSP `Diagnostic[]`. Inferred from the current
---- position if not provided.
---- - only (table|nil):
---- List of LSP `CodeActionKind`s used to filter the code actions.
---- Most language servers support values like `refactor`
---- or `quickfix`.
---- - triggerKind (number|nil): The reason why code actions were requested.
---- - filter: (function|nil)
---- Predicate taking an `CodeAction` and returning a boolean.
---- - apply: (boolean|nil)
---- When set to `true`, and there is just one remaining action
---- (after filtering), the action is applied without user query.
----
---- - range: (table|nil)
---- Range for which code actions should be requested.
---- If in visual mode this defaults to the active selection.
---- Table must contain `start` and `end` keys with {row,col} tuples
---- using mark-like indexing. See |api-indexing|
----
+---@param options? vim.lsp.buf.code_action.Opts
---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_codeAction
---@see vim.lsp.protocol.CodeActionTriggerKind
function M.code_action(options)
@@ -756,6 +782,7 @@ function M.code_action(options)
options = options or {}
-- Detect old API call code_action(context) which should now be
-- code_action({ context = context} )
+ --- @diagnostic disable-next-line:undefined-field
if options.diagnostics or options.only then
options = { options = options }
end
@@ -814,9 +841,8 @@ function M.code_action(options)
end
--- Executes an LSP server command.
----
----@param command_params table A valid `ExecuteCommandParams` object
----@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_executeCommand
+--- @param command_params lsp.ExecuteCommandParams
+--- @see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_executeCommand
function M.execute_command(command_params)
validate({
command = { command_params.command, 's' },
diff --git a/runtime/lua/vim/lsp/client.lua b/runtime/lua/vim/lsp/client.lua
index c1b41a7183..d48be131f3 100644
--- a/runtime/lua/vim/lsp/client.lua
+++ b/runtime/lua/vim/lsp/client.lua
@@ -6,38 +6,136 @@ local ms = lsp.protocol.Methods
local changetracking = lsp._changetracking
local validate = vim.validate
---- @alias vim.lsp.client.on_init_cb fun(client: lsp.Client, initialize_result: lsp.InitializeResult)
---- @alias vim.lsp.client.on_attach_cb fun(client: lsp.Client, bufnr: integer)
+--- @alias vim.lsp.client.on_init_cb fun(client: vim.lsp.Client, initialize_result: lsp.InitializeResult)
+--- @alias vim.lsp.client.on_attach_cb fun(client: vim.lsp.Client, bufnr: integer)
--- @alias vim.lsp.client.on_exit_cb fun(code: integer, signal: integer, client_id: integer)
---- @alias vim.lsp.client.before_init_cb fun(params: lsp.InitializeParams, config: lsp.ClientConfig)
+--- @alias vim.lsp.client.before_init_cb fun(params: lsp.InitializeParams, config: vim.lsp.ClientConfig)
---- @class lsp.ClientConfig
+--- @class vim.lsp.Client.Flags
+--- @inlinedoc
+---
+--- Allow using incremental sync for buffer edits
+--- (default: `true`)
+--- @field allow_incremental_sync? boolean
+---
+--- Debounce `didChange` notifications to the server by the given number in milliseconds.
+--- No debounce occurs if `nil`.
+--- (default: `150`)
+--- @field debounce_text_changes integer
+---
+--- Milliseconds to wait for server to exit cleanly after sending the
+--- "shutdown" request before sending kill -15. If set to false, nvim exits
+--- immediately after sending the "shutdown" request to the server.
+--- (default: `false`)
+--- @field exit_timeout integer|false
+
+--- @class vim.lsp.ClientConfig
+--- command string[] that launches the language
+--- server (treated as in |jobstart()|, must be absolute or on `$PATH`, shell constructs like
+--- "~" are not expanded), or function that creates an RPC client. Function receives
+--- a `dispatchers` table and returns a table with member functions `request`, `notify`,
+--- `is_closing` and `terminate`.
+--- See |vim.lsp.rpc.request()|, |vim.lsp.rpc.notify()|.
+--- For TCP there is a builtin RPC client factory: |vim.lsp.rpc.connect()|
--- @field cmd string[]|fun(dispatchers: vim.lsp.rpc.Dispatchers): vim.lsp.rpc.PublicClient?
+---
+--- Directory to launch the `cmd` process. Not related to `root_dir`.
+--- (default: cwd)
--- @field cmd_cwd? string
+---
+--- Environment flags to pass to the LSP on spawn.
+--- Must be specified using a table.
+--- Non-string values are coerced to string.
+--- Example:
+--- ```lua
+--- { PORT = 8080; HOST = "0.0.0.0"; }
+--- ```
--- @field cmd_env? table
+---
+--- Daemonize the server process so that it runs in a separate process group from Nvim.
+--- Nvim will shutdown the process on exit, but if Nvim fails to exit cleanly this could leave
+--- behind orphaned server processes.
+--- (default: true)
--- @field detached? boolean
+---
+--- List of workspace folders passed to the language server.
+--- For backwards compatibility rootUri and rootPath will be derived from the first workspace
+--- folder in this list. See `workspaceFolders` in the LSP spec.
--- @field workspace_folders? lsp.WorkspaceFolder[]
+---
+--- Map overriding the default capabilities defined by |vim.lsp.protocol.make_client_capabilities()|,
+--- passed to the language server on initialization. Hint: use make_client_capabilities() and modify
+--- its result.
+--- - Note: To send an empty dictionary use |vim.empty_dict()|, else it will be encoded as an
+--- array.
--- @field capabilities? lsp.ClientCapabilities
+---
+--- Map of language server method names to |lsp-handler|
--- @field handlers? table<string,function>
+---
+--- Map with language server specific settings.
+--- See the {settings} in |vim.lsp.Client|.
--- @field settings? table
+---
+--- Table that maps string of clientside commands to user-defined functions.
+--- Commands passed to start_client take precedence over the global command registry. Each key
+--- must be a unique command name, and the value is a function which is called if any LSP action
+--- (code action, code lenses, ...) triggers the command.
--- @field commands? table<string,fun(command: lsp.Command, ctx: table)>
+---
+--- Values to pass in the initialization request as `initializationOptions`. See `initialize` in
+--- the LSP spec.
--- @field init_options? table
+---
+--- Name in log messages.
+--- (default: client-id)
--- @field name? string
+---
+--- Language ID as string. Defaults to the filetype.
--- @field get_language_id? fun(bufnr: integer, filetype: string): string
---- @field offset_encoding? string
+---
+--- The encoding that the LSP server expects. Client does not verify this is correct.
+--- @field offset_encoding? 'utf-8'|'utf-16'|'utf-32'
+---
+--- Callback invoked when the client operation throws an error. `code` is a number describing the error.
+--- Other arguments may be passed depending on the error kind. See `vim.lsp.rpc.client_errors`
+--- for possible errors. Use `vim.lsp.rpc.client_errors[code]` to get human-friendly name.
--- @field on_error? fun(code: integer, err: string)
---- @field before_init? vim.lsp.client.before_init_cb
---- @field on_init? elem_or_list<vim.lsp.client.on_init_cb>
---- @field on_exit? elem_or_list<vim.lsp.client.on_exit_cb>
---- @field on_attach? elem_or_list<vim.lsp.client.on_attach_cb>
+---
+--- Callback invoked before the LSP "initialize" phase, where `params` contains the parameters
+--- being sent to the server and `config` is the config that was passed to |vim.lsp.start_client()|.
+--- You can use this to modify parameters before they are sent.
+--- @field before_init? fun(params: lsp.InitializeParams, config: vim.lsp.ClientConfig)
+---
+--- Callback invoked after LSP "initialize", where `result` is a table of `capabilities`
+--- and anything else the server may send. For example, clangd sends
+--- `initialize_result.offsetEncoding` if `capabilities.offsetEncoding` was sent to it.
+--- You can only modify the `client.offset_encoding` here before any notifications are sent.
+--- @field on_init? elem_or_list<fun(client: vim.lsp.Client, initialize_result: lsp.InitializeResult)>
+---
+--- Callback invoked on client exit.
+--- - code: exit code of the process
+--- - signal: number describing the signal used to terminate (if any)
+--- - client_id: client handle
+--- @field on_exit? elem_or_list<fun(code: integer, signal: integer, client_id: integer)>
+---
+--- Callback invoked when client attaches to a buffer.
+--- @field on_attach? elem_or_list<fun(client: vim.lsp.Client, bufnr: integer)>
+---
+--- Passed directly to the language server in the initialize request. Invalid/empty values will
+--- (default: "off")
--- @field trace? 'off'|'messages'|'verbose'
---- @field flags? table
+---
+--- A table with flags for the client. The current (experimental) flags are:
+--- @field flags? vim.lsp.Client.Flags
+---
+--- Directory where the LSP server will base its workspaceFolders, rootUri, and rootPath on initialization.
--- @field root_dir? string
---- @class lsp.Client.Progress: vim.Ringbuf<{token: integer|string, value: any}>
+--- @class vim.lsp.Client.Progress: vim.Ringbuf<{token: integer|string, value: any}>
--- @field pending table<lsp.ProgressToken,lsp.LSPAny>
---- @class lsp.Client
+--- @class vim.lsp.Client
---
--- The id allocated to the client.
--- @field id integer
@@ -58,7 +156,7 @@ local validate = vim.validate
--- @field handlers table<string,lsp.Handler>
---
--- The current pending requests in flight to the server. Entries are key-value
---- pairs with the key being the request ID while the value is a table with
+--- pairs with the key being the request id while the value is a table with
--- `type`, `bufnr`, and `method` key-value pairs. `type` is either "pending"
--- for an active request, or "cancel" for a cancel request. It will be
--- "complete" ephemerally while executing |LspRequest| autocmds when replies
@@ -67,15 +165,15 @@ local validate = vim.validate
---
--- copy of the table that was passed by the user
--- to |vim.lsp.start_client()|.
---- @field config lsp.ClientConfig
+--- @field config vim.lsp.ClientConfig
---
---- Response from the server sent on
---- initialize` describing the server's capabilities.
+--- Response from the server sent on `initialize` describing the server's
+--- capabilities.
--- @field server_capabilities lsp.ServerCapabilities?
---
--- A ring buffer (|vim.ringbuf()|) containing progress messages
--- sent by the server.
---- @field progress lsp.Client.Progress
+--- @field progress vim.lsp.Client.Progress
---
--- @field initialized true?
---
@@ -102,8 +200,14 @@ local validate = vim.validate
--- Client commands take precedence over the global command registry.
--- @field commands table<string,fun(command: lsp.Command, ctx: table)>
---
+--- Map with language server specific settings. These are returned to the
+--- language server if requested via `workspace/configuration`. Keys are
+--- case-sensitive.
--- @field settings table
---- @field flags table
+---
+--- A table with flags for the client. The current (experimental) flags are:
+--- @field flags vim.lsp.Client.Flags
+---
--- @field get_language_id fun(bufnr: integer, filetype: string): string
---
--- The capabilities provided by the client (editor or tool)
@@ -113,10 +217,11 @@ local validate = vim.validate
--- Sends a request to the server.
--- This is a thin wrapper around {client.rpc.request} with some additional
--- checking.
---- If {handler} is not specified, If one is not found there, then an error
---- will occur. Returns: {status}, {[client_id]}. {status} is a boolean
---- indicating if the notification was successful. If it is `false`, then it
---- will always be `false` (the client has shutdown).
+--- If {handler} is not specified and if there's no respective global
+--- handler, then an error will occur.
+--- Returns: {status}, {client_id}?. {status} is a boolean indicating if
+--- the notification was successful. If it is `false`, then it will always
+--- be `false` (the client has shutdown).
--- If {status} is `true`, the function returns {request_id} as the second
--- result. You can use this with `client.cancel_request(request_id)` to cancel
--- the request.
@@ -157,7 +262,7 @@ local validate = vim.validate
---
--- Checks if a client supports a given method.
--- Always returns true for unknown off-spec methods.
---- [opts] is a optional `{bufnr?: integer}` table.
+--- {opts} is a optional `{bufnr?: integer}` table.
--- Some language server capabilities can be file specific.
--- @field supports_method fun(method: string, opts?: {bufnr: integer?}): boolean
---
@@ -239,7 +344,7 @@ local function default_get_language_id(_bufnr, filetype)
end
--- Validates a client configuration as given to |vim.lsp.start_client()|.
---- @param config lsp.ClientConfig
+--- @param config vim.lsp.ClientConfig
local function validate_config(config)
validate({
config = { config, 't' },
@@ -285,7 +390,7 @@ local function get_trace(trace)
end
--- @param id integer
---- @param config lsp.ClientConfig
+--- @param config vim.lsp.ClientConfig
--- @return string
local function get_name(id, config)
local name = config.name
@@ -328,8 +433,8 @@ local function ensure_list(x)
end
--- @package
---- @param config lsp.ClientConfig
---- @return lsp.Client?
+--- @param config vim.lsp.ClientConfig
+--- @return vim.lsp.Client?
function Client.create(config)
validate_config(config)
@@ -337,7 +442,7 @@ function Client.create(config)
local id = client_index
local name = get_name(id, config)
- --- @class lsp.Client
+ --- @class vim.lsp.Client
local self = {
id = id,
config = config,
@@ -370,7 +475,7 @@ function Client.create(config)
--- - lsp.WorkDoneProgressBegin,
--- - lsp.WorkDoneProgressReport (extended with title from Begin)
--- - lsp.WorkDoneProgressEnd (extended with title from Begin)
- progress = vim.ringbuf(50) --[[@as lsp.Client.Progress]],
+ progress = vim.ringbuf(50) --[[@as vim.lsp.Client.Progress]],
--- @deprecated use client.progress instead
messages = { name = name, messages = {}, progress = {}, status = {} },
@@ -421,6 +526,7 @@ function Client.create(config)
return self
end
+--- @private
--- @param cbs function[]
--- @param error_id integer
--- @param ... any
@@ -603,8 +709,16 @@ local wait_result_reason = { [-1] = 'timeout', [-2] = 'interrupted', [-3] = 'err
---
--- @param ... string List to write to the buffer
local function err_message(...)
- api.nvim_err_writeln(table.concat(vim.tbl_flatten({ ... })))
- api.nvim_command('redraw')
+ local message = table.concat(vim.tbl_flatten({ ... }))
+ if vim.in_fast_event() then
+ vim.schedule(function()
+ api.nvim_err_writeln(message)
+ api.nvim_command('redraw')
+ end)
+ else
+ api.nvim_err_writeln(message)
+ api.nvim_command('redraw')
+ end
end
--- @private
@@ -698,7 +812,7 @@ function Client:_cancel_request(id)
return self.rpc.notify(ms.dollar_cancelRequest, { id = id })
end
---- @nodoc
+--- @private
--- Stops a client, optionally with force.
---
--- By default, it will just ask the - server to shutdown without force. If
@@ -727,6 +841,7 @@ function Client:_stop(force)
rpc.terminate()
self._graceful_shutdown_failed = true
end
+ vim.lsp._watchfiles.cancel(self.id)
end)
end
@@ -853,6 +968,7 @@ function Client:write_error(code, err)
err_message(self._log_prefix, ': Error ', client_error, ': ', vim.inspect(err))
end
+--- @private
--- @param method string
--- @param opts? {bufnr: integer?}
function Client:_supports_method(method, opts)
diff --git a/runtime/lua/vim/lsp/codelens.lua b/runtime/lua/vim/lsp/codelens.lua
index 7aed6f99e3..48c096c0c1 100644
--- a/runtime/lua/vim/lsp/codelens.lua
+++ b/runtime/lua/vim/lsp/codelens.lua
@@ -279,7 +279,8 @@ function M.on_codelens(err, result, ctx, _)
end)
end
---- @class vim.lsp.codelens.RefreshOptions
+--- @class vim.lsp.codelens.refresh.Opts
+--- @inlinedoc
--- @field bufnr integer? filter by buffer. All buffers if nil, 0 for current buffer
--- Refresh the lenses.
@@ -292,8 +293,7 @@ end
--- autocmd BufEnter,CursorHold,InsertLeave <buffer> lua vim.lsp.codelens.refresh({ bufnr = 0 })
--- ```
---
---- @param opts? vim.lsp.codelens.RefreshOptions Table with the following fields:
---- - `bufnr` (integer|nil): filter by buffer. All buffers if nil, 0 for current buffer
+--- @param opts? vim.lsp.codelens.refresh.Opts Optional fields
function M.refresh(opts)
opts = opts or {}
local bufnr = opts.bufnr and resolve_bufnr(opts.bufnr)
diff --git a/runtime/lua/vim/lsp/diagnostic.lua b/runtime/lua/vim/lsp/diagnostic.lua
index 1fa67fc473..08cea13548 100644
--- a/runtime/lua/vim/lsp/diagnostic.lua
+++ b/runtime/lua/vim/lsp/diagnostic.lua
@@ -136,7 +136,7 @@ end
--- @param diagnostic vim.Diagnostic
--- @return lsp.DiagnosticTag[]?
-local function tags_vim_to_vim(diagnostic)
+local function tags_vim_to_lsp(diagnostic)
if not diagnostic._tags then
return
end
@@ -173,7 +173,7 @@ local function diagnostic_vim_to_lsp(diagnostics)
message = diagnostic.message,
source = diagnostic.source,
code = diagnostic.code,
- tags = tags_vim_to_vim(diagnostics),
+ tags = tags_vim_to_lsp(diagnostic),
}, diagnostic.user_data and (diagnostic.user_data.lsp or {}) or {})
end, diagnostics)
end
@@ -321,7 +321,7 @@ end
---@param _ lsp.ResponseError?
---@param result lsp.DocumentDiagnosticReport
---@param ctx lsp.HandlerContext
----@param config table Configuration table (see |vim.diagnostic.config()|).
+---@param config vim.diagnostic.Opts Configuration table (see |vim.diagnostic.config()|).
function M.on_diagnostic(_, result, ctx, config)
if result == nil or result.kind == 'unchanged' then
return
@@ -390,7 +390,7 @@ local function clear(bufnr)
end
end
----@class lsp.diagnostic.bufstate
+---@class (private) lsp.diagnostic.bufstate
---@field enabled boolean Whether inlay hints are enabled for this buffer
---@type table<integer, lsp.diagnostic.bufstate>
local bufstates = {}
diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua
index 781d720486..daf4fec8d2 100644
--- a/runtime/lua/vim/lsp/handlers.lua
+++ b/runtime/lua/vim/lsp/handlers.lua
@@ -16,12 +16,12 @@ local function err_message(...)
api.nvim_command('redraw')
end
---see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_executeCommand
+--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_executeCommand
M[ms.workspace_executeCommand] = function(_, _, _, _)
-- Error handling is done implicitly by wrapping all handlers; see end of this file
end
---see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#progress
+--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#progress
---@param result lsp.ProgressParams
---@param ctx lsp.HandlerContext
M[ms.dollar_progress] = function(_, result, ctx)
@@ -57,7 +57,7 @@ M[ms.dollar_progress] = function(_, result, ctx)
})
end
---see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window_workDoneProgress_create
+--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window_workDoneProgress_create
---@param result lsp.WorkDoneProgressCreateParams
---@param ctx lsp.HandlerContext
M[ms.window_workDoneProgress_create] = function(_, result, ctx)
@@ -70,7 +70,7 @@ M[ms.window_workDoneProgress_create] = function(_, result, ctx)
return vim.NIL
end
---see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window_showMessageRequest
+--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window_showMessageRequest
---@param result lsp.ShowMessageRequestParams
M[ms.window_showMessageRequest] = function(_, result)
local actions = result.actions or {}
@@ -106,7 +106,8 @@ M[ms.window_showMessageRequest] = function(_, result)
end
end
---see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#client_registerCapability
+--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#client_registerCapability
+--- @param result lsp.RegistrationParams
M[ms.client_registerCapability] = function(_, result, ctx)
local client_id = ctx.client_id
local client = assert(vim.lsp.get_client_by_id(client_id))
@@ -136,7 +137,8 @@ M[ms.client_registerCapability] = function(_, result, ctx)
return vim.NIL
end
---see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#client_unregisterCapability
+--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#client_unregisterCapability
+--- @param result lsp.UnregistrationParams
M[ms.client_unregisterCapability] = function(_, result, ctx)
local client_id = ctx.client_id
local client = assert(vim.lsp.get_client_by_id(client_id))
@@ -150,7 +152,7 @@ M[ms.client_unregisterCapability] = function(_, result, ctx)
return vim.NIL
end
---see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_applyEdit
+--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_applyEdit
M[ms.workspace_applyEdit] = function(_, workspace_edit, ctx)
assert(
workspace_edit,
@@ -178,7 +180,8 @@ local function lookup_section(table, section)
return vim.tbl_get(table, unpack(keys))
end
---see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_configuration
+--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_configuration
+--- @param result lsp.ConfigurationParams
M[ms.workspace_configuration] = function(_, result, ctx)
local client_id = ctx.client_id
local client = vim.lsp.get_client_by_id(client_id)
@@ -211,7 +214,7 @@ M[ms.workspace_configuration] = function(_, result, ctx)
return response
end
---see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_workspaceFolders
+--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_workspaceFolders
M[ms.workspace_workspaceFolders] = function(_, _, ctx)
local client_id = ctx.client_id
local client = vim.lsp.get_client_by_id(client_id)
@@ -238,7 +241,7 @@ M[ms.textDocument_inlayHint] = function(...)
return vim.lsp.inlay_hint.on_inlayhint(...)
end
---see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_references
+--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_references
M[ms.textDocument_references] = function(_, result, ctx, config)
if not result or vim.tbl_isempty(result) then
vim.notify('No references found')
@@ -296,7 +299,7 @@ local function response_to_list(map_result, entity, title_fn)
end
end
---see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_documentSymbol
+--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_documentSymbol
M[ms.textDocument_documentSymbol] = response_to_list(
util.symbols_to_items,
'document symbols',
@@ -306,12 +309,12 @@ M[ms.textDocument_documentSymbol] = response_to_list(
end
)
---see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_symbol
+--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_symbol
M[ms.workspace_symbol] = response_to_list(util.symbols_to_items, 'symbols', function(ctx)
return string.format("Symbols matching '%s'", ctx.params.query)
end)
---see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_rename
+--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_rename
M[ms.textDocument_rename] = function(_, result, ctx, _)
if not result then
vim.notify("Language server couldn't provide rename result", vim.log.levels.INFO)
@@ -321,7 +324,7 @@ M[ms.textDocument_rename] = function(_, result, ctx, _)
util.apply_workspace_edit(result, client.offset_encoding)
end
---see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_rangeFormatting
+--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_rangeFormatting
M[ms.textDocument_rangeFormatting] = function(_, result, ctx, _)
if not result then
return
@@ -330,7 +333,7 @@ M[ms.textDocument_rangeFormatting] = function(_, result, ctx, _)
util.apply_text_edits(result, ctx.bufnr, client.offset_encoding)
end
---see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_formatting
+--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_formatting
M[ms.textDocument_formatting] = function(_, result, ctx, _)
if not result then
return
@@ -339,7 +342,7 @@ M[ms.textDocument_formatting] = function(_, result, ctx, _)
util.apply_text_edits(result, ctx.bufnr, client.offset_encoding)
end
---see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion
+--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion
M[ms.textDocument_completion] = function(_, result, _, _)
if vim.tbl_isempty(result or {}) then
return
@@ -405,13 +408,14 @@ function M.hover(_, result, ctx, config)
return util.open_floating_preview(contents, format, config)
end
---see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_hover
+--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_hover
M[ms.textDocument_hover] = M.hover
--- Jumps to a location. Used as a handler for multiple LSP methods.
---@param _ nil not used
---@param result (table) result of LSP method; a location or a list of locations.
---@param ctx (lsp.HandlerContext) table containing the context of the request, including the method
+---@param config? vim.lsp.LocationOpts
---(`textDocument/definition` can return `Location` or `Location[]`
local function location_handler(_, result, ctx, config)
if result == nil or vim.tbl_isempty(result) then
@@ -444,13 +448,13 @@ local function location_handler(_, result, ctx, config)
api.nvim_command('botright copen')
end
---see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_declaration
+--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_declaration
M[ms.textDocument_declaration] = location_handler
---see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_definition
+--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_definition
M[ms.textDocument_definition] = location_handler
---see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_typeDefinition
+--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_typeDefinition
M[ms.textDocument_typeDefinition] = location_handler
---see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_implementation
+--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_implementation
M[ms.textDocument_implementation] = location_handler
--- |lsp-handler| for the method "textDocument/signatureHelp".
@@ -508,10 +512,10 @@ function M.signature_help(_, result, ctx, config)
return fbuf, fwin
end
---see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_signatureHelp
+--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_signatureHelp
M[ms.textDocument_signatureHelp] = M.signature_help
---see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_documentHighlight
+--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_documentHighlight
M[ms.textDocument_documentHighlight] = function(_, result, ctx, _)
if not result then
return
@@ -524,21 +528,22 @@ M[ms.textDocument_documentHighlight] = function(_, result, ctx, _)
util.buf_highlight_references(ctx.bufnr, result, client.offset_encoding)
end
----@private
+--- @private
---
--- Displays call hierarchy in the quickfix window.
---
----@param direction 'from'|'to' `"from"` for incoming calls and `"to"` for outgoing calls
----@return function
---- `CallHierarchyIncomingCall[]` if {direction} is `"from"`,
---- `CallHierarchyOutgoingCall[]` if {direction} is `"to"`,
-local make_call_hierarchy_handler = function(direction)
+--- @param direction 'from'|'to' `"from"` for incoming calls and `"to"` for outgoing calls
+--- @overload fun(direction:'from'): fun(_, result: lsp.CallHierarchyIncomingCall[]?)
+--- @overload fun(direction:'to'): fun(_, result: lsp.CallHierarchyOutgoingCall[]?)
+local function make_call_hierarchy_handler(direction)
+ --- @param result lsp.CallHierarchyIncomingCall[]|lsp.CallHierarchyOutgoingCall[]
return function(_, result)
if not result then
return
end
local items = {}
for _, call_hierarchy_call in pairs(result) do
+ --- @type lsp.CallHierarchyItem
local call_hierarchy_item = call_hierarchy_call[direction]
for _, range in pairs(call_hierarchy_call.fromRanges) do
table.insert(items, {
@@ -554,13 +559,14 @@ local make_call_hierarchy_handler = function(direction)
end
end
---see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#callHierarchy_incomingCalls
+--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#callHierarchy_incomingCalls
M[ms.callHierarchy_incomingCalls] = make_call_hierarchy_handler('from')
---see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#callHierarchy_outgoingCalls
+--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#callHierarchy_outgoingCalls
M[ms.callHierarchy_outgoingCalls] = make_call_hierarchy_handler('to')
---see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window_logMessage
+--- @see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window_logMessage
+--- @param result lsp.LogMessageParams
M[ms.window_logMessage] = function(_, result, ctx, _)
local message_type = result.type
local message = result.message
@@ -582,7 +588,8 @@ M[ms.window_logMessage] = function(_, result, ctx, _)
return result
end
---see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window_showMessage
+--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window_showMessage
+--- @param result lsp.ShowMessageParams
M[ms.window_showMessage] = function(_, result, ctx, _)
local message_type = result.type
local message = result.message
@@ -601,7 +608,8 @@ M[ms.window_showMessage] = function(_, result, ctx, _)
return result
end
---see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window_showDocument
+--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window_showDocument
+--- @param result lsp.ShowDocumentParams
M[ms.window_showDocument] = function(_, result, ctx, _)
local uri = result.uri
diff --git a/runtime/lua/vim/lsp/health.lua b/runtime/lua/vim/lsp/health.lua
index 15e4555b55..797a1097f9 100644
--- a/runtime/lua/vim/lsp/health.lua
+++ b/runtime/lua/vim/lsp/health.lua
@@ -1,10 +1,9 @@
local M = {}
---- Performs a healthcheck for LSP
-function M.check()
- local report_info = vim.health.info
- local report_warn = vim.health.warn
+local report_info = vim.health.info
+local report_warn = vim.health.warn
+local function check_log()
local log = vim.lsp.log
local current_log_level = log.get_level()
local log_level_string = log.levels[current_log_level] ---@type string
@@ -27,9 +26,11 @@ function M.check()
local report_fn = (log_size / 1000000 > 100 and report_warn or report_info)
report_fn(string.format('Log size: %d KB', log_size / 1000))
+end
- local clients = vim.lsp.get_clients()
+local function check_active_clients()
vim.health.start('vim.lsp: Active Clients')
+ local clients = vim.lsp.get_clients()
if next(clients) then
for _, client in pairs(clients) do
local attached_to = table.concat(vim.tbl_keys(client.attached_buffers or {}), ',')
@@ -48,4 +49,33 @@ function M.check()
end
end
+local function check_watcher()
+ vim.health.start('vim.lsp: File watcher')
+ local watchfunc = vim.lsp._watchfiles._watchfunc
+ assert(watchfunc)
+ local watchfunc_name --- @type string
+ if watchfunc == vim._watch.watch then
+ watchfunc_name = 'libuv-watch'
+ elseif watchfunc == vim._watch.watchdirs then
+ watchfunc_name = 'libuv-watchdirs'
+ elseif watchfunc == vim._watch.fswatch then
+ watchfunc_name = 'fswatch'
+ else
+ local nm = debug.getinfo(watchfunc, 'S').source
+ watchfunc_name = string.format('Custom (%s)', nm)
+ end
+
+ report_info('File watch backend: ' .. watchfunc_name)
+ if watchfunc_name == 'libuv-watchdirs' then
+ report_warn('libuv-watchdirs has known performance issues. Consider installing fswatch.')
+ end
+end
+
+--- Performs a healthcheck for LSP
+function M.check()
+ check_log()
+ check_active_clients()
+ check_watcher()
+end
+
return M
diff --git a/runtime/lua/vim/lsp/inlay_hint.lua b/runtime/lua/vim/lsp/inlay_hint.lua
index 49dc35fdf6..ec676ea97f 100644
--- a/runtime/lua/vim/lsp/inlay_hint.lua
+++ b/runtime/lua/vim/lsp/inlay_hint.lua
@@ -4,12 +4,12 @@ local ms = require('vim.lsp.protocol').Methods
local api = vim.api
local M = {}
----@class lsp.inlay_hint.bufstate
+---@class (private) vim.lsp.inlay_hint.bufstate
---@field version? integer
---@field client_hints? table<integer, table<integer, lsp.InlayHint[]>> client_id -> (lnum -> hints)
---@field applied table<integer, integer> Last version of hints applied to this line
---@field enabled boolean Whether inlay hints are enabled for this buffer
----@type table<integer, lsp.inlay_hint.bufstate>
+---@type table<integer, vim.lsp.inlay_hint.bufstate>
local bufstates = {}
local namespace = api.nvim_create_namespace('vim_lsp_inlayhint')
@@ -103,11 +103,14 @@ function M.on_refresh(err, _, ctx, _)
return vim.NIL
end
---- @class vim.lsp.inlay_hint.get.filter
+--- Optional filters |kwargs|:
+--- @class vim.lsp.inlay_hint.get.Filter
+--- @inlinedoc
--- @field bufnr integer?
--- @field range lsp.Range?
----
+
--- @class vim.lsp.inlay_hint.get.ret
+--- @inlinedoc
--- @field bufnr integer
--- @field client_id integer
--- @field inlay_hint lsp.InlayHint
@@ -130,17 +133,8 @@ end
--- })
--- ```
---
---- @param filter vim.lsp.inlay_hint.get.filter?
---- Optional filters |kwargs|:
---- - bufnr (integer?): 0 for current buffer
---- - range (lsp.Range?)
----
+--- @param filter vim.lsp.inlay_hint.get.Filter?
--- @return vim.lsp.inlay_hint.get.ret[]
---- Each list item is a table with the following fields:
---- - bufnr (integer)
---- - client_id (integer)
---- - inlay_hint (lsp.InlayHint)
----
--- @since 12
function M.get(filter)
vim.validate({ filter = { filter, 'table', true } })
@@ -241,7 +235,7 @@ end
--- Refresh inlay hints, only if we have attached clients that support it
---@param bufnr (integer) Buffer handle, or 0 for current
----@param opts? lsp.util.RefreshOptions Additional options to pass to util._refresh
+---@param opts? vim.lsp.util._refresh.Opts Additional options to pass to util._refresh
---@private
local function _refresh(bufnr, opts)
opts = opts or {}
diff --git a/runtime/lua/vim/lsp/log.lua b/runtime/lua/vim/lsp/log.lua
index 018003bb81..9f2bd71158 100644
--- a/runtime/lua/vim/lsp/log.lua
+++ b/runtime/lua/vim/lsp/log.lua
@@ -2,16 +2,19 @@
local log = {}
+local log_levels = vim.log.levels
+
--- Log level dictionary with reverse lookup as well.
---
--- Can be used to lookup the number from the name or the name from the number.
--- Levels by name: "TRACE", "DEBUG", "INFO", "WARN", "ERROR", "OFF"
--- Level numbers begin with "TRACE" at 0
+--- @type table<string|integer, string|integer>
--- @nodoc
-log.levels = vim.deepcopy(vim.log.levels)
+log.levels = vim.deepcopy(log_levels)
-- Default log level is warn.
-local current_log_level = log.levels.WARN
+local current_log_level = log_levels.WARN
local log_date_format = '%F %H:%M:%S'
@@ -58,7 +61,7 @@ local function open_logfile()
logfile, openerr = io.open(logfilename, 'a+')
if not logfile then
local err_msg = string.format('Failed to open LSP client log file: %s', openerr)
- notify(err_msg, vim.log.levels.ERROR)
+ notify(err_msg, log_levels.ERROR)
return false
end
@@ -77,12 +80,13 @@ local function open_logfile()
return true
end
-for level, levelnr in pairs(log.levels) do
+for level, levelnr in pairs(log_levels) do
-- Also export the log level on the root object.
log[level] = levelnr
-end
-vim.tbl_add_reverse_lookup(log.levels)
+ -- Add a reverse lookup.
+ log.levels[levelnr] = level
+end
--- @param level string
--- @param levelnr integer
@@ -123,19 +127,19 @@ end
-- log at that level (if applicable, it is checked either way).
--- @nodoc
-log.debug = create_logger('DEBUG', vim.log.levels.DEBUG)
+log.debug = create_logger('DEBUG', log_levels.DEBUG)
--- @nodoc
-log.error = create_logger('ERROR', vim.log.levels.ERROR)
+log.error = create_logger('ERROR', log_levels.ERROR)
--- @nodoc
-log.info = create_logger('INFO', vim.log.levels.INFO)
+log.info = create_logger('INFO', log_levels.INFO)
--- @nodoc
-log.trace = create_logger('TRACE', vim.log.levels.TRACE)
+log.trace = create_logger('TRACE', log_levels.TRACE)
--- @nodoc
-log.warn = create_logger('WARN', vim.log.levels.WARN)
+log.warn = create_logger('WARN', log_levels.WARN)
--- Sets the current log level.
---@param level (string|integer) One of `vim.lsp.log.levels`
diff --git a/runtime/lua/vim/lsp/protocol.lua b/runtime/lua/vim/lsp/protocol.lua
index fa614780c2..599f02425e 100644
--- a/runtime/lua/vim/lsp/protocol.lua
+++ b/runtime/lua/vim/lsp/protocol.lua
@@ -1,14 +1,19 @@
--- @diagnostic disable: duplicate-doc-alias
-local function get_value_set(t)
- return vim.iter.filter(function(i)
- return type(i) == 'number'
- end, ipairs(t))
+---@param tbl table<string, string|number>
+local function get_value_set(tbl)
+ local value_set = {}
+ for _, v in pairs(tbl) do
+ table.insert(value_set, v)
+ end
+ table.sort(value_set)
+ return value_set
end
-- Protocol for the Microsoft Language Server Protocol (mslsp)
+local protocol = {}
-local protocol = {
+local constants = {
--- @enum lsp.DiagnosticSeverity
DiagnosticSeverity = {
-- Reports an error.
@@ -301,11 +306,13 @@ local protocol = {
},
}
--- TODO(mariasolos): Remove this reverse lookup.
-for k, v in pairs(protocol) do
- local tbl = vim.deepcopy(v, true)
- vim.tbl_add_reverse_lookup(tbl)
- protocol[k] = tbl
+for k1, v1 in pairs(constants) do
+ local tbl = vim.deepcopy(v1, true)
+ for _, k2 in ipairs(vim.tbl_keys(tbl)) do
+ local v2 = tbl[k2]
+ tbl[v2] = k2
+ end
+ protocol[k1] = tbl
end
--[=[
@@ -711,14 +718,7 @@ function protocol.make_client_capabilities()
codeActionLiteralSupport = {
codeActionKind = {
- valueSet = (function()
- local res = vim.iter.filter(function(value)
- -- Filter out the keys that were added by the reverse lookup.
- return value:match('^%l')
- end, vim.tbl_values(protocol.CodeActionKind))
- table.sort(res)
- return res
- end)(),
+ valueSet = get_value_set(constants.CodeActionKind),
},
},
isPreferredSupport = true,
@@ -743,10 +743,10 @@ function protocol.make_client_capabilities()
commitCharactersSupport = false,
preselectSupport = false,
deprecatedSupport = false,
- documentationFormat = { protocol.MarkupKind.Markdown, protocol.MarkupKind.PlainText },
+ documentationFormat = { constants.MarkupKind.Markdown, constants.MarkupKind.PlainText },
},
completionItemKind = {
- valueSet = get_value_set(protocol.CompletionItemKind),
+ valueSet = get_value_set(constants.CompletionItemKind),
},
completionList = {
itemDefaults = {
@@ -775,13 +775,13 @@ function protocol.make_client_capabilities()
},
hover = {
dynamicRegistration = true,
- contentFormat = { protocol.MarkupKind.Markdown, protocol.MarkupKind.PlainText },
+ contentFormat = { constants.MarkupKind.Markdown, constants.MarkupKind.PlainText },
},
signatureHelp = {
dynamicRegistration = false,
signatureInformation = {
activeParameterSupport = true,
- documentationFormat = { protocol.MarkupKind.Markdown, protocol.MarkupKind.PlainText },
+ documentationFormat = { constants.MarkupKind.Markdown, constants.MarkupKind.PlainText },
parameterInformation = {
labelOffsetSupport = true,
},
@@ -796,7 +796,7 @@ function protocol.make_client_capabilities()
documentSymbol = {
dynamicRegistration = false,
symbolKind = {
- valueSet = get_value_set(protocol.SymbolKind),
+ valueSet = get_value_set(constants.SymbolKind),
},
hierarchicalDocumentSymbolSupport = true,
},
@@ -807,7 +807,7 @@ function protocol.make_client_capabilities()
publishDiagnostics = {
relatedInformation = true,
tagSupport = {
- valueSet = get_value_set(protocol.DiagnosticTag),
+ valueSet = get_value_set(constants.DiagnosticTag),
},
dataSupport = true,
},
@@ -819,7 +819,7 @@ function protocol.make_client_capabilities()
symbol = {
dynamicRegistration = false,
symbolKind = {
- valueSet = get_value_set(protocol.SymbolKind),
+ valueSet = get_value_set(constants.SymbolKind),
},
},
configuration = true,
@@ -859,9 +859,9 @@ end
--- Creates a normalized object describing LSP server capabilities.
---@param server_capabilities table Table of capabilities supported by the server
----@return lsp.ServerCapabilities|nil Normalized table of capabilities
+---@return lsp.ServerCapabilities|nil : Normalized table of capabilities
function protocol.resolve_capabilities(server_capabilities)
- local TextDocumentSyncKind = protocol.TextDocumentSyncKind
+ local TextDocumentSyncKind = protocol.TextDocumentSyncKind ---@type table<string|number, string|number>
local textDocumentSync = server_capabilities.textDocumentSync
if textDocumentSync == nil then
-- Defaults if omitted.
diff --git a/runtime/lua/vim/lsp/rpc.lua b/runtime/lua/vim/lsp/rpc.lua
index e52c06a1bd..984e4f040a 100644
--- a/runtime/lua/vim/lsp/rpc.lua
+++ b/runtime/lua/vim/lsp/rpc.lua
@@ -26,7 +26,7 @@ local function format_message_with_content_length(message)
})
end
----@class vim.lsp.rpc.Headers: {string: any}
+---@class (private) vim.lsp.rpc.Headers: {string: any}
---@field content_length integer
--- Parses an LSP Message's header
@@ -130,7 +130,7 @@ local M = {}
--- Mapping of error codes used by the client
--- @nodoc
-M.client_errors = {
+local client_errors = {
INVALID_SERVER_MESSAGE = 1,
INVALID_SERVER_JSON = 2,
NO_RESULT_CALLBACK_FOUND = 3,
@@ -140,7 +140,12 @@ M.client_errors = {
SERVER_RESULT_CALLBACK_ERROR = 7,
}
-M.client_errors = vim.tbl_add_reverse_lookup(M.client_errors)
+--- @type table<string|integer, string|integer>
+--- @nodoc
+M.client_errors = vim.deepcopy(client_errors)
+for k, v in pairs(client_errors) do
+ M.client_errors[v] = k
+end
--- Constructs an error message from an LSP error object.
---
@@ -193,7 +198,9 @@ function M.rpc_response_error(code, message, data)
})
end
+--- Dispatchers for LSP message types.
--- @class vim.lsp.rpc.Dispatchers
+--- @inlinedoc
--- @field notification fun(method: string, params: table)
--- @field server_request fun(method: string, params: table): any?, lsp.ResponseError?
--- @field on_exit fun(code: integer, signal: integer)
@@ -266,8 +273,7 @@ function M.create_read_loop(handle_body, on_no_chunk, on_error)
end
end
----@private
----@class vim.lsp.rpc.Client
+---@class (private) vim.lsp.rpc.Client
---@field message_index integer
---@field message_callbacks table<integer, function> dict of message_id to callback
---@field notify_reply_callbacks table<integer, function> dict of message_id to callback
@@ -522,7 +528,7 @@ function Client:handle_body(body)
end
end
----@class vim.lsp.rpc.Transport
+---@class (private) vim.lsp.rpc.Transport
---@field write fun(msg: string)
---@field is_closing fun(): boolean
---@field terminate fun()
@@ -721,32 +727,21 @@ function M.domain_socket_connect(pipe_path)
end
end
----@class vim.lsp.rpc.ExtraSpawnParams
----@field cwd? string Working directory for the LSP server process
----@field detached? boolean Detach the LSP server process from the current process
----@field env? table<string,string> Additional environment variables for LSP server process. See |vim.system|
+--- Additional context for the LSP server process.
+--- @class vim.lsp.rpc.ExtraSpawnParams
+--- @inlinedoc
+--- @field cwd? string Working directory for the LSP server process
+--- @field detached? boolean Detach the LSP server process from the current process
+--- @field env? table<string,string> Additional environment variables for LSP server process. See |vim.system()|
--- Starts an LSP server process and create an LSP RPC client object to
--- interact with it. Communication with the spawned process happens via stdio. For
--- communication via TCP, spawn a process manually and use |vim.lsp.rpc.connect()|
---
----@param cmd string[] Command to start the LSP server.
----
----@param dispatchers? vim.lsp.rpc.Dispatchers Dispatchers for LSP message types.
---- Valid dispatcher names are:
---- - `"notification"`
---- - `"server_request"`
---- - `"on_error"`
---- - `"on_exit"`
----
----@param extra_spawn_params? vim.lsp.rpc.ExtraSpawnParams Additional context for the LSP
---- server process. May contain:
---- - {cwd} (string) Working directory for the LSP server process
---- - {detached?} (boolean) Detach the LSP server process from the current process.
---- Defaults to false on Windows and true otherwise.
---- - {env?} (table) Additional environment variables for LSP server process
----
----@return vim.lsp.rpc.PublicClient? Client RPC object, with these methods:
+--- @param cmd string[] Command to start the LSP server.
+--- @param dispatchers? vim.lsp.rpc.Dispatchers
+--- @param extra_spawn_params? vim.lsp.rpc.ExtraSpawnParams
+--- @return vim.lsp.rpc.PublicClient? : Client RPC object, with these methods:
--- - `notify()` |vim.lsp.rpc.notify()|
--- - `request()` |vim.lsp.rpc.request()|
--- - `is_closing()` returns a boolean indicating if the RPC is closing.
diff --git a/runtime/lua/vim/lsp/semantic_tokens.lua b/runtime/lua/vim/lsp/semantic_tokens.lua
index fe8638bdfa..20ac0a125f 100644
--- a/runtime/lua/vim/lsp/semantic_tokens.lua
+++ b/runtime/lua/vim/lsp/semantic_tokens.lua
@@ -4,7 +4,7 @@ local ms = require('vim.lsp.protocol').Methods
local util = require('vim.lsp.util')
local uv = vim.uv
---- @class STTokenRange
+--- @class (private) STTokenRange
--- @field line integer line number 0-based
--- @field start_col integer start column 0-based
--- @field end_col integer end column 0-based
@@ -12,23 +12,23 @@ local uv = vim.uv
--- @field modifiers table<string,boolean> token modifiers as a set. E.g., { static = true, readonly = true }
--- @field marked boolean whether this token has had extmarks applied
---
---- @class STCurrentResult
+--- @class (private) STCurrentResult
--- @field version? integer document version associated with this result
--- @field result_id? string resultId from the server; used with delta requests
--- @field highlights? STTokenRange[] cache of highlight ranges for this document version
--- @field tokens? integer[] raw token array as received by the server. used for calculating delta responses
--- @field namespace_cleared? boolean whether the namespace was cleared for this result yet
---
---- @class STActiveRequest
+--- @class (private) STActiveRequest
--- @field request_id? integer the LSP request ID of the most recent request sent to the server
--- @field version? integer the document version associated with the most recent request
---
---- @class STClientState
+--- @class (private) STClientState
--- @field namespace integer
--- @field active_request STActiveRequest
--- @field current_result STCurrentResult
----@class STHighlighter
+---@class (private) STHighlighter
---@field active table<integer, STHighlighter>
---@field bufnr integer
---@field augroup integer augroup for buffer events
@@ -92,7 +92,7 @@ end
---
---@param data integer[]
---@param bufnr integer
----@param client lsp.Client
+---@param client vim.lsp.Client
---@param request STActiveRequest
---@return STTokenRange[]
local function tokens_to_ranges(data, bufnr, client, request)
@@ -572,7 +572,7 @@ local M = {}
---
---@param bufnr integer
---@param client_id integer
----@param opts (nil|table) Optional keyword arguments
+---@param opts? table Optional keyword arguments
--- - debounce (integer, default: 200): Debounce token requests
--- to the server by the given number in milliseconds
function M.start(bufnr, client_id, opts)
@@ -646,6 +646,7 @@ function M.stop(bufnr, client_id)
end
end
+--- @nodoc
--- @class STTokenRangeInspect : STTokenRange
--- @field client_id integer
@@ -727,6 +728,13 @@ function M.force_refresh(bufnr)
end
end
+--- @class vim.lsp.semantic_tokens.highlight_token.Opts
+--- @inlinedoc
+---
+--- Priority for the applied extmark.
+--- (Default: `vim.highlight.priorities.semantic_tokens + 3`)
+--- @field priority? integer
+
--- Highlight a semantic token.
---
--- Apply an extmark with a given highlight group for a semantic token. The
@@ -735,11 +743,9 @@ end
--- use inside |LspTokenUpdate| callbacks.
---@param token (table) a semantic token, found as `args.data.token` in |LspTokenUpdate|.
---@param bufnr (integer) the buffer to highlight
----@param client_id (integer) The ID of the |vim.lsp.client|
+---@param client_id (integer) The ID of the |vim.lsp.Client|
---@param hl_group (string) Highlight group name
----@param opts (table|nil) Optional parameters.
---- - priority: (integer|nil) Priority for the applied extmark. Defaults
---- to `vim.highlight.priorities.semantic_tokens + 3`
+---@param opts? vim.lsp.semantic_tokens.highlight_token.Opts Optional parameters:
function M.highlight_token(token, bufnr, client_id, hl_group, opts)
local highlighter = STHighlighter.active[bufnr]
if not highlighter then
diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua
index d2a5d9a08e..fc99f54d03 100644
--- a/runtime/lua/vim/lsp/util.lua
+++ b/runtime/lua/vim/lsp/util.lua
@@ -675,13 +675,26 @@ local function get_bufs_with_prefix(prefix)
return buffers
end
+local function escape_gsub_repl(s)
+ return (s:gsub('%%', '%%%%'))
+end
+
+--- @class vim.lsp.util.rename.Opts
+--- @inlinedoc
+--- @field overwrite? boolean
+--- @field ignoreIfExists? boolean
+
--- Rename old_fname to new_fname
---
----@param old_fname string
----@param new_fname string
----@param opts? table options
---- - overwrite? boolean
---- - ignoreIfExists? boolean
+--- Existing buffers are renamed as well, while maintaining their bufnr.
+---
+--- It deletes existing buffers that conflict with the renamed file name only when
+--- * `opts` requests overwriting; or
+--- * the conflicting buffers are not loaded, so that deleting them does not result in data loss.
+---
+--- @param old_fname string
+--- @param new_fname string
+--- @param opts? vim.lsp.util.rename.Opts Options:
function M.rename(old_fname, new_fname, opts)
opts = opts or {}
local skip = not opts.overwrite or opts.ignoreIfExists
@@ -698,24 +711,36 @@ function M.rename(old_fname, new_fname, opts)
return
end
- local oldbufs = {}
- local win = nil
-
- if vim.fn.isdirectory(old_fname_full) == 1 then
- oldbufs = get_bufs_with_prefix(old_fname_full)
- else
- local oldbuf = vim.fn.bufadd(old_fname_full)
- table.insert(oldbufs, oldbuf)
- win = vim.fn.win_findbuf(oldbuf)[1]
- end
-
- for _, b in ipairs(oldbufs) do
- -- There may be pending changes in the buffer
- if api.nvim_buf_is_loaded(b) then
- api.nvim_buf_call(b, function()
- vim.cmd('update!')
- end)
+ local buf_rename = {} ---@type table<integer, {from: string, to: string}>
+ local old_fname_pat = '^' .. vim.pesc(old_fname_full)
+ for b in
+ vim.iter(get_bufs_with_prefix(old_fname_full)):filter(function(b)
+ -- No need to care about unloaded or nofile buffers. Also :saveas won't work for them.
+ return api.nvim_buf_is_loaded(b)
+ and not vim.list_contains({ 'nofile', 'nowrite' }, vim.bo[b].buftype)
+ end)
+ do
+ -- Renaming a buffer may conflict with another buffer that happens to have the same name. In
+ -- most cases, this would have been already detected by the file conflict check above, but the
+ -- conflicting buffer may not be associated with a file. For example, 'buftype' can be "nofile"
+ -- or "nowrite", or the buffer can be a normal buffer but has not been written to the file yet.
+ -- Renaming should fail in such cases to avoid losing the contents of the conflicting buffer.
+ local old_bname = vim.api.nvim_buf_get_name(b)
+ local new_bname = old_bname:gsub(old_fname_pat, escape_gsub_repl(new_fname))
+ if vim.fn.bufexists(new_bname) == 1 then
+ local existing_buf = vim.fn.bufnr(new_bname)
+ if api.nvim_buf_is_loaded(existing_buf) and skip then
+ vim.notify(
+ new_bname .. ' already exists in the buffer list. Skipping rename.',
+ vim.log.levels.ERROR
+ )
+ return
+ end
+ -- no need to preserve if such a buffer is empty
+ api.nvim_buf_delete(existing_buf, {})
end
+
+ buf_rename[b] = { from = old_bname, to = new_bname }
end
local newdir = assert(vim.fs.dirname(new_fname))
@@ -724,17 +749,23 @@ function M.rename(old_fname, new_fname, opts)
local ok, err = os.rename(old_fname_full, new_fname)
assert(ok, err)
- if vim.fn.isdirectory(new_fname) == 0 then
- local newbuf = vim.fn.bufadd(new_fname)
- if win then
- vim.fn.bufload(newbuf)
- vim.bo[newbuf].buflisted = true
- api.nvim_win_set_buf(win, newbuf)
- end
+ local old_undofile = vim.fn.undofile(old_fname_full)
+ if uv.fs_stat(old_undofile) ~= nil then
+ local new_undofile = vim.fn.undofile(new_fname)
+ vim.fn.mkdir(assert(vim.fs.dirname(new_undofile)), 'p')
+ os.rename(old_undofile, new_undofile)
end
- for _, b in ipairs(oldbufs) do
- api.nvim_buf_delete(b, {})
+ for b, rename in pairs(buf_rename) do
+ -- Rename with :saveas. This does two things:
+ -- * Unset BF_WRITE_MASK, so that users don't get E13 when they do :write.
+ -- * Send didClose and didOpen via textDocument/didSave handler.
+ api.nvim_buf_call(b, function()
+ vim.cmd('keepalt saveas! ' .. vim.fn.fnameescape(rename.to))
+ end)
+ -- Delete the new buffer with the old name created by :saveas. nvim_buf_delete and
+ -- :bwipeout are futile because the buffer will be added again somewhere else.
+ vim.cmd('bdelete! ' .. vim.fn.bufnr(rename.from))
end
end
@@ -1443,7 +1474,7 @@ function M.stylize_markdown(bufnr, contents, opts)
return stripped
end
---- @class lsp.util.NormalizeMarkdownOptions
+--- @class (private) vim.lsp.util._normalize_markdown.Opts
--- @field width integer Thematic breaks are expanded to this size. Defaults to 80.
--- Normalizes Markdown input to a canonical form.
@@ -1459,7 +1490,7 @@ end
---
---@private
---@param contents string[]
----@param opts? lsp.util.NormalizeMarkdownOptions
+---@param opts? vim.lsp.util._normalize_markdown.Opts
---@return string[] table of lines containing normalized Markdown
---@see https://github.github.com/gfm
function M._normalize_markdown(contents, opts)
@@ -1530,7 +1561,7 @@ local function close_preview_autocmd(events, winnr, bufnrs)
end
end
----@internal
+---@private
--- Computes size of float needed to show contents (with optional wrapping)
---
---@param contents table of lines to show in window
@@ -1606,24 +1637,50 @@ function M._make_floating_popup_size(contents, opts)
return width, height
end
+--- @class vim.lsp.util.open_floating_preview.Opts
+--- @inlinedoc
+---
+--- Height of floating window
+--- @field height? integer
+---
+--- Width of floating window
+--- @field width? integer
+---
+--- Wrap long lines
+--- (default: `true`)
+--- @field wrap? boolean
+---
+--- Character to wrap at for computing height when wrap is enabled
+--- @field wrap_at? integer
+---
+--- Maximal width of floating window
+--- @field max_width? integer
+---
+--- Maximal height of floating window
+--- @field max_height? integer
+---
+--- If a popup with this id is opened, then focus it
+--- @field focus_id? string
+---
+--- List of events that closes the floating window
+--- @field close_events? table
+---
+--- Make float focusable.
+--- (default: `true`)
+--- @field focusable? boolean
+---
+--- If `true`, and if {focusable} is also `true`, focus an existing floating
+--- window with the same {focus_id}
+--- (default: `true`)
+--- @field focus? boolean
+
--- Shows contents in a floating window.
---
---@param contents table of lines to show in window
---@param syntax string of syntax to set for opened buffer
----@param opts table with optional fields (additional keys are filtered with |vim.lsp.util.make_floating_popup_options()|
---- before they are passed on to |nvim_open_win()|)
---- - height: (integer) height of floating window
---- - width: (integer) width of floating window
---- - wrap: (boolean, default true) wrap long lines
---- - wrap_at: (integer) character to wrap at for computing height when wrap is enabled
---- - max_width: (integer) maximal width of floating window
---- - max_height: (integer) maximal height of floating window
---- - focus_id: (string) if a popup with this id is opened, then focus it
---- - close_events: (table) list of events that closes the floating window
---- - focusable: (boolean, default true) Make float focusable
---- - focus: (boolean, default true) If `true`, and if {focusable}
---- is also `true`, focus an existing floating window with the same
---- {focus_id}
+---@param opts? vim.lsp.util.open_floating_preview.Opts with optional fields
+--- (additional keys are filtered with |vim.lsp.util.make_floating_popup_options()|
+--- before they are passed on to |nvim_open_win()|)
---@return integer bufnr of newly created float window
---@return integer winid of newly created float window preview window
function M.open_floating_preview(contents, syntax, opts)
@@ -1787,7 +1844,8 @@ local position_sort = sort_by_key(function(v)
return { v.start.line, v.start.character }
end)
----@class vim.lsp.util.LocationItem
+---@class vim.lsp.util.locations_to_items.ret
+---@inlinedoc
---@field filename string
---@field lnum integer 1-indexed line number
---@field col integer 1-indexed column
@@ -1806,7 +1864,7 @@ end)
---@param locations lsp.Location[]|lsp.LocationLink[]
---@param offset_encoding string offset_encoding for locations utf-8|utf-16|utf-32
--- default to first client of buffer
----@return vim.lsp.util.LocationItem[] list of items
+---@return vim.lsp.util.locations_to_items.ret[]
function M.locations_to_items(locations, offset_encoding)
if offset_encoding == nil then
vim.notify_once(
@@ -2214,16 +2272,16 @@ local function make_line_range_params(bufnr, start_line, end_line, offset_encodi
}
end
----@private
---- Request updated LSP information for a buffer.
----
----@class lsp.util.RefreshOptions
+---@class (private) vim.lsp.util._refresh.Opts
---@field bufnr integer? Buffer to refresh (default: 0)
---@field only_visible? boolean Whether to only refresh for the visible regions of the buffer (default: false)
---@field client_id? integer Client ID to refresh (default: all clients)
---
+
+---@private
+--- Request updated LSP information for a buffer.
+---
---@param method string LSP method to call
----@param opts? lsp.util.RefreshOptions Options table
+---@param opts? vim.lsp.util._refresh.Opts Options table
function M._refresh(method, opts)
opts = opts or {}
local bufnr = opts.bufnr
diff --git a/runtime/lua/vim/secure.lua b/runtime/lua/vim/secure.lua
index d29c356af3..3992eef78a 100644
--- a/runtime/lua/vim/secure.lua
+++ b/runtime/lua/vim/secure.lua
@@ -108,22 +108,25 @@ function M.read(path)
return contents
end
----@class vim.trust.opts
----@field action string
----@field path? string
----@field bufnr? integer
+--- @class vim.trust.opts
+--- @inlinedoc
+---
+--- - `'allow'` to add a file to the trust database and trust it,
+--- - `'deny'` to add a file to the trust database and deny it,
+--- - `'remove'` to remove file from the trust database
+--- @field action 'allow'|'deny'|'remove'
+---
+--- Path to a file to update. Mutually exclusive with {bufnr}.
+--- Cannot be used when {action} is "allow".
+--- @field path? string
+--- Buffer number to update. Mutually exclusive with {path}.
+--- @field bufnr? integer
--- Manage the trust database.
---
--- The trust database is located at |$XDG_STATE_HOME|/nvim/trust.
---
----@param opts (table):
---- - action (string): "allow" to add a file to the trust database and trust it,
---- "deny" to add a file to the trust database and deny it,
---- "remove" to remove file from the trust database
---- - path (string|nil): Path to a file to update. Mutually exclusive with {bufnr}.
---- Cannot be used when {action} is "allow".
---- - bufnr (number|nil): Buffer number to update. Mutually exclusive with {path}.
+---@param opts? vim.trust.opts
---@return boolean success true if operation was successful
---@return string msg full path if operation was successful, else error message
function M.trust(opts)
diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua
index 83fdfede89..a9eebf36da 100644
--- a/runtime/lua/vim/shared.lua
+++ b/runtime/lua/vim/shared.lua
@@ -65,8 +65,13 @@ function vim.deepcopy(orig, noref)
end
--- @class vim.gsplit.Opts
---- @field plain? boolean Use `sep` literally (as in string.find).
---- @field trimempty? boolean Discard empty segments at start and end of the sequence.
+--- @inlinedoc
+---
+--- Use `sep` literally (as in string.find).
+--- @field plain? boolean
+---
+--- Discard empty segments at start and end of the sequence.
+--- @field trimempty? boolean
--- Gets an |iterator| that splits a string at each instance of a separator, in "lazy" fashion
--- (as opposed to |vim.split()| which is "eager").
@@ -96,10 +101,8 @@ end
---
--- @param s string String to split
--- @param sep string Separator or pattern
---- @param opts? vim.gsplit.Opts (table) Keyword arguments |kwargs|:
---- - plain: (boolean) Use `sep` literally (as in string.find).
---- - trimempty: (boolean) Discard empty segments at start and end of the sequence.
----@return fun():string|nil (function) Iterator over the split components
+--- @param opts? vim.gsplit.Opts Keyword arguments |kwargs|:
+--- @return fun():string? : Iterator over the split components
function vim.gsplit(s, sep, opts)
local plain --- @type boolean?
local trimempty = false
@@ -192,7 +195,7 @@ end
---
---@param s string String to split
---@param sep string Separator or pattern
----@param opts? table Keyword arguments |kwargs| accepted by |vim.gsplit()|
+---@param opts? vim.gsplit.Opts Keyword arguments |kwargs|:
---@return string[] : List of split components
function vim.split(s, sep, opts)
local t = {}
@@ -242,8 +245,8 @@ end
--- Apply a function to all values of a table.
---
---@generic T
----@param func fun(value: T): any (function) Function
----@param t table<any, T> (table) Table
+---@param func fun(value: T): any Function
+---@param t table<any, T> Table
---@return table : Table of transformed values
function vim.tbl_map(func, t)
vim.validate({ func = { func, 'c' }, t = { t, 't' } })
@@ -276,6 +279,9 @@ function vim.tbl_filter(func, t)
end
--- @class vim.tbl_contains.Opts
+--- @inlinedoc
+---
+--- `value` is a function reference to be checked (default false)
--- @field predicate? boolean
--- Checks if a table contains a given value, specified either directly or via
@@ -294,8 +300,7 @@ end
---
---@param t table Table to check
---@param value any Value to compare or predicate function reference
----@param opts? vim.tbl_contains.Opts (table) Keyword arguments |kwargs|:
---- - predicate: (boolean) `value` is a function reference to be checked (default false)
+---@param opts? vim.tbl_contains.Opts Keyword arguments |kwargs|:
---@return boolean `true` if `t` contains `value`
function vim.tbl_contains(t, value, opts)
vim.validate({ t = { t, 't' }, opts = { opts, 't', true } })
@@ -397,7 +402,7 @@ end
---
---@see |extend()|
---
----@param behavior string Decides what to do if a key is found in more than one map:
+---@param behavior 'error'|'keep'|'force' Decides what to do if a key is found in more than one map:
--- - "error": raise an error
--- - "keep": use value from the leftmost map
--- - "force": use value from the rightmost map
@@ -413,7 +418,7 @@ end
---
---@generic T1: table
---@generic T2: table
----@param behavior "error"|"keep"|"force" (string) Decides what to do if a key is found in more than one map:
+---@param behavior 'error'|'keep'|'force' Decides what to do if a key is found in more than one map:
--- - "error": raise an error
--- - "keep": use value from the leftmost map
--- - "force": use value from the rightmost map
@@ -460,9 +465,12 @@ end
--- `tbl_add_reverse_lookup { A = 1 } == { [1] = 'A', A = 1 }`
---
--- Note that this *modifies* the input.
+---@deprecated
---@param o table Table to add the reverse to
---@return table o
function vim.tbl_add_reverse_lookup(o)
+ vim.deprecate('vim.tbl_add_reverse_lookup', nil, '0.12')
+
--- @cast o table<any,any>
--- @type any[]
local keys = vim.tbl_keys(o)
@@ -565,8 +573,10 @@ end
---
---@see Based on https://github.com/premake/premake-core/blob/master/src/base/table.lua
---
----@param t table Dict-like table
----@return function # |for-in| iterator over sorted keys and their values
+---@generic T: table, K, V
+---@param t T Dict-like table
+---@return fun(table: table<K, V>, index?: K):K, V # |for-in| iterator over sorted keys and their values
+---@return T
function vim.spairs(t)
vim.validate({ t = { t, 't' } })
--- @cast t table<any,any>
@@ -585,7 +595,8 @@ function vim.spairs(t)
if keys[i] then
return keys[i], t[keys[i]]
end
- end
+ end,
+ t
end
--- Tests if `t` is an "array": a table indexed _only_ by integers (potentially non-contiguous).
@@ -644,18 +655,21 @@ function vim.tbl_islist(t)
return false
end
- local num_elem = vim.tbl_count(t)
-
- if num_elem == 0 then
+ if next(t) == nil then
return getmetatable(t) ~= vim._empty_dict_mt
- else
- for i = 1, num_elem do
- if t[i] == nil then
- return false
- end
+ end
+
+ local j = 1
+ for _ in
+ pairs(t--[[@as table<any,any>]])
+ do
+ if t[j] == nil then
+ return false
end
- return true
+ j = j + 1
end
+
+ return true
end
--- Counts the number of non-nil values in table `t`.
@@ -764,6 +778,7 @@ do
['userdata'] = 'userdata',
}
+ --- @nodoc
--- @class vim.validate.Spec {[1]: any, [2]: string|string[], [3]: boolean }
--- @field [1] any Argument value
--- @field [2] string|string[]|fun(v:any):boolean, string? Type name, or callable
@@ -1012,7 +1027,7 @@ do
--- - |Ringbuf:clear()|
---
---@param size integer
- ---@return vim.Ringbuf ringbuf (table)
+ ---@return vim.Ringbuf ringbuf
function vim.ringbuf(size)
local ringbuf = {
_items = {},
diff --git a/runtime/lua/vim/snippet.lua b/runtime/lua/vim/snippet.lua
index a660d6f301..2ffd89367f 100644
--- a/runtime/lua/vim/snippet.lua
+++ b/runtime/lua/vim/snippet.lua
@@ -101,7 +101,7 @@ local function get_extmark_range(bufnr, extmark_id)
return { mark[1], mark[2], mark[3].end_row, mark[3].end_col }
end
---- @class vim.snippet.Tabstop
+--- @class (private) vim.snippet.Tabstop
--- @field extmark_id integer
--- @field bufnr integer
--- @field index integer
@@ -177,7 +177,7 @@ function Tabstop:set_right_gravity(right_gravity)
})
end
---- @class vim.snippet.Session
+--- @class (private) vim.snippet.Session
--- @field bufnr integer
--- @field extmark_id integer
--- @field tabstops table<integer, vim.snippet.Tabstop[]>
diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua
index 9b69f95f54..a09619f369 100644
--- a/runtime/lua/vim/treesitter.lua
+++ b/runtime/lua/vim/treesitter.lua
@@ -1,6 +1,6 @@
local api = vim.api
----@type table<integer,LanguageTree>
+---@type table<integer,vim.treesitter.LanguageTree>
local parsers = setmetatable({}, { __mode = 'v' })
local M = vim._defer_require('vim.treesitter', {
@@ -30,7 +30,7 @@ M.minimum_language_version = vim._ts_get_minimum_language_version()
---@param lang string Language of the parser
---@param opts (table|nil) Options to pass to the created language tree
---
----@return LanguageTree object to use for parsing
+---@return vim.treesitter.LanguageTree object to use for parsing
function M._create_parser(bufnr, lang, opts)
if bufnr == 0 then
bufnr = vim.api.nvim_get_current_buf()
@@ -77,10 +77,10 @@ end
--- If needed, this will create the parser.
---
---@param bufnr (integer|nil) Buffer the parser should be tied to (default: current buffer)
----@param lang (string|nil) Filetype of this parser (default: buffer filetype)
+---@param lang (string|nil) Language of this parser (default: from buffer filetype)
---@param opts (table|nil) Options to pass to the created language tree
---
----@return LanguageTree object to use for parsing
+---@return vim.treesitter.LanguageTree object to use for parsing
function M.get_parser(bufnr, lang, opts)
opts = opts or {}
@@ -119,7 +119,7 @@ end
---@param lang string Language of this string
---@param opts (table|nil) Options to pass to the created language tree
---
----@return LanguageTree object to use for parsing
+---@return vim.treesitter.LanguageTree object to use for parsing
function M.get_string_parser(str, lang, opts)
vim.validate({
str = { str, 'string' },
@@ -172,7 +172,7 @@ end
---to get the range with directives applied.
---@param node TSNode
---@param source integer|string|nil Buffer or string from which the {node} is extracted
----@param metadata TSMetadata|nil
+---@param metadata vim.treesitter.query.TSMetadata|nil
---@return Range6
function M.get_range(node, source, metadata)
if metadata and metadata.range then
@@ -326,10 +326,21 @@ function M.get_captures_at_cursor(winnr)
return captures
end
---- @class vim.treesitter.GetNodeOpts
+--- Optional keyword arguments:
+--- @class vim.treesitter.get_node.Opts
+--- @inlinedoc
+---
+--- Buffer number (nil or 0 for current buffer)
--- @field bufnr integer?
+---
+--- 0-indexed (row, col) tuple. Defaults to cursor position in the
+--- current window. Required if {bufnr} is not the current buffer
--- @field pos { [1]: integer, [2]: integer }?
+---
+--- Parser language. (default: from buffer filetype)
--- @field lang string?
+---
+--- Ignore injected languages (default true)
--- @field ignore_injections boolean?
--- Returns the smallest named node at the given position
@@ -342,12 +353,7 @@ end
--- vim.treesitter.get_parser(bufnr):parse(range)
--- ```
---
----@param opts vim.treesitter.GetNodeOpts? Optional keyword arguments:
---- - bufnr integer|nil Buffer number (nil or 0 for current buffer)
---- - pos table|nil 0-indexed (row, col) tuple. Defaults to cursor position in the
---- current window. Required if {bufnr} is not the current buffer
---- - lang string|nil Parser language. (default: from buffer filetype)
---- - ignore_injections boolean Ignore injected languages (default true)
+---@param opts vim.treesitter.get_node.Opts?
---
---@return TSNode | nil Node at the given position
function M.get_node(opts)
@@ -385,48 +391,6 @@ function M.get_node(opts)
return root_lang_tree:named_node_for_range(ts_range, opts)
end
---- Returns the smallest named node at the given position
----
----@param bufnr integer Buffer number (0 for current buffer)
----@param row integer Position row
----@param col integer Position column
----@param opts table Optional keyword arguments:
---- - lang string|nil Parser language
---- - ignore_injections boolean Ignore injected languages (default true)
----
----@return TSNode | nil Node at the given position
----@deprecated
-function M.get_node_at_pos(bufnr, row, col, opts)
- vim.deprecate('vim.treesitter.get_node_at_pos()', 'vim.treesitter.get_node()', '0.10')
- if bufnr == 0 then
- bufnr = api.nvim_get_current_buf()
- end
- local ts_range = { row, col, row, col }
-
- opts = opts or {}
-
- local root_lang_tree = M.get_parser(bufnr, opts.lang)
- if not root_lang_tree then
- return
- end
-
- return root_lang_tree:named_node_for_range(ts_range, opts)
-end
-
---- Returns the smallest named node under the cursor
----
----@param winnr (integer|nil) Window handle or 0 for current window (default)
----
----@return string Name of node under the cursor
----@deprecated
-function M.get_node_at_cursor(winnr)
- vim.deprecate('vim.treesitter.get_node_at_cursor()', 'vim.treesitter.get_node():type()', '0.10')
- winnr = winnr or 0
- local bufnr = api.nvim_win_get_buf(winnr)
-
- return M.get_node({ bufnr = bufnr, ignore_injections = false }):type()
-end
-
--- Starts treesitter highlighting for a buffer
---
--- Can be used in an ftplugin or FileType autocommand.
@@ -446,7 +410,7 @@ end
--- ```
---
---@param bufnr (integer|nil) Buffer to be highlighted (default: current buffer)
----@param lang (string|nil) Language of the parser (default: buffer filetype)
+---@param lang (string|nil) Language of the parser (default: from buffer filetype)
function M.start(bufnr, lang)
bufnr = bufnr or api.nvim_get_current_buf()
local parser = M.get_parser(bufnr, lang)
@@ -468,14 +432,14 @@ end
---
--- While in the window, press "a" to toggle display of anonymous nodes, "I" to toggle the
--- display of the source language of each node, "o" to toggle the query editor, and press
---- <Enter> to jump to the node under the cursor in the source buffer. Folding also works
+--- [<Enter>] to jump to the node under the cursor in the source buffer. Folding also works
--- (try |zo|, |zc|, etc.).
---
---- Can also be shown with `:InspectTree`. *:InspectTree*
+--- Can also be shown with `:InspectTree`. [:InspectTree]()
---
---@param opts table|nil Optional options table with the following possible keys:
---- - lang (string|nil): The language of the source buffer. If omitted, the
---- filetype of the source buffer is used.
+--- - lang (string|nil): The language of the source buffer. If omitted, detect
+--- from the filetype of the source buffer.
--- - bufnr (integer|nil): Buffer to draw the tree into. If omitted, a new
--- buffer is created.
--- - winid (integer|nil): Window id to display the tree buffer in. If omitted,
diff --git a/runtime/lua/vim/treesitter/_meta.lua b/runtime/lua/vim/treesitter/_meta.lua
index 0b285d2d7f..19d97d2820 100644
--- a/runtime/lua/vim/treesitter/_meta.lua
+++ b/runtime/lua/vim/treesitter/_meta.lua
@@ -39,7 +39,7 @@ local TSNode = {}
---@param start? integer
---@param end_? integer
---@param opts? table
----@return fun(): integer, TSNode, TSMatch
+---@return fun(): integer, TSNode, vim.treesitter.query.TSMatch
function TSNode:_rawquery(query, captures, start, end_, opts) end
---@param query TSQuery
@@ -47,14 +47,13 @@ function TSNode:_rawquery(query, captures, start, end_, opts) end
---@param start? integer
---@param end_? integer
---@param opts? table
----@return fun(): integer, TSMatch
+---@return fun(): integer, vim.treesitter.query.TSMatch
function TSNode:_rawquery(query, captures, start, end_, opts) end
---@alias TSLoggerCallback fun(logtype: 'parse'|'lex', msg: string)
---@class TSParser: userdata
----@field parse fun(self: TSParser, tree: TSTree?, source: integer|string, include_bytes: true): TSTree, Range6[]
----@field parse fun(self: TSParser, tree: TSTree?, source: integer|string, include_bytes: false|nil): TSTree, Range4[]
+---@field parse fun(self: TSParser, tree: TSTree?, source: integer|string, include_bytes: boolean): TSTree, (Range4|Range6)[]
---@field reset fun(self: TSParser)
---@field included_ranges fun(self: TSParser, include_bytes: boolean?): integer[]
---@field set_included_ranges fun(self: TSParser, ranges: (Range6|TSNode)[])
diff --git a/runtime/lua/vim/treesitter/_query_linter.lua b/runtime/lua/vim/treesitter/_query_linter.lua
index 6ec997eb4a..6216d4e891 100644
--- a/runtime/lua/vim/treesitter/_query_linter.lua
+++ b/runtime/lua/vim/treesitter/_query_linter.lua
@@ -17,7 +17,7 @@ local M = {}
--- @field is_first_lang boolean Whether this is the first language of a linter run checking queries for multiple `langs`
--- Adds a diagnostic for node in the query buffer
---- @param diagnostics Diagnostic[]
+--- @param diagnostics vim.Diagnostic[]
--- @param range Range4
--- @param lint string
--- @param lang string?
@@ -45,7 +45,7 @@ local function guess_query_lang(buf)
end
--- @param buf integer
---- @param opts QueryLinterOpts|QueryLinterNormalizedOpts|nil
+--- @param opts vim.treesitter.query.lint.Opts|QueryLinterNormalizedOpts|nil
--- @return QueryLinterNormalizedOpts
local function normalize_opts(buf, opts)
opts = opts or {}
@@ -114,7 +114,7 @@ end
--- @return vim.treesitter.ParseError?
local parse = vim.func._memoize(hash_parse, function(node, buf, lang)
local query_text = vim.treesitter.get_node_text(node, buf)
- local ok, err = pcall(vim.treesitter.query.parse, lang, query_text) ---@type boolean|vim.treesitter.ParseError, string|Query
+ local ok, err = pcall(vim.treesitter.query.parse, lang, query_text) ---@type boolean|vim.treesitter.ParseError, string|vim.treesitter.Query
if not ok and type(err) == 'string' then
return get_error_entry(err, node)
@@ -122,10 +122,10 @@ local parse = vim.func._memoize(hash_parse, function(node, buf, lang)
end)
--- @param buf integer
---- @param match TSMatch
---- @param query Query
+--- @param match vim.treesitter.query.TSMatch
+--- @param query vim.treesitter.Query
--- @param lang_context QueryLinterLanguageContext
---- @param diagnostics Diagnostic[]
+--- @param diagnostics vim.Diagnostic[]
local function lint_match(buf, match, query, lang_context, diagnostics)
local lang = lang_context.lang
local parser_info = lang_context.parser_info
@@ -153,7 +153,7 @@ end
--- @private
--- @param buf integer Buffer to lint
---- @param opts QueryLinterOpts|QueryLinterNormalizedOpts|nil Options for linting
+--- @param opts vim.treesitter.query.lint.Opts|QueryLinterNormalizedOpts|nil Options for linting
function M.lint(buf, opts)
if buf == 0 then
buf = api.nvim_get_current_buf()
diff --git a/runtime/lua/vim/treesitter/dev.lua b/runtime/lua/vim/treesitter/dev.lua
index 4c8f6e466f..dc2a14d238 100644
--- a/runtime/lua/vim/treesitter/dev.lua
+++ b/runtime/lua/vim/treesitter/dev.lua
@@ -1,23 +1,21 @@
local api = vim.api
----@class TSDevModule
local M = {}
----@private
----@class TSTreeView
+---@class (private) vim.treesitter.dev.TSTreeView
---@field ns integer API namespace
----@field opts TSTreeViewOpts
----@field nodes TSP.Node[]
----@field named TSP.Node[]
+---@field opts vim.treesitter.dev.TSTreeViewOpts
+---@field nodes vim.treesitter.dev.Node[]
+---@field named vim.treesitter.dev.Node[]
local TSTreeView = {}
---@private
----@class TSTreeViewOpts
+---@class (private) vim.treesitter.dev.TSTreeViewOpts
---@field anon boolean If true, display anonymous nodes.
---@field lang boolean If true, display the language alongside each node.
---@field indent number Number of spaces to indent nested lines.
----@class TSP.Node
+---@class (private) vim.treesitter.dev.Node
---@field node TSNode Treesitter node
---@field field string? Node field
---@field depth integer Depth of this node in the tree
@@ -25,7 +23,7 @@ local TSTreeView = {}
--- inspector is drawn.
---@field lang string Source language of this node
----@class TSP.Injection
+---@class (private) vim.treesitter.dev.Injection
---@field lang string Source language of this injection
---@field root TSNode Root node of the injection
@@ -45,9 +43,9 @@ local TSTreeView = {}
---@param depth integer Current recursion depth
---@param field string|nil The field of the current node
---@param lang string Language of the tree currently being traversed
----@param injections table<string, TSP.Injection> Mapping of node ids to root nodes
+---@param injections table<string, vim.treesitter.dev.Injection> Mapping of node ids to root nodes
--- of injected language trees (see explanation above)
----@param tree TSP.Node[] Output table containing a list of tables each representing a node in the tree
+---@param tree vim.treesitter.dev.Node[] Output table containing a list of tables each representing a node in the tree
local function traverse(node, depth, field, lang, injections, tree)
table.insert(tree, {
node = node,
@@ -73,7 +71,7 @@ end
---@param bufnr integer Source buffer number
---@param lang string|nil Language of source buffer
---
----@return TSTreeView|nil
+---@return vim.treesitter.dev.TSTreeView|nil
---@return string|nil Error message, if any
---
---@package
@@ -88,7 +86,7 @@ function TSTreeView:new(bufnr, lang)
-- the primary tree that contains that root. Add a mapping from the node in the primary tree to
-- the root in the child tree to the {injections} table.
local root = parser:parse(true)[1]:root()
- local injections = {} ---@type table<string, TSP.Injection>
+ local injections = {} ---@type table<string, vim.treesitter.dev.Injection>
parser:for_each_tree(function(parent_tree, parent_ltree)
local parent = parent_tree:root()
@@ -109,7 +107,7 @@ function TSTreeView:new(bufnr, lang)
local nodes = traverse(root, 0, nil, parser:lang(), injections, {})
- local named = {} ---@type TSP.Node[]
+ local named = {} ---@type vim.treesitter.dev.Node[]
for _, v in ipairs(nodes) do
if v.node:named() then
named[#named + 1] = v
@@ -120,7 +118,7 @@ function TSTreeView:new(bufnr, lang)
ns = api.nvim_create_namespace('treesitter/dev-inspect'),
nodes = nodes,
named = named,
- ---@type TSTreeViewOpts
+ ---@type vim.treesitter.dev.TSTreeViewOpts
opts = {
anon = false,
lang = false,
@@ -171,7 +169,7 @@ end
--- Updates the cursor position in the inspector to match the node under the cursor.
---
---- @param treeview TSTreeView
+--- @param treeview vim.treesitter.dev.TSTreeView
--- @param lang string
--- @param source_buf integer
--- @param inspect_buf integer
@@ -278,7 +276,7 @@ end
--- The node number is dependent on whether or not anonymous nodes are displayed.
---
---@param i integer Node number to get
----@return TSP.Node
+---@return vim.treesitter.dev.Node
---@package
function TSTreeView:get(i)
local t = self.opts.anon and self.nodes or self.named
@@ -287,7 +285,7 @@ end
--- Iterate over all of the nodes in this View.
---
----@return (fun(): integer, TSP.Node) Iterator over all nodes in this View
+---@return (fun(): integer, vim.treesitter.dev.Node) Iterator over all nodes in this View
---@return table
---@return integer
---@package
@@ -295,22 +293,31 @@ function TSTreeView:iter()
return ipairs(self.opts.anon and self.nodes or self.named)
end
---- @class InspectTreeOpts
---- @field lang string? The language of the source buffer. If omitted, the
---- filetype of the source buffer is used.
---- @field bufnr integer? Buffer to draw the tree into. If omitted, a new
---- buffer is created.
---- @field winid integer? Window id to display the tree buffer in. If omitted,
---- a new window is created with {command}.
---- @field command string? Vimscript command to create the window. Default
---- value is "60vnew". Only used when {winid} is nil.
---- @field title (string|fun(bufnr:integer):string|nil) Title of the window. If a
---- function, it accepts the buffer number of the source
---- buffer as its only argument and should return a string.
+--- @class vim.treesitter.dev.inspect_tree.Opts
+--- @inlinedoc
+---
+--- The language of the source buffer. If omitted, the filetype of the source
+--- buffer is used.
+--- @field lang string?
+---
+--- Buffer to draw the tree into. If omitted, a new buffer is created.
+--- @field bufnr integer?
+---
+--- Window id to display the tree buffer in. If omitted, a new window is
+--- created with {command}.
+--- @field winid integer?
+---
+--- Vimscript command to create the window. Default value is "60vnew".
+--- Only used when {winid} is nil.
+--- @field command string?
+---
+--- Title of the window. If a function, it accepts the buffer number of the
+--- source buffer as its only argument and should return a string.
+--- @field title (string|fun(bufnr:integer):string|nil)
--- @private
---
---- @param opts InspectTreeOpts?
+--- @param opts vim.treesitter.dev.inspect_tree.Opts?
function M.inspect_tree(opts)
vim.validate({
opts = { opts, 't', true },
diff --git a/runtime/lua/vim/treesitter/highlighter.lua b/runtime/lua/vim/treesitter/highlighter.lua
index 8fb591bc46..7bc6e5c019 100644
--- a/runtime/lua/vim/treesitter/highlighter.lua
+++ b/runtime/lua/vim/treesitter/highlighter.lua
@@ -4,9 +4,9 @@ local Range = require('vim.treesitter._range')
local ns = api.nvim_create_namespace('treesitter/highlighter')
----@alias vim.treesitter.highlighter.Iter fun(end_line: integer|nil): integer, TSNode, TSMetadata
+---@alias vim.treesitter.highlighter.Iter fun(): integer, table<integer, TSNode[]>, vim.treesitter.query.TSMetadata
----@class vim.treesitter.highlighter.Query
+---@class (private) vim.treesitter.highlighter.Query
---@field private _query vim.treesitter.Query?
---@field private lang string
---@field private hl_cache table<integer,integer>
@@ -52,22 +52,24 @@ function TSHighlighterQuery:query()
return self._query
end
----@class vim.treesitter.highlighter.State
+---@class (private) vim.treesitter.highlighter.State
---@field tstree TSTree
---@field next_row integer
---@field iter vim.treesitter.highlighter.Iter?
---@field highlighter_query vim.treesitter.highlighter.Query
+---@field level integer Injection level
+---@nodoc
---@class vim.treesitter.highlighter
---@field active table<integer,vim.treesitter.highlighter>
---@field bufnr integer
----@field orig_spelloptions string
+---@field private orig_spelloptions string
--- A map of highlight states.
--- This state is kept during rendering across each line update.
----@field _highlight_states vim.treesitter.highlighter.State[]
----@field _queries table<string,vim.treesitter.highlighter.Query>
----@field tree LanguageTree
----@field redraw_count integer
+---@field private _highlight_states vim.treesitter.highlighter.State[]
+---@field private _queries table<string,vim.treesitter.highlighter.Query>
+---@field tree vim.treesitter.LanguageTree
+---@field private redraw_count integer
local TSHighlighter = {
active = {},
}
@@ -78,7 +80,7 @@ TSHighlighter.__index = TSHighlighter
---
--- Creates a highlighter for `tree`.
---
----@param tree LanguageTree parser object to use for highlighting
+---@param tree vim.treesitter.LanguageTree parser object to use for highlighting
---@param opts (table|nil) Configuration of the highlighter:
--- - queries table overwrite queries used by the highlighter
---@return vim.treesitter.highlighter Created highlighter object
@@ -191,12 +193,20 @@ function TSHighlighter:prepare_highlight_states(srow, erow)
return
end
+ local level = 0
+ local t = tree
+ while t do
+ t = t:parent()
+ level = level + 1
+ end
+
-- _highlight_states should be a list so that the highlights are added in the same order as
-- for_each_tree traversal. This ensures that parents' highlight don't override children's.
table.insert(self._highlight_states, {
tstree = tstree,
next_row = 0,
iter = nil,
+ level = level,
highlighter_query = highlighter_query,
})
end)
@@ -242,12 +252,53 @@ function TSHighlighter:get_query(lang)
return self._queries[lang]
end
+--- @param match table<integer,TSNode[]>
+--- @param bufnr integer
+--- @param capture integer
+--- @param metadata vim.treesitter.query.TSMetadata
+--- @return string?
+local function get_url(match, bufnr, capture, metadata)
+ ---@type string|number|nil
+ local url = metadata[capture] and metadata[capture].url
+
+ if not url or type(url) == 'string' then
+ return url
+ end
+
+ if not match or not match[url] then
+ return
+ end
+
+ -- Assume there is only one matching node. If there is more than one, take the URL
+ -- from the first.
+ local other_node = match[url][1]
+
+ return vim.treesitter.get_node_text(other_node, bufnr, {
+ metadata = metadata[url],
+ })
+end
+
+--- @param capture_name string
+--- @return boolean?, integer
+local function get_spell(capture_name)
+ if capture_name == 'spell' then
+ return true, 0
+ elseif capture_name == 'nospell' then
+ -- Give nospell a higher priority so it always overrides spell captures.
+ return false, 1
+ end
+ return nil, 0
+end
+
---@param self vim.treesitter.highlighter
---@param buf integer
---@param line integer
---@param is_spell_nav boolean
local function on_line_impl(self, buf, line, is_spell_nav)
self:for_each_highlight_state(function(state)
+ -- Use the injection level to offset the subpriority passed to nvim_buf_set_extmark
+ -- so injections always appear over base highlights.
+ local pattern_offset = state.level * 1000
local root_node = state.tstree:root()
local root_start_row, _, root_end_row, _ = root_node:range()
@@ -257,50 +308,57 @@ local function on_line_impl(self, buf, line, is_spell_nav)
end
if state.iter == nil or state.next_row < line then
- state.iter =
- state.highlighter_query:query():iter_captures(root_node, self.bufnr, line, root_end_row + 1)
+ state.iter = state.highlighter_query
+ :query()
+ :iter_matches(root_node, self.bufnr, line, root_end_row + 1, { all = true })
end
while line >= state.next_row do
- local capture, node, metadata = state.iter(line)
+ local pattern, match, metadata = state.iter()
- local range = { root_end_row + 1, 0, root_end_row + 1, 0 }
- if node then
- range = vim.treesitter.get_range(node, buf, metadata and metadata[capture])
+ if not match then
+ state.next_row = root_end_row + 1
end
- local start_row, start_col, end_row, end_col = Range.unpack4(range)
-
- if capture then
- local hl = state.highlighter_query:get_hl_from_capture(capture)
+ for capture, nodes in pairs(match or {}) do
local capture_name = state.highlighter_query:query().captures[capture]
- local spell = nil ---@type boolean?
- if capture_name == 'spell' then
- spell = true
- elseif capture_name == 'nospell' then
- spell = false
- end
+ local spell, spell_pri_offset = get_spell(capture_name)
- -- Give nospell a higher priority so it always overrides spell captures.
- local spell_pri_offset = capture_name == 'nospell' and 1 or 0
-
- if hl and end_row >= line and (not is_spell_nav or spell ~= nil) then
- local priority = (tonumber(metadata.priority) or vim.highlight.priorities.treesitter)
- + spell_pri_offset
- api.nvim_buf_set_extmark(buf, ns, start_row, start_col, {
- end_line = end_row,
- end_col = end_col,
- hl_group = hl,
- ephemeral = true,
- priority = priority,
- conceal = metadata.conceal,
- spell = spell,
- })
- end
- end
+ local hl = state.highlighter_query:get_hl_from_capture(capture)
- if start_row > line then
- state.next_row = start_row
+ -- The "priority" attribute can be set at the pattern level or on a particular capture
+ local priority = (
+ tonumber(metadata.priority or metadata[capture] and metadata[capture].priority)
+ or vim.highlight.priorities.treesitter
+ ) + spell_pri_offset
+
+ local url = get_url(match, buf, capture, metadata)
+
+ -- The "conceal" attribute can be set at the pattern level or on a particular capture
+ local conceal = metadata.conceal or metadata[capture] and metadata[capture].conceal
+
+ for _, node in ipairs(nodes) do
+ local range = vim.treesitter.get_range(node, buf, metadata[capture])
+ local start_row, start_col, end_row, end_col = Range.unpack4(range)
+
+ if hl and end_row >= line and (not is_spell_nav or spell ~= nil) then
+ api.nvim_buf_set_extmark(buf, ns, start_row, start_col, {
+ end_line = end_row,
+ end_col = end_col,
+ hl_group = hl,
+ ephemeral = true,
+ priority = priority,
+ _subpriority = pattern_offset + pattern,
+ conceal = conceal,
+ spell = spell,
+ url = url,
+ })
+ end
+
+ if start_row > line then
+ state.next_row = start_row
+ end
+ end
end
end
end)
diff --git a/runtime/lua/vim/treesitter/language.lua b/runtime/lua/vim/treesitter/language.lua
index 0f6d5ecbd0..47abf65332 100644
--- a/runtime/lua/vim/treesitter/language.lua
+++ b/runtime/lua/vim/treesitter/language.lua
@@ -56,10 +56,17 @@ function M.require_language(lang, path, silent, symbol_name)
return true
end
----@class vim.treesitter.language.RequireLangOpts
----@field path? string
----@field silent? boolean
+---@class vim.treesitter.language.add.Opts
+---@inlinedoc
+---
+---Default filetype the parser should be associated with.
+---(Default: {lang})
---@field filetype? string|string[]
+---
+---Optional path the parser is located at
+---@field path? string
+---
+---Internal symbol name for the language to load
---@field symbol_name? string
--- Load parser with name {lang}
@@ -67,13 +74,8 @@ end
--- Parsers are searched in the `parser` runtime directory, or the provided {path}
---
---@param lang string Name of the parser (alphanumerical and `_` only)
----@param opts (table|nil) Options:
---- - filetype (string|string[]) Default filetype the parser should be associated with.
---- Defaults to {lang}.
---- - path (string|nil) Optional path the parser is located at
---- - symbol_name (string|nil) Internal symbol name for the language to load
+---@param opts? vim.treesitter.language.add.Opts Options:
function M.add(lang, opts)
- ---@cast opts vim.treesitter.language.RequireLangOpts
opts = opts or {}
local path = opts.path
local filetype = opts.filetype or lang
@@ -148,14 +150,4 @@ function M.inspect(lang)
return vim._ts_inspect_language(lang)
end
----@deprecated
-function M.inspect_language(...)
- vim.deprecate(
- 'vim.treesitter.language.inspect_language()',
- 'vim.treesitter.language.inspect()',
- '0.10'
- )
- return M.inspect(...)
-end
-
return M
diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua
index d01da8be71..ec933f5194 100644
--- a/runtime/lua/vim/treesitter/languagetree.lua
+++ b/runtime/lua/vim/treesitter/languagetree.lua
@@ -1,4 +1,4 @@
---- @brief A \*LanguageTree\* contains a tree of parsers: the root treesitter parser for {lang} and
+--- @brief A [LanguageTree]() contains a tree of parsers: the root treesitter parser for {lang} and
--- any "injected" language parsers, which themselves may inject other languages, recursively.
--- For example a Lua buffer containing some Vimscript commands needs multiple parsers to fully
--- understand its contents.
@@ -67,11 +67,12 @@ local TSCallbackNames = {
on_child_removed = 'child_removed',
}
----@class LanguageTree
+---@nodoc
+---@class vim.treesitter.LanguageTree
---@field private _callbacks table<TSCallbackName,function[]> Callback handlers
---@field package _callbacks_rec table<TSCallbackName,function[]> Callback handlers (recursive)
----@field private _children table<string,LanguageTree> Injected languages
----@field private _injection_query Query Queries defining injected languages
+---@field private _children table<string,vim.treesitter.LanguageTree> Injected languages
+---@field private _injection_query vim.treesitter.Query Queries defining injected languages
---@field private _injections_processed boolean
---@field private _opts table Options
---@field private _parser TSParser Parser for language
@@ -80,7 +81,7 @@ local TSCallbackNames = {
---List of regions this tree should manage and parse. If nil then regions are
---taken from _trees. This is mostly a short-lived cache for included_regions()
---@field private _lang string Language name
----@field private _parent_lang? string Parent language name
+---@field private _parent? vim.treesitter.LanguageTree Parent LanguageTree
---@field private _source (integer|string) Buffer or string to parse
---@field private _trees table<integer, TSTree> Reference to parsed tree (one for each language).
---Each key is the index of region, which is synced with _regions and _valid.
@@ -89,9 +90,11 @@ local TSCallbackNames = {
---@field private _logfile? file*
local LanguageTree = {}
----@class LanguageTreeOpts
----@field queries table<string,string> -- Deprecated
----@field injections table<string,string>
+---Optional arguments:
+---@class vim.treesitter.LanguageTree.new.Opts
+---@inlinedoc
+---@field queries? table<string,string> -- Deprecated
+---@field injections? table<string,string>
LanguageTree.__index = LanguageTree
@@ -102,14 +105,10 @@ LanguageTree.__index = LanguageTree
---
---@param source (integer|string) Buffer or text string to parse
---@param lang string Root language of this tree
----@param opts (table|nil) Optional arguments:
---- - injections table Map of language to injection query strings. Overrides the
---- built-in runtime file searching for language injections.
----@param parent_lang? string Parent language name of this tree
----@return LanguageTree parser object
-function LanguageTree.new(source, lang, opts, parent_lang)
+---@param opts vim.treesitter.LanguageTree.new.Opts?
+---@return vim.treesitter.LanguageTree parser object
+function LanguageTree.new(source, lang, opts)
language.add(lang)
- ---@type LanguageTreeOpts
opts = opts or {}
if source == 0 then
@@ -118,11 +117,10 @@ function LanguageTree.new(source, lang, opts, parent_lang)
local injections = opts.injections or {}
- --- @type LanguageTree
+ --- @type vim.treesitter.LanguageTree
local self = {
_source = source,
_lang = lang,
- _parent_lang = parent_lang,
_children = {},
_trees = {},
_opts = opts,
@@ -194,7 +192,7 @@ local function tcall(f, ...)
end
---@private
----@vararg any
+---@param ... any
function LanguageTree:_log(...)
if not self._logger then
return
@@ -464,7 +462,7 @@ end
--- add recursion yourself if needed.
--- Invokes the callback for each |LanguageTree| and its children recursively
---
----@param fn fun(tree: LanguageTree, lang: string)
+---@param fn fun(tree: vim.treesitter.LanguageTree, lang: string)
---@param include_self boolean|nil Whether to include the invoking tree in the results
function LanguageTree:for_each_child(fn, include_self)
vim.deprecate('LanguageTree:for_each_child()', 'LanguageTree:children()', '0.11')
@@ -473,6 +471,7 @@ function LanguageTree:for_each_child(fn, include_self)
end
for _, child in pairs(self._children) do
+ --- @diagnostic disable-next-line:deprecated
child:for_each_child(fn, true)
end
end
@@ -481,7 +480,7 @@ end
---
--- Note: This includes the invoking tree's child trees as well.
---
----@param fn fun(tree: TSTree, ltree: LanguageTree)
+---@param fn fun(tree: TSTree, ltree: vim.treesitter.LanguageTree)
function LanguageTree:for_each_tree(fn)
for _, tree in pairs(self._trees) do
fn(tree, self)
@@ -498,25 +497,31 @@ end
---
---@private
---@param lang string Language to add.
----@return LanguageTree injected
+---@return vim.treesitter.LanguageTree injected
function LanguageTree:add_child(lang)
if self._children[lang] then
self:remove_child(lang)
end
- local child = LanguageTree.new(self._source, lang, self._opts, self:lang())
+ local child = LanguageTree.new(self._source, lang, self._opts)
-- Inherit recursive callbacks
for nm, cb in pairs(self._callbacks_rec) do
vim.list_extend(child._callbacks_rec[nm], cb)
end
+ child._parent = self
self._children[lang] = child
self:_do_callback('child_added', self._children[lang])
return self._children[lang]
end
+--- @package
+function LanguageTree:parent()
+ return self._parent
+end
+
--- Removes a child language from this |LanguageTree|.
---
---@private
@@ -668,7 +673,7 @@ end
---@param node TSNode
---@param source string|integer
----@param metadata TSMetadata
+---@param metadata vim.treesitter.query.TSMetadata
---@param include_children boolean
---@return Range6[]
local function get_node_ranges(node, source, metadata, include_children)
@@ -702,13 +707,14 @@ local function get_node_ranges(node, source, metadata, include_children)
return ranges
end
----@class TSInjectionElem
+---@nodoc
+---@class vim.treesitter.languagetree.InjectionElem
---@field combined boolean
---@field regions Range6[][]
----@alias TSInjection table<string,table<integer,TSInjectionElem>>
+---@alias vim.treesitter.languagetree.Injection table<string,table<integer,vim.treesitter.languagetree.InjectionElem>>
----@param t table<integer,TSInjection>
+---@param t table<integer,vim.treesitter.languagetree.Injection>
---@param tree_index integer
---@param pattern integer
---@param lang string
@@ -783,14 +789,14 @@ end
--- Extract injections according to:
--- https://tree-sitter.github.io/tree-sitter/syntax-highlighting#language-injection
---@param match table<integer,TSNode[]>
----@param metadata TSMetadata
+---@param metadata vim.treesitter.query.TSMetadata
---@return string?, boolean, Range6[]
function LanguageTree:_get_injection(match, metadata)
local ranges = {} ---@type Range6[]
local combined = metadata['injection.combined'] ~= nil
local injection_lang = metadata['injection.language'] --[[@as string?]]
local lang = metadata['injection.self'] ~= nil and self:lang()
- or metadata['injection.parent'] ~= nil and self._parent_lang
+ or metadata['injection.parent'] ~= nil and self._parent
or (injection_lang and resolve_lang(injection_lang))
local include_children = metadata['injection.include-children'] ~= nil
@@ -836,7 +842,7 @@ function LanguageTree:_get_injections()
return {}
end
- ---@type table<integer,TSInjection>
+ ---@type table<integer,vim.treesitter.languagetree.Injection>
local injections = {}
for index, tree in pairs(self._trees) do
@@ -1150,7 +1156,7 @@ end
--- Gets the appropriate language that contains {range}.
---
---@param range Range4 `{ start_line, start_col, end_line, end_col }`
----@return LanguageTree Managing {range}
+---@return vim.treesitter.LanguageTree Managing {range}
function LanguageTree:language_for_range(range)
for _, child in pairs(self._children) do
if child:contains(range) then
diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua
index 57272dbd60..a086f5e876 100644
--- a/runtime/lua/vim/treesitter/query.lua
+++ b/runtime/lua/vim/treesitter/query.lua
@@ -3,6 +3,7 @@ local language = require('vim.treesitter.language')
local M = {}
+---@nodoc
---Parsed query, see |vim.treesitter.query.parse()|
---
---@class vim.treesitter.Query
@@ -31,6 +32,7 @@ function Query.new(lang, ts_query)
return self
end
+---@nodoc
---Information for Query, see |vim.treesitter.query.parse()|
---@class vim.treesitter.QueryInfo
---
@@ -82,16 +84,6 @@ local function add_included_lang(base_langs, lang, ilang)
return false
end
----@deprecated
-function M.get_query_files(...)
- vim.deprecate(
- 'vim.treesitter.query.get_query_files()',
- 'vim.treesitter.query.get_files()',
- '0.10'
- )
- return M.get_files(...)
-end
-
--- Gets the list of files used to make up a query
---
---@param lang string Language to get query for
@@ -202,12 +194,6 @@ local explicit_queries = setmetatable({}, {
end,
})
----@deprecated
-function M.set_query(...)
- vim.deprecate('vim.treesitter.query.set_query()', 'vim.treesitter.query.set()', '0.10')
- M.set(...)
-end
-
--- Sets the runtime query named {query_name} for {lang}
---
--- This allows users to override any runtime files and/or configuration
@@ -220,12 +206,6 @@ function M.set(lang, query_name, text)
explicit_queries[lang][query_name] = M.parse(lang, text)
end
----@deprecated
-function M.get_query(...)
- vim.deprecate('vim.treesitter.query.get_query()', 'vim.treesitter.query.get()', '0.10')
- return M.get(...)
-end
-
--- Returns the runtime query {query_name} for {lang}.
---
---@param lang string Language to use for the query
@@ -247,12 +227,6 @@ M.get = vim.func._memoize('concat-2', function(lang, query_name)
return M.parse(lang, query_string)
end)
----@deprecated
-function M.parse_query(...)
- vim.deprecate('vim.treesitter.query.parse_query()', 'vim.treesitter.query.parse()', '0.10')
- return M.parse(...)
-end
-
--- Parse {query} as a string. (If the query is in a file, the caller
--- should read the contents into a string before calling).
---
@@ -278,25 +252,13 @@ M.parse = vim.func._memoize('concat-2', function(lang, query)
return Query.new(lang, ts_query)
end)
----@deprecated
-function M.get_range(...)
- vim.deprecate('vim.treesitter.query.get_range()', 'vim.treesitter.get_range()', '0.10')
- return vim.treesitter.get_range(...)
-end
-
----@deprecated
-function M.get_node_text(...)
- vim.deprecate('vim.treesitter.query.get_node_text()', 'vim.treesitter.get_node_text()', '0.10')
- return vim.treesitter.get_node_text(...)
-end
-
--- Implementations of predicates that can optionally be prefixed with "any-".
---
--- These functions contain the implementations for each predicate, correctly
--- handling the "any" vs "all" semantics. They are called from the
--- predicate_handlers table with the appropriate arguments for each predicate.
local impl = {
- --- @param match TSMatch
+ --- @param match vim.treesitter.query.TSMatch
--- @param source integer|string
--- @param predicate any[]
--- @param any boolean
@@ -331,7 +293,7 @@ local impl = {
return not any
end,
- --- @param match TSMatch
+ --- @param match vim.treesitter.query.TSMatch
--- @param source integer|string
--- @param predicate any[]
--- @param any boolean
@@ -371,7 +333,7 @@ local impl = {
end,
})
- --- @param match TSMatch
+ --- @param match vim.treesitter.query.TSMatch
--- @param source integer|string
--- @param predicate any[]
--- @param any boolean
@@ -394,7 +356,7 @@ local impl = {
end
end)(),
- --- @param match TSMatch
+ --- @param match vim.treesitter.query.TSMatch
--- @param source integer|string
--- @param predicate any[]
--- @param any boolean
@@ -421,12 +383,13 @@ local impl = {
end,
}
----@class TSMatch
+---@nodoc
+---@class vim.treesitter.query.TSMatch
---@field pattern? integer
---@field active? boolean
---@field [integer] TSNode[]
----@alias TSPredicate fun(match: TSMatch, pattern: integer, source: integer|string, predicate: any[]): boolean
+---@alias TSPredicate fun(match: vim.treesitter.query.TSMatch, pattern: integer, source: integer|string, predicate: any[]): boolean
-- Predicate handler receive the following arguments
-- (match, pattern, bufnr, predicate)
@@ -534,13 +497,14 @@ local predicate_handlers = {
predicate_handlers['vim-match?'] = predicate_handlers['match?']
predicate_handlers['any-vim-match?'] = predicate_handlers['any-match?']
----@class TSMetadata
+---@nodoc
+---@class vim.treesitter.query.TSMetadata
---@field range? Range
---@field conceal? string
----@field [integer] TSMetadata
+---@field [integer] vim.treesitter.query.TSMetadata
---@field [string] integer|string
----@alias TSDirective fun(match: TSMatch, _, _, predicate: (string|integer)[], metadata: TSMetadata)
+---@alias TSDirective fun(match: vim.treesitter.query.TSMatch, _, _, predicate: (string|integer)[], metadata: vim.treesitter.query.TSMetadata)
-- Predicate handler receive the following arguments
-- (match, pattern, bufnr, predicate)
@@ -767,7 +731,7 @@ local function is_directive(name)
end
---@private
----@param match TSMatch
+---@param match vim.treesitter.query.TSMatch
---@param pattern integer
---@param source integer|string
function Query:match_preds(match, pattern, source)
@@ -806,8 +770,8 @@ function Query:match_preds(match, pattern, source)
end
---@private
----@param match TSMatch
----@param metadata TSMetadata
+---@param match vim.treesitter.query.TSMatch
+---@param metadata vim.treesitter.query.TSMetadata
function Query:apply_directives(match, pattern, source, metadata)
local preds = self.info.patterns[pattern]
@@ -871,7 +835,7 @@ end
---@param start? integer Starting line for the search. Defaults to `node:start()`.
---@param stop? integer Stopping line for the search (end-exclusive). Defaults to `node:end_()`.
---
----@return (fun(end_line: integer|nil): integer, TSNode, TSMetadata):
+---@return (fun(end_line: integer|nil): integer, TSNode, vim.treesitter.query.TSMetadata):
--- capture id, capture node, metadata
function Query:iter_captures(node, source, start, stop)
if type(source) == 'number' and source == 0 then
@@ -880,7 +844,7 @@ function Query:iter_captures(node, source, start, stop)
start, stop = value_or_node_range(start, stop, node)
- local raw_iter = node:_rawquery(self.query, true, start, stop) ---@type fun(): integer, TSNode, TSMatch
+ local raw_iter = node:_rawquery(self.query, true, start, stop) ---@type fun(): integer, TSNode, vim.treesitter.query.TSMatch
local function iter(end_line)
local capture, captured_node, match = raw_iter()
local metadata = {}
@@ -952,7 +916,7 @@ function Query:iter_matches(node, source, start, stop, opts)
start, stop = value_or_node_range(start, stop, node)
- local raw_iter = node:_rawquery(self.query, false, start, stop, opts) ---@type fun(): integer, TSMatch
+ local raw_iter = node:_rawquery(self.query, false, start, stop, opts) ---@type fun(): integer, vim.treesitter.query.TSMatch
local function iter()
local pattern, match = raw_iter()
local metadata = {}
@@ -982,9 +946,16 @@ function Query:iter_matches(node, source, start, stop, opts)
return iter
end
----@class QueryLinterOpts
----@field langs (string|string[]|nil)
----@field clear (boolean)
+--- Optional keyword arguments:
+--- @class vim.treesitter.query.lint.Opts
+--- @inlinedoc
+---
+--- Language(s) to use for checking the query.
+--- If multiple languages are specified, queries are validated for all of them
+--- @field langs? string|string[]
+---
+--- Just clear current lint errors
+--- @field clear boolean
--- Lint treesitter queries using installed parser, or clear lint errors.
---
@@ -999,10 +970,7 @@ end
--- of the query file, e.g., if the path ends in `/lua/highlights.scm`, the parser for the
--- `lua` language will be used.
---@param buf (integer) Buffer handle
----@param opts? QueryLinterOpts (table) Optional keyword arguments:
---- - langs (string|string[]|nil) Language(s) to use for checking the query.
---- If multiple languages are specified, queries are validated for all of them
---- - clear (boolean) if `true`, just clear current lint errors
+---@param opts? vim.treesitter.query.lint.Opts
function M.lint(buf, opts)
if opts and opts.clear then
vim.treesitter._query_linter.clear(buf)
@@ -1027,7 +995,7 @@ end
--- Opens a live editor to query the buffer you started from.
---
---- Can also be shown with *:EditQuery*.
+--- Can also be shown with [:EditQuery]().
---
--- If you move the cursor to a capture name ("@foo"), text matching the capture is highlighted in
--- the source buffer. The query editor is a scratch buffer, use `:write` to save it. You can find
diff --git a/runtime/lua/vim/version.lua b/runtime/lua/vim/version.lua
index 09a6fa825b..0b149700b5 100644
--- a/runtime/lua/vim/version.lua
+++ b/runtime/lua/vim/version.lua
@@ -12,9 +12,9 @@
--- end
--- ```
---
---- *vim.version()* returns the version of the current Nvim process.
+--- [vim.version()]() returns the version of the current Nvim process.
---
---- VERSION RANGE SPEC *version-range*
+--- VERSION RANGE SPEC [version-range]()
---
--- A version "range spec" defines a semantic version range which can be tested against a version,
--- using |vim.version.range()|.
@@ -54,7 +54,8 @@
local M = {}
----@class Version
+---@nodoc
+---@class vim.Version
---@field [1] number
---@field [2] number
---@field [3] number
@@ -111,7 +112,7 @@ function Version:__newindex(key, value)
end
end
----@param other Version
+---@param other vim.Version
function Version:__eq(other)
for i = 1, 3 do
if self[i] ~= other[i] then
@@ -132,7 +133,7 @@ function Version:__tostring()
return ret
end
----@param other Version
+---@param other vim.Version
function Version:__lt(other)
for i = 1, 3 do
if self[i] > other[i] then
@@ -144,7 +145,7 @@ function Version:__lt(other)
return -1 == cmp_prerel(self.prerelease, other.prerelease)
end
----@param other Version
+---@param other vim.Version
function Version:__le(other)
return self < other or self == other
end
@@ -153,9 +154,9 @@ end
---
--- Creates a new Version object, or returns `nil` if `version` is invalid.
---
---- @param version string|number[]|Version
+--- @param version string|number[]|vim.Version
--- @param strict? boolean Reject "1.0", "0-x", "3.2a" or other non-conforming version strings
---- @return Version?
+--- @return vim.Version?
function M._version(version, strict) -- Adapted from https://github.com/folke/lazy.nvim
if type(version) == 'table' then
if version.major then
@@ -203,7 +204,7 @@ end
---TODO: generalize this, move to func.lua
---
----@generic T: Version
+---@generic T: vim.Version
---@param versions T[]
---@return T?
function M.last(versions)
@@ -216,14 +217,15 @@ function M.last(versions)
return last
end
----@class VersionRange
----@field from Version
----@field to? Version
+---@class vim.VersionRange
+---@inlinedoc
+---@field from vim.Version
+---@field to? vim.Version
local VersionRange = {}
--- @private
---
----@param version string|Version
+---@param version string|vim.Version
function VersionRange:has(version)
if type(version) == 'string' then
---@diagnostic disable-next-line: cast-local-type
@@ -272,6 +274,7 @@ end
--- @see # https://github.com/npm/node-semver#ranges
---
--- @param spec string Version range "spec"
+--- @return vim.VersionRange?
function M.range(spec) -- Adapted from https://github.com/folke/lazy.nvim
if spec == '*' or spec == '' then
return setmetatable({ from = M.parse('0.0.0') }, { __index = VersionRange })
@@ -300,8 +303,8 @@ function M.range(spec) -- Adapted from https://github.com/folke/lazy.nvim
local semver = M.parse(version)
if semver then
- local from = semver --- @type Version?
- local to = vim.deepcopy(semver, true) --- @type Version?
+ local from = semver --- @type vim.Version?
+ local to = vim.deepcopy(semver, true) --- @type vim.Version?
---@diagnostic disable: need-check-nil
if mods == '' or mods == '=' then
to.patch = to.patch + 1
@@ -340,7 +343,7 @@ function M.range(spec) -- Adapted from https://github.com/folke/lazy.nvim
end
end
----@param v string|Version
+---@param v string|vim.Version
---@return string
local function create_err_msg(v)
if type(v) == 'string' then
@@ -369,8 +372,8 @@ end
---
--- @note Per semver, build metadata is ignored when comparing two otherwise-equivalent versions.
---
----@param v1 Version|number[]|string Version object.
----@param v2 Version|number[]|string Version to compare with `v1`.
+---@param v1 vim.Version|number[]|string Version object.
+---@param v2 vim.Version|number[]|string Version to compare with `v1`.
---@return integer -1 if `v1 < v2`, 0 if `v1 == v2`, 1 if `v1 > v2`.
function M.cmp(v1, v2)
local v1_parsed = assert(M._version(v1), create_err_msg(v1))
@@ -385,40 +388,40 @@ function M.cmp(v1, v2)
end
---Returns `true` if the given versions are equal. See |vim.version.cmp()| for usage.
----@param v1 Version|number[]|string
----@param v2 Version|number[]|string
+---@param v1 vim.Version|number[]|string
+---@param v2 vim.Version|number[]|string
---@return boolean
function M.eq(v1, v2)
return M.cmp(v1, v2) == 0
end
---Returns `true` if `v1 <= v2`. See |vim.version.cmp()| for usage.
----@param v1 Version|number[]|string
----@param v2 Version|number[]|string
+---@param v1 vim.Version|number[]|string
+---@param v2 vim.Version|number[]|string
---@return boolean
function M.le(v1, v2)
return M.cmp(v1, v2) <= 0
end
---Returns `true` if `v1 < v2`. See |vim.version.cmp()| for usage.
----@param v1 Version|number[]|string
----@param v2 Version|number[]|string
+---@param v1 vim.Version|number[]|string
+---@param v2 vim.Version|number[]|string
---@return boolean
function M.lt(v1, v2)
return M.cmp(v1, v2) == -1
end
---Returns `true` if `v1 >= v2`. See |vim.version.cmp()| for usage.
----@param v1 Version|number[]|string
----@param v2 Version|number[]|string
+---@param v1 vim.Version|number[]|string
+---@param v2 vim.Version|number[]|string
---@return boolean
function M.ge(v1, v2)
return M.cmp(v1, v2) >= 0
end
---Returns `true` if `v1 > v2`. See |vim.version.cmp()| for usage.
----@param v1 Version|number[]|string
----@param v2 Version|number[]|string
+---@param v1 vim.Version|number[]|string
+---@param v2 vim.Version|number[]|string
---@return boolean
function M.gt(v1, v2)
return M.cmp(v1, v2) == 1
@@ -438,7 +441,7 @@ end
--- - strict (boolean): Default false. If `true`, no coercion is attempted on
--- input not conforming to semver v2.0.0. If `false`, `parse()` attempts to
--- coerce input such as "1.0", "0-x", "tmux 3.2a" into valid versions.
----@return Version? parsed_version Version object or `nil` if input is invalid.
+---@return vim.Version? parsed_version Version object or `nil` if input is invalid.
function M.parse(version, opts)
assert(type(version) == 'string', create_err_msg(version))
opts = opts or { strict = false }
@@ -447,9 +450,9 @@ end
setmetatable(M, {
--- Returns the current Nvim version.
- ---@return Version
+ ---@return vim.Version
__call = function()
- local version = vim.fn.api_info().version ---@type Version
+ local version = vim.fn.api_info().version ---@type vim.Version
-- Workaround: vim.fn.api_info().version reports "prerelease" as a boolean.
version.prerelease = version.prerelease and 'dev' or nil
return setmetatable(version, Version)