aboutsummaryrefslogtreecommitdiff
path: root/runtime/lua/vim
diff options
context:
space:
mode:
Diffstat (limited to 'runtime/lua/vim')
-rw-r--r--runtime/lua/vim/F.lua5
-rw-r--r--runtime/lua/vim/_defaults.lua386
-rw-r--r--runtime/lua/vim/_editor.lua135
-rw-r--r--runtime/lua/vim/_init_packages.lua10
-rw-r--r--runtime/lua/vim/_inspector.lua35
-rw-r--r--runtime/lua/vim/_meta.lua4
-rw-r--r--runtime/lua/vim/_meta/api.lua1021
-rw-r--r--runtime/lua/vim/_meta/api_keysets.lua101
-rw-r--r--runtime/lua/vim/_meta/api_keysets_extra.lua167
-rw-r--r--runtime/lua/vim/_meta/builtin.lua133
-rw-r--r--runtime/lua/vim/_meta/builtin_types.lua6
-rw-r--r--runtime/lua/vim/_meta/json.lua2
-rw-r--r--runtime/lua/vim/_meta/lpeg.lua103
-rw-r--r--runtime/lua/vim/_meta/misc.lua3
-rw-r--r--runtime/lua/vim/_meta/mpack.lua5
-rw-r--r--runtime/lua/vim/_meta/options.lua736
-rw-r--r--runtime/lua/vim/_meta/re.lua54
-rw-r--r--runtime/lua/vim/_meta/regex.lua3
-rw-r--r--runtime/lua/vim/_meta/vimfn.lua268
-rw-r--r--runtime/lua/vim/_meta/vvars.lua779
-rw-r--r--runtime/lua/vim/_options.lua433
-rw-r--r--runtime/lua/vim/_system.lua12
-rw-r--r--runtime/lua/vim/_watch.lua304
-rw-r--r--runtime/lua/vim/diagnostic.lua1010
-rw-r--r--runtime/lua/vim/filetype.lua54
-rw-r--r--runtime/lua/vim/filetype/detect.lua78
-rw-r--r--runtime/lua/vim/fs.lua74
-rw-r--r--runtime/lua/vim/glob.lua84
-rw-r--r--runtime/lua/vim/health.lua235
-rw-r--r--runtime/lua/vim/highlight.lua37
-rw-r--r--runtime/lua/vim/iter.lua274
-rw-r--r--runtime/lua/vim/keymap.lua4
-rw-r--r--runtime/lua/vim/loader.lua90
-rw-r--r--runtime/lua/vim/lsp.lua1769
-rw-r--r--runtime/lua/vim/lsp/_changetracking.lua373
-rw-r--r--runtime/lua/vim/lsp/_completion.lua52
-rw-r--r--runtime/lua/vim/lsp/_dynamic.lua31
-rw-r--r--runtime/lua/vim/lsp/_meta.lua10
-rw-r--r--runtime/lua/vim/lsp/_meta/protocol.lua1320
-rw-r--r--runtime/lua/vim/lsp/_tagfunc.lua (renamed from runtime/lua/vim/lsp/tagfunc.lua)0
-rw-r--r--runtime/lua/vim/lsp/_watchfiles.lua118
-rw-r--r--runtime/lua/vim/lsp/buf.lua279
-rw-r--r--runtime/lua/vim/lsp/client.lua1056
-rw-r--r--runtime/lua/vim/lsp/codelens.lua45
-rw-r--r--runtime/lua/vim/lsp/diagnostic.lua208
-rw-r--r--runtime/lua/vim/lsp/handlers.lua212
-rw-r--r--runtime/lua/vim/lsp/health.lua46
-rw-r--r--runtime/lua/vim/lsp/inlay_hint.lua72
-rw-r--r--runtime/lua/vim/lsp/log.lua214
-rw-r--r--runtime/lua/vim/lsp/protocol.lua150
-rw-r--r--runtime/lua/vim/lsp/rpc.lua456
-rw-r--r--runtime/lua/vim/lsp/semantic_tokens.lua90
-rw-r--r--runtime/lua/vim/lsp/sync.lua57
-rw-r--r--runtime/lua/vim/lsp/util.lua274
-rw-r--r--runtime/lua/vim/provider.lua7
-rw-r--r--runtime/lua/vim/provider/perl.lua66
-rw-r--r--runtime/lua/vim/provider/python.lua150
-rw-r--r--runtime/lua/vim/provider/ruby.lua61
-rw-r--r--runtime/lua/vim/re.lua1
-rw-r--r--runtime/lua/vim/secure.lua25
-rw-r--r--runtime/lua/vim/shared.lua330
-rw-r--r--runtime/lua/vim/snippet.lua49
-rw-r--r--runtime/lua/vim/termcap.lua59
-rw-r--r--runtime/lua/vim/text.lua2
-rw-r--r--runtime/lua/vim/treesitter.lua164
-rw-r--r--runtime/lua/vim/treesitter/_fold.lua300
-rw-r--r--runtime/lua/vim/treesitter/_meta.lua28
-rw-r--r--runtime/lua/vim/treesitter/_query_linter.lua46
-rw-r--r--runtime/lua/vim/treesitter/dev.lua228
-rw-r--r--runtime/lua/vim/treesitter/health.lua2
-rw-r--r--runtime/lua/vim/treesitter/highlighter.lua194
-rw-r--r--runtime/lua/vim/treesitter/language.lua40
-rw-r--r--runtime/lua/vim/treesitter/languagetree.lua95
-rw-r--r--runtime/lua/vim/treesitter/query.lua602
-rw-r--r--runtime/lua/vim/ui.lua17
-rw-r--r--runtime/lua/vim/ui/clipboard/osc52.lua4
-rw-r--r--runtime/lua/vim/uri.lua19
-rw-r--r--runtime/lua/vim/userreg.lua51
-rw-r--r--runtime/lua/vim/version.lua101
-rw-r--r--runtime/lua/vim/vimhelp.lua36
80 files changed, 10479 insertions, 5675 deletions
diff --git a/runtime/lua/vim/F.lua b/runtime/lua/vim/F.lua
index 5ed60ca8ab..8eb17a4c95 100644
--- a/runtime/lua/vim/F.lua
+++ b/runtime/lua/vim/F.lua
@@ -14,8 +14,9 @@ local F = {}
--- assert(vim.F.if_nil(a, b, c, d) == 42)
--- ```
---
----@param ... any
----@return any
+---@generic T
+---@param ... T
+---@return T
function F.if_nil(...)
local nargs = select('#', ...)
for i = 1, nargs do
diff --git a/runtime/lua/vim/_defaults.lua b/runtime/lua/vim/_defaults.lua
index cc872dea83..91baee1a1e 100644
--- a/runtime/lua/vim/_defaults.lua
+++ b/runtime/lua/vim/_defaults.lua
@@ -4,27 +4,10 @@ do
---
--- See |v_star-default| and |v_#-default|
do
- local function region_chunks(region)
- local chunks = {}
- local maxcol = vim.v.maxcol
- for line, cols in vim.spairs(region) do
- local endcol = cols[2] == maxcol and -1 or cols[2]
- local chunk = vim.api.nvim_buf_get_text(0, line, cols[1], line, endcol, {})[1]
- table.insert(chunks, chunk)
- end
- return chunks
- end
-
local function _visual_search(cmd)
assert(cmd == '/' or cmd == '?')
- local region = vim.region(
- 0,
- '.',
- 'v',
- vim.api.nvim_get_mode().mode:sub(1, 1),
- vim.o.selection == 'inclusive'
- )
- local chunks = region_chunks(region)
+ local chunks =
+ vim.fn.getregion(vim.fn.getpos('.'), vim.fn.getpos('v'), { type = vim.fn.mode() })
local esc_chunks = vim
.iter(chunks)
:map(function(v)
@@ -67,17 +50,23 @@ do
--- See |&-default|
vim.keymap.set('n', '&', ':&&<CR>', { desc = ':help &-default' })
+ --- Use Q in visual mode to execute a macro on each line of the selection. #21422
+ ---
+ --- Applies to @x and includes @@ too.
+ vim.keymap.set(
+ 'x',
+ 'Q',
+ ':normal! @<C-R>=reg_recorded()<CR><CR>',
+ { silent = true, desc = ':help v_Q-default' }
+ )
+ vim.keymap.set(
+ 'x',
+ '@',
+ "':normal! @'.getcharstr().'<CR>'",
+ { silent = true, expr = true, desc = ':help v_@-default' }
+ )
--- Map |gx| to call |vim.ui.open| on the identifier under the cursor
do
- -- TODO: use vim.region() when it lands... #13896 #16843
- local function get_visual_selection()
- local save_a = vim.fn.getreginfo('a')
- vim.cmd([[norm! "ay]])
- local selection = vim.fn.getreg('a', 1)
- vim.fn.setreg('a', save_a)
- return selection
- end
-
local function do_open(uri)
local _, err = vim.ui.open(uri)
if err then
@@ -91,7 +80,10 @@ do
do_open(vim.fn.expand('<cfile>'))
end, { desc = gx_desc })
vim.keymap.set({ 'x' }, 'gx', function()
- do_open(get_visual_selection())
+ local lines =
+ vim.fn.getregion(vim.fn.getpos('.'), vim.fn.getpos('v'), { type = vim.fn.mode() })
+ -- Trim whitespace on each line and concatenate.
+ do_open(table.concat(vim.iter(lines):map(vim.trim):totable()))
end, { desc = gx_desc })
end
end
@@ -128,14 +120,43 @@ do
vim.api.nvim_create_autocmd({ 'TermClose' }, {
group = nvim_terminal_augroup,
+ nested = true,
desc = 'Automatically close terminal buffers when started with no arguments and exiting without an error',
callback = function(args)
- if vim.v.event.status == 0 then
- local info = vim.api.nvim_get_chan_info(vim.bo[args.buf].channel)
- local argv = info.argv or {}
- if #argv == 1 and argv[1] == vim.o.shell then
- vim.cmd({ cmd = 'bdelete', args = { args.buf }, bang = true })
+ if vim.v.event.status ~= 0 then
+ return
+ end
+ local info = vim.api.nvim_get_chan_info(vim.bo[args.buf].channel)
+ local argv = info.argv or {}
+ if #argv == 1 and argv[1] == vim.o.shell then
+ vim.api.nvim_buf_delete(args.buf, { force = true })
+ end
+ end,
+ })
+
+ vim.api.nvim_create_autocmd('TermRequest', {
+ group = nvim_terminal_augroup,
+ desc = 'Respond to OSC foreground/background color requests',
+ callback = function(args)
+ local channel = vim.bo[args.buf].channel
+ if channel == 0 then
+ return
+ end
+ local fg_request = args.data == '\027]10;?'
+ local bg_request = args.data == '\027]11;?'
+ if fg_request or bg_request then
+ -- WARN: This does not return the actual foreground/background color,
+ -- but rather returns:
+ -- - fg=white/bg=black when Nvim option 'background' is 'dark'
+ -- - fg=black/bg=white when Nvim option 'background' is 'light'
+ local red, green, blue = 0, 0, 0
+ local bg_option_dark = vim.o.background == 'dark'
+ if (fg_request and bg_option_dark) or (bg_request and not bg_option_dark) then
+ red, green, blue = 65535, 65535, 65535
end
+ local command = fg_request and 10 or 11
+ local data = string.format('\027]%d;rgb:%04x/%04x/%04x\007', command, red, green, blue)
+ vim.api.nvim_chan_send(channel, data)
end
end,
})
@@ -165,118 +186,125 @@ do
})
end
---- Guess value of 'background' based on terminal color.
----
---- We write Operating System Command (OSC) 11 to the terminal to request the
---- terminal's background color. We then wait for a response. If the response
---- matches `rgba:RRRR/GGGG/BBBB/AAAA` where R, G, B, and A are hex digits, then
---- compute the luminance[1] of the RGB color and classify it as light/dark
---- accordingly. Note that the color components may have anywhere from one to
---- four hex digits, and require scaling accordingly as values out of 4, 8, 12,
---- or 16 bits. Also note the A(lpha) component is optional, and is parsed but
---- ignored in the calculations.
----
---- [1] https://en.wikipedia.org/wiki/Luma_%28video%29
-do
- --- Parse a string of hex characters as a color.
- ---
- --- The string can contain 1 to 4 hex characters. The returned value is
- --- between 0.0 and 1.0 (inclusive) representing the intensity of the color.
- ---
- --- For instance, if only a single hex char "a" is used, then this function
- --- returns 0.625 (10 / 16), while a value of "aa" would return 0.664 (170 /
- --- 256).
+-- Only do the following when the TUI is attached
+local tty = nil
+for _, ui in ipairs(vim.api.nvim_list_uis()) do
+ if ui.chan == 1 and ui.stdout_tty then
+ tty = ui
+ break
+ end
+end
+
+if tty then
+ local group = vim.api.nvim_create_augroup('nvim_tty', {})
+
+ --- Set an option after startup (so that OptionSet is fired), but only if not
+ --- already set by the user.
---
- --- @param c string Color as a string of hex chars
- --- @return number? Intensity of the color
- local function parsecolor(c)
- if #c == 0 or #c > 4 then
- return nil
+ --- @param option string Option name
+ --- @param value any Option value
+ local function setoption(option, value)
+ if vim.api.nvim_get_option_info2(option, {}).was_set then
+ -- Don't do anything if option is already set
+ return
end
- local val = tonumber(c, 16)
- if not val then
- return nil
+ -- Wait until Nvim is finished starting to set the option to ensure the
+ -- OptionSet event fires.
+ if vim.v.vim_did_enter == 1 then
+ vim.o[option] = value
+ else
+ vim.api.nvim_create_autocmd('VimEnter', {
+ group = group,
+ once = true,
+ nested = true,
+ callback = function()
+ setoption(option, value)
+ end,
+ })
end
-
- local max = tonumber(string.rep('f', #c), 16)
- return val / max
end
- --- Parse an OSC 11 response
- ---
- --- Either of the two formats below are accepted:
- ---
- --- OSC 11 ; rgb:<red>/<green>/<blue>
+ --- Guess value of 'background' based on terminal color.
---
- --- or
+ --- We write Operating System Command (OSC) 11 to the terminal to request the
+ --- terminal's background color. We then wait for a response. If the response
+ --- matches `rgba:RRRR/GGGG/BBBB/AAAA` where R, G, B, and A are hex digits, then
+ --- compute the luminance[1] of the RGB color and classify it as light/dark
+ --- accordingly. Note that the color components may have anywhere from one to
+ --- four hex digits, and require scaling accordingly as values out of 4, 8, 12,
+ --- or 16 bits. Also note the A(lpha) component is optional, and is parsed but
+ --- ignored in the calculations.
---
- --- OSC 11 ; rgba:<red>/<green>/<blue>/<alpha>
- ---
- --- where
- ---
- --- <red>, <green>, <blue>, <alpha> := h | hh | hhh | hhhh
- ---
- --- The alpha component is ignored, if present.
- ---
- --- @param resp string OSC 11 response
- --- @return string? Red component
- --- @return string? Green component
- --- @return string? Blue component
- local function parseosc11(resp)
- local r, g, b
- r, g, b = resp:match('^\027%]11;rgb:(%x+)/(%x+)/(%x+)$')
- if not r and not g and not b then
- local a
- r, g, b, a = resp:match('^\027%]11;rgba:(%x+)/(%x+)/(%x+)/(%x+)$')
- if not a or #a > 4 then
- return nil, nil, nil
+ --- [1] https://en.wikipedia.org/wiki/Luma_%28video%29
+ do
+ --- Parse a string of hex characters as a color.
+ ---
+ --- The string can contain 1 to 4 hex characters. The returned value is
+ --- between 0.0 and 1.0 (inclusive) representing the intensity of the color.
+ ---
+ --- For instance, if only a single hex char "a" is used, then this function
+ --- returns 0.625 (10 / 16), while a value of "aa" would return 0.664 (170 /
+ --- 256).
+ ---
+ --- @param c string Color as a string of hex chars
+ --- @return number? Intensity of the color
+ local function parsecolor(c)
+ if #c == 0 or #c > 4 then
+ return nil
end
- end
-
- if r and g and b and #r <= 4 and #g <= 4 and #b <= 4 then
- return r, g, b
- end
- return nil, nil, nil
- end
+ local val = tonumber(c, 16)
+ if not val then
+ return nil
+ end
- local tty = false
- for _, ui in ipairs(vim.api.nvim_list_uis()) do
- if ui.chan == 1 and ui.stdout_tty then
- tty = true
- break
+ local max = tonumber(string.rep('f', #c), 16)
+ return val / max
end
- end
-
- if tty then
- local timer = assert(vim.uv.new_timer())
- ---@param bg string New value of the 'background' option
- local function setbg(bg)
- if vim.api.nvim_get_option_info2('background', {}).was_set then
- -- Don't do anything if 'background' is already set
- return
+ --- Parse an OSC 11 response
+ ---
+ --- Either of the two formats below are accepted:
+ ---
+ --- OSC 11 ; rgb:<red>/<green>/<blue>
+ ---
+ --- or
+ ---
+ --- OSC 11 ; rgba:<red>/<green>/<blue>/<alpha>
+ ---
+ --- where
+ ---
+ --- <red>, <green>, <blue>, <alpha> := h | hh | hhh | hhhh
+ ---
+ --- The alpha component is ignored, if present.
+ ---
+ --- @param resp string OSC 11 response
+ --- @return string? Red component
+ --- @return string? Green component
+ --- @return string? Blue component
+ local function parseosc11(resp)
+ local r, g, b
+ r, g, b = resp:match('^\027%]11;rgb:(%x+)/(%x+)/(%x+)$')
+ if not r and not g and not b then
+ local a
+ r, g, b, a = resp:match('^\027%]11;rgba:(%x+)/(%x+)/(%x+)/(%x+)$')
+ if not a or #a > 4 then
+ return nil, nil, nil
+ end
end
- -- Wait until Nvim is finished starting to set 'background' to ensure the
- -- OptionSet event fires.
- if vim.v.vim_did_enter == 1 then
- if vim.o.background ~= bg then
- vim.o.background = bg
- end
- else
- vim.api.nvim_create_autocmd('VimEnter', {
- once = true,
- nested = true,
- callback = function()
- setbg(bg)
- end,
- })
+ if r and g and b and #r <= 4 and #g <= 4 and #b <= 4 then
+ return r, g, b
end
+
+ return nil, nil, nil
end
+ local timer = assert(vim.uv.new_timer())
+
local id = vim.api.nvim_create_autocmd('TermResponse', {
+ group = group,
nested = true,
callback = function(args)
local resp = args.data ---@type string
@@ -289,7 +317,7 @@ do
if rr and gg and bb then
local luminance = (0.299 * rr) + (0.587 * gg) + (0.114 * bb)
local bg = luminance < 0.5 and 'dark' or 'light'
- setbg(bg)
+ setoption('background', bg)
end
return true
@@ -297,10 +325,10 @@ do
end,
})
- io.stdout:write('\027]11;?\027\\')
+ io.stdout:write('\027]11;?\007')
timer:start(1000, 0, function()
- -- No response received. Delete the autocommand
+ -- Delete the autocommand if no response was received
vim.schedule(function()
-- Suppress error if autocommand has already been deleted
pcall(vim.api.nvim_del_autocmd, id)
@@ -311,4 +339,102 @@ do
end
end)
end
+
+ --- If the TUI (term_has_truecolor) was able to determine that the host
+ --- terminal supports truecolor, enable 'termguicolors'. Otherwise, query the
+ --- terminal (using both XTGETTCAP and SGR + DECRQSS). If the terminal's
+ --- response indicates that it does support truecolor enable 'termguicolors',
+ --- but only if the user has not already disabled it.
+ do
+ if tty.rgb then
+ -- The TUI was able to determine truecolor support
+ setoption('termguicolors', true)
+ else
+ local caps = {} ---@type table<string, boolean>
+ require('vim.termcap').query({ 'Tc', 'RGB', 'setrgbf', 'setrgbb' }, function(cap, found)
+ if not found then
+ return
+ end
+
+ caps[cap] = true
+ if caps.Tc or caps.RGB or (caps.setrgbf and caps.setrgbb) then
+ setoption('termguicolors', true)
+ end
+ end)
+
+ local timer = assert(vim.uv.new_timer())
+
+ -- Arbitrary colors to set in the SGR sequence
+ local r = 1
+ local g = 2
+ local b = 3
+
+ local id = vim.api.nvim_create_autocmd('TermResponse', {
+ group = group,
+ nested = true,
+ callback = function(args)
+ local resp = args.data ---@type string
+ local decrqss = resp:match('^\027P1%$r([%d;:]+)m$')
+
+ if decrqss then
+ -- The DECRQSS SGR response first contains attributes separated by
+ -- semicolons, followed by the SGR itself with parameters separated
+ -- by colons. Some terminals include "0" in the attribute list
+ -- unconditionally; others do not. Our SGR sequence did not set any
+ -- attributes, so there should be no attributes in the list.
+ local attrs = vim.split(decrqss, ';')
+ if #attrs ~= 1 and (#attrs ~= 2 or attrs[1] ~= '0') then
+ return true
+ end
+
+ -- The returned SGR sequence should begin with 48:2
+ local sgr = attrs[#attrs]:match('^48:2:([%d:]+)$')
+ if not sgr then
+ return true
+ end
+
+ -- The remaining elements of the SGR sequence should be the 3 colors
+ -- we set. Some terminals also include an additional parameter
+ -- (which can even be empty!), so handle those cases as well
+ local params = vim.split(sgr, ':')
+ if #params ~= 3 and (#params ~= 4 or (params[1] ~= '' and params[1] ~= '1')) then
+ return true
+ end
+
+ if
+ tonumber(params[#params - 2]) == r
+ and tonumber(params[#params - 1]) == g
+ and tonumber(params[#params]) == b
+ then
+ setoption('termguicolors', true)
+ end
+
+ return true
+ end
+ end,
+ })
+
+ -- Write SGR followed by DECRQSS. This sets the background color then
+ -- immediately asks the terminal what the background color is. If the
+ -- terminal responds to the DECRQSS with the same SGR sequence that we
+ -- sent then the terminal supports truecolor.
+ local decrqss = '\027P$qm\027\\'
+ if os.getenv('TMUX') then
+ decrqss = string.format('\027Ptmux;%s\027\\', decrqss:gsub('\027', '\027\027'))
+ end
+ io.stdout:write(string.format('\027[48;2;%d;%d;%dm%s', r, g, b, decrqss))
+
+ timer:start(1000, 0, function()
+ -- Delete the autocommand if no response was received
+ vim.schedule(function()
+ -- Suppress error if autocommand has already been deleted
+ pcall(vim.api.nvim_del_autocmd, id)
+ end)
+
+ if not timer:is_closing() then
+ timer:close()
+ end
+ end)
+ end
+ end
end
diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua
index 6cccbe8313..6cf77b4648 100644
--- a/runtime/lua/vim/_editor.lua
+++ b/runtime/lua/vim/_editor.lua
@@ -95,7 +95,7 @@ vim.log = {
--- throws an error if {cmd} cannot be run.
---
--- @param cmd (string[]) Command to execute
---- @param opts (SystemOpts|nil) Options:
+--- @param opts vim.SystemOpts? Options:
--- - cwd: (string) Set the current working directory for the sub-process.
--- - env: table<string,string> Set environment variables for the new process. Inherits the
--- current environment with `NVIM` set to |v:servername|.
@@ -118,7 +118,7 @@ vim.log = {
--- parent exits. Note that the child process will still keep the parent's event loop alive
--- unless the parent process calls |uv.unref()| on the child's process handle.
---
---- @param on_exit (function|nil) Called when subprocess exits. When provided, the command runs
+--- @param on_exit? fun(out: vim.SystemCompleted) Called when subprocess exits. When provided, the command runs
--- asynchronously. Receives SystemCompleted object, see return of SystemObj:wait().
---
--- @return vim.SystemObj Object with the fields:
@@ -127,10 +127,10 @@ vim.log = {
--- timeout the process is sent the KILL signal (9) and the exit code is set to 124. Cannot
--- be called in |api-fast|.
--- - SystemCompleted is an object with the fields:
---- - code: (integer)
---- - signal: (integer)
---- - stdout: (string), nil if stdout argument is passed
---- - stderr: (string), nil if stderr argument is passed
+--- - code: (integer)
+--- - signal: (integer)
+--- - stdout: (string), nil if stdout argument is passed
+--- - stderr: (string), nil if stderr argument is passed
--- - kill (fun(signal: integer|string))
--- - write (fun(data: string|nil)) Requires `stdin=true`. Pass `nil` to close the stream.
--- - is_closing (fun(): boolean)
@@ -156,10 +156,10 @@ function vim._os_proc_info(pid)
elseif r.code ~= 0 then
error('command failed: ' .. vim.fn.string(cmd))
end
- local ppid = assert(vim.system({ 'ps', '-p', pid, '-o', 'ppid=' }):wait().stdout)
+ local ppid_string = assert(vim.system({ 'ps', '-p', pid, '-o', 'ppid=' }):wait().stdout)
-- Remove trailing whitespace.
name = vim.trim(name):gsub('^.*/', '')
- ppid = tonumber(ppid) or -1
+ local ppid = tonumber(ppid_string) or -1
return {
name = name,
pid = pid,
@@ -190,12 +190,19 @@ function vim._os_proc_children(ppid)
return children
end
+--- @nodoc
+--- @class vim.inspect.Opts
+--- @field depth? integer
+--- @field newline? string
+--- @field process? fun(item:any, path: string[]): any
+
--- Gets a human-readable representation of the given object.
---
---@see |vim.print()|
---@see https://github.com/kikito/inspect.lua
---@see https://github.com/mpeterv/vinspect
---@return string
+---@overload fun(x: any, opts?: vim.inspect.Opts): string
vim.inspect = vim.inspect
do
@@ -219,10 +226,9 @@ do
--- ```
---
---@see |paste|
- ---@alias paste_phase -1 | 1 | 2 | 3
---
---@param lines string[] # |readfile()|-style list of lines to paste. |channel-lines|
- ---@param phase paste_phase -1: "non-streaming" paste: the call contains all lines.
+ ---@param phase (-1|1|2|3) -1: "non-streaming" paste: the call contains all lines.
--- If paste is "streamed", `phase` indicates the stream state:
--- - 1: starts the paste (exactly once)
--- - 2: continues the paste (zero or more times)
@@ -269,6 +275,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]
@@ -347,10 +354,13 @@ function vim.schedule_wrap(fn)
end
-- vim.fn.{func}(...)
----@private
+---@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))
@@ -449,7 +459,7 @@ vim.cmd = setmetatable({}, {
end,
})
---- @class vim.var_accessor
+--- @class (private) vim.var_accessor
--- @field [string] any
--- @field [integer] vim.var_accessor
@@ -478,7 +488,7 @@ do
end
vim.g = make_dict_accessor('g', false)
- vim.v = make_dict_accessor('v', false)
+ vim.v = make_dict_accessor('v', false) --[[@as vim.v]]
vim.b = make_dict_accessor('b')
vim.w = make_dict_accessor('w')
vim.t = make_dict_accessor('t')
@@ -492,7 +502,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}`.
@@ -533,20 +543,21 @@ function vim.region(bufnr, pos1, pos2, regtype, inclusive)
local region = {}
for l = pos1[1], pos2[1] do
- local c1, c2
+ local c1 --- @type number
+ local c2 --- @type number
if regtype:byte() == 22 then -- block selection: take width from regtype
c1 = pos1[2]
- c2 = c1 + regtype:sub(2)
+ c2 = c1 + tonumber(regtype:sub(2))
-- and adjust for non-ASCII characters
local bufline = vim.api.nvim_buf_get_lines(bufnr, l, l + 1, true)[1]
local utflen = vim.str_utfindex(bufline, #bufline)
if c1 <= utflen then
- c1 = vim.str_byteindex(bufline, c1)
+ c1 = assert(tonumber(vim.str_byteindex(bufline, c1)))
else
c1 = #bufline + 1
end
if c2 <= utflen then
- c2 = vim.str_byteindex(bufline, c2)
+ c2 = assert(tonumber(vim.str_byteindex(bufline, c2)))
else
c2 = #bufline + 1
end
@@ -576,7 +587,7 @@ end
---@return table timer luv timer object
function vim.defer_fn(fn, timeout)
vim.validate({ fn = { fn, 'c', true } })
- local timer = vim.uv.new_timer()
+ local timer = assert(vim.uv.new_timer())
timer:start(
timeout,
0,
@@ -601,6 +612,7 @@ end
---@param msg string Content of the notification to show to the user.
---@param level integer|nil One of the values from |vim.log.levels|.
---@param opts table|nil Optional parameters. Unused by default.
+---@diagnostic disable-next-line: unused-local
function vim.notify(msg, level, opts) -- luacheck: no unused args
if level == vim.log.levels.ERROR then
vim.api.nvim_err_writeln(msg)
@@ -612,7 +624,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.
---
@@ -633,7 +645,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.
@@ -645,8 +657,9 @@ local on_key_cbs = {}
---@note {fn} will not be cleared by |nvim_buf_clear_namespace()|
---@note {fn} will receive the keys after mappings have been evaluated
---
----@param fn fun(key: string) Function invoked on every key press. |i_CTRL-V|
---- Returning nil removes the callback associated with namespace {ns_id}.
+---@param fn fun(key: string)? Function invoked on every key press. |i_CTRL-V|
+--- Passing in nil when {ns_id} is specified removes the
+--- callback associated with namespace {ns_id}.
---@param ns_id integer? Namespace ID. If nil or 0, generates and returns a
--- new |nvim_create_namespace()| id.
---
@@ -698,8 +711,11 @@ end
--- Generates a list of possible completions for the string.
--- String has the pattern.
---
---- 1. Can we get it to just return things in the global namespace with that name prefix
---- 2. Can we get it to return things from global namespace even with `print(` in front.
+--- 1. Can we get it to just return things in the global namespace with that name prefix
+--- 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
@@ -732,7 +748,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
@@ -774,7 +790,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
@@ -801,11 +818,14 @@ function vim._expand_pat(pat, env)
return keys, #prefix_match_pat
end
+--- @param lua_string string
+--- @return (string|string[])[], integer
vim._expand_pat_get_parts = function(lua_string)
local parts = {}
local accumulator, search_index = '', 1
- local in_brackets, bracket_end = false, -1
+ local in_brackets = false
+ local bracket_end = -1 --- @type integer?
local string_char = nil
for idx = 1, #lua_string do
local s = lua_string:sub(idx, idx)
@@ -857,6 +877,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)
@@ -867,12 +888,13 @@ 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.
---
--- Activate using `set omnifunc=v:lua.vim.lua_omnifunc` in a Lua buffer.
+ --- @param find_start 1|0
function vim.lua_omnifunc(find_start, _)
if find_start == 1 then
local line = vim.api.nvim_get_current_line()
@@ -886,12 +908,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:
@@ -902,6 +918,7 @@ end
---
--- @see |vim.inspect()|
--- @see |:=|
+--- @param ... any
--- @return any # given arguments.
function vim.print(...)
if vim.in_fast_event() then
@@ -938,9 +955,12 @@ function vim.keycode(str)
return vim.api.nvim_replace_termcodes(str, true, true, true)
end
+--- @param server_addr string
+--- @param connect_error string
function vim._cs_remote(rcid, server_addr, connect_error, args)
+ --- @return string
local function connection_failure_errmsg(consequence)
- local explanation
+ local explanation --- @type string
if server_addr == '' then
explanation = 'No server specified with --server'
else
@@ -983,7 +1003,7 @@ function vim._cs_remote(rcid, server_addr, connect_error, args)
local res = tostring(vim.rpcrequest(rcid, 'nvim_eval', args[2]))
return { result = res, should_exit = true, tabbed = false }
elseif subcmd ~= '' then
- return { errmsg = 'Unknown option argument: ' .. args[1] }
+ return { errmsg = 'Unknown option argument: ' .. tostring(args[1]) }
end
if rcid == 0 then
@@ -1019,9 +1039,44 @@ end
---
---@return string|nil # Deprecated message, or nil if no message was shown.
function vim.deprecate(name, alternative, version, plugin, backtrace)
- local msg = ('%s is deprecated'):format(name)
+ vim.validate {
+ name = { name, 'string' },
+ alternative = { alternative, 'string', true },
+ version = { version, 'string', true },
+ plugin = { plugin, 'string', true },
+ }
plugin = plugin or 'Nvim'
- msg = alternative and ('%s, use %s instead.'):format(msg, alternative) or msg
+
+ -- Only issue warning if feature is hard-deprecated as specified by MAINTAIN.md.
+ -- 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 vim.Version
+ local removal_version = assert(vim.version.parse(version))
+ local is_hard_deprecated ---@type boolean
+
+ if removal_version.minor > 0 then
+ local hard_deprecated_since = assert(vim.version._version({
+ major = removal_version.major,
+ minor = removal_version.minor - 1,
+ patch = 0,
+ prerelease = 'dev', -- Show deprecation warnings in devel (nightly) version as well
+ }))
+ is_hard_deprecated = (current_version >= hard_deprecated_since)
+ else
+ -- Assume there will be no next minor version before bumping up the major version;
+ -- therefore we can always show a warning.
+ assert(removal_version.minor == 0, vim.inspect(removal_version))
+ is_hard_deprecated = true
+ end
+
+ if not is_hard_deprecated then
+ return
+ end
+ end
+
+ local msg = ('%s is deprecated'):format(name)
+ msg = alternative and ('%s, use %s instead.'):format(msg, alternative) or (msg .. '.')
msg = ('%s%s\nThis feature will be removed in %s version %s'):format(
msg,
(plugin == 'Nvim' and ' :help deprecated' or ''),
diff --git a/runtime/lua/vim/_init_packages.lua b/runtime/lua/vim/_init_packages.lua
index 4a961970cc..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
@@ -12,6 +12,7 @@ for s in (package.cpath .. ';'):gmatch('[^;]*;') do
end
end
+--- @param name string
function vim._load_package(name)
local basename = name:gsub('%.', '/')
local paths = { 'lua/' .. basename .. '.lua', 'lua/' .. basename .. '/init.lua' }
@@ -55,13 +56,16 @@ vim._submodules = {
inspect = true,
version = true,
fs = true,
+ glob = true,
iter = true,
re = true,
text = true,
+ provider = true,
}
-- 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)
@@ -70,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.
@@ -83,6 +88,7 @@ setmetatable(vim, {
--- <Docs described in |vim.empty_dict()| >
---@private
--- TODO: should be in vim.shared when vim.shared always uses nvim-lua
+--- @diagnostic disable-next-line:duplicate-set-field
function vim.empty_dict()
return setmetatable({}, vim._empty_dict_mt)
end
diff --git a/runtime/lua/vim/_inspector.lua b/runtime/lua/vim/_inspector.lua
index 3f7b9d2c23..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|nil) 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|nil) 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.lua b/runtime/lua/vim/_meta.lua
index e3b99f6b3d..731dd5b923 100644
--- a/runtime/lua/vim/_meta.lua
+++ b/runtime/lua/vim/_meta.lua
@@ -1,5 +1,7 @@
--- @meta
+--- @alias elem_or_list<T> T|T[]
+
---@type uv
vim.uv = ...
@@ -11,6 +13,7 @@ vim.diagnostic = require('vim.diagnostic')
vim.filetype = require('vim.filetype')
vim.fs = require('vim.fs')
vim.func = require('vim.func')
+vim.glob = require('vim.glob')
vim.health = require('vim.health')
vim.highlight = require('vim.highlight')
vim.iter = require('vim.iter')
@@ -20,6 +23,7 @@ vim.lsp = require('vim.lsp')
vim.re = require('vim.re')
vim.secure = require('vim.secure')
vim.snippet = require('vim.snippet')
+vim.text = require('vim.text')
vim.treesitter = require('vim.treesitter')
vim.ui = require('vim.ui')
vim.version = require('vim.version')
diff --git a/runtime/lua/vim/_meta/api.lua b/runtime/lua/vim/_meta/api.lua
index 49269ba631..cb4c8749b8 100644
--- a/runtime/lua/vim/_meta/api.lua
+++ b/runtime/lua/vim/_meta/api.lua
@@ -38,6 +38,7 @@ function vim.api.nvim__get_runtime(pat, all, opts) end
--- @private
--- Returns object given as argument.
+---
--- This API function is used for testing. One should not rely on its presence
--- in plugins.
---
@@ -47,6 +48,7 @@ function vim.api.nvim__id(obj) end
--- @private
--- Returns array given as argument.
+---
--- This API function is used for testing. One should not rely on its presence
--- in plugins.
---
@@ -56,6 +58,7 @@ function vim.api.nvim__id_array(arr) end
--- @private
--- Returns dictionary given as argument.
+---
--- This API function is used for testing. One should not rely on its presence
--- in plugins.
---
@@ -65,6 +68,7 @@ function vim.api.nvim__id_dictionary(dct) end
--- @private
--- Returns floating-point value given as argument.
+---
--- This API function is used for testing. One should not rely on its presence
--- in plugins.
---
@@ -73,6 +77,9 @@ function vim.api.nvim__id_dictionary(dct) end
function vim.api.nvim__id_float(flt) end
--- @private
+--- NB: if your UI doesn't use hlstate, this will not return hlstate first
+--- time.
+---
--- @param grid integer
--- @param row integer
--- @param col integer
@@ -105,17 +112,20 @@ function vim.api.nvim__stats() end
function vim.api.nvim__unpack(str) end
--- Adds a highlight to buffer.
+---
--- Useful for plugins that dynamically generate highlights to a buffer (like
--- a semantic highlighter or linter). The function adds a single highlight to
--- a buffer. Unlike `matchaddpos()` highlights follow changes to line
--- numbering (as lines are inserted/removed above the highlighted line), like
--- signs and marks do.
+---
--- Namespaces are used for batch deletion/updating of a set of highlights. To
--- create a namespace, use `nvim_create_namespace()` which returns a
--- namespace id. Pass it in to this function as `ns_id` to add highlights to
--- the namespace. All highlights in the same namespace can then be cleared
--- with single call to `nvim_buf_clear_namespace()`. If the highlight never
--- will be deleted by an API call, pass `ns_id = -1`.
+---
--- As a shorthand, `ns_id = 0` can be used to create a new namespace for the
--- highlight, the allocated id is then returned. If `hl_group` is the empty
--- string no highlight is added, but a new `ns_id` is still returned. This is
@@ -128,11 +138,12 @@ function vim.api.nvim__unpack(str) end
--- @param line integer Line to highlight (zero-indexed)
--- @param col_start integer Start of (byte-indexed) column range to highlight
--- @param col_end integer End of (byte-indexed) column range to highlight, or -1 to
---- highlight to end of line
+--- highlight to end of line
--- @return integer
function vim.api.nvim_buf_add_highlight(buffer, ns_id, hl_group, line, col_start, col_end) end
--- Activates buffer-update events on a channel, or as Lua callbacks.
+---
--- Example (Lua): capture buffer updates in a global `events` variable (use
--- "vim.print(events)" to see its contents):
---
@@ -145,76 +156,78 @@ function vim.api.nvim_buf_add_highlight(buffer, ns_id, hl_group, line, col_start
--- })
--- ```
---
+---
--- @param buffer integer Buffer handle, or 0 for current buffer
--- @param send_buffer boolean True if the initial notification should contain the
--- whole buffer: first notification will be
--- `nvim_buf_lines_event`. Else the first notification
--- will be `nvim_buf_changedtick_event`. Not for Lua
--- callbacks.
---- @param opts table<string,function> Optional parameters.
---- • on_lines: Lua callback invoked on change. Return `true` to detach. Args:
---- • the string "lines"
---- • buffer handle
---- • b:changedtick
---- • first line that changed (zero-indexed)
---- • last line that was changed
---- • last line in the updated range
---- • byte count of previous contents
---- • deleted_codepoints (if `utf_sizes` is true)
---- • deleted_codeunits (if `utf_sizes` is true)
----
---- • on_bytes: Lua callback invoked on change. This
---- callback receives more granular information about the
---- change compared to on_lines. Return `true` to detach. Args:
---- • the string "bytes"
---- • buffer handle
---- • b:changedtick
---- • start row of the changed text (zero-indexed)
---- • start column of the changed text
---- • byte offset of the changed text (from the start of
---- the buffer)
---- • old end row of the changed text
---- • old end column of the changed text
---- • old end byte length of the changed text
---- • new end row of the changed text
---- • new end column of the changed text
---- • new end byte length of the changed text
----
---- • on_changedtick: Lua callback invoked on changedtick
---- increment without text change. Args:
---- • the string "changedtick"
---- • buffer handle
---- • b:changedtick
----
---- • on_detach: Lua callback invoked on detach. Args:
---- • the string "detach"
---- • buffer handle
----
---- • on_reload: Lua callback invoked on reload. The entire
---- buffer content should be considered changed. Args:
---- • the string "reload"
---- • buffer handle
----
---- • utf_sizes: include UTF-32 and UTF-16 size of the
---- replaced region, as args to `on_lines`.
---- • preview: also attach to command preview (i.e.
---- 'inccommand') events.
+--- @param opts vim.api.keyset.buf_attach Optional parameters.
+--- • on_lines: Lua callback invoked on change. Return a truthy
+--- value (not `false` or `nil`) to detach. Args:
+--- • the string "lines"
+--- • buffer handle
+--- • b:changedtick
+--- • first line that changed (zero-indexed)
+--- • last line that was changed
+--- • last line in the updated range
+--- • byte count of previous contents
+--- • deleted_codepoints (if `utf_sizes` is true)
+--- • deleted_codeunits (if `utf_sizes` is true)
+--- • on_bytes: Lua callback invoked on change. This callback
+--- receives more granular information about the change compared
+--- to on_lines. Return a truthy value (not `false` or `nil`) to
+--- detach. Args:
+--- • the string "bytes"
+--- • buffer handle
+--- • b:changedtick
+--- • start row of the changed text (zero-indexed)
+--- • start column of the changed text
+--- • byte offset of the changed text (from the start of the
+--- buffer)
+--- • old end row of the changed text (offset from start row)
+--- • old end column of the changed text (if old end row = 0,
+--- offset from start column)
+--- • old end byte length of the changed text
+--- • new end row of the changed text (offset from start row)
+--- • new end column of the changed text (if new end row = 0,
+--- offset from start column)
+--- • new end byte length of the changed text
+--- • on_changedtick: Lua callback invoked on changedtick
+--- increment without text change. Args:
+--- • the string "changedtick"
+--- • buffer handle
+--- • b:changedtick
+--- • on_detach: Lua callback invoked on detach. Args:
+--- • the string "detach"
+--- • buffer handle
+--- • on_reload: Lua callback invoked on reload. The entire buffer
+--- content should be considered changed. Args:
+--- • the string "reload"
+--- • buffer handle
+--- • utf_sizes: include UTF-32 and UTF-16 size of the replaced
+--- region, as args to `on_lines`.
+--- • preview: also attach to command preview (i.e. 'inccommand')
+--- events.
--- @return boolean
function vim.api.nvim_buf_attach(buffer, send_buffer, opts) end
--- call a function with buffer as temporary current buffer
+---
--- This temporarily switches current buffer to "buffer". If the current
--- window already shows "buffer", the window is not switched If a window
--- inside the current tabpage (including a float) already shows the buffer
--- One of these windows will be set as current window temporarily. Otherwise
--- a temporary scratch window (called the "autocmd window" for historical
--- reasons) will be used.
+---
--- This is useful e.g. to call Vimscript functions that only work with the
--- current buffer/window currently, like `termopen()`.
---
--- @param buffer integer Buffer handle, or 0 for current buffer
--- @param fun function Function to call inside the buffer (currently Lua callable
---- only)
+--- only)
--- @return any
function vim.api.nvim_buf_call(buffer, fun) end
@@ -227,14 +240,15 @@ function vim.api.nvim_buf_clear_highlight(buffer, ns_id, line_start, line_end) e
--- Clears `namespace`d objects (highlights, `extmarks`, virtual text) from a
--- region.
+---
--- Lines are 0-indexed. `api-indexing` To clear the namespace in the entire
--- buffer, specify line_start=0 and line_end=-1.
---
--- @param buffer integer Buffer handle, or 0 for current buffer
--- @param ns_id integer Namespace to clear, or -1 to clear all namespaces.
--- @param line_start integer Start of range of lines to clear
---- @param line_end integer End of range of lines to clear (exclusive) or -1 to
---- clear to end of buffer.
+--- @param line_end integer End of range of lines to clear (exclusive) or -1 to clear
+--- to end of buffer.
function vim.api.nvim_buf_clear_namespace(buffer, ns_id, line_start, line_end) end
--- Creates a buffer-local command `user-commands`.
@@ -268,6 +282,7 @@ function vim.api.nvim_buf_del_keymap(buffer, mode, lhs) end
function vim.api.nvim_buf_del_mark(buffer, name) end
--- Delete a buffer-local user-defined command.
+---
--- Only commands created with `:command-buffer` or
--- `nvim_buf_create_user_command()` can be deleted with this function.
---
@@ -284,9 +299,9 @@ function vim.api.nvim_buf_del_var(buffer, name) end
--- Deletes the buffer. See `:bwipeout`
---
--- @param buffer integer Buffer handle, or 0 for current buffer
---- @param opts table<string,any> Optional parameters. Keys:
---- • force: Force deletion and ignore unsaved changes.
---- • unload: Unloaded only, do not delete. See `:bunload`
+--- @param opts vim.api.keyset.buf_delete Optional parameters. Keys:
+--- • force: Force deletion and ignore unsaved changes.
+--- • unload: Unloaded only, do not delete. See `:bunload`
function vim.api.nvim_buf_delete(buffer, opts) end
--- Gets a changed tick of a buffer
@@ -307,15 +322,16 @@ function vim.api.nvim_buf_get_commands(buffer, opts) end
--- @param buffer integer Buffer handle, or 0 for current buffer
--- @param ns_id integer Namespace id from `nvim_create_namespace()`
--- @param id integer Extmark id
---- @param opts table<string,any> Optional parameters. Keys:
---- • details: Whether to include the details dict
---- • hl_name: Whether to include highlight group name instead
---- of id, true if omitted
---- @return integer[]
+--- @param opts vim.api.keyset.get_extmark Optional parameters. Keys:
+--- • details: Whether to include the details dict
+--- • hl_name: Whether to include highlight group name instead of
+--- id, true if omitted
+--- @return vim.api.keyset.get_extmark_item
function vim.api.nvim_buf_get_extmark_by_id(buffer, ns_id, id, opts) end
---- Gets `extmarks` (including `signs`) in "traversal order" from a `charwise`
---- region defined by buffer positions (inclusive, 0-indexed `api-indexing`).
+--- Gets `extmarks` in "traversal order" from a `charwise` region defined by
+--- buffer positions (inclusive, 0-indexed `api-indexing`).
+---
--- Region can be given as (row,col) tuples, or valid extmark ids (whose
--- positions define the bounds). 0 and -1 are understood as (0,0) and (-1,-1)
--- respectively, thus the following are equivalent:
@@ -327,9 +343,15 @@ function vim.api.nvim_buf_get_extmark_by_id(buffer, ns_id, id, opts) end
---
--- If `end` is less than `start`, traversal works backwards. (Useful with
--- `limit`, to get the first marks prior to a given position.)
+---
--- Note: when using extmark ranges (marks with a end_row/end_col position)
--- the `overlap` option might be useful. Otherwise only the start position of
--- an extmark will be considered.
+---
+--- Note: legacy signs placed through the `:sign` commands are implemented as
+--- extmarks and will show up here. Their details array will contain a
+--- `sign_name` field.
+---
--- Example:
---
--- ```lua
@@ -347,37 +369,39 @@ function vim.api.nvim_buf_get_extmark_by_id(buffer, ns_id, id, opts) end
--- vim.print(ms)
--- ```
---
+---
--- @param buffer integer Buffer handle, or 0 for current buffer
--- @param ns_id integer Namespace id from `nvim_create_namespace()` or -1 for all
---- namespaces
+--- namespaces
--- @param start any Start of range: a 0-indexed (row, col) or valid extmark id
---- (whose position defines the bound). `api-indexing`
+--- (whose position defines the bound). `api-indexing`
--- @param end_ any End of range (inclusive): a 0-indexed (row, col) or valid
---- extmark id (whose position defines the bound).
---- `api-indexing`
+--- extmark id (whose position defines the bound). `api-indexing`
--- @param opts vim.api.keyset.get_extmarks Optional parameters. Keys:
---- • limit: Maximum number of marks to return
---- • details: Whether to include the details dict
---- • hl_name: Whether to include highlight group name instead
---- of id, true if omitted
---- • overlap: Also include marks which overlap the range, even
---- if their start position is less than `start`
---- • type: Filter marks by type: "highlight", "sign",
---- "virt_text" and "virt_lines"
---- @return any[]
+--- • limit: Maximum number of marks to return
+--- • details: Whether to include the details dict
+--- • hl_name: Whether to include highlight group name instead of
+--- id, true if omitted
+--- • overlap: Also include marks which overlap the range, even if
+--- their start position is less than `start`
+--- • type: Filter marks by type: "highlight", "sign", "virt_text"
+--- and "virt_lines"
+--- @return vim.api.keyset.get_extmark_item[]
function vim.api.nvim_buf_get_extmarks(buffer, ns_id, start, end_, opts) end
--- Gets a list of buffer-local `mapping` definitions.
---
--- @param buffer integer Buffer handle, or 0 for current buffer
--- @param mode string Mode short-name ("n", "i", "v", ...)
---- @return table<string,any>[]
+--- @return vim.api.keyset.keymap[]
function vim.api.nvim_buf_get_keymap(buffer, mode) end
--- Gets a line-range from the buffer.
+---
--- Indexing is zero-based, end-exclusive. Negative indices are interpreted as
--- length+1+index: -1 refers to the index past the end. So to get the last
--- element use start=-2 and end=-1.
+---
--- Out-of-bounds indices are clamped to the nearest valid value, unless
--- `strict_indexing` is set.
---
@@ -391,6 +415,7 @@ function vim.api.nvim_buf_get_lines(buffer, start, end_, strict_indexing) end
--- Returns a `(row,col)` tuple representing the position of the named mark.
--- "End of line" column position is returned as `v:maxcol` (big number). See
--- `mark-motions`.
+---
--- Marks are (1,0)-indexed. `api-indexing`
---
--- @param buffer integer Buffer handle, or 0 for current buffer
@@ -410,10 +435,12 @@ function vim.api.nvim_buf_get_name(buffer) end
function vim.api.nvim_buf_get_number(buffer) end
--- Returns the byte offset of a line (0-indexed). `api-indexing`
+---
--- Line 1 (index=0) has offset 0. UTF-8 bytes are counted. EOL is one byte.
--- 'fileformat' and 'fileencoding' are ignored. The line index just after the
--- last line gives the total byte-count of the buffer. A final EOL byte is
--- counted if it would be written, see 'eol'.
+---
--- Unlike `line2byte()`, throws error for out-of-bounds indexing. Returns -1
--- for unloaded buffer.
---
@@ -429,10 +456,13 @@ function vim.api.nvim_buf_get_offset(buffer, index) end
function vim.api.nvim_buf_get_option(buffer, name) end
--- Gets a range from the buffer.
+---
--- This differs from `nvim_buf_get_lines()` in that it allows retrieving only
--- portions of a line.
+---
--- Indexing is zero-based. Row indices are end-inclusive, and column indices
--- are end-exclusive.
+---
--- Prefer `nvim_buf_get_lines()` when retrieving entire lines.
---
--- @param buffer integer Buffer handle, or 0 for current buffer
@@ -440,7 +470,7 @@ function vim.api.nvim_buf_get_option(buffer, name) end
--- @param start_col integer Starting column (byte offset) on first line
--- @param end_row integer Last line index, inclusive
--- @param end_col integer Ending column (byte offset) on last line, exclusive
---- @param opts table<string,any> Optional parameters. Currently unused.
+--- @param opts vim.api.keyset.empty Optional parameters. Currently unused.
--- @return string[]
function vim.api.nvim_buf_get_text(buffer, start_row, start_col, end_row, end_col, opts) end
@@ -471,13 +501,16 @@ function vim.api.nvim_buf_is_valid(buffer) end
function vim.api.nvim_buf_line_count(buffer) end
--- Creates or updates an `extmark`.
+---
--- By default a new extmark is created when no id is passed in, but it is
--- also possible to create a new mark by passing in a previously unused id or
--- move an existing mark by passing in its id. The caller must then keep
--- track of existing and unused ids itself. (Useful over RPC, to avoid
--- waiting for the return value.)
+---
--- Using the optional arguments, it is possible to use this to highlight a
--- range of text, and also to associate virtual text to the mark.
+---
--- If present, the position defined by `end_col` and `end_row` should be
--- after the start position in order for the extmark to cover a range. An
--- earlier end position is not an error, but then it behaves like an empty
@@ -488,108 +521,110 @@ function vim.api.nvim_buf_line_count(buffer) end
--- @param line integer Line where to place the mark, 0-based. `api-indexing`
--- @param col integer Column where to place the mark, 0-based. `api-indexing`
--- @param opts vim.api.keyset.set_extmark Optional parameters.
---- • id : id of the extmark to edit.
---- • end_row : ending line of the mark, 0-based inclusive.
---- • end_col : ending col of the mark, 0-based exclusive.
---- • hl_group : name of the highlight group used to highlight
---- this mark.
---- • hl_eol : when true, for a multiline highlight covering the
---- 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
---- 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 highlight group can be supplied either as a
---- string or as an integer, the latter which can be obtained
---- using `nvim_get_hl_id_by_name()`.
---- • virt_text_pos : position of virtual text. Possible values:
---- • "eol": right after eol character (default).
---- • "overlay": display over the specified column, without
---- shifting the underlying text.
---- • "right_align": display right aligned in the window.
---- • "inline": display at the specified column, and shift the
---- buffer text to the right as needed.
----
---- • virt_text_win_col : position the virtual text at a fixed
---- window column (starting from the first text column of the
---- screen line) instead of "virt_text_pos".
---- • virt_text_hide : hide the virtual text when the background
---- text is selected or hidden because of scrolling with
---- 'nowrap' or 'smoothscroll'. Currently only affects
---- "overlay" virt_text.
---- • hl_mode : control how highlights are combined with the
---- highlights of the text. Currently only affects virt_text
---- highlights, but might affect `hl_group` in later versions.
---- • "replace": only show the virt_text color. This is the
---- default.
---- • "combine": combine with background text color.
---- • "blend": blend with background text color. Not supported
---- 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 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
---- option is still used for hard tabs. By default lines are
---- placed below the buffer line containing the mark.
---- • virt_lines_above: place virtual lines above instead.
---- • virt_lines_leftcol: Place extmarks in the leftmost column
---- of the window, bypassing sign and number columns.
---- • ephemeral : for use with `nvim_set_decoration_provider()`
---- callbacks. The mark will only be used for the current
---- redraw cycle, and not be permantently stored in the
---- buffer.
---- • right_gravity : boolean that indicates the direction the
---- extmark will be shifted in when new text is inserted (true
---- for right, false for left). Defaults to true.
---- • end_right_gravity : boolean that indicates the direction
---- the extmark end position (if it exists) will be shifted in
---- when new text is inserted (true for right, false for
---- left). Defaults to false.
---- • undo_restore : Restore the exact position of the mark if
---- text around the mark was deleted and then restored by
---- undo. Defaults to true.
---- • invalidate : boolean that indicates whether to hide the
---- extmark if the entirety of its range is deleted. If
---- "undo_restore" is false, the extmark is deleted instead.
---- • priority: a priority value for the highlight group or sign
---- attribute. For example treesitter highlighting uses a
---- value of 100.
---- • strict: boolean that indicates extmark should not be
---- placed if the line or column value is past the end of the
---- buffer or end of the line respectively. Defaults to true.
---- • sign_text: string of length 1-2 used to display in the
---- sign column. Note: ranges are unsupported and decorations
---- are only applied to start_row
---- • sign_hl_group: name of the highlight group used to
---- highlight the sign column text. Note: ranges are
---- unsupported and decorations are only applied to start_row
---- • number_hl_group: name of the highlight group used to
---- highlight the number column. Note: ranges are unsupported
---- and decorations are only applied to start_row
---- • line_hl_group: name of the highlight group used to
---- highlight the whole line. Note: ranges are unsupported and
---- decorations are only applied to start_row
---- • cursorline_hl_group: name of the highlight group used to
---- highlight the line when the cursor is on the same line as
---- the mark and 'cursorline' is enabled. Note: ranges are
---- unsupported and decorations are only applied to start_row
---- • conceal: string which should be either empty or a single
---- character. Enable concealing similar to `:syn-conceal`.
---- When a character is supplied it is used as `:syn-cchar`.
---- "hl_group" is used as highlight for the cchar if provided,
---- otherwise it defaults to `hl-Conceal`.
---- • spell: boolean indicating that spell checking should be
---- performed within this extmark
---- • ui_watched: boolean that indicates the mark should be
---- drawn by a UI. When set, the UI will receive win_extmark
---- events. Note: the mark is positioned by virt_text
---- attributes. Can be used together with virt_text.
+--- • id : id of the extmark to edit.
+--- • end_row : ending line of the mark, 0-based inclusive.
+--- • end_col : ending col of the mark, 0-based exclusive.
+--- • hl_group : name of the highlight group used to highlight
+--- this mark.
+--- • hl_eol : when true, for a multiline highlight covering the
+--- 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
+--- 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
+--- highlight group can be supplied either as a string or as an
+--- integer, the latter which can be obtained using
+--- `nvim_get_hl_id_by_name()`.
+--- • virt_text_pos : position of virtual text. Possible values:
+--- • "eol": right after eol character (default).
+--- • "overlay": display over the specified column, without
+--- shifting the underlying text.
+--- • "right_align": display right aligned in the window.
+--- • "inline": display at the specified column, and shift the
+--- buffer text to the right as needed.
+--- • virt_text_win_col : position the virtual text at a fixed
+--- window column (starting from the first text column of the
+--- screen line) instead of "virt_text_pos".
+--- • virt_text_hide : hide the virtual text when the background
+--- text is selected or hidden because of scrolling with
+--- 'nowrap' or 'smoothscroll'. Currently only affects "overlay"
+--- virt_text.
+--- • virt_text_repeat_linebreak : repeat the virtual text on
+--- wrapped lines.
+--- • hl_mode : control how highlights are combined with the
+--- highlights of the text. Currently only affects virt_text
+--- highlights, but might affect `hl_group` in later versions.
+--- • "replace": only show the virt_text color. This is the
+--- default.
+--- • "combine": combine with background text color.
+--- • "blend": blend with background text color. Not supported
+--- 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
+--- 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
+--- option is still used for hard tabs. By default lines are
+--- placed below the buffer line containing the mark.
+--- • virt_lines_above: place virtual lines above instead.
+--- • virt_lines_leftcol: Place extmarks in the leftmost column of
+--- the window, bypassing sign and number columns.
+--- • ephemeral : for use with `nvim_set_decoration_provider()`
+--- callbacks. The mark will only be used for the current redraw
+--- cycle, and not be permantently stored in the buffer.
+--- • right_gravity : boolean that indicates the direction the
+--- extmark will be shifted in when new text is inserted (true
+--- for right, false for left). Defaults to true.
+--- • end_right_gravity : boolean that indicates the direction the
+--- extmark end position (if it exists) will be shifted in when
+--- new text is inserted (true for right, false for left).
+--- Defaults to false.
+--- • undo_restore : Restore the exact position of the mark if
+--- text around the mark was deleted and then restored by undo.
+--- Defaults to true.
+--- • invalidate : boolean that indicates whether to hide the
+--- extmark if the entirety of its range is deleted. For hidden
+--- marks, an "invalid" key is added to the "details" array of
+--- `nvim_buf_get_extmarks()` and family. If "undo_restore" is
+--- false, the extmark is deleted instead.
+--- • priority: a priority value for the highlight group, sign
+--- attribute or virtual text. For virtual text, item with
+--- highest priority is drawn last. For example treesitter
+--- highlighting uses a value of 100.
+--- • strict: boolean that indicates extmark should not be placed
+--- if the line or column value is past the end of the buffer or
+--- end of the line respectively. Defaults to true.
+--- • sign_text: string of length 1-2 used to display in the sign
+--- column.
+--- • sign_hl_group: name of the highlight group used to highlight
+--- the sign column text.
+--- • number_hl_group: name of the highlight group used to
+--- highlight the number column.
+--- • line_hl_group: name of the highlight group used to highlight
+--- the whole line.
+--- • cursorline_hl_group: name of the highlight group used to
+--- highlight the sign column text when the cursor is on the
+--- same line as the mark and 'cursorline' is enabled.
+--- • conceal: string which should be either empty or a single
+--- character. Enable concealing similar to `:syn-conceal`. When
+--- a character is supplied it is used as `:syn-cchar`.
+--- "hl_group" is used as highlight for the cchar if provided,
+--- otherwise it defaults to `hl-Conceal`.
+--- • spell: boolean indicating that spell checking should be
+--- performed within this extmark
+--- • ui_watched: boolean that indicates the mark should be drawn
+--- by a UI. When set, the UI will receive win_extmark events.
+--- Note: the mark is positioned by virt_text attributes. Can be
+--- used together with virt_text.
+--- • url: A URL to associate with this extmark. In the TUI, the
+--- OSC 8 control sequence is used to generate a clickable
+--- hyperlink to this URL.
+--- • scoped: boolean that indicates that the extmark should only
+--- be displayed in the namespace scope. (experimental)
--- @return integer
function vim.api.nvim_buf_set_extmark(buffer, ns_id, line, col, opts) end
@@ -603,11 +638,14 @@ function vim.api.nvim_buf_set_extmark(buffer, ns_id, line, col, opts) end
function vim.api.nvim_buf_set_keymap(buffer, mode, lhs, rhs, opts) end
--- Sets (replaces) a line-range in the buffer.
+---
--- Indexing is zero-based, end-exclusive. Negative indices are interpreted as
--- length+1+index: -1 refers to the index past the end. So to change or
--- delete the last element use start=-2 and end=-1.
+---
--- To insert lines at a given index, set `start` and `end` to the same index.
--- To delete a range of lines, set `replacement` to an empty array.
+---
--- Out-of-bounds indices are clamped to the nearest valid value, unless
--- `strict_indexing` is set.
---
@@ -620,13 +658,14 @@ function vim.api.nvim_buf_set_lines(buffer, start, end_, strict_indexing, replac
--- Sets a named mark in the given buffer, all marks are allowed
--- file/uppercase, visual, last change, etc. See `mark-motions`.
+---
--- Marks are (1,0)-indexed. `api-indexing`
---
--- @param buffer integer Buffer to set the mark on
--- @param name string Mark name
--- @param line integer Line number
--- @param col integer Column/row number
---- @param opts table<string,any> Optional parameters. Reserved for future use.
+--- @param opts vim.api.keyset.empty Optional parameters. Reserved for future use.
--- @return boolean
function vim.api.nvim_buf_set_mark(buffer, name, line, col, opts) end
@@ -643,16 +682,21 @@ function vim.api.nvim_buf_set_name(buffer, name) end
function vim.api.nvim_buf_set_option(buffer, name, value) end
--- Sets (replaces) a range in the buffer
+---
--- This is recommended over `nvim_buf_set_lines()` when only modifying parts
--- of a line, as extmarks will be preserved on non-modified parts of the
--- touched lines.
+---
--- 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.
+---
--- Prefer `nvim_put()` if you want to insert text at the cursor position.
---
--- @param buffer integer Buffer handle, or 0 for current buffer
@@ -675,11 +719,12 @@ function vim.api.nvim_buf_set_var(buffer, name, value) end
--- @param src_id integer
--- @param line integer
--- @param chunks any[]
---- @param opts table<string,any>
+--- @param opts vim.api.keyset.empty
--- @return integer
function vim.api.nvim_buf_set_virtual_text(buffer, src_id, line, chunks, opts) end
--- Calls a Vimscript `Dictionary-function` with the given arguments.
+---
--- On execution error: fails with Vimscript error, updates v:errmsg.
---
--- @param dict any Dictionary, or String evaluating to a Vimscript `self` dict
@@ -689,6 +734,7 @@ function vim.api.nvim_buf_set_virtual_text(buffer, src_id, line, chunks, opts) e
function vim.api.nvim_call_dict_function(dict, fn, args) end
--- Calls a Vimscript function with the given arguments.
+---
--- On execution error: fails with Vimscript error, updates v:errmsg.
---
--- @param fn string Function to call
@@ -700,6 +746,7 @@ function vim.api.nvim_call_function(fn, args) end
--- process. For the stdio channel `channel-stdio`, it writes to Nvim's
--- stdout. For an internal terminal instance (`nvim_open_term()`) it writes
--- directly to terminal output. See `channel-bytes` for more information.
+---
--- This function writes raw data, not RPC messages. If the channel was
--- created with `rpc=true` then the channel expects RPC messages, use
--- `vim.rpcnotify()` and `vim.rpcrequest()` instead.
@@ -716,41 +763,41 @@ function vim.api.nvim_chan_send(chan, data) end
--- • event: "pat1"
--- • event: { "pat1" }
--- • event: { "pat1", "pat2", "pat3" }
----
--- • pattern: (string|table)
--- • pattern or patterns to match exactly.
--- • For example, if you have `*.py` as that pattern for the
--- autocmd, you must pass `*.py` exactly to clear it.
--- `test.py` will not match the pattern.
----
--- • defaults to clearing all patterns.
--- • NOTE: Cannot be used with {buffer}
----
--- • buffer: (bufnr)
--- • clear only `autocmd-buflocal` autocommands.
--- • NOTE: Cannot be used with {pattern}
----
--- • group: (string|int) The augroup name or id.
---- • NOTE: If not passed, will only delete autocmds not in any group.
+--- • NOTE: If not passed, will only delete autocmds not in any
+--- group.
function vim.api.nvim_clear_autocmds(opts) end
--- Executes an Ex command.
+---
--- Unlike `nvim_command()` this command takes a structured Dictionary instead
--- of a String. This allows for easier construction and manipulation of an Ex
--- command. This also allows for things such as having spaces inside a
--- command argument, expanding filenames in a command that otherwise doesn't
--- expand filenames, etc. Command arguments may also be Number, Boolean or
--- String.
+---
--- The first argument may also be used instead of count for commands that
--- support it in order to make their usage simpler with `vim.cmd()`. For
--- example, instead of `vim.cmd.bdelete{ count = 2 }`, you may do
--- `vim.cmd.bdelete(2)`.
+---
--- On execution error: fails with Vimscript error, updates v:errmsg.
---
--- @param cmd vim.api.keyset.cmd Command to execute. Must be a Dictionary that can contain the
---- same values as the return value of `nvim_parse_cmd()` except
---- "addr", "nargs" and "nextcmd" which are ignored if provided.
---- All values except for "cmd" are optional.
+--- same values as the return value of `nvim_parse_cmd()` except
+--- "addr", "nargs" and "nextcmd" which are ignored if provided.
+--- All values except for "cmd" are optional.
--- @param opts vim.api.keyset.cmd_opts Optional parameters.
--- • output: (boolean, default false) Whether to return command
--- output.
@@ -758,7 +805,9 @@ function vim.api.nvim_clear_autocmds(opts) end
function vim.api.nvim_cmd(cmd, opts) end
--- Executes an Ex command.
+---
--- On execution error: fails with Vimscript error, updates v:errmsg.
+---
--- Prefer using `nvim_cmd()` or `nvim_exec2()` over this. To evaluate
--- multiple lines of Vim script or an Ex command directly, use
--- `nvim_exec2()`. To construct an Ex command using a structured format and
@@ -773,7 +822,18 @@ function vim.api.nvim_command(command) end
--- @return string
function vim.api.nvim_command_output(command) end
+--- Set info for the completion candidate index. if the info was shown in a
+--- window, then the window and buffer ids are returned for further
+--- customization. If the text was not shown, an empty dict is returned.
+---
+--- @param index integer the completion candidate index
+--- @param opts vim.api.keyset.complete_set Optional parameters.
+--- • info: (string) info text.
+--- @return table<string,any>
+function vim.api.nvim_complete_set(index, opts) end
+
--- Create or get an autocommand group `autocmd-groups`.
+---
--- To get an existing group id, do:
---
--- ```lua
@@ -782,6 +842,7 @@ function vim.api.nvim_command_output(command) end
--- })
--- ```
---
+---
--- @param name string String: The name of the group
--- @param opts vim.api.keyset.create_augroup Dictionary Parameters
--- • clear (bool) optional: defaults to true. Clear existing
@@ -789,7 +850,10 @@ function vim.api.nvim_command_output(command) end
--- @return integer
function vim.api.nvim_create_augroup(name, opts) end
---- Creates an `autocommand` event handler, defined by `callback` (Lua function or Vimscript function name string) or `command` (Ex command string).
+--- Creates an `autocommand` event handler, defined by `callback` (Lua
+--- function or Vimscript function name string) or `command` (Ex command
+--- string).
+---
--- Example using Lua callback:
---
--- ```lua
@@ -817,39 +881,39 @@ function vim.api.nvim_create_augroup(name, opts) end
--- pattern = vim.fn.expand("~") .. "/some/path/*.py"
--- ```
---
+---
--- @param event any (string|array) Event(s) that will trigger the handler
--- (`callback` or `command`).
--- @param opts vim.api.keyset.create_autocmd Options dict:
---- • group (string|integer) optional: autocommand group name or
---- id to match against.
---- • pattern (string|array) optional: pattern(s) to match
---- literally `autocmd-pattern`.
---- • buffer (integer) optional: buffer number for buffer-local
---- autocommands `autocmd-buflocal`. Cannot be used with
---- {pattern}.
---- • desc (string) optional: description (for documentation and
---- troubleshooting).
---- • callback (function|string) optional: Lua function (or
---- Vimscript function name, if string) called when the
---- event(s) is triggered. Lua callback can return true to
---- delete the autocommand, and receives a table argument with
---- these keys:
---- • id: (number) autocommand id
---- • event: (string) name of the triggered event
---- `autocmd-events`
---- • group: (number|nil) autocommand group id, if any
---- • match: (string) expanded value of `<amatch>`
---- • buf: (number) expanded value of `<abuf>`
---- • file: (string) expanded value of `<afile>`
---- • data: (any) arbitrary data passed from
---- `nvim_exec_autocmds()`
----
---- • command (string) optional: Vim command to execute on event.
---- Cannot be used with {callback}
---- • once (boolean) optional: defaults to false. Run the
---- autocommand only once `autocmd-once`.
---- • nested (boolean) optional: defaults to false. Run nested
---- autocommands `autocmd-nested`.
+--- • group (string|integer) optional: autocommand group name or
+--- id to match against.
+--- • pattern (string|array) optional: pattern(s) to match
+--- literally `autocmd-pattern`.
+--- • buffer (integer) optional: buffer number for buffer-local
+--- autocommands `autocmd-buflocal`. Cannot be used with
+--- {pattern}.
+--- • desc (string) optional: description (for documentation and
+--- troubleshooting).
+--- • callback (function|string) optional: Lua function (or
+--- Vimscript function name, if string) called when the event(s)
+--- is triggered. Lua callback can return a truthy value (not
+--- `false` or `nil`) to delete the autocommand. Receives a
+--- table argument with these keys:
+--- • id: (number) autocommand id
+--- • event: (string) name of the triggered event
+--- `autocmd-events`
+--- • group: (number|nil) autocommand group id, if any
+--- • match: (string) expanded value of <amatch>
+--- • buf: (number) expanded value of <abuf>
+--- • file: (string) expanded value of <afile>
+--- • data: (any) arbitrary data passed from
+--- `nvim_exec_autocmds()`
+--- • command (string) optional: Vim command to execute on event.
+--- Cannot be used with {callback}
+--- • once (boolean) optional: defaults to false. Run the
+--- autocommand only once `autocmd-once`.
+--- • nested (boolean) optional: defaults to false. Run nested
+--- autocommands `autocmd-nested`.
--- @return integer
function vim.api.nvim_create_autocmd(event, opts) end
@@ -862,9 +926,11 @@ function vim.api.nvim_create_autocmd(event, opts) end
--- @return integer
function vim.api.nvim_create_buf(listed, scratch) end
---- Creates a new namespace or gets an existing one. *namespace*
+--- Creates a new namespace or gets an existing one. *namespace*
+---
--- Namespaces are used for buffer highlights and virtual text, see
--- `nvim_buf_add_highlight()` and `nvim_buf_set_extmark()`.
+---
--- Namespaces can be named or anonymous. If `name` matches an existing
--- namespace, the associated id is returned. If `name` is an empty string a
--- new, anonymous namespace is created.
@@ -874,7 +940,9 @@ function vim.api.nvim_create_buf(listed, scratch) end
function vim.api.nvim_create_namespace(name) end
--- Creates a global `user-commands` command.
+---
--- For Lua usage see `lua-guide-commands-create`.
+---
--- Example:
---
--- ```vim
@@ -883,51 +951,52 @@ function vim.api.nvim_create_namespace(name) end
--- Hello world!
--- ```
---
+---
--- @param name string Name of the new user command. Must begin with an uppercase
---- letter.
+--- letter.
--- @param command any Replacement command to execute when this user command is
--- executed. When called from Lua, the command can also be a
--- Lua function. The function is called with a single table
--- 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()`.
--- @param opts vim.api.keyset.user_command Optional `command-attributes`.
---- • Set boolean attributes such as `:command-bang` or
---- `:command-bar` to true (but not `:command-buffer`, use
---- `nvim_buf_create_user_command()` instead).
---- • "complete" `:command-complete` also accepts a Lua
---- function which works like
---- `:command-completion-customlist`.
---- • Other parameters:
---- • desc: (string) Used for listing the command when a Lua
---- function is used for {command}.
---- • force: (boolean, default true) Override any previous
---- definition.
---- • preview: (function) Preview callback for 'inccommand'
---- `:command-preview`
+--- • Set boolean attributes such as `:command-bang` or
+--- `:command-bar` to true (but not `:command-buffer`, use
+--- `nvim_buf_create_user_command()` instead).
+--- • "complete" `:command-complete` also accepts a Lua function
+--- which works like `:command-completion-customlist`.
+--- • Other parameters:
+--- • desc: (string) Used for listing the command when a Lua
+--- function is used for {command}.
+--- • force: (boolean, default true) Override any previous
+--- definition.
+--- • preview: (function) Preview callback for 'inccommand'
+--- `:command-preview`
function vim.api.nvim_create_user_command(name, command, opts) end
--- Delete an autocommand group by id.
+---
--- To get a group id one can use `nvim_get_autocmds()`.
+---
--- NOTE: behavior differs from `:augroup-delete`. When deleting a group,
--- autocommands contained in this group will also be deleted and cleared.
--- This group will no longer exist.
@@ -936,6 +1005,7 @@ function vim.api.nvim_create_user_command(name, command, opts) end
function vim.api.nvim_del_augroup_by_id(id) end
--- Delete an autocommand group by name.
+---
--- NOTE: behavior differs from `:augroup-delete`. When deleting a group,
--- autocommands contained in this group will also be deleted and cleared.
--- This group will no longer exist.
@@ -953,6 +1023,7 @@ function vim.api.nvim_del_autocmd(id) end
function vim.api.nvim_del_current_line() end
--- Unmaps a global `mapping` for the given mode.
+---
--- To unmap a buffer-local mapping, use `nvim_buf_del_keymap()`.
---
--- @param mode string
@@ -977,15 +1048,15 @@ 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 if Nvim was invoked with -V3log_file, the message
---- will be redirected to the log_file and suppressed from
---- direct output.
+--- • verbose: Message was printed as a result of 'verbose' option
+--- if Nvim was invoked with -V3log_file, the message will be
+--- redirected to the log_file and suppressed from direct
+--- output.
function vim.api.nvim_echo(chunks, history, opts) end
--- Writes a message to the Vim error buffer. Does not append "\n", the
@@ -1002,6 +1073,7 @@ function vim.api.nvim_err_writeln(str) end
--- Evaluates a Vimscript `expression`. Dictionaries and Lists are recursively
--- expanded.
+---
--- On execution error: fails with Vimscript error, updates v:errmsg.
---
--- @param expr string Vimscript expression string
@@ -1036,8 +1108,10 @@ function vim.api.nvim_exec(src, output) end
--- Executes Vimscript (multiline block of Ex commands), like anonymous
--- `:source`.
+---
--- Unlike `nvim_command()` this function supports heredocs, script-scope
--- (s:), etc.
+---
--- On execution error: fails with Vimscript error, updates v:errmsg.
---
--- @param src string Vimscript code
@@ -1052,24 +1126,27 @@ function vim.api.nvim_exec2(src, opts) end
---
--- @param event any (String|Array) The event or events to execute
--- @param opts vim.api.keyset.exec_autocmds Dictionary of autocommand options:
---- • group (string|integer) optional: the autocommand group name
---- or id to match against. `autocmd-groups`.
---- • pattern (string|array) optional: defaults to "*"
---- `autocmd-pattern`. Cannot be used with {buffer}.
---- • 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>`.
---- • data (any): arbitrary data to send to the autocommand
---- callback. See `nvim_create_autocmd()` for details.
+--- • group (string|integer) optional: the autocommand group name
+--- or id to match against. `autocmd-groups`.
+--- • pattern (string|array) optional: defaults to "*"
+--- `autocmd-pattern`. Cannot be used with {buffer}.
+--- • 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>.
+--- • 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
--- Sends input-keys to Nvim, subject to various quirks controlled by `mode`
--- flags. This is a blocking call, unlike `nvim_input()`.
+---
--- On execution error: does not fail, but updates v:errmsg.
+---
--- To input sequences like <C-o> use `nvim_replace_termcodes()` (typically
--- with escape_ks=false) to replace `keycodes`, then pass the result to
--- nvim_feedkeys().
+---
--- Example:
---
--- ```vim
@@ -1077,6 +1154,7 @@ function vim.api.nvim_exec_autocmds(event, opts) end
--- :call nvim_feedkeys(key, 'n', v:false)
--- ```
---
+---
--- @param keys string to be typed
--- @param mode string behavior flags, see `feedkeys()`
--- @param escape_ks boolean If true, escape K_SPECIAL bytes in `keys`. This should be
@@ -1085,6 +1163,7 @@ function vim.api.nvim_exec_autocmds(event, opts) end
function vim.api.nvim_feedkeys(keys, mode, escape_ks) end
--- Gets the option information for all options.
+---
--- The dictionary has the full option names as keys and option metadata
--- dictionaries as detailed at `nvim_get_option_info2()`.
---
@@ -1092,6 +1171,7 @@ function vim.api.nvim_feedkeys(keys, mode, escape_ks) end
function vim.api.nvim_get_all_options_info() end
--- Get all autocommands that match the corresponding {opts}.
+---
--- These examples will get autocommands matching ALL the given criteria:
---
--- ```lua
@@ -1121,17 +1201,18 @@ function vim.api.nvim_get_all_options_info() end
--- • buffer: Buffer number or list of buffer numbers for buffer
--- local autocommands `autocmd-buflocal`. Cannot be used with
--- {pattern}
---- @return any[]
+--- @return vim.api.keyset.get_autocmds.ret[]
function vim.api.nvim_get_autocmds(opts) end
--- Gets information about a channel.
---
---- @param chan integer
+--- @param chan integer channel_id, or 0 for current channel
--- @return table<string,any>
function vim.api.nvim_get_chan_info(chan) end
--- Returns the 24-bit RGB value of a `nvim_get_color_map()` color name or
--- "#rrggbb" hexadecimal string.
+---
--- Example:
---
--- ```vim
@@ -1139,18 +1220,21 @@ function vim.api.nvim_get_chan_info(chan) end
--- :echo nvim_get_color_by_name("#cbcbcb")
--- ```
---
+---
--- @param name string Color name or "#rrggbb" string
--- @return integer
function vim.api.nvim_get_color_by_name(name) end
--- Returns a map of color names and RGB values.
+---
--- Keys are color names (e.g. "Aqua") and values are 24-bit RGB color values
--- (e.g. 65535).
---
---- @return table<string,any>
+--- @return table<string,integer>
function vim.api.nvim_get_color_map() end
--- Gets a map of global (non-buffer-local) Ex commands.
+---
--- Currently only `user-commands` are supported, not builtin Ex commands.
---
--- @param opts vim.api.keyset.get_commands Optional parameters. Currently only supports {"builtin":false}
@@ -1191,13 +1275,13 @@ function vim.api.nvim_get_current_win() end
--- `nvim_get_namespaces()`. Use 0 to get global highlight groups
--- `:highlight`.
--- @param opts vim.api.keyset.get_highlight Options dict:
---- • name: (string) Get a highlight definition by name.
---- • id: (integer) Get a highlight definition by id.
---- • link: (boolean, default true) Show linked group name
---- instead of effective definition `:hi-link`.
---- • create: (boolean, default true) When highlight group
---- doesn't exist create it.
---- @return table<string,any>
+--- • name: (string) Get a highlight definition by name.
+--- • id: (integer) Get a highlight definition by id.
+--- • link: (boolean, default true) Show linked group name instead
+--- of effective definition `:hi-link`.
+--- • create: (boolean, default true) When highlight group doesn't
+--- exist create it.
+--- @return vim.api.keyset.hl_info
function vim.api.nvim_get_hl(ns_id, opts) end
--- @deprecated
@@ -1213,6 +1297,7 @@ function vim.api.nvim_get_hl_by_id(hl_id, rgb) end
function vim.api.nvim_get_hl_by_name(name, rgb) end
--- Gets a highlight group by name
+---
--- similar to `hlID()`, but allocates a new ID if not present.
---
--- @param name string
@@ -1232,28 +1317,29 @@ function vim.api.nvim_get_hl_ns(opts) end
--- Gets a list of global (non-buffer-local) `mapping` definitions.
---
--- @param mode string Mode short-name ("n", "i", "v", ...)
---- @return table<string,any>[]
+--- @return vim.api.keyset.keymap[]
function vim.api.nvim_get_keymap(mode) end
--- Returns a `(row, col, buffer, buffername)` tuple representing the position
--- of the uppercase/file named mark. "End of line" column position is
--- returned as `v:maxcol` (big number). See `mark-motions`.
+---
--- Marks are (1,0)-indexed. `api-indexing`
---
--- @param name string Mark name
---- @param opts table<string,any> Optional parameters. Reserved for future use.
---- @return any[]
+--- @param opts vim.api.keyset.empty Optional parameters. Reserved for future use.
+--- @return vim.api.keyset.get_mark
function vim.api.nvim_get_mark(name, opts) end
--- Gets the current mode. `mode()` "blocking" is true if Nvim is waiting for
--- input.
---
---- @return table<string,any>
+--- @return vim.api.keyset.get_mode
function vim.api.nvim_get_mode() end
--- Gets existing, non-anonymous `namespace`s.
---
---- @return table<string,any>
+--- @return table<string,integer>
function vim.api.nvim_get_namespaces() end
--- @deprecated
@@ -1263,10 +1349,11 @@ function vim.api.nvim_get_option(name) end
--- @deprecated
--- @param name string
---- @return table<string,any>
+--- @return vim.api.keyset.get_option_info
function vim.api.nvim_get_option_info(name) end
--- Gets the option information for one option from arbitrary buffer or window
+---
--- Resulting dictionary has keys:
--- • name: Name of the option (like 'filetype')
--- • shortname: Shortened name of the option (like 'ft')
@@ -1293,7 +1380,7 @@ function vim.api.nvim_get_option_info(name) end
--- • win: `window-ID`. Used for getting window local options.
--- • buf: Buffer number. Used for getting buffer local options.
--- Implies {scope} is "local".
---- @return table<string,any>
+--- @return vim.api.keyset.get_option_info
function vim.api.nvim_get_option_info2(name, opts) end
--- Gets the value of an option. The behavior of this function matches that of
@@ -1328,10 +1415,12 @@ function vim.api.nvim_get_proc(pid) end
function vim.api.nvim_get_proc_children(pid) end
--- Find files in runtime directories
+---
--- "name" can contain wildcards. For example
--- nvim_get_runtime_file("colors/*.vim", true) will return all color scheme
--- files. Always use forward slashes (/) in the search pattern for
--- subdirectories regardless of platform.
+---
--- It is not an error to not find any files. An empty array is returned then.
---
--- @param name string pattern of files to search for
@@ -1354,6 +1443,7 @@ function vim.api.nvim_get_vvar(name) end
--- Queues raw user-input. Unlike `nvim_feedkeys()`, this uses a low-level
--- input buffer and the call is non-blocking (input is processed
--- asynchronously by the eventloop).
+---
--- On execution error: does not fail, but updates v:errmsg.
---
--- @param keys string to be typed
@@ -1361,14 +1451,15 @@ function vim.api.nvim_get_vvar(name) end
function vim.api.nvim_input(keys) end
--- Send mouse event from GUI.
+---
--- Non-blocking: does not wait on any result, but queues the event to be
--- processed soon by the event loop.
---
--- @param button string Mouse button: one of "left", "right", "middle", "wheel",
---- "move".
---- @param action string For ordinary buttons, one of "press", "drag", "release".
---- For the wheel, one of "up", "down", "left", "right".
---- Ignored for "move".
+--- "move", "x1", "x2".
+--- @param action string For ordinary buttons, one of "press", "drag", "release". For
+--- the wheel, one of "up", "down", "left", "right". Ignored for
+--- "move".
--- @param modifier string String of modifiers each represented by a single char. The
--- same specifiers are used as for a key press, except that
--- the "-" separator is optional, so "C-A-", "c-a" and "CA"
@@ -1379,6 +1470,7 @@ function vim.api.nvim_input(keys) end
function vim.api.nvim_input_mouse(button, action, modifier, grid, row, col) end
--- Gets the current list of buffer handles
+---
--- Includes unlisted (unloaded/deleted) buffers, like `:ls!`. Use
--- `nvim_buf_is_loaded()` to check if a buffer is loaded.
---
@@ -1417,6 +1509,7 @@ function vim.api.nvim_list_wins() end
function vim.api.nvim_load_context(dict) end
--- Notify the user with a message
+---
--- Relays the call to vim.notify . By default forwards your message in the
--- echo area but can be overridden to trigger desktop notifications.
---
@@ -1427,10 +1520,12 @@ function vim.api.nvim_load_context(dict) end
function vim.api.nvim_notify(msg, log_level, opts) end
--- Open a terminal instance in a buffer
+---
--- By default (and currently the only option) the terminal will not be
--- connected to an external process. Instead, input send on the channel will
--- be echoed directly by the terminal. This is useful to display ANSI
--- terminal sequences returned as part of a rpc message, or similar.
+---
--- Note: to directly initiate the terminal using the right size, display the
--- buffer in a configured window before calling this. For instance, for a
--- floating display, first create an empty buffer using `nvim_create_buf()`,
@@ -1439,34 +1534,51 @@ function vim.api.nvim_notify(msg, log_level, opts) end
--- virtual terminal having the intended size.
---
--- @param buffer integer the buffer to use (expected to be empty)
---- @param opts table<string,function> Optional parameters.
---- • on_input: Lua callback for input sent, i e keypresses in
---- terminal mode. Note: keypresses are sent raw as they would
---- 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]
+--- @param opts vim.api.keyset.open_term Optional parameters.
+--- • on_input: Lua callback for input sent, i e keypresses in
+--- terminal mode. Note: keypresses are sent raw as they would
+--- 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]`
+--- • force_crlf: (boolean, default true) Convert "\n" to "\r\n".
--- @return integer
function vim.api.nvim_open_term(buffer, opts) end
---- Open a new window.
---- Currently this is used to open floating and external windows. Floats are
---- windows that are drawn above the split layout, at some anchor position in
---- some other window. Floats can be drawn internally or by external GUI with
---- the `ui-multigrid` extension. External windows are only supported with
---- multigrid GUIs, and are displayed as separate top-level windows.
+--- Opens a new split window, or a floating window if `relative` is specified,
+--- or an external window (managed by the UI) if `external` is specified.
+---
+--- Floats are windows that are drawn above the split layout, at some anchor
+--- position in some other window. Floats can be drawn internally or by
+--- external GUI with the `ui-multigrid` extension. External windows are only
+--- supported with multigrid GUIs, and are displayed as separate top-level
+--- windows.
+---
--- For a general overview of floats, see `api-floatwin`.
---- Exactly one of `external` and `relative` must be specified. The `width`
---- and `height` of the new window must be specified.
+---
+--- The `width` and `height` of the new window must be specified when opening
+--- a floating window, but are optional for normal windows.
+---
+--- If `relative` and `external` are omitted, a normal "split" window is
+--- created. The `win` property determines which window will be split. If no
+--- `win` is provided or `win == 0`, a window will be created adjacent to the
+--- current window. If -1 is provided, a top-level split will be created.
+--- `vertical` and `split` are only valid for normal windows, and are used to
+--- control split direction. For `vertical`, the exact direction is determined
+--- by `'splitright'` and `'splitbelow'`. Split windows cannot have
+--- `bufpos`/`row`/`col`/`border`/`title`/`footer` properties.
+---
--- With relative=editor (row=0,col=0) refers to the top-left corner of the
--- screen-grid and (row=Lines-1,col=Columns-1) refers to the bottom-right
--- corner. Fractional values are allowed, but the builtin implementation
--- (used by non-multigrid UIs) will always round down to nearest integer.
+---
--- Out-of-bounds values, and configurations that make the float not fit
--- inside the main editor, are allowed. The builtin implementation truncates
--- values so floats are fully within the main screen grid. External GUIs
--- could let floats hover outside of the main window like a tooltip, but this
--- should not be used to specify arbitrary WM screen positions.
+---
--- Example (Lua): window-relative float
---
--- ```lua
@@ -1479,12 +1591,21 @@ function vim.api.nvim_open_term(buffer, opts) end
--- ```lua
--- vim.api.nvim_open_win(0, false,
--- {relative='win', width=12, height=3, bufpos={100,10}})
+--- ```
+---
+--- Example (Lua): vertical split left of the current window
+---
+--- ```lua
+--- vim.api.nvim_open_win(0, false, {
+--- split = 'left',
+--- win = 0
--- })
--- ```
---
+---
--- @param buffer integer Buffer to display, or 0 for current buffer
--- @param enter boolean Enter the window (make it the current window)
---- @param config vim.api.keyset.float_config Map defining the window configuration. Keys:
+--- @param config vim.api.keyset.win_config Map defining the window configuration. Keys:
--- • relative: Sets the window layout to "floating", placed at
--- (row,col) coordinates relative to:
--- • "editor" The global editor grid
@@ -1492,25 +1613,23 @@ function vim.api.nvim_open_term(buffer, opts) end
--- window.
--- • "cursor" Cursor position in current window.
--- • "mouse" Mouse position
----
---- • win: `window-ID` for relative="win".
+--- • win: `window-ID` window to split, or relative window when
+--- creating a float (relative="win").
--- • anchor: Decides which corner of the float to place at
--- (row,col):
--- • "NW" northwest (default)
--- • "NE" northeast
--- • "SW" southwest
--- • "SE" southeast
----
--- • 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).
----
--- • row: Row position in units of "screen cell height", may be
--- fractional.
--- • col: Column position in units of "screen cell width", may
@@ -1521,8 +1640,9 @@ function vim.api.nvim_open_term(buffer, opts) end
--- • external: GUI should display the window as an external
--- top-level window. Currently accepts no other positioning
--- configuration together with this.
---- • zindex: Stacking order. floats with higher `zindex` go on top on floats with lower indices. Must be larger
---- than zero. The following screen elements have hard-coded
+--- • zindex: Stacking order. floats with higher `zindex` go on
+--- top on floats with lower indices. Must be larger than
+--- zero. The following screen elements have hard-coded
--- z-indices:
--- • 100: insert completion popupmenu
--- • 200: message scrollback
@@ -1530,7 +1650,6 @@ function vim.api.nvim_open_term(buffer, opts) end
--- wildoptions+=pum) The default value for floats are 50.
--- In general, values below 100 are recommended, unless
--- there is a good reason to overshadow builtin elements.
----
--- • style: (optional) Configure the appearance of the window.
--- Currently only supports one value:
--- • "minimal" Nvim will display the window with many UI
@@ -1543,14 +1662,13 @@ function vim.api.nvim_open_term(buffer, opts) end
--- empty. The end-of-buffer region is hidden by setting
--- `eob` flag of 'fillchars' to a space char, and clearing
--- the `hl-EndOfBuffer` region in 'winhighlight'.
----
--- • border: Style of (optional) window border. This can either
--- be a string or an array. The string values are
--- • "none": No border (default).
--- • "single": A single line box.
--- • "double": A double line box.
---- • "rounded": Like "single", but with rounded corners ("╭"
---- etc.).
+--- • "rounded": Like "single", but with rounded corners
+--- ("╭" etc.).
--- • "solid": Adds padding by a single whitespace cell.
--- • "shadow": A drop shadow effect by blending with the
--- background.
@@ -1558,18 +1676,35 @@ function vim.api.nvim_open_term(buffer, opts) end
--- any divisor of eight. The array will specify the eight
--- chars building up the border in a clockwise fashion
--- starting with the top-left corner. As an example, the
---- double box style could be specified as [ "╔", "═" ,"╗",
---- "║", "╝", "═", "╚", "║" ]. If the number of chars are
---- less than eight, they will be repeated. Thus an ASCII
---- border could be specified as [ "/", "-", "\\", "|" ], or
---- all chars the same as [ "x" ]. An empty string can be
---- used to turn off a specific border, for instance, [ "",
---- "", "", ">", "", "", "", "<" ] will only make vertical
---- borders but not horizontal ones. By default,
---- `FloatBorder` highlight is used, which links to
---- `WinSeparator` when not defined. It could also be
---- specified by character: [ ["+", "MyCorner"], ["x",
---- "MyBorder"] ].
+--- double box style could be specified as:
+--- ```
+--- [ "╔", "═" ,"╗", "║", "╝", "═", "╚", "║" ].
+--- ```
+---
+--- If the number of chars are less than eight, they will be
+--- repeated. Thus an ASCII border could be specified as
+--- ```
+--- [ "/", "-", \"\\\\\", "|" ],
+--- ```
+---
+--- or all chars the same as
+--- ```
+--- [ "x" ].
+--- ```
+---
+--- An empty string can be used to turn off a specific border,
+--- for instance,
+--- ```
+--- [ "", "", "", ">", "", "", "", "<" ]
+--- ```
+---
+--- will only make vertical borders but not horizontal ones.
+--- By default, `FloatBorder` highlight is used, which links
+--- to `WinSeparator` when not defined. It could also be
+--- specified by character:
+--- ```
+--- [ ["+", "MyCorner"], ["x", "MyBorder"] ].
+--- ```
---
--- • title: Title (optional) in window border, string or list.
--- List should consist of `[text, highlight]` tuples. If
@@ -1589,6 +1724,8 @@ function vim.api.nvim_open_term(buffer, opts) end
--- • 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.
+--- • vertical: Split vertically `:vertical`.
+--- • split: Split direction: "left", "right", "above", "below".
--- @return integer
function vim.api.nvim_open_win(buffer, enter, config) end
@@ -1599,28 +1736,29 @@ function vim.api.nvim_open_win(buffer, enter, config) end
function vim.api.nvim_out_write(str) end
--- Parse command line.
+---
--- Doesn't check the validity of command arguments.
---
--- @param str string Command line string to parse. Cannot contain "\n".
---- @param opts table<string,any> Optional parameters. Reserved for future use.
---- @return table<string,any>
+--- @param opts vim.api.keyset.empty Optional parameters. Reserved for future use.
+--- @return vim.api.keyset.parse_cmd
function vim.api.nvim_parse_cmd(str, opts) end
--- Parse a Vimscript expression.
---
--- @param expr string Expression to parse. Always treated as a single line.
--- @param flags string Flags:
---- • "m" if multiple expressions in a row are allowed (only
---- the first one will be parsed),
---- • "E" if EOC tokens are not allowed (determines whether
---- they will stop parsing process or be recognized as an
---- 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>=".
---- • empty string for ":call".
---- • "lm" to parse for ":let".
+--- • "m" if multiple expressions in a row are allowed (only the
+--- first one will be parsed),
+--- • "E" if EOC tokens are not allowed (determines whether they
+--- will stop parsing process or be recognized as an
+--- 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>="`.
+--- • empty string for ":call".
+--- • "lm" to parse for ":let".
--- @param highlight boolean If true, return value will also include "highlight" key
--- containing array of 4-tuples (arrays) (Integer, Integer,
--- Integer, String), where first three numbers define the
@@ -1631,8 +1769,10 @@ function vim.api.nvim_parse_cmd(str, opts) end
function vim.api.nvim_parse_expression(expr, flags, highlight) end
--- Pastes at cursor, in any mode.
+---
--- Invokes the `vim.paste` handler, which handles each mode appropriately.
--- Sets redo/undo. Faster than `nvim_input()`. Lines break at LF ("\n").
+---
--- Errors ('nomodifiable', `vim.paste()` failure, …) are reflected in `err`
--- but do not affect the return value (which is strictly decided by
--- `vim.paste()`). On error, subsequent calls are ignored ("drained") until
@@ -1641,7 +1781,8 @@ function vim.api.nvim_parse_expression(expr, flags, highlight) end
--- @param data string Multiline input. May be binary (containing NUL bytes).
--- @param crlf boolean Also break lines at CR and CRLF.
--- @param phase integer -1: paste in a single call (i.e. without streaming). To
---- "stream" a paste, call `nvim_paste` sequentially with these `phase` values:
+--- "stream" a paste, call `nvim_paste` sequentially with these
+--- `phase` values:
--- • 1: starts the paste (exactly once)
--- • 2: continues the paste (zero or more times)
--- • 3: ends the paste (exactly once)
@@ -1649,16 +1790,16 @@ function vim.api.nvim_parse_expression(expr, flags, highlight) end
function vim.api.nvim_paste(data, crlf, phase) end
--- Puts text at cursor, in any mode.
+---
--- Compare `:put` and `p` which are always linewise.
---
--- @param lines string[] `readfile()`-style list of lines. `channel-lines`
--- @param type string Edit behavior: any `getregtype()` result, or:
---- • "b" `blockwise-visual` mode (may include width, e.g. "b3")
---- • "c" `charwise` mode
---- • "l" `linewise` mode
---- • "" guess by contents, see `setreg()`
---- @param after boolean If true insert after cursor (like `p`), or before (like
---- `P`).
+--- • "b" `blockwise-visual` mode (may include width, e.g. "b3")
+--- • "c" `charwise` mode
+--- • "l" `linewise` mode
+--- • "" guess by contents, see `setreg()`
+--- @param after boolean If true insert after cursor (like `p`), or before (like `P`).
--- @param follow boolean If true place cursor at end of inserted text.
function vim.api.nvim_put(lines, type, after, follow) end
@@ -1673,19 +1814,20 @@ function vim.api.nvim_put(lines, type, after, follow) end
function vim.api.nvim_replace_termcodes(str, from_part, do_lt, special) end
--- Selects an item in the completion popup menu.
+---
--- If neither `ins-completion` nor `cmdline-completion` popup menu is active
--- this API call is silently ignored. Useful for an external UI using
--- `ui-popupmenu` to control the popup menu with the mouse. Can also be used
--- in a mapping; use <Cmd> `:map-cmd` or a Lua mapping to ensure the mapping
--- doesn't end completion mode.
---
---- @param item integer Index (zero-based) of the item to select. Value of -1
---- selects nothing and restores the original text.
+--- @param item integer Index (zero-based) of the item to select. Value of -1 selects
+--- nothing and restores the original text.
--- @param insert boolean For `ins-completion`, whether the selection should be
--- inserted in the buffer. Ignored for `cmdline-completion`.
--- @param finish boolean Finish the completion and dismiss the popup menu. Implies
--- {insert}.
---- @param opts table<string,any> Optional parameters. Reserved for future use.
+--- @param opts vim.api.keyset.empty Optional parameters. Reserved for future use.
function vim.api.nvim_select_popupmenu_item(item, insert, finish, opts) end
--- Sets the current buffer.
@@ -1714,13 +1856,16 @@ function vim.api.nvim_set_current_tabpage(tabpage) end
function vim.api.nvim_set_current_win(window) end
--- Set or change decoration provider for a `namespace`
+---
--- This is a very general purpose interface for having Lua callbacks being
--- triggered during the redraw code.
+---
--- The expected usage is to set `extmarks` for the currently redrawn buffer.
--- `nvim_buf_set_extmark()` can be called to add marks on a per-window or
--- per-lines basis. Use the `ephemeral` key to only use the mark for the
--- current screen redraw (the callback will be called again for the next
--- redraw).
+---
--- Note: this function should not be called often. Rather, the callbacks
--- themselves can be used to throttle unneeded callbacks. the `on_start`
--- callback can return `false` to disable the provider until the next redraw.
@@ -1729,28 +1874,44 @@ function vim.api.nvim_set_current_win(window) end
--- plugin managing multiple sources of decoration should ideally only set one
--- provider, and merge the sources internally. You can use multiple `ns_id`
--- for the extmarks set/modified inside the callback anyway.
+---
--- Note: doing anything other than setting extmarks is considered
--- experimental. Doing things like changing options are not explicitly
--- forbidden, but is likely to have unexpected consequences (such as 100% CPU
--- consumption). doing `vim.rpcnotify` should be OK, but `vim.rpcrequest` is
--- quite dubious for the moment.
+---
--- Note: It is not allowed to remove or update extmarks in 'on_line'
--- callbacks.
---
--- @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_buf: called for each buffer being redrawn (before window
---- callbacks) ["buf", bufnr, tick]
---- • on_win: called when starting to redraw a specific window.
---- botline_guess is an approximation that does not exceed the
---- last line number. ["win", winid, bufnr, topline,
---- botline_guess]
---- • on_line: called for each buffer line being redrawn. (The
---- interaction with fold lines is subject to change) ["win",
---- winid, bufnr, row]
---- • on_end: called at the end of a redraw cycle ["end", 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]
+--- ```
+---
+--- • on_win: called when starting to redraw a specific window.
+--- ```
+--- ["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]
+--- ```
function vim.api.nvim_set_decoration_provider(ns_id, opts) end
--- Sets a highlight group.
@@ -1762,31 +1923,31 @@ function vim.api.nvim_set_decoration_provider(ns_id, opts) end
--- activate them.
--- @param name string Highlight group name, e.g. "ErrorMsg"
--- @param val vim.api.keyset.highlight Highlight definition map, accepts the following keys:
---- • fg (or foreground): color name or "#RRGGBB", see note.
---- • bg (or background): color name or "#RRGGBB", see note.
---- • sp (or special): color name or "#RRGGBB"
---- • blend: integer between 0 and 100
---- • bold: boolean
---- • standout: boolean
---- • underline: boolean
---- • undercurl: boolean
---- • underdouble: boolean
---- • underdotted: boolean
---- • underdashed: boolean
---- • strikethrough: boolean
---- • italic: boolean
---- • reverse: boolean
---- • nocombine: boolean
---- • link: name of another highlight group to link to, see
---- `:hi-link`.
---- • default: Don't override existing definition `:hi-default`
---- • ctermfg: Sets foreground of cterm color `ctermfg`
---- • ctermbg: Sets background of cterm color `ctermbg`
---- • cterm: cterm attribute map, like `highlight-args`. If not
---- set, cterm attributes will match those from the attribute
---- map documented above.
---- • force: if true force update the highlight group when it
---- exists.
+--- • fg: color name or "#RRGGBB", see note.
+--- • bg: color name or "#RRGGBB", see note.
+--- • sp: color name or "#RRGGBB"
+--- • blend: integer between 0 and 100
+--- • bold: boolean
+--- • standout: boolean
+--- • underline: boolean
+--- • undercurl: boolean
+--- • underdouble: boolean
+--- • underdotted: boolean
+--- • underdashed: boolean
+--- • strikethrough: boolean
+--- • italic: boolean
+--- • reverse: boolean
+--- • nocombine: boolean
+--- • link: name of another highlight group to link to, see
+--- `:hi-link`.
+--- • default: Don't override existing definition `:hi-default`
+--- • ctermfg: Sets foreground of cterm color `ctermfg`
+--- • ctermbg: Sets background of cterm color `ctermbg`
+--- • cterm: cterm attribute map, like `highlight-args`. If not
+--- set, cterm attributes will match those from the attribute map
+--- documented above.
+--- • force: if true force update the highlight group when it
+--- exists.
function vim.api.nvim_set_hl(ns_id, name, val) end
--- Set active namespace for highlights defined with `nvim_set_hl()`. This can
@@ -1797,6 +1958,7 @@ function vim.api.nvim_set_hl_ns(ns_id) end
--- Set active namespace for highlights defined with `nvim_set_hl()` while
--- redrawing.
+---
--- This function meant to be called while redrawing, primarily from
--- `nvim_set_decoration_provider()` on_win and on_line callbacks, which are
--- allowed to change the namespace during a redraw cycle.
@@ -1805,9 +1967,12 @@ function vim.api.nvim_set_hl_ns(ns_id) end
function vim.api.nvim_set_hl_ns_fast(ns_id) end
--- Sets a global `mapping` for the given mode.
+---
--- 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:
---
--- ```vim
@@ -1820,14 +1985,15 @@ function vim.api.nvim_set_hl_ns_fast(ns_id) end
--- nmap <nowait> <Space><NL> <Nop>
--- ```
---
---- @param mode string Mode short-name (map command prefix: "n", "i", "v", "x", …) or
---- "!" for `:map!`, or empty string for `:map`. "ia", "ca" or
+---
+--- @param mode string Mode short-name (map command prefix: "n", "i", "v", "x", …)
+--- or "!" for `:map!`, or empty string for `:map`. "ia", "ca" or
--- "!a" for abbreviation in Insert mode, Cmdline mode, or both,
--- respectively
--- @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}.
@@ -1845,15 +2011,16 @@ function vim.api.nvim_set_option(name, value) end
--- Sets the value of an option. The behavior of this function matches that of
--- `:set`: for global-local options, both the global and local value are set
--- unless otherwise specified with {scope}.
+---
--- Note the options {win} and {buf} cannot be used together.
---
--- @param name string Option name
--- @param value any New option value
--- @param opts vim.api.keyset.option Optional parameters
---- • scope: One of "global" or "local". Analogous to
---- `:setglobal` and `:setlocal`, respectively.
---- • win: `window-ID`. Used for setting window local option.
---- • buf: Buffer number. Used for setting buffer local option.
+--- • scope: One of "global" or "local". Analogous to `:setglobal`
+--- and `:setlocal`, respectively.
+--- • win: `window-ID`. Used for setting window local option.
+--- • buf: Buffer number. Used for setting buffer local option.
function vim.api.nvim_set_option_value(name, value, opts) end
--- Sets a global (g:) variable.
@@ -1919,11 +2086,24 @@ function vim.api.nvim_tabpage_list_wins(tabpage) end
--- @param value any Variable value
function vim.api.nvim_tabpage_set_var(tabpage, name, value) end
+--- Sets the current window in a tabpage
+---
+--- @param tabpage integer Tabpage handle, or 0 for current tabpage
+--- @param win integer Window handle, must already belong to {tabpage}
+function vim.api.nvim_tabpage_set_win(tabpage, win) end
+
+--- Adds the namespace scope to the window.
+---
+--- @param window integer Window handle, or 0 for current window
+--- @param ns_id integer the namespace to add
+--- @return boolean
+function vim.api.nvim_win_add_ns(window, ns_id) end
+
--- Calls a function with window as temporary current window.
---
--- @param window integer Window handle, or 0 for current window
--- @param fun function Function to call inside the window (currently Lua callable
---- only)
+--- only)
--- @return any
function vim.api.nvim_win_call(window, fun) end
@@ -1931,8 +2111,8 @@ function vim.api.nvim_win_call(window, fun) end
---
--- @param window integer Window handle, or 0 for current window
--- @param force boolean Behave like `:close!` The last window of a buffer with
---- unwritten changes can be closed. The buffer will become
---- hidden, even if 'hidden' is not set.
+--- unwritten changes can be closed. The buffer will become
+--- hidden, even if 'hidden' is not set.
function vim.api.nvim_win_close(window, force) end
--- Removes a window-scoped (w:) variable
@@ -1948,11 +2128,13 @@ function vim.api.nvim_win_del_var(window, name) end
function vim.api.nvim_win_get_buf(window) end
--- Gets window configuration.
+---
--- The returned value may be given to `nvim_open_win()`.
+---
--- `relative` is empty for normal windows.
---
--- @param window integer Window handle, or 0 for current window
---- @return table<string,any>
+--- @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
@@ -1969,6 +2151,12 @@ function vim.api.nvim_win_get_cursor(window) end
--- @return integer
function vim.api.nvim_win_get_height(window) end
+--- Gets all the namespaces scopes associated with a window.
+---
+--- @param window integer Window handle, or 0 for current window
+--- @return integer[]
+function vim.api.nvim_win_get_ns(window) end
+
--- Gets the window number
---
--- @param window integer Window handle, or 0 for current window
@@ -2008,6 +2196,7 @@ function vim.api.nvim_win_get_width(window) end
--- Closes the window and hide the buffer it contains (like `:hide` with a
--- `window-ID`).
+---
--- Like `:hide` the buffer becomes hidden unless another window is editing
--- it, or 'bufhidden' is `unload`, `delete` or `wipe` as opposed to `:close`
--- or `nvim_win_close()`, which will close the buffer.
@@ -2021,6 +2210,13 @@ function vim.api.nvim_win_hide(window) end
--- @return boolean
function vim.api.nvim_win_is_valid(window) end
+--- Removes the namespace scope from the window.
+---
+--- @param window integer Window handle, or 0 for current window
+--- @param ns_id integer the namespace to remove
+--- @return boolean
+function vim.api.nvim_win_remove_ns(window, ns_id) end
+
--- Sets the current buffer in a window, without side effects
---
--- @param window integer Window handle, or 0 for current window
@@ -2029,11 +2225,12 @@ 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).
+---
--- When reconfiguring a floating 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.float_config Map defining the window configuration, see `nvim_open_win()`
+--- @param config vim.api.keyset.win_config Map defining the window configuration, see `nvim_open_win()`
function vim.api.nvim_win_set_config(window, config) end
--- Sets the (1,0)-indexed cursor position in the window. `api-indexing` This
@@ -2052,6 +2249,7 @@ function vim.api.nvim_win_set_height(window, height) end
--- Set highlight namespace for a window. This will use highlights defined
--- with `nvim_set_hl()` for this namespace, but fall back to global
--- highlights (ns=0) when missing.
+---
--- This takes precedence over the 'winhighlight' option.
---
--- @param window integer
@@ -2080,23 +2278,26 @@ function vim.api.nvim_win_set_width(window, width) end
--- Computes the number of screen lines occupied by a range of text in a given
--- window. Works for off-screen text and takes folds into account.
+---
--- Diff filler or virtual lines above a line are counted as a part of that
--- line, unless the line is on "start_row" and "start_vcol" is specified.
+---
--- Diff filler or virtual lines below the last buffer line are counted in the
--- result when "end_row" is omitted.
+---
--- Line indexing is similar to `nvim_buf_get_text()`.
---
--- @param window integer Window handle, or 0 for current window.
--- @param opts vim.api.keyset.win_text_height Optional parameters:
---- • start_row: Starting line index, 0-based inclusive. When
---- omitted start at the very top.
---- • end_row: Ending line index, 0-based inclusive. When
---- omitted end at the very bottom.
---- • start_vcol: Starting virtual column index on "start_row",
---- 0-based inclusive, rounded down to full screen lines. When
---- omitted include the whole line.
---- • end_vcol: Ending virtual column index on "end_row",
---- 0-based exclusive, rounded up to full screen lines. When
---- omitted include the whole line.
+--- • start_row: Starting line index, 0-based inclusive. When
+--- omitted start at the very top.
+--- • end_row: Ending line index, 0-based inclusive. When omitted
+--- end at the very bottom.
+--- • start_vcol: Starting virtual column index on "start_row",
+--- 0-based inclusive, rounded down to full screen lines. When
+--- omitted include the whole line.
+--- • end_vcol: Ending virtual column index on "end_row", 0-based
+--- exclusive, rounded up to full screen lines. When omitted
+--- include the whole line.
--- @return table<string,any>
function vim.api.nvim_win_text_height(window, opts) end
diff --git a/runtime/lua/vim/_meta/api_keysets.lua b/runtime/lua/vim/_meta/api_keysets.lua
index f69e5a92c7..37e4372196 100644
--- a/runtime/lua/vim/_meta/api_keysets.lua
+++ b/runtime/lua/vim/_meta/api_keysets.lua
@@ -3,6 +3,19 @@
-- DO NOT EDIT
error('Cannot require a meta file')
+--- @class vim.api.keyset.buf_attach
+--- @field on_lines? function
+--- @field on_bytes? function
+--- @field on_changedtick? function
+--- @field on_detach? function
+--- @field on_reload? function
+--- @field utf_sizes? boolean
+--- @field preview? boolean
+
+--- @class vim.api.keyset.buf_delete
+--- @field force? boolean
+--- @field unload? boolean
+
--- @class vim.api.keyset.clear_autocmds
--- @field buffer? integer
--- @field event? any
@@ -55,6 +68,9 @@ error('Cannot require a meta file')
--- @class vim.api.keyset.cmd_opts
--- @field output? boolean
+--- @class vim.api.keyset.complete_set
+--- @field info? string
+
--- @class vim.api.keyset.context
--- @field types? any[]
@@ -74,6 +90,8 @@ error('Cannot require a meta file')
--- @class vim.api.keyset.echo_opts
--- @field verbose? boolean
+--- @class vim.api.keyset.empty
+
--- @class vim.api.keyset.eval_statusline
--- @field winid? integer
--- @field maxwidth? integer
@@ -93,28 +111,6 @@ error('Cannot require a meta file')
--- @class vim.api.keyset.exec_opts
--- @field output? boolean
---- @class vim.api.keyset.float_config
---- @field row? number
---- @field col? number
---- @field width? integer
---- @field height? integer
---- @field anchor? string
---- @field relative? string
---- @field win? integer
---- @field bufpos? any[]
---- @field external? boolean
---- @field focusable? boolean
---- @field zindex? integer
---- @field border? any
---- @field title? any
---- @field title_pos? string
---- @field footer? any
---- @field footer_pos? string
---- @field style? string
---- @field noautocmd? boolean
---- @field fixed? boolean
---- @field hide? boolean
-
--- @class vim.api.keyset.get_autocmds
--- @field event? any
--- @field group? any
@@ -124,6 +120,10 @@ error('Cannot require a meta file')
--- @class vim.api.keyset.get_commands
--- @field builtin? boolean
+--- @class vim.api.keyset.get_extmark
+--- @field details? boolean
+--- @field hl_name? boolean
+
--- @class vim.api.keyset.get_extmarks
--- @field limit? integer
--- @field details? boolean
@@ -170,6 +170,7 @@ error('Cannot require a meta file')
--- @field fg_indexed? boolean
--- @field bg_indexed? boolean
--- @field force? boolean
+--- @field url? string
--- @class vim.api.keyset.highlight_cterm
--- @field bold? boolean
@@ -196,6 +197,10 @@ error('Cannot require a meta file')
--- @field desc? string
--- @field replace_keycodes? boolean
+--- @class vim.api.keyset.open_term
+--- @field on_input? function
+--- @field force_crlf? boolean
+
--- @class vim.api.keyset.option
--- @field scope? string
--- @field win? integer
@@ -220,11 +225,12 @@ error('Cannot require a meta file')
--- @field end_line? integer
--- @field end_row? integer
--- @field end_col? integer
---- @field hl_group? any
+--- @field hl_group? number|string
--- @field virt_text? any[]
--- @field virt_text_pos? string
--- @field virt_text_win_col? integer
--- @field virt_text_hide? boolean
+--- @field virt_text_repeat_linebreak? boolean
--- @field hl_eol? boolean
--- @field hl_mode? string
--- @field invalidate? boolean
@@ -237,14 +243,17 @@ error('Cannot require a meta file')
--- @field virt_lines_leftcol? boolean
--- @field strict? boolean
--- @field sign_text? string
---- @field sign_hl_group? any
---- @field number_hl_group? any
---- @field line_hl_group? any
---- @field cursorline_hl_group? any
+--- @field sign_hl_group? number|string
+--- @field number_hl_group? number|string
+--- @field line_hl_group? number|string
+--- @field cursorline_hl_group? number|string
--- @field conceal? string
--- @field spell? boolean
--- @field ui_watched? boolean
--- @field undo_restore? boolean
+--- @field url? string
+--- @field scoped? boolean
+--- @field _subpriority? integer
--- @class vim.api.keyset.user_command
--- @field addr? any
@@ -260,8 +269,46 @@ error('Cannot require a meta file')
--- @field range? any
--- @field register? boolean
+--- @class vim.api.keyset.win_config
+--- @field row? number
+--- @field col? number
+--- @field width? integer
+--- @field height? integer
+--- @field anchor? string
+--- @field relative? string
+--- @field split? string
+--- @field win? integer
+--- @field bufpos? any[]
+--- @field external? boolean
+--- @field focusable? boolean
+--- @field vertical? boolean
+--- @field zindex? integer
+--- @field border? any
+--- @field title? any
+--- @field title_pos? string
+--- @field footer? any
+--- @field footer_pos? string
+--- @field style? string
+--- @field noautocmd? boolean
+--- @field fixed? boolean
+--- @field hide? boolean
+
--- @class vim.api.keyset.win_text_height
--- @field start_row? integer
--- @field end_row? integer
--- @field start_vcol? integer
--- @field end_vcol? integer
+
+--- @class vim.api.keyset.xdl_diff
+--- @field on_hunk? function
+--- @field result_type? string
+--- @field algorithm? string
+--- @field ctxlen? integer
+--- @field interhunkctxlen? integer
+--- @field linematch? any
+--- @field ignore_whitespace? boolean
+--- @field ignore_whitespace_change? boolean
+--- @field ignore_whitespace_change_at_eol? boolean
+--- @field ignore_cr_at_eol? boolean
+--- @field ignore_blank_lines? boolean
+--- @field indent_heuristic? boolean
diff --git a/runtime/lua/vim/_meta/api_keysets_extra.lua b/runtime/lua/vim/_meta/api_keysets_extra.lua
new file mode 100644
index 0000000000..d61dd2c02f
--- /dev/null
+++ b/runtime/lua/vim/_meta/api_keysets_extra.lua
@@ -0,0 +1,167 @@
+--- @meta _
+error('Cannot require a meta file')
+
+--- Extra types we can't generate keysets for
+
+--- @class vim.api.keyset.extmark_details
+--- @field ns_id integer
+--- @field right_gravity boolean
+---
+--- @field end_row? integer
+--- @field end_col? integer
+--- @field end_right_gravity? integer
+---
+--- @field priority? integer
+---
+--- @field undo_restore? false
+--- @field invalidate? true
+--- @field invalid? true
+---
+--- @field hl_group? string
+--- @field hl_eol? boolean
+---
+--- @field conceal? boolean
+--- @field spell? boolean
+--- @field ui_watched? boolean
+--- @field url? boolean
+--- @field hl_mode? string
+---
+--- @field virt_text? {[1]: string, [2]: string}[]
+--- @field virt_text_hide? boolean
+--- @field virt_text_repeat_linebreak? boolean
+--- @field virt_text_win_col? integer
+--- @field virt_text_pos? string
+---
+--- @field virt_lines? {[1]: string, [2]: string}[][]
+--- @field virt_lines_above? boolean
+--- @field virt_lines_leftcol? boolean
+---
+--- @field sign_text? string
+--- @field sign_name? string
+--- @field sign_hl_group? string
+--- @field number_hl_group? string
+--- @field line_hl_group? string
+--- @field cursorline_hl_group? string
+
+--- @class vim.api.keyset.get_extmark_item
+--- @field [1] integer row
+--- @field [2] integer col
+--- @field [3] vim.api.keyset.extmark_details?
+
+--- @class vim.api.keyset.get_mark
+--- @field [1] integer row
+--- @field [2] integer col
+--- @field [3] integer buffer
+--- @field [4] string buffername
+
+--- @class vim.api.keyset.get_autocmds.ret
+--- @field id? integer
+--- @field group? integer
+--- @field group_name? integer
+--- @field desc? string
+--- @field event? string
+--- @field command? string
+--- @field callback? function
+--- @field once? boolean
+--- @field pattern? string
+--- @field buflocal? boolean
+--- @field buffer? integer
+
+--- @class vim.api.keyset.command_info
+--- @field name string
+--- @field definition string
+--- @field script_id integer
+--- @field bang boolean
+--- @field bar boolean
+--- @field register boolean
+--- @field keepscript boolean
+--- @field preview boolean
+--- @field nargs string
+--- @field complete? string
+--- @field complete_arg? string
+--- @field count? string
+--- @field range? string
+--- @field addr? string
+
+--- @class vim.api.keyset.hl_info.base
+--- @field reverse? true
+--- @field bold? true
+--- @field italic? true
+--- @field underline? true
+--- @field undercurl? true
+--- @field underdouble? true
+--- @field underdotted? true
+--- @field underdashed? true
+--- @field standout? true
+--- @field strikethrough? true
+--- @field altfont? true
+--- @field nocombine? true
+
+--- @class vim.api.keyset.hl_info.cterm : vim.api.keyset.hl_info.base
+--- @field ctermfg? integer
+--- @field ctermbg? integer
+--- @field foreground? integer
+--- @field background? integer
+
+--- @class vim.api.keyset.hl_info : vim.api.keyset.hl_info.base
+--- @field fg? integer
+--- @field bg? integer
+--- @field sp? integer
+--- @field default? true
+--- @field link? string
+--- @field blend? integer
+--- @field cterm? vim.api.keyset.hl_info.cterm
+
+--- @class vim.api.keyset.get_mode
+--- @field blocking boolean
+--- @field mode string
+
+--- @class vim.api.keyset.get_option_info
+--- @field name string
+--- @field shortname string
+--- @field scope 'buf'|'win'|'global'
+--- @field global_local boolean
+--- @field commalist boolean
+--- @field flaglist boolean
+--- @field was_set boolean
+--- @field last_set_id integer
+--- @field last_set_linenr integer
+--- @field last_set_chan integer
+--- @field type 'string'|'boolean'|'number'
+--- @field default string|boolean|integer
+--- @field allows_duplicates boolean
+
+--- @class vim.api.keyset.parse_cmd.mods
+--- @field filter { force: boolean, pattern: string }
+--- @field silent boolean
+--- @field emsg_silent boolean
+--- @field unsilent boolean
+--- @field sandbox boolean
+--- @field noautocmd boolean
+--- @field tab integer
+--- @field verbose integer
+--- @field browse boolean
+--- @field confirm boolean
+--- @field hide boolean
+--- @field keepalt boolean
+--- @field keepjumps boolean
+--- @field keepmarks boolean
+--- @field keeppatterns boolean
+--- @field lockmarks boolean
+--- @field noswapfile boolean
+--- @field vertical boolean
+--- @field horizontal boolean
+--- @field split ''|'botright'|'topleft'|'belowright'|'aboveleft'
+
+--- @class vim.api.keyset.parse_cmd
+--- @field addr 'line'|'arg'|'buf'|'load'|'win'|'tab'|'qf'|'none'|'?'
+--- @field args string[]
+--- @field bang boolean
+--- @field cmd string
+--- @field magic {bar: boolean, file: boolean}
+--- @field mods vim.api.keyset.parse_cmd.mods
+--- @field nargs '0'|'1'|'?'|'+'|'*'
+--- @field nextcmd string
+--- @field range? integer[]
+--- @field count? integer
+--- @field reg? string
diff --git a/runtime/lua/vim/_meta/builtin.lua b/runtime/lua/vim/_meta/builtin.lua
index eeba356672..9a67667f02 100644
--- a/runtime/lua/vim/_meta/builtin.lua
+++ b/runtime/lua/vim/_meta/builtin.lua
@@ -1,66 +1,72 @@
---@meta
-
-- luacheck: no unused args
----@defgroup vim.builtin
----
----@brief <pre>help
----vim.api.{func}({...}) *vim.api*
---- Invokes Nvim |API| function {func} with arguments {...}.
---- Example: call the "nvim_get_current_line()" API function: >lua
---- print(tostring(vim.api.nvim_get_current_line()))
----
----vim.NIL *vim.NIL*
---- Special value representing NIL in |RPC| and |v:null| in Vimscript
---- conversion, and similar cases. Lua `nil` cannot be used as part of a Lua
---- table representing a Dictionary or Array, because it is treated as
---- missing: `{"foo", nil}` is the same as `{"foo"}`.
----
----vim.type_idx *vim.type_idx*
---- Type index for use in |lua-special-tbl|. Specifying one of the values from
---- |vim.types| allows typing the empty table (it is unclear whether empty Lua
---- table represents empty list or empty array) and forcing integral numbers
---- to be |Float|. See |lua-special-tbl| for more details.
----
----vim.val_idx *vim.val_idx*
---- Value index for tables representing |Float|s. A table representing
---- floating-point value 1.0 looks like this: >lua
---- {
---- [vim.type_idx] = vim.types.float,
---- [vim.val_idx] = 1.0,
---- }
----< See also |vim.type_idx| and |lua-special-tbl|.
----
----vim.types *vim.types*
---- Table with possible values for |vim.type_idx|. Contains two sets of
---- key-value pairs: first maps possible values for |vim.type_idx| to
---- human-readable strings, second maps human-readable type names to values
---- for |vim.type_idx|. Currently contains pairs for `float`, `array` and
---- `dictionary` types.
----
---- Note: One must expect that values corresponding to `vim.types.float`,
---- `vim.types.array` and `vim.types.dictionary` fall under only two following
---- assumptions:
---- 1. Value may serve both as a key and as a value in a table. Given the
---- properties of Lua tables this basically means “value is not `nil`”.
---- 2. For each value in `vim.types` table `vim.types[vim.types[value]]` is the
---- same as `value`.
---- No other restrictions are put on types, and it is not guaranteed that
---- values corresponding to `vim.types.float`, `vim.types.array` and
---- `vim.types.dictionary` will not change or that `vim.types` table will only
---- contain values for these three types.
----
---- *log_levels* *vim.log.levels*
----Log levels are one of the values defined in `vim.log.levels`:
----
---- vim.log.levels.DEBUG
---- vim.log.levels.ERROR
---- vim.log.levels.INFO
---- vim.log.levels.TRACE
---- vim.log.levels.WARN
---- vim.log.levels.OFF
----
----</pre>
+error('Cannot require a meta file')
+
+--- @brief <pre>help
+--- vim.api.{func}({...}) *vim.api*
+--- Invokes Nvim |API| function {func} with arguments {...}.
+--- Example: call the "nvim_get_current_line()" API function: >lua
+--- print(tostring(vim.api.nvim_get_current_line()))
+---
+--- vim.NIL *vim.NIL*
+--- Special value representing NIL in |RPC| and |v:null| in Vimscript
+--- conversion, and similar cases. Lua `nil` cannot be used as part of a Lua
+--- table representing a Dictionary or Array, because it is treated as
+--- missing: `{"foo", nil}` is the same as `{"foo"}`.
+---
+--- vim.type_idx *vim.type_idx*
+--- Type index for use in |lua-special-tbl|. Specifying one of the values from
+--- |vim.types| allows typing the empty table (it is unclear whether empty Lua
+--- table represents empty list or empty array) and forcing integral numbers
+--- to be |Float|. See |lua-special-tbl| for more details.
+---
+--- vim.val_idx *vim.val_idx*
+--- Value index for tables representing |Float|s. A table representing
+--- floating-point value 1.0 looks like this: >lua
+--- {
+--- [vim.type_idx] = vim.types.float,
+--- [vim.val_idx] = 1.0,
+--- }
+--- < See also |vim.type_idx| and |lua-special-tbl|.
+---
+--- vim.types *vim.types*
+--- Table with possible values for |vim.type_idx|. Contains two sets of
+--- key-value pairs: first maps possible values for |vim.type_idx| to
+--- human-readable strings, second maps human-readable type names to values
+--- for |vim.type_idx|. Currently contains pairs for `float`, `array` and
+--- `dictionary` types.
+---
+--- Note: One must expect that values corresponding to `vim.types.float`,
+--- `vim.types.array` and `vim.types.dictionary` fall under only two following
+--- assumptions:
+--- 1. Value may serve both as a key and as a value in a table. Given the
+--- properties of Lua tables this basically means “value is not `nil`”.
+--- 2. For each value in `vim.types` table `vim.types[vim.types[value]]` is the
+--- same as `value`.
+--- No other restrictions are put on types, and it is not guaranteed that
+--- values corresponding to `vim.types.float`, `vim.types.array` and
+--- `vim.types.dictionary` will not change or that `vim.types` table will only
+--- contain values for these three types.
+---
+--- *log_levels* *vim.log.levels*
+--- Log levels are one of the values defined in `vim.log.levels`:
+---
+--- vim.log.levels.DEBUG
+--- vim.log.levels.ERROR
+--- vim.log.levels.INFO
+--- vim.log.levels.TRACE
+--- vim.log.levels.WARN
+--- vim.log.levels.OFF
+---
+--- </pre>
+
+---@nodoc
+---@class vim.NIL
+
+---@type vim.NIL
+---@nodoc
+vim.NIL = ...
--- Returns true if the code is executing as part of a "fast" event handler,
--- where most of the API is disabled. These are low-level events (e.g.
@@ -76,6 +82,7 @@ function vim.in_fast_event() end
---
--- Note: If numeric keys are present in the table, Nvim ignores the metatable
--- marker and converts the dict to a list/array anyway.
+--- @return table
function vim.empty_dict() end
--- Sends {event} to {channel} via |RPC| and returns immediately. If {channel}
@@ -84,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.
@@ -95,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
diff --git a/runtime/lua/vim/_meta/builtin_types.lua b/runtime/lua/vim/_meta/builtin_types.lua
index ef0452c649..0bbc3e9bc8 100644
--- a/runtime/lua/vim/_meta/builtin_types.lua
+++ b/runtime/lua/vim/_meta/builtin_types.lua
@@ -81,10 +81,10 @@
--- @class vim.fn.sign_getplaced.dict
--- @field group? string
--- @field id? integer
---- @field lnum? string
+--- @field lnum? string|integer
--- @class vim.fn.sign_getplaced.ret.item
---- @field buf integer
+--- @field bufnr integer
--- @field signs vim.fn.sign[]
--- @class vim.fn.sign_place.dict
@@ -118,7 +118,7 @@
--- @field topfill? integer
--- @field topline? integer
---- @class vim.fn.winsaveview.ret
+--- @class vim.fn.winsaveview.ret: vim.fn.winrestview.dict
--- @field col integer
--- @field coladd integer
--- @field curswant integer
diff --git a/runtime/lua/vim/_meta/json.lua b/runtime/lua/vim/_meta/json.lua
index e010086615..07d89aafc8 100644
--- a/runtime/lua/vim/_meta/json.lua
+++ b/runtime/lua/vim/_meta/json.lua
@@ -5,7 +5,7 @@ vim.json = {}
-- luacheck: no unused args
----@defgroup vim.json
+---@brief
---
--- This module provides encoding and decoding of Lua objects to and
--- from JSON-encoded strings. Supports |vim.NIL| and |vim.empty_dict()|.
diff --git a/runtime/lua/vim/_meta/lpeg.lua b/runtime/lua/vim/_meta/lpeg.lua
index 42c9a6449e..1ce40f3340 100644
--- a/runtime/lua/vim/_meta/lpeg.lua
+++ b/runtime/lua/vim/_meta/lpeg.lua
@@ -1,11 +1,27 @@
--- @meta
+error('Cannot require a meta file')
--- These types were taken from https://github.com/LuaCATS/lpeg, with types being renamed to include
--- the vim namespace and with some descriptions made less verbose.
+-- These types were taken from https://github.com/LuaCATS/lpeg
+-- (based on revision e6789e28e5b91a4a277a2a03081d708c403a3e34)
+-- with types being renamed to include the vim namespace and with some descriptions made less verbose.
+
+--- @brief <pre>help
+--- LPeg is a pattern-matching library for Lua, based on
+--- Parsing Expression Grammars (https://bford.info/packrat/) (PEGs).
+---
+--- *lua-lpeg*
+--- *vim.lpeg.Pattern*
+--- The LPeg library for parsing expression grammars is included as `vim.lpeg`
+--- (https://www.inf.puc-rio.br/~roberto/lpeg/).
+---
+--- In addition, its regex-like interface is available as |vim.re|
+--- (https://www.inf.puc-rio.br/~roberto/lpeg/re.html).
+---
+--- </pre>
---- *LPeg* is a new pattern-matching library for Lua, based on [Parsing Expression Grammars](https://bford.info/packrat/) (PEGs).
vim.lpeg = {}
+--- @nodoc
--- @class vim.lpeg.Pattern
--- @operator unm: vim.lpeg.Pattern
--- @operator add(vim.lpeg.Pattern): vim.lpeg.Pattern
@@ -32,11 +48,12 @@ local Pattern = {}
--- matches anywhere.
---
--- Example:
+---
--- ```lua
---- local pattern = lpeg.R("az") ^ 1 * -1
---- assert(pattern:match("hello") == 6)
---- assert(lpeg.match(pattern, "hello") == 6)
---- assert(pattern:match("1 hello") == nil)
+--- local pattern = lpeg.R('az') ^ 1 * -1
+--- assert(pattern:match('hello') == 6)
+--- assert(lpeg.match(pattern, 'hello') == 6)
+--- assert(pattern:match('1 hello') == nil)
--- ```
---
--- @param pattern vim.lpeg.Pattern
@@ -55,11 +72,12 @@ function vim.lpeg.match(pattern, subject, init) end
--- we must either write a loop in Lua or write a pattern that matches anywhere.
---
--- Example:
+---
--- ```lua
---- local pattern = lpeg.R("az") ^ 1 * -1
---- assert(pattern:match("hello") == 6)
---- assert(lpeg.match(pattern, "hello") == 6)
---- assert(pattern:match("1 hello") == nil)
+--- local pattern = lpeg.R('az') ^ 1 * -1
+--- assert(pattern:match('hello') == 6)
+--- assert(lpeg.match(pattern, 'hello') == 6)
+--- assert(pattern:match('1 hello') == nil)
--- ```
---
--- @param subject string
@@ -69,7 +87,8 @@ function Pattern:match(subject, init) end
--- Returns the string `"pattern"` if the given value is a pattern, otherwise `nil`.
---
---- @return 'pattern'|nil
+--- @param value vim.lpeg.Pattern|string|integer|boolean|table|function
+--- @return "pattern"|nil
function vim.lpeg.type(value) end
--- Returns a string with the running version of LPeg.
@@ -85,7 +104,7 @@ function vim.lpeg.version() end
--- @param max integer
function vim.lpeg.setmaxstack(max) end
---- Converts the given value into a proper pattern. This following rules are applied:
+--- Converts the given value into a proper pattern. The following rules are applied:
--- * If the argument is a pattern, it is returned unmodified.
--- * If the argument is a string, it is translated to a pattern that matches the string literally.
--- * If the argument is a non-negative number `n`, the result is a pattern that matches exactly `n` characters.
@@ -95,7 +114,7 @@ function vim.lpeg.setmaxstack(max) end
--- * If the argument is a boolean, the result is a pattern that always succeeds or always fails
--- (according to the boolean value), without consuming any input.
--- * If the argument is a table, it is interpreted as a grammar (see Grammars).
---- * If the argument is a function, returns a pattern equivalent to a match-time captureover the empty string.
+--- * If the argument is a function, returns a pattern equivalent to a match-time capture over the empty string.
---
--- @param value vim.lpeg.Pattern|string|integer|boolean|table|function
--- @return vim.lpeg.Pattern
@@ -103,7 +122,7 @@ function vim.lpeg.P(value) end
--- Returns a pattern that matches only if the input string at the current position is preceded by `patt`.
--- Pattern `patt` must match only strings with some fixed length, and it cannot contain captures.
---- Like the and predicate, this pattern never consumes any input, independently of success or failure.
+--- Like the `and` predicate, this pattern never consumes any input, independently of success or failure.
---
--- @param pattern vim.lpeg.Pattern
--- @return vim.lpeg.Pattern
@@ -111,13 +130,14 @@ function vim.lpeg.B(pattern) end
--- Returns a pattern that matches any single character belonging to one of the given ranges.
--- Each `range` is a string `xy` of length 2, representing all characters with code between the codes of
---- `x` and `y` (both inclusive). As an example, the pattern `lpeg.R("09")` matches any digit, and
---- `lpeg.R("az", "AZ")` matches any ASCII letter.
+--- `x` and `y` (both inclusive). As an example, the pattern `lpeg.R('09')` matches any digit, and
+--- `lpeg.R('az', 'AZ')` matches any ASCII letter.
---
--- Example:
+---
--- ```lua
---- local pattern = lpeg.R("az") ^ 1 * -1
---- assert(pattern:match("hello") == 6)
+--- local pattern = lpeg.R('az') ^ 1 * -1
+--- assert(pattern:match('hello') == 6)
--- ```
---
--- @param ... string
@@ -125,9 +145,9 @@ function vim.lpeg.B(pattern) end
function vim.lpeg.R(...) end
--- Returns a pattern that matches any single character that appears in the given string (the `S` stands for Set).
---- As an example, the pattern `lpeg.S("+-*/")` matches any arithmetic operator. Note that, if `s` is a character
+--- As an example, the pattern `lpeg.S('+-*/')` matches any arithmetic operator. Note that, if `s` is a character
--- (that is, a string of length 1), then `lpeg.P(s)` is equivalent to `lpeg.S(s)` which is equivalent to
---- `lpeg.R(s..s)`. Note also that both `lpeg.S("")` and `lpeg.R()` are patterns that always fail.
+--- `lpeg.R(s..s)`. Note also that both `lpeg.S('')` and `lpeg.R()` are patterns that always fail.
---
--- @param string string
--- @return vim.lpeg.Pattern
@@ -137,8 +157,9 @@ function vim.lpeg.S(string) end
--- for a grammar. The created non-terminal refers to the rule indexed by `v` in the enclosing grammar.
---
--- Example:
+---
--- ```lua
---- local b = lpeg.P({"(" * ((1 - lpeg.S "()") + lpeg.V(1)) ^ 0 * ")"})
+--- local b = lpeg.P({'(' * ((1 - lpeg.S '()') + lpeg.V(1)) ^ 0 * ')'})
--- assert(b:match('((string))') == 11)
--- assert(b:match('(') == nil)
--- ```
@@ -147,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
@@ -168,14 +190,15 @@ function vim.lpeg.V(v) end
--- that table.
---
--- Example:
+---
--- ```lua
--- lpeg.locale(lpeg)
---- local space = lpeg.space^0
---- local name = lpeg.C(lpeg.alpha^1) * space
---- local sep = lpeg.S(",;") * space
---- local pair = lpeg.Cg(name * "=" * space * name) * sep^-1
---- local list = lpeg.Cf(lpeg.Ct("") * pair^0, rawset)
---- local t = list:match("a=b, c = hi; next = pi")
+--- local space = lpeg.space ^ 0
+--- local name = lpeg.C(lpeg.alpha ^ 1) * space
+--- local sep = lpeg.S(',;') * space
+--- local pair = lpeg.Cg(name * '=' * space * name) * sep ^ -1
+--- local list = lpeg.Cf(lpeg.Ct('') * pair ^ 0, rawset)
+--- local t = list:match('a=b, c = hi; next = pi')
--- assert(t.a == 'b')
--- assert(t.c == 'hi')
--- assert(t.next == 'pi')
@@ -191,11 +214,12 @@ function vim.lpeg.locale(tab) end
--- The captured value is a string. If `patt` has other captures, their values are returned after this one.
---
--- Example:
+---
--- ```lua
--- local function split (s, sep)
--- sep = lpeg.P(sep)
---- local elem = lpeg.C((1 - sep)^0)
---- local p = elem * (sep * elem)^0
+--- local elem = lpeg.C((1 - sep) ^ 0)
+--- local p = elem * (sep * elem) ^ 0
--- return lpeg.match(p, s)
--- end
--- local a, b, c = split('a,b,c', ',')
@@ -241,12 +265,13 @@ function vim.lpeg.Cc(...) end
--- becomes the captured value.
---
--- Example:
+---
--- ```lua
---- local number = lpeg.R("09") ^ 1 / tonumber
---- local list = number * ("," * number) ^ 0
+--- local number = lpeg.R('09') ^ 1 / tonumber
+--- local list = number * (',' * number) ^ 0
--- local function add(acc, newvalue) return acc + newvalue end
--- local sum = lpeg.Cf(list, add)
---- assert(sum:match("10,30,43") == 83)
+--- assert(sum:match('10,30,43') == 83)
--- ```
---
--- @param patt vim.lpeg.Pattern
@@ -267,10 +292,11 @@ function vim.lpeg.Cg(patt, name) end
--- subject where the match occurs. The captured value is a number.
---
--- Example:
+---
--- ```lua
--- local I = lpeg.Cp()
--- local function anywhere(p) return lpeg.P({I * p * I + 1 * lpeg.V(1)}) end
---- local match_start, match_end = anywhere("world"):match("hello world!")
+--- local match_start, match_end = anywhere('world'):match('hello world!')
--- assert(match_start == 7)
--- assert(match_end == 12)
--- ```
@@ -285,10 +311,11 @@ function vim.lpeg.Cp() end
--- value is the string resulting from all replacements.
---
--- Example:
+---
--- ```lua
--- local function gsub (s, patt, repl)
--- patt = lpeg.P(patt)
---- patt = lpeg.Cs((patt / repl + 1)^0)
+--- patt = lpeg.Cs((patt / repl + 1) ^ 0)
--- return lpeg.match(patt, s)
--- end
--- assert(gsub('Hello, xxx!', 'xxx', 'World') == 'Hello, World!')
@@ -312,9 +339,9 @@ function vim.lpeg.Ct(patt) end
--- and then calls `function`. The given function gets as arguments the entire subject, the current position
--- (after the match of `patt`), plus any capture values produced by `patt`. The first value returned by `function`
--- defines how the match happens. If the call returns a number, the match succeeds and the returned number
---- becomes the new current position. (Assuming a subject sand current position i, the returned number must be
---- in the range [i, len(s) + 1].) If the call returns true, the match succeeds without consuming any input
---- (so, to return true is equivalent to return i). If the call returns false, nil, or no value, the match fails.
+--- becomes the new current position. (Assuming a subject sand current position `i`, the returned number must be
+--- in the range `[i, len(s) + 1]`.) If the call returns `true`, the match succeeds without consuming any input
+--- (so, to return true is equivalent to return `i`). If the call returns `false`, `nil`, or no value, the match fails.
--- Any extra values returned by the function become the values produced by the capture.
---
--- @param patt vim.lpeg.Pattern
diff --git a/runtime/lua/vim/_meta/misc.lua b/runtime/lua/vim/_meta/misc.lua
index 0d70e16314..ab80e18434 100644
--- a/runtime/lua/vim/_meta/misc.lua
+++ b/runtime/lua/vim/_meta/misc.lua
@@ -10,6 +10,7 @@
--- vim.fn[func]({...})
--- ```
---
---- @param func fun()
+--- @param func string
--- @param ... any
+--- @return any
function vim.call(func, ...) end
diff --git a/runtime/lua/vim/_meta/mpack.lua b/runtime/lua/vim/_meta/mpack.lua
index 54e097ad97..3970341b78 100644
--- a/runtime/lua/vim/_meta/mpack.lua
+++ b/runtime/lua/vim/_meta/mpack.lua
@@ -2,14 +2,17 @@
-- luacheck: no unused args
---- @defgroup vim.mpack
+--- @brief
---
--- This module provides encoding and decoding of Lua objects to and
--- from msgpack-encoded strings. Supports |vim.NIL| and |vim.empty_dict()|.
--- Decodes (or "unpacks") the msgpack-encoded {str} to a Lua object.
--- @param str string
+--- @return any
function vim.mpack.decode(str) end
--- Encodes (or "packs") Lua object {obj} as msgpack in a Lua string.
+--- @param obj any
+--- @return string
function vim.mpack.encode(obj) end
diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua
index d2bdab4d28..757720d8fb 100644
--- a/runtime/lua/vim/_meta/options.lua
+++ b/runtime/lua/vim/_meta/options.lua
@@ -137,8 +137,9 @@ vim.bo.ai = vim.bo.autoindent
--- `timestamp`
--- If this option has a local value, use this command to switch back to
--- using the global value:
---- ```
---- :set autoread<
+---
+--- ```vim
+--- set autoread<
--- ```
---
---
@@ -191,25 +192,22 @@ vim.go.awa = vim.go.autowriteall
--- See `:hi-normal` if you want to set the background color explicitly.
--- *g:colors_name*
--- When a color scheme is loaded (the "g:colors_name" variable is set)
---- setting 'background' will cause the color scheme to be reloaded. If
+--- changing 'background' will cause the color scheme to be reloaded. If
--- the color scheme adjusts to the value of 'background' this will work.
--- However, if the color scheme sets 'background' itself the effect may
--- be undone. First delete the "g:colors_name" variable when needed.
---
--- Normally this option would be set in the vimrc file. Possibly
--- depending on the terminal name. Example:
+---
+--- ```vim
+--- if $TERM ==# "xterm"
+--- set background=dark
+--- endif
--- ```
---- :if $TERM ==# "xterm"
---- : set background=dark
---- :endif
---- ```
---- When this option is set, the default settings for the highlight groups
+--- When this option is changed, the default settings for the highlight groups
--- will change. To use other settings, place ":highlight" commands AFTER
--- the setting of the 'background' option.
---- This option is also used in the "$VIMRUNTIME/syntax/syntax.vim" file
---- to select the colors for syntax highlighting. After changing this
---- option, you must load syntax.vim again to see the result. This can be
---- done with ":syntax on".
---
--- @type string
vim.o.background = "dark"
@@ -352,14 +350,16 @@ vim.go.bkc = vim.go.backupcopy
--- - Environment variables are expanded `:set_env`.
--- - Careful with '\' characters, type one before a space, type two to
--- get one in the option (see `option-backslash`), for example:
---- ```
---- :set bdir=c:\\tmp,\ dir\\,with\\,commas,\\\ dir\ with\ spaces
+---
+--- ```vim
+--- set bdir=c:\\tmp,\ dir\\,with\\,commas,\\\ dir\ with\ spaces
--- ```
---
--- See also 'backup' and 'writebackup' options.
--- If you want to hide your backup files on Unix, consider this value:
---- ```
---- :set backupdir=./.backup,~/.backup,.,/tmp
+---
+--- ```vim
+--- set backupdir=./.backup,~/.backup,.,/tmp
--- ```
--- You must create a ".backup" directory in each directory and in your
--- home directory for this to work properly.
@@ -385,8 +385,9 @@ vim.go.bdir = vim.go.backupdir
--- If you like to keep a lot of backups, you could use a BufWritePre
--- autocommand to change 'backupext' just before writing the file to
--- include a timestamp.
---- ```
---- :au BufWritePre * let &bex = '-' .. strftime("%Y%b%d%X") .. '~'
+---
+--- ```vim
+--- au BufWritePre * let &bex = '-' .. strftime("%Y%b%d%X") .. '~'
--- ```
--- Use 'backupdir' to put the backup in a different directory.
---
@@ -413,7 +414,7 @@ vim.go.bex = vim.go.backupext
--- $HOME you must expand it explicitly, e.g.:
---
--- ```vim
---- :let &backupskip = escape(expand('$HOME'), '\') .. '/tmp/*'
+--- let &backupskip = escape(expand('$HOME'), '\') .. '/tmp/*'
--- ```
--- Note that the default also makes sure that "crontab -e" works (when a
--- backup would be made by renaming the original file crontab won't see
@@ -714,8 +715,9 @@ vim.go.cdh = vim.go.cdhome
--- If the default value taken from $CDPATH is not what you want, include
--- a modified version of the following command in your vimrc file to
--- override it:
---- ```
---- :let &cdpath = ',' .. substitute(substitute($CDPATH, '[, ]', '\\\0', 'g'), ':', ',', 'g')
+---
+--- ```vim
+--- let &cdpath = ',' .. substitute(substitute($CDPATH, '[, ]', '\\\0', 'g'), ':', ',', 'g')
--- ```
--- This option cannot be set from a `modeline` or in the `sandbox`, for
--- security reasons.
@@ -731,9 +733,10 @@ vim.go.cd = vim.go.cdpath
--- Only non-printable keys are allowed.
--- The key can be specified as a single character, but it is difficult to
--- type. The preferred way is to use the <> notation. Examples:
---- ```
---- :exe "set cedit=\\<C-Y>"
---- :exe "set cedit=\\<Esc>"
+---
+--- ```vim
+--- exe "set cedit=\\<C-Y>"
+--- exe "set cedit=\\<Esc>"
--- ```
--- `Nvi` also has this option, but it only uses the first character.
--- See `cmdwin`.
@@ -767,7 +770,8 @@ vim.bo.channel = vim.o.channel
--- is done internally by Vim, 'charconvert' is not used for this.
--- Also used for Unicode conversion.
--- Example:
---- ```
+---
+--- ```vim
--- set charconvert=CharConvert()
--- fun CharConvert()
--- system("recode "
@@ -833,7 +837,8 @@ vim.bo.cino = vim.bo.cinoptions
--- Keywords that are interpreted as a C++ scope declaration by `cino-g`.
--- Useful e.g. for working with the Qt framework that defines additional
--- scope declarations "signals", "public slots" and "private slots":
---- ```
+---
+--- ```vim
--- set cinscopedecls+=signals,public\ slots,private\ slots
--- ```
---
@@ -920,10 +925,12 @@ vim.go.cwh = vim.go.cmdwinheight
--- text. Will make screen redrawing slower.
--- The screen column can be an absolute number, or a number preceded with
--- '+' or '-', which is added to or subtracted from 'textwidth'.
---- ```
---- :set cc=+1 " highlight column after 'textwidth'
---- :set cc=+1,+2,+3 " highlight three columns after 'textwidth'
---- :hi ColorColumn ctermbg=lightgrey guibg=lightgrey
+---
+--- ```vim
+---
+--- set cc=+1 " highlight column after 'textwidth'
+--- set cc=+1,+2,+3 " highlight three columns after 'textwidth'
+--- hi ColorColumn ctermbg=lightgrey guibg=lightgrey
--- ```
---
--- When 'textwidth' is zero then the items with '-' and '+' are not used.
@@ -945,8 +952,9 @@ vim.wo.cc = vim.wo.colorcolumn
--- the GUI it is always possible and Vim limits the number of columns to
--- what fits on the screen. You can use this command to get the widest
--- window possible:
---- ```
---- :set columns=9999
+---
+--- ```vim
+--- set columns=9999
--- ```
--- Minimum value is 12, maximum value is 10000.
---
@@ -989,8 +997,9 @@ vim.bo.cms = vim.bo.commentstring
--- kspell use the currently active spell checking `spell`
--- k{dict} scan the file {dict}. Several "k" flags can be given,
--- patterns are valid too. For example:
---- ```
---- :set cpt=k/usr/dict/*,k~/spanish
+---
+--- ```vim
+--- set cpt=k/usr/dict/*,k~/spanish
--- ```
--- s scan the files given with the 'thesaurus' option
--- s{tsr} scan the file {tsr}. Several "s" flags can be given, patterns
@@ -1053,14 +1062,18 @@ vim.bo.cfu = vim.bo.completefunc
--- completion in the preview window. Only works in
--- combination with "menu" or "menuone".
---
---- noinsert Do not insert any text for a match until the user selects
+--- noinsert Do not insert any text for a match until the user selects
--- a match from the menu. Only works in combination with
--- "menu" or "menuone". No effect if "longest" is present.
---
---- noselect Do not select a match in the menu, force the user to
+--- noselect Do not select a match in the menu, force the user to
--- select one from the menu. Only works in combination with
--- "menu" or "menuone".
---
+--- popup Show extra information about the currently selected
+--- completion in a popup window. Only works in combination
+--- with "menu" or "menuone". Overrides "preview".
+---
--- @type string
vim.o.completeopt = "menu,preview"
vim.o.cot = vim.o.completeopt
@@ -1283,9 +1296,6 @@ vim.bo.ci = vim.bo.copyindent
--- when it didn't exist when editing it. This is a
--- protection against a file unexpectedly created by
--- someone else. Vi didn't complain about this.
---- *cpo-p*
---- p Vi compatible Lisp indenting. When not present, a
---- slightly better algorithm is used.
--- *cpo-P*
--- P When included, a ":write" command that appends to a
--- file will set the file name for the current buffer, if
@@ -1419,7 +1429,8 @@ vim.wo.crb = vim.wo.cursorbind
--- slower.
--- If you only want the highlighting in the current window you can use
--- these autocommands:
---- ```
+---
+--- ```vim
--- au WinLeave * set nocursorline nocursorcolumn
--- au WinEnter * set cursorline cursorcolumn
--- ```
@@ -1502,7 +1513,8 @@ vim.go.debug = vim.o.debug
--- ```
--- When using the ":set" command, you need to double the backslashes!
--- To avoid that use `:let` with a single quote string:
---- ```
+---
+--- ```vim
--- let &l:define = '^\s*\ze\k\+\s*=\s*function('
--- ```
---
@@ -1681,11 +1693,12 @@ vim.go.dex = vim.go.diffexpr
--- histogram histogram diff algorithm
---
--- Examples:
---- ```
---- :set diffopt=internal,filler,context:4
---- :set diffopt=
---- :set diffopt=internal,filler,foldcolumn:3
---- :set diffopt-=internal " do NOT use the internal diff parser
+---
+--- ```vim
+--- set diffopt=internal,filler,context:4
+--- set diffopt=
+--- set diffopt=internal,filler,foldcolumn:3
+--- set diffopt-=internal " do NOT use the internal diff parser
--- ```
---
---
@@ -1736,8 +1749,9 @@ vim.go.dg = vim.go.digraph
--- - Environment variables are expanded `:set_env`.
--- - Careful with '\' characters, type one before a space, type two to
--- get one in the option (see `option-backslash`), for example:
---- ```
---- :set dir=c:\\tmp,\ dir\\,with\\,commas,\\\ dir\ with\ spaces
+---
+--- ```vim
+--- set dir=c:\\tmp,\ dir\\,with\\,commas,\\\ dir\ with\ spaces
--- ```
---
--- Editing the same file twice will result in a warning. Using "/tmp" on
@@ -1924,8 +1938,9 @@ vim.go.efm = vim.go.errorformat
--- When set to "all" or when "all" is one of the items, all autocommand
--- events are ignored, autocommands will not be executed.
--- Otherwise this is a comma-separated list of event names. Example:
---- ```
---- :set ei=WinEnter,WinLeave
+---
+--- ```vim
+--- set ei=WinEnter,WinLeave
--- ```
---
---
@@ -2021,7 +2036,8 @@ vim.bo.fenc = vim.bo.fileencoding
--- "ucs-bom", which requires the BOM to be present). If you prefer
--- another encoding use an BufReadPost autocommand event to test if your
--- preferred encoding is to be used. Example:
---- ```
+---
+--- ```vim
--- au BufReadPost * if search('\S', 'w') == 0 |
--- \ set fenc=iso-2022-jp | endif
--- ```
@@ -2031,8 +2047,9 @@ vim.bo.fenc = vim.bo.fileencoding
--- not used.
--- Note that 'fileencodings' is not used for a new file, the global value
--- of 'fileencoding' is used instead. You can set it with:
---- ```
---- :setglobal fenc=iso-8859-2
+---
+--- ```vim
+--- setglobal fenc=iso-8859-2
--- ```
--- This means that a non-existing file may get a different encoding than
--- an empty file.
@@ -2156,14 +2173,12 @@ vim.go.fic = vim.go.fileignorecase
--- this use the ":filetype on" command. `:filetype`
--- Setting this option to a different value is most useful in a modeline,
--- for a file for which the file type is not automatically recognized.
---- Example, for in an IDL file:
---- ```
+--- Example, for in an IDL file: >c
--- /* vim: set filetype=idl : */
--- ```
--- `FileType` `filetypes`
--- When a dot appears in the value then this separates two filetype
---- names. Example:
---- ```
+--- names. Example: >c
--- /* vim: set filetype=c.doxygen : */
--- ```
--- This will use the "c" filetype first, then the "doxygen" filetype.
@@ -2182,7 +2197,7 @@ vim.bo.ft = vim.bo.filetype
--- Characters to fill the statuslines, vertical separators and special
--- lines in the window.
--- It is a comma-separated list of items. Each item has a name, a colon
---- and the value of that item:
+--- and the value of that item: `E1511`
---
--- item default Used for ~
--- stl ' ' statusline of the current window
@@ -2216,13 +2231,14 @@ vim.bo.ft = vim.bo.filetype
--- default to single-byte alternatives.
---
--- Example:
---- ```
---- :set fillchars=stl:\ ,stlnc:\ ,vert:│,fold:·,diff:-
+---
+--- ```vim
+--- set fillchars=stl:\ ,stlnc:\ ,vert:│,fold:·,diff:-
--- ```
---
--- For the "stl", "stlnc", "foldopen", "foldclose" and "foldsep" items
--- single-byte and multibyte characters are supported. But double-width
---- characters are not supported.
+--- characters are not supported. `E1512`
---
--- The highlighting used for these items:
--- item highlight group ~
@@ -2457,6 +2473,9 @@ vim.go.fdo = vim.go.foldopen
--- It is not allowed to change text or jump to another window while
--- evaluating 'foldtext' `textlock`.
---
+--- When set to an empty string, foldtext is disabled, and the line
+--- is displayed normally with highlighting and no line wrapping.
+---
--- @type string
vim.o.foldtext = "foldtext()"
vim.o.fdt = vim.o.foldtext
@@ -2475,8 +2494,9 @@ vim.wo.fdt = vim.wo.foldtext
--- it yet!
---
--- Example:
---- ```
---- :set formatexpr=mylang#Format()
+---
+--- ```vim
+--- set formatexpr=mylang#Format()
--- ```
--- This will invoke the mylang#Format() function in the
--- autoload/mylang.vim file in 'runtimepath'. `autoload`
@@ -2492,7 +2512,8 @@ vim.wo.fdt = vim.wo.foldtext
---
--- If the expression starts with s: or `<SID>`, then it is replaced with
--- the script ID (`local-function`). Example:
---- ```
+---
+--- ```vim
--- set formatexpr=s:MyFormatExpr()
--- set formatexpr=<SID>SomeFormatExpr()
--- ```
@@ -2591,9 +2612,9 @@ vim.go.fs = vim.go.fsync
--- :s///g subst. one subst. all
--- :s///gg subst. all subst. one
---
---- DEPRECATED: Setting this option may break plugins that are not aware
---- of this option. Also, many users get confused that adding the /g flag
---- has the opposite effect of that it normally does.
+--- NOTE: Setting this option may break plugins that rely on the default
+--- behavior of the 'g' flag. This will also make the 'g' flag have the
+--- opposite effect of that documented in `:s_g`.
---
--- @type boolean
vim.o.gdefault = false
@@ -2618,8 +2639,9 @@ vim.go.gfm = vim.go.grepformat
--- `option-backslash` about including spaces and backslashes.
--- When your "grep" accepts the "-H" argument, use this to make ":grep"
--- also work well with a single file:
---- ```
---- :set grepprg=grep\ -nH
+---
+--- ```vim
+--- set grepprg=grep\ -nH
--- ```
--- Special value: When 'grepprg' is set to "internal" the `:grep` command
--- works like `:vimgrep`, `:lgrep` like `:lvimgrep`, `:grepadd` like
@@ -2641,12 +2663,14 @@ vim.go.gp = vim.go.grepprg
--- terminals. See `tui-cursor-shape`.
---
--- To disable cursor-styling, reset the option:
---- ```
---- :set guicursor=
+---
+--- ```vim
+--- set guicursor=
--- ```
--- To enable mode shapes, "Cursor" highlight, and blinking:
---- ```
---- :set guicursor=n-v-c:block,i-ci-ve:ver25,r-cr:hor20,o:hor50
+---
+--- ```vim
+--- set guicursor=n-v-c:block,i-ci-ve:ver25,r-cr:hor20,o:hor50
--- \,a:blinkwait700-blinkoff400-blinkon250-Cursor/lCursor
--- \,sm:block-blinkwait175-blinkoff150-blinkon175
--- ```
@@ -2680,8 +2704,9 @@ vim.go.gp = vim.go.grepprg
--- the cursor is shown and blinkoff is the time that the
--- cursor is not shown. Times are in msec. When one of
--- the numbers is zero, there is no blinking. E.g.:
---- ```
---- :set guicursor=n:blinkon0
+---
+--- ```vim
+--- set guicursor=n:blinkon0
--- ```
--- - Default is "blinkon0" for each mode.
--- {group-name}
@@ -2721,9 +2746,10 @@ vim.go.gp = vim.go.grepprg
--- blinking: "a:blinkon0"
---
--- Examples of cursor highlighting:
---- ```
---- :highlight Cursor gui=reverse guifg=NONE guibg=NONE
---- :highlight Cursor gui=NONE guifg=bg guibg=fg
+---
+--- ```vim
+--- highlight Cursor gui=reverse guifg=NONE guibg=NONE
+--- highlight Cursor gui=NONE guifg=bg guibg=fg
--- ```
---
---
@@ -2743,8 +2769,9 @@ vim.go.gcr = vim.go.guicursor
--- precede it with a backslash. Setting an option requires an extra
--- backslash before a space and a backslash. See also
--- `option-backslash`. For example:
---- ```
---- :set guifont=Screen15,\ 7x13,font\\,with\\,commas
+---
+--- ```vim
+--- set guifont=Screen15,\ 7x13,font\\,with\\,commas
--- ```
--- will make Vim try to use the font "Screen15" first, and if it fails it
--- will try to use "7x13" and then "font,with,commas" instead.
@@ -2757,16 +2784,18 @@ vim.go.gcr = vim.go.guicursor
--- will try to find the related bold and italic fonts.
---
--- For Win32 and Mac OS:
---- ```
---- :set guifont=*
+---
+--- ```vim
+--- set guifont=*
--- ```
--- will bring up a font requester, where you can pick the font you want.
---
--- The font name depends on the GUI used.
---
--- For Mac OSX you can use something like this:
---- ```
---- :set guifont=Monaco:h10
+---
+--- ```vim
+--- set guifont=Monaco:h10
--- ```
--- *E236*
--- Note that the fonts must be mono-spaced (all characters have the same
@@ -2793,9 +2822,10 @@ vim.go.gcr = vim.go.guicursor
--- - A '_' can be used in the place of a space, so you don't need to use
--- backslashes to escape the spaces.
--- - Examples:
---- ```
---- :set guifont=courier_new:h12:w5:b:cRUSSIAN
---- :set guifont=Andale_Mono:h7.5:w4.5
+---
+--- ```vim
+--- set guifont=courier_new:h12:w5:b:cRUSSIAN
+--- set guifont=Andale_Mono:h7.5:w4.5
--- ```
---
---
@@ -2949,8 +2979,9 @@ vim.go.gtl = vim.go.guitablabel
--- pages line. When empty Vim will use a default tooltip.
--- This option is otherwise just like 'guitablabel' above.
--- You can include a line break. Simplest method is to use `:let`:
---- ```
---- :let &guitabtooltip = "line one\nline two"
+---
+--- ```vim
+--- let &guitabtooltip = "line one\nline two"
--- ```
---
---
@@ -2994,8 +3025,9 @@ vim.go.hh = vim.go.helpheight
--- another language, but that will only find tags that exist in that
--- language and not in the English help.
--- Example:
---- ```
---- :set helplang=de,it
+---
+--- ```vim
+--- set helplang=de,it
--- ```
--- This will first search German, then Italian and finally English help
--- files.
@@ -3131,8 +3163,9 @@ vim.go.imd = vim.go.imdisable
--- 2 :lmap is off and IM is ON
--- To always reset the option to zero when leaving Insert mode with <Esc>
--- this can be used:
---- ```
---- :inoremap <ESC> <ESC>:set iminsert=0<CR>
+---
+--- ```vim
+--- inoremap <ESC> <ESC>:set iminsert=0<CR>
--- ```
--- This makes :lmap and IM turn off automatically when leaving Insert
--- mode.
@@ -3206,15 +3239,17 @@ vim.go.inc = vim.go.include
--- Expression to be used to transform the string found with the 'include'
--- option to a file name. Mostly useful to change "." to "/" for Java:
---- ```
---- :setlocal includeexpr=substitute(v:fname,'\\.','/','g')
+---
+--- ```vim
+--- setlocal includeexpr=substitute(v:fname,'\\.','/','g')
--- ```
--- The "v:fname" variable will be set to the file name that was detected.
--- Note the double backslash: the `:set` command first halves them, then
--- one remains in the value, where "\." matches a dot literally. For
--- simple character replacements `tr()` avoids the need for escaping:
---- ```
---- :setlocal includeexpr=tr(v:fname,'.','/')
+---
+--- ```vim
+--- setlocal includeexpr=tr(v:fname,'.','/')
--- ```
---
--- Also used for the `gf` command if an unmodified file name can't be
@@ -3223,7 +3258,8 @@ vim.go.inc = vim.go.include
---
--- If the expression starts with s: or `<SID>`, then it is replaced with
--- the script ID (`local-function`). Example:
---- ```
+---
+--- ```vim
--- setlocal includeexpr=s:MyIncludeExpr(v:fname)
--- setlocal includeexpr=<SID>SomeIncludeExpr(v:fname)
--- ```
@@ -3262,7 +3298,8 @@ vim.bo.inex = vim.bo.includeexpr
--- If you don't want to turn 'hlsearch' on, but want to highlight all
--- matches while searching, you can turn on and off 'hlsearch' with
--- autocmd. Example:
---- ```
+---
+--- ```vim
--- augroup vimrc-incsearch-highlight
--- autocmd!
--- autocmd CmdlineEnter /,\? :set hlsearch
@@ -3295,7 +3332,8 @@ vim.go.is = vim.go.incsearch
---
--- If the expression starts with s: or `<SID>`, then it is replaced with
--- the script ID (`local-function`). Example:
---- ```
+---
+--- ```vim
--- set indentexpr=s:MyIndentExpr()
--- set indentexpr=<SID>SomeIndentExpr()
--- ```
@@ -3311,8 +3349,9 @@ vim.go.is = vim.go.incsearch
--- not change the text, jump to another window, etc. Afterwards the
--- cursor position is always restored, thus the cursor may be moved.
--- Normally this option would be set to call a function:
---- ```
---- :set indentexpr=GetMyIndent()
+---
+--- ```vim
+--- set indentexpr=GetMyIndent()
--- ```
--- Error messages will be suppressed, unless the 'debug' option contains
--- "msg".
@@ -3540,9 +3579,10 @@ vim.go.km = vim.go.keymodel
--- a [count] for the "K" command to a section number.
--- See `option-backslash` about including spaces and backslashes.
--- Example:
---- ```
---- :set keywordprg=man\ -s
---- :set keywordprg=:Man
+---
+--- ```vim
+--- set keywordprg=man\ -s
+--- set keywordprg=:Man
--- ```
--- This option cannot be set from a `modeline` or in the `sandbox`, for
--- security reasons.
@@ -3569,12 +3609,14 @@ vim.go.kp = vim.go.keywordprg
--- security reasons.
---
--- Example (for Greek, in UTF-8): *greek*
---- ```
---- :set langmap=ΑA,ΒB,ΨC,ΔD,ΕE,ΦF,ΓG,ΗH,ΙI,ΞJ,ΚK,ΛL,ΜM,ΝN,ΟO,ΠP,QQ,ΡR,ΣS,ΤT,ΘU,ΩV,WW,ΧX,ΥY,ΖZ,αa,βb,ψc,δd,εe,φf,γg,ηh,ιi,ξj,κk,λl,μm,νn,οo,πp,qq,ρr,σs,τt,θu,ωv,ςw,χx,υy,ζz
+---
+--- ```vim
+--- set langmap=ΑA,ΒB,ΨC,ΔD,ΕE,ΦF,ΓG,ΗH,ΙI,ΞJ,ΚK,ΛL,ΜM,ΝN,ΟO,ΠP,QQ,ΡR,ΣS,ΤT,ΘU,ΩV,WW,ΧX,ΥY,ΖZ,αa,βb,ψc,δd,εe,φf,γg,ηh,ιi,ξj,κk,λl,μm,νn,οo,πp,qq,ρr,σs,τt,θu,ωv,ςw,χx,υy,ζz
--- ```
--- Example (exchanges meaning of z and y for commands):
---- ```
---- :set langmap=zy,yz,ZY,YZ
+---
+--- ```vim
+--- set langmap=zy,yz,ZY,YZ
--- ```
---
--- The 'langmap' option is a list of parts, separated with commas. Each
@@ -3607,28 +3649,32 @@ vim.go.lmap = vim.go.langmap
--- Language to use for menu translation. Tells which file is loaded
--- from the "lang" directory in 'runtimepath':
---- ```
+---
+--- ```vim
--- "lang/menu_" .. &langmenu .. ".vim"
--- ```
--- (without the spaces). For example, to always use the Dutch menus, no
--- matter what $LANG is set to:
---- ```
---- :set langmenu=nl_NL.ISO_8859-1
+---
+--- ```vim
+--- set langmenu=nl_NL.ISO_8859-1
--- ```
--- When 'langmenu' is empty, `v:lang` is used.
--- Only normal file name characters can be used, `/\*?[|<>` are illegal.
--- If your $LANG is set to a non-English language but you do want to use
--- the English menus:
---- ```
---- :set langmenu=none
+---
+--- ```vim
+--- set langmenu=none
--- ```
--- This option must be set before loading menus, switching on filetype
--- detection or syntax highlighting. Once the menus are defined setting
--- this option has no effect. But you could do this:
---- ```
---- :source $VIMRUNTIME/delmenu.vim
---- :set langmenu=de_DE.ISO_8859-1
---- :source $VIMRUNTIME/menu.vim
+---
+--- ```vim
+--- source $VIMRUNTIME/delmenu.vim
+--- set langmenu=de_DE.ISO_8859-1
+--- source $VIMRUNTIME/menu.vim
--- ```
--- Warning: This deletes all menus that you defined yourself!
---
@@ -3701,8 +3747,9 @@ vim.wo.lbr = vim.wo.linebreak
--- to use the size for the GUI, put the command in your `gvimrc` file.
--- Vim limits the number of lines to what fits on the screen. You can
--- use this command to get the tallest window possible:
---- ```
---- :set lines=999
+---
+--- ```vim
+--- set lines=999
--- ```
--- Minimum value is 2, maximum value is 1000.
---
@@ -3771,8 +3818,9 @@ vim.go.lw = vim.go.lispwords
--- The cursor is displayed at the start of the space a Tab character
--- occupies, not at the end as usual in Normal mode. To get this cursor
--- position while displaying Tabs with spaces, use:
---- ```
---- :set list lcs=tab:\ \
+---
+--- ```vim
+--- set list lcs=tab:\ \
--- ```
---
--- Note that list mode will also affect formatting (set with 'textwidth'
@@ -3784,7 +3832,7 @@ vim.o.list = false
vim.wo.list = vim.o.list
--- Strings to use in 'list' mode and for the `:list` command. It is a
---- comma-separated list of string settings.
+--- comma-separated list of string settings. *E1511*
---
--- *lcs-eol*
--- eol:c Character to show at the end of each line. When
@@ -3837,8 +3885,9 @@ vim.wo.list = vim.o.list
--- leading spaces are blank. Overrides the "space" and
--- "multispace" settings for leading spaces. You can
--- combine it with "tab:", for example:
---- ```
---- :set listchars+=tab:>-,lead:.
+---
+--- ```vim
+--- set listchars+=tab:>-,lead:.
--- ```
---
--- *lcs-leadmultispace*
@@ -3875,10 +3924,11 @@ vim.wo.list = vim.o.list
--- omitted.
---
--- The characters ':' and ',' should not be used. UTF-8 characters can
---- be used. All characters must be single width.
+--- be used. All characters must be single width. *E1512*
---
--- Each character can be specified as hex:
---- ```
+---
+--- ```vim
--- set listchars=eol:\\x24
--- set listchars=eol:\\u21b5
--- set listchars=eol:\\U000021b5
@@ -3887,10 +3937,11 @@ vim.wo.list = vim.o.list
--- must be exactly 2 for \\x, 4 for \\u and 8 for \\U.
---
--- Examples:
---- ```
---- :set lcs=tab:>-,trail:-
---- :set lcs=tab:>-,eol:<,nbsp:%
---- :set lcs=extends:>,precedes:<
+---
+--- ```vim
+--- set lcs=tab:>-,trail:-
+--- set lcs=tab:>-,eol:<,nbsp:%
+--- set lcs=extends:>,precedes:<
--- ```
--- `hl-NonText` highlighting will be used for "eol", "extends" and
--- "precedes". `hl-Whitespace` for "nbsp", "space", "tab", "multispace",
@@ -3955,8 +4006,9 @@ vim.go.mef = vim.go.makeef
--- This would be mostly useful when you use MS-Windows. If iconv is
--- enabled, setting 'makeencoding' to "char" has the same effect as
--- setting to the system locale encoding. Example:
---- ```
---- :set makeencoding=char " system locale is used
+---
+--- ```vim
+--- set makeencoding=char " system locale is used
--- ```
---
---
@@ -3977,13 +4029,15 @@ vim.go.menc = vim.go.makeencoding
--- Note that a '|' must be escaped twice: once for ":set" and once for
--- the interpretation of a command. When you use a filter called
--- "myfilter" do it like this:
---- ```
---- :set makeprg=gmake\ \\\|\ myfilter
+---
+--- ```vim
+--- set makeprg=gmake\ \\\|\ myfilter
--- ```
--- The placeholder "$*" can be given (even multiple times) to specify
--- where the arguments will be included, for example:
---- ```
---- :set makeprg=latex\ \\\\nonstopmode\ \\\\input\\{$*}
+---
+--- ```vim
+--- set makeprg=latex\ \\\\nonstopmode\ \\\\input\\{$*}
--- ```
--- This option cannot be set from a `modeline` or in the `sandbox`, for
--- security reasons.
@@ -4003,13 +4057,15 @@ vim.go.mp = vim.go.makeprg
--- The characters must be separated by a colon.
--- The pairs must be separated by a comma. Example for including '<' and
--- '>' (for HTML):
---- ```
---- :set mps+=<:>
+---
+--- ```vim
+--- set mps+=<:>
--- ```
--- A more exotic example, to jump between the '=' and ';' in an
--- assignment, useful for languages like C and Java:
---- ```
---- :au FileType c,cpp,java set mps+==:;
+---
+--- ```vim
+--- au FileType c,cpp,java set mps+==:;
--- ```
--- For a more advanced way of using "%", see the matchit.vim plugin in
--- the $VIMRUNTIME/plugin directory. `add-local-help`
@@ -4037,6 +4093,7 @@ vim.go.mat = vim.go.matchtime
--- Increasing this limit above 200 also changes the maximum for Ex
--- command recursion, see `E169`.
--- See also `:function`.
+--- Also used for maximum depth of callback functions.
---
--- @type integer
vim.o.maxfuncdepth = 100
@@ -4116,8 +4173,9 @@ vim.go.mis = vim.go.menuitems
--- The languages for which these numbers are important are Italian and
--- Hungarian. The default works for when you have about 512 Mbyte. If
--- you have 1 Gbyte you could use:
---- ```
---- :set mkspellmem=900000,3000,800
+---
+--- ```vim
+--- set mkspellmem=900000,3000,800
--- ```
--- If you have less than 512 Mbyte `:mkspell` may fail for some
--- languages, no matter what you set 'mkspellmem' to.
@@ -4212,8 +4270,9 @@ vim.go.more = vim.o.more
--- Enables mouse support. For example, to enable the mouse in Normal mode
--- and Visual mode:
---- ```
---- :set mouse=nv
+---
+--- ```vim
+--- set mouse=nv
--- ```
---
--- To temporarily disable mouse support, hold the shift key while using
@@ -4305,19 +4364,20 @@ vim.go.mh = vim.go.mousehide
--- See `mouse-overview`. But mappings are NOT used for modeless selection.
---
--- Example:
---- ```
---- :map <S-LeftMouse> <RightMouse>
---- :map <S-LeftDrag> <RightDrag>
---- :map <S-LeftRelease> <RightRelease>
---- :map <2-S-LeftMouse> <2-RightMouse>
---- :map <2-S-LeftDrag> <2-RightDrag>
---- :map <2-S-LeftRelease> <2-RightRelease>
---- :map <3-S-LeftMouse> <3-RightMouse>
---- :map <3-S-LeftDrag> <3-RightDrag>
---- :map <3-S-LeftRelease> <3-RightRelease>
---- :map <4-S-LeftMouse> <4-RightMouse>
---- :map <4-S-LeftDrag> <4-RightDrag>
---- :map <4-S-LeftRelease> <4-RightRelease>
+---
+--- ```vim
+--- map <S-LeftMouse> <RightMouse>
+--- map <S-LeftDrag> <RightDrag>
+--- map <S-LeftRelease> <RightRelease>
+--- map <2-S-LeftMouse> <2-RightMouse>
+--- map <2-S-LeftDrag> <2-RightDrag>
+--- map <2-S-LeftRelease> <2-RightRelease>
+--- map <3-S-LeftMouse> <3-RightMouse>
+--- map <3-S-LeftDrag> <3-RightDrag>
+--- map <3-S-LeftRelease> <3-RightRelease>
+--- map <4-S-LeftMouse> <4-RightMouse>
+--- map <4-S-LeftDrag> <4-RightDrag>
+--- map <4-S-LeftRelease> <4-RightRelease>
--- ```
---
--- Mouse commands requiring the CTRL modifier can be simulated by typing
@@ -4357,8 +4417,9 @@ vim.go.mousemev = vim.go.mousemoveevent
--- a count of 0.
---
--- Example:
---- ```
---- :set mousescroll=ver:5,hor:2
+---
+--- ```vim
+--- set mousescroll=ver:5,hor:2
--- ```
--- Will make Nvim scroll 5 lines at a time when scrolling vertically, and
--- scroll 2 columns at a time when scrolling horizontally.
@@ -4421,8 +4482,9 @@ vim.go.mousescroll = vim.o.mousescroll
--- pointer.
---
--- Example:
---- ```
---- :set mouseshape=s:udsizing,m:no
+---
+--- ```vim
+--- set mouseshape=s:udsizing,m:no
--- ```
--- will make the mouse turn to a sizing arrow over the status lines and
--- indicate no input when the hit-enter prompt is displayed (since
@@ -4626,28 +4688,33 @@ vim.go.pm = vim.go.patchmode
--- starting with "/", "./" or "../"). The directories in the 'path'
--- option may be relative or absolute.
--- - Use commas to separate directory names:
---- ```
---- :set path=.,/usr/local/include,/usr/include
+---
+--- ```vim
+--- set path=.,/usr/local/include,/usr/include
--- ```
--- - Spaces can also be used to separate directory names. To have a
--- space in a directory name, precede it with an extra backslash, and
--- escape the space:
---- ```
---- :set path=.,/dir/with\\\ space
+---
+--- ```vim
+--- set path=.,/dir/with\\\ space
--- ```
--- - To include a comma in a directory name precede it with an extra
--- backslash:
---- ```
---- :set path=.,/dir/with\\,comma
+---
+--- ```vim
+--- set path=.,/dir/with\\,comma
--- ```
--- - To search relative to the directory of the current file, use:
---- ```
---- :set path=.
+---
+--- ```vim
+--- set path=.
--- ```
--- - To search in the current directory use an empty string between two
--- commas:
---- ```
---- :set path=,,
+---
+--- ```vim
+--- set path=,,
--- ```
--- - A directory name may end in a ':' or '/'.
--- - Environment variables are expanded `:set_env`.
@@ -4656,12 +4723,14 @@ vim.go.pm = vim.go.patchmode
--- - Search upwards and downwards in a directory tree using "*", "**" and
--- ";". See `file-searching` for info and syntax.
--- - Careful with '\' characters, type two to get one in the option:
---- ```
---- :set path=.,c:\\include
+---
+--- ```vim
+--- set path=.,c:\\include
--- ```
--- Or just use '/' instead:
---- ```
---- :set path=.,c:/include
+---
+--- ```vim
+--- set path=.,c:/include
--- ```
--- Don't forget "." or files won't even be found in the same directory as
--- the file!
@@ -4672,18 +4741,21 @@ vim.go.pm = vim.go.patchmode
--- The use of `:set+=` and `:set-=` is preferred when adding or removing
--- directories from the list. This avoids problems when a future version
--- uses another default. To remove the current directory use:
---- ```
---- :set path-=
+---
+--- ```vim
+--- set path-=
--- ```
--- To add the current directory use:
---- ```
---- :set path+=
+---
+--- ```vim
+--- set path+=
--- ```
--- To use an environment variable, you probably need to replace the
--- separator. Here is an example to append $INCL, in which directory
--- names are separated with a semi-colon:
---- ```
---- :let &path = &path .. "," .. substitute($INCL, ';', ',', 'g')
+---
+--- ```vim
+--- let &path = &path .. "," .. substitute($INCL, ';', ',', 'g')
--- ```
--- Replace the ';' with a ':' or whatever separator is used. Note that
--- this doesn't work when $INCL contains a comma or white space.
@@ -4741,9 +4813,11 @@ vim.wo.pvw = vim.wo.previewwindow
--- It is possible to override the level for individual highlights within
--- the popupmenu using `highlight-blend`. For instance, to enable
--- transparency but force the current selected element to be fully opaque:
---- ```
---- :set pumblend=15
---- :hi PmenuSel blend=0
+---
+--- ```vim
+---
+--- set pumblend=15
+--- hi PmenuSel blend=0
--- ```
---
--- UI-dependent. Works best with RGB colors. 'termguicolors'
@@ -5014,8 +5088,9 @@ vim.go.ru = vim.go.ruler
--- The default ruler width is 17 characters. To make the ruler 15
--- characters wide, put "%15(" at the start and "%)" at the end.
--- Example:
---- ```
---- :set rulerformat=%15(%c%V\ %p%%%)
+---
+--- ```vim
+--- set rulerformat=%15(%c%V\ %p%%%)
--- ```
---
---
@@ -5082,8 +5157,9 @@ vim.go.ruf = vim.go.rulerformat
--- wildcards.
--- See `:runtime`.
--- Example:
---- ```
---- :set runtimepath=~/vimruntime,/mygroup/vim,$VIMRUNTIME
+---
+--- ```vim
+--- set runtimepath=~/vimruntime,/mygroup/vim,$VIMRUNTIME
--- ```
--- This will use the directory "~/vimruntime" first (containing your
--- personal Nvim runtime files), then "/mygroup/vim", and finally
@@ -5164,7 +5240,8 @@ vim.go.sj = vim.go.scrolljump
--- when long lines wrap).
--- After using the local value, go back the global value with one of
--- these two:
---- ```
+---
+--- ```vim
--- setlocal scrolloff<
--- setlocal scrolloff=-1
--- ```
@@ -5393,8 +5470,9 @@ vim.go.ssop = vim.go.sessionoptions
--- contents size) = 10253 bytes.
---
--- Example:
---- ```
---- :set shada='50,<1000,s100,:0,n~/nvim/shada
+---
+--- ```vim
+--- set shada='50,<1000,s100,:0,n~/nvim/shada
--- ```
---
--- '50 Marks will be remembered for the last 50 files you
@@ -5445,14 +5523,16 @@ vim.go.sdf = vim.go.shadafile
---
--- If the name of the shell contains a space, you need to enclose it in
--- quotes. Example with quotes:
---- ```
---- :set shell=\"c:\program\ files\unix\sh.exe\"\ -f
+---
+--- ```vim
+--- set shell=\"c:\program\ files\unix\sh.exe\"\ -f
--- ```
--- Note the backslash before each quote (to avoid starting a comment) and
--- each space (to avoid ending the option value), so better use `:let-&`
--- like this:
---- ```
---- :let &shell='"C:\Program Files\unix\sh.exe" -f'
+---
+--- ```vim
+--- let &shell='"C:\Program Files\unix\sh.exe" -f'
--- ```
--- Also note that the "-f" is not inside the quotes, because it is not
--- part of the command name.
@@ -5477,7 +5557,8 @@ vim.go.sdf = vim.go.shadafile
--- unescaping, so to keep yourself sane use `:let-&` like shown above.
--- *shell-powershell*
--- To use PowerShell:
---- ```
+---
+--- ```vim
--- let &shell = executable('pwsh') ? 'pwsh' : 'powershell'
--- let &shellcmdflag = '-NoLogo -ExecutionPolicy RemoteSigned -Command [Console]::InputEncoding=[Console]::OutputEncoding=[System.Text.UTF8Encoding]::new();$PSDefaultParameterValues[''Out-File:Encoding'']=''utf8'';Remove-Alias -Force -ErrorAction SilentlyContinue tee;'
--- let &shellredir = '2>&1 | %%{ "$_" } | Out-File %s; exit $LastExitCode'
@@ -5602,7 +5683,8 @@ vim.go.srr = vim.go.shellredir
--- any file for best results. This might change in the future.
--- 'shellslash' only works when a backslash can be used as a path
--- separator. To test if this is so use:
---- ```
+---
+--- ```vim
--- if exists('+shellslash')
--- ```
--- Also see 'completeslash'.
@@ -5715,9 +5797,10 @@ vim.bo.sw = vim.bo.shiftwidth
--- match", "Pattern not found", "Back at original", etc.
--- C don't give messages while scanning for ins-completion *shm-C*
--- items, for instance "scanning tags"
---- q use "recording" instead of "recording @a" *shm-q*
+--- 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]"
---
@@ -5738,9 +5821,10 @@ vim.go.shm = vim.go.shortmess
--- String to put at the start of lines that have been wrapped. Useful
--- values are "> " or "+++ ":
---- ```
---- :let &showbreak = "> "
---- :let &showbreak = '+++ '
+---
+--- ```vim
+--- let &showbreak = "> "
+--- let &showbreak = '+++ '
--- ```
--- Only printable single-cell characters are allowed, excluding <Tab> and
--- comma (in a future version the comma might be used to separate the
@@ -5751,8 +5835,9 @@ vim.go.shm = vim.go.shortmess
--- "n" flag to 'cpoptions'.
--- A window-local value overrules a global value. If the global value is
--- set and you want no value in the current window use NONE:
---- ```
---- :setlocal showbreak=NONE
+---
+--- ```vim
+--- setlocal showbreak=NONE
--- ```
---
---
@@ -5884,7 +5969,8 @@ vim.go.ss = vim.go.sidescroll
--- close to the beginning of the line.
--- After using the local value, go back the global value with one of
--- these two:
---- ```
+---
+--- ```vim
--- setlocal sidescrolloff<
--- setlocal sidescrolloff=-1
--- ```
@@ -5892,9 +5978,11 @@ vim.go.ss = vim.go.sidescroll
--- Example: Try this together with 'sidescroll' and 'listchars' as
--- in the following example to never allow the cursor to move
--- onto the "extends" character:
---- ```
---- :set nowrap sidescroll=1 listchars=extends:>,precedes:<
---- :set sidescrolloff=1
+---
+--- ```vim
+---
+--- set nowrap sidescroll=1 listchars=extends:>,precedes:<
+--- set sidescrolloff=1
--- ```
---
---
@@ -6048,7 +6136,7 @@ vim.bo.spc = vim.bo.spellcapcheck
--- Name of the word list file where words are added for the `zg` and `zw`
--- commands. It must end in ".{encoding}.add". You need to include the
--- path, otherwise the file is placed in the current directory.
---- The path may include characters from 'isfname', space, comma and '@'.
+--- The path may include characters from 'isfname', ' ', ',', '@' and ':'.
--- *E765*
--- It may also be a comma-separated list of names. A count before the
--- `zg` and `zw` commands can be used to access each. This allows using
@@ -6075,7 +6163,8 @@ vim.bo.spf = vim.bo.spellfile
--- A comma-separated list of word list names. When the 'spell' option is
--- on spellchecking will be done for these languages. Example:
---- ```
+---
+--- ```vim
--- set spelllang=en_us,nl,medical
--- ```
--- This means US English, Dutch and medical words are recognized. Words
@@ -6193,8 +6282,9 @@ vim.bo.spo = vim.bo.spelloptions
---
--- Only one of "best", "double" or "fast" may be used. The others may
--- appear several times in any order. Example:
---- ```
---- :set sps=file:~/.config/nvim/sugg,best,expr:MySuggest()
+---
+--- ```vim
+--- set sps=file:~/.config/nvim/sugg,best,expr:MySuggest()
--- ```
---
--- This option cannot be set from a `modeline` or in the `sandbox`, for
@@ -6273,9 +6363,13 @@ vim.go.sol = vim.go.startofline
--- %s sign column for currently drawn line
--- %C fold column for currently drawn line
---
---- NOTE: To draw the sign and fold columns, their items must be included in
---- 'statuscolumn'. Even when they are not included, the status column width
---- will adapt to the 'signcolumn' and 'foldcolumn' width.
+--- The 'statuscolumn' width follows that of the default columns and
+--- adapts to the `'numberwidth'`, `'signcolumn'` and `'foldcolumn'` option
+--- values (regardless of whether the sign and fold items are present).
+--- Additionally, the 'statuscolumn' grows with the size of the evaluated
+--- format string, up to a point (following the maximum size of the default
+--- fold, sign and number columns). Shrinking only happens when the number
+--- of lines in a buffer changes, or the 'statuscolumn' option is set.
---
--- The `v:lnum` variable holds the line number to be drawn.
--- The `v:relnum` variable holds the relative line number to be drawn.
@@ -6283,6 +6377,9 @@ vim.go.sol = vim.go.startofline
--- when drawing the actual buffer line, and positive when
--- drawing the wrapped part of a buffer line.
---
+--- When using `v:relnum`, keep in mind that cursor movement by itself will
+--- not cause the 'statuscolumn' to update unless `'relativenumber'` is set.
+---
--- NOTE: The %@ click execute function item is supported as well but the
--- specified function will be the same for each row in the same column.
--- It cannot be switched out through a dynamic 'statuscolumn' format, the
@@ -6292,21 +6389,21 @@ vim.go.sol = vim.go.startofline
---
--- ```vim
--- " Relative number with bar separator and click handlers:
---- :set statuscolumn=%@SignCb@%s%=%T%@NumCb@%r│%T
+--- set statuscolumn=%@SignCb@%s%=%T%@NumCb@%r│%T
---
--- " Right aligned relative cursor line number:
---- :let &stc='%=%{v:relnum?v:relnum:v:lnum} '
+--- let &stc='%=%{v:relnum?v:relnum:v:lnum} '
---
--- " Line numbers in hexadecimal for non wrapped part of lines:
---- :let &stc='%=%{v:virtnum>0?"":printf("%x",v:lnum)} '
+--- let &stc='%=%{v:virtnum>0?"":printf("%x",v:lnum)} '
---
--- " Human readable line numbers with thousands separator:
---- :let &stc='%{substitute(v:lnum,"\\d\\zs\\ze\\'
+--- let &stc='%{substitute(v:lnum,"\\d\\zs\\ze\\'
--- . '%(\\d\\d\\d\\)\\+$",",","g")}'
---
--- " Both relative and absolute line numbers with different
--- " highlighting for odd and even relative numbers:
---- :let &stc='%#NonText#%{&nu?v:lnum:""}' .
+--- let &stc='%#NonText#%{&nu?v:lnum:""}' .
--- '%=%{&rnu&&(v:lnum%2)?"\ ".v:relnum:""}' .
--- '%#LineNr#%{&rnu&&!(v:lnum%2)?"\ ".v:relnum:""}'
--- ```
@@ -6330,8 +6427,9 @@ vim.wo.stc = vim.wo.statuscolumn
---
--- When the option starts with "%!" then it is used as an expression,
--- evaluated and the result is used as the option value. Example:
---- ```
---- :set statusline=%!MyStatusLine()
+---
+--- ```vim
+--- set statusline=%!MyStatusLine()
--- ```
--- The *g:statusline_winid* variable will be set to the `window-ID` of the
--- window that the status line belongs to.
@@ -6414,7 +6512,8 @@ vim.wo.stc = vim.wo.statuscolumn
--- The expression can contain the "}" character, the end of
--- expression is denoted by "%}".
--- For example:
---- ```
+---
+--- ```vim
--- func! Stl_filename() abort
--- return "%t"
--- endfunc
@@ -6427,16 +6526,17 @@ vim.wo.stc = vim.wo.statuscolumn
--- ) - End of item group. No width fields allowed.
--- T N For 'tabline': start of tab page N label. Use %T or %X to end
--- the label. Clicking this label with left mouse button switches
---- to the specified tab page.
+--- to the specified tab page, while clicking it with middle mouse
+--- button closes the specified tab page.
--- X N For 'tabline': start of close tab N label. Use %X or %T to end
--- the label, e.g.: %3Xclose%X. Use %999X for a "close current
---- tab" label. Clicking this label with left mouse button closes
---- specified tab page.
---- @ N Start of execute function label. Use %X or %T to
---- end the label, e.g.: %10@SwitchBuffer@foo.c%X. Clicking this
---- label runs specified function: in the example when clicking once
---- using left mouse button on "foo.c" "SwitchBuffer(10, 1, 'l',
---- ' ')" expression will be run. Function receives the
+--- tab" label. Clicking this label with left mouse button closes
+--- the specified tab page.
+--- @ N Start of execute function label. Use %X or %T to end the label,
+--- e.g.: %10@SwitchBuffer@foo.c%X. Clicking this label runs the
+--- specified function: in the example when clicking once using left
+--- mouse button on "foo.c", a `SwitchBuffer(10, 1, 'l', ' ')`
+--- expression will be run. The specified function receives the
--- following arguments in order:
--- 1. minwid field value or zero if no N was specified
--- 2. number of mouse clicks to detect multiple clicks
@@ -6483,8 +6583,9 @@ vim.wo.stc = vim.wo.statuscolumn
--- not set) and a minwid is not set for the group, the whole group will
--- become empty. This will make a group like the following disappear
--- completely from the statusline when none of the flags are set.
---- ```
---- :set statusline=...%(\ [%M%R%H]%)...
+---
+--- ```vim
+--- set statusline=...%(\ [%M%R%H]%)...
--- ```
--- Beware that an expression is evaluated each and every time the status
--- line is displayed.
@@ -6517,35 +6618,42 @@ vim.wo.stc = vim.wo.statuscolumn
---
--- Examples:
--- Emulate standard status line with 'ruler' set
---- ```
---- :set statusline=%<%f\ %h%m%r%=%-14.(%l,%c%V%)\ %P
+---
+--- ```vim
+--- set statusline=%<%f\ %h%m%r%=%-14.(%l,%c%V%)\ %P
--- ```
--- Similar, but add ASCII value of char under the cursor (like "ga")
---- ```
---- :set statusline=%<%f%h%m%r%=%b\ 0x%B\ \ %l,%c%V\ %P
+---
+--- ```vim
+--- set statusline=%<%f%h%m%r%=%b\ 0x%B\ \ %l,%c%V\ %P
--- ```
--- Display byte count and byte value, modified flag in red.
---- ```
---- :set statusline=%<%f%=\ [%1*%M%*%n%R%H]\ %-19(%3l,%02c%03V%)%O'%02b'
---- :hi User1 term=inverse,bold cterm=inverse,bold ctermfg=red
+---
+--- ```vim
+--- set statusline=%<%f%=\ [%1*%M%*%n%R%H]\ %-19(%3l,%02c%03V%)%O'%02b'
+--- hi User1 term=inverse,bold cterm=inverse,bold ctermfg=red
--- ```
--- Display a ,GZ flag if a compressed file is loaded
---- ```
---- :set statusline=...%r%{VarExists('b:gzflag','\ [GZ]')}%h...
+---
+--- ```vim
+--- set statusline=...%r%{VarExists('b:gzflag','\ [GZ]')}%h...
--- ```
--- In the `:autocmd`'s:
---- ```
---- :let b:gzflag = 1
+---
+--- ```vim
+--- let b:gzflag = 1
--- ```
--- And:
---- ```
---- :unlet b:gzflag
+---
+--- ```vim
+--- unlet b:gzflag
--- ```
--- And define this function:
---- ```
---- :function VarExists(var, val)
---- : if exists(a:var) | return a:val | else | return '' | endif
---- :endfunction
+---
+--- ```vim
+--- function VarExists(var, val)
+--- if exists(a:var) | return a:val | else | return '' | endif
+--- endfunction
--- ```
---
---
@@ -6576,8 +6684,9 @@ vim.go.su = vim.go.suffixes
--- Comma-separated list of suffixes, which are used when searching for a
--- file for the "gf", "[I", etc. commands. Example:
---- ```
---- :set suffixesadd=.java
+---
+--- ```vim
+--- set suffixesadd=.java
--- ```
---
---
@@ -6662,26 +6771,26 @@ vim.bo.smc = vim.bo.synmaxcol
--- Otherwise this option does not always reflect the current syntax (the
--- b:current_syntax variable does).
--- This option is most useful in a modeline, for a file which syntax is
---- not automatically recognized. Example, in an IDL file:
---- ```
+--- not automatically recognized. Example, in an IDL file: >c
--- /* vim: set syntax=idl : */
--- ```
--- When a dot appears in the value then this separates two filetype
---- names. Example:
---- ```
+--- names. Example: >c
--- /* vim: set syntax=c.doxygen : */
--- ```
--- This will use the "c" syntax first, then the "doxygen" syntax.
--- Note that the second one must be prepared to be loaded as an addition,
--- otherwise it will be skipped. More than one dot may appear.
--- To switch off syntax highlighting for the current file, use:
---- ```
---- :set syntax=OFF
+---
+--- ```vim
+--- set syntax=OFF
--- ```
--- To switch syntax highlighting on according to the current value of the
--- 'filetype' option:
---- ```
---- :set syntax=ON
+---
+--- ```vim
+--- set syntax=ON
--- ```
--- What actually happens when setting the 'syntax' option is that the
--- Syntax autocommand event is triggered with the value as argument.
@@ -6941,6 +7050,10 @@ vim.go.tbidi = vim.go.termbidi
--- attributes instead of "cterm" attributes. `guifg`
--- Requires an ISO-8613-3 compatible terminal.
---
+--- Nvim will automatically attempt to determine if the host terminal
+--- supports 24-bit color and will enable this option if it does
+--- (unless explicitly disabled by the user).
+---
--- @type boolean
vim.o.termguicolors = false
vim.o.tgc = vim.o.termguicolors
@@ -7107,15 +7220,17 @@ vim.go.titleold = vim.o.titleold
--- This option cannot be set in a modeline when 'modelineexpr' is off.
---
--- Example:
---- ```
---- :auto BufEnter * let &titlestring = hostname() .. "/" .. expand("%:p")
---- :set title titlestring=%<%F%=%l/%L-%P titlelen=70
+---
+--- ```vim
+--- auto BufEnter * let &titlestring = hostname() .. "/" .. expand("%:p")
+--- set title titlestring=%<%F%=%l/%L-%P titlelen=70
--- ```
--- The value of 'titlelen' is used to align items in the middle or right
--- of the available space.
--- Some people prefer to have the file name first:
---- ```
---- :set titlestring=%t%(\ %M%)%(\ (%{expand(\"%:~:.:h\")})%)%(\ %a%)
+---
+--- ```vim
+--- set titlestring=%t%(\ %M%)%(\ (%{expand(\"%:~:.:h\")})%)%(\ %a%)
--- ```
--- Note the use of "%{ }" and an expression to get the path of the file,
--- without the file name. The "%( %)" constructs are used to add a
@@ -7200,7 +7315,8 @@ vim.bo.udf = vim.bo.undofile
--- Nevertheless, a single change can already use a large amount of memory.
--- Set to 0 for Vi compatibility: One level of undo and "u" undoes
--- itself:
---- ```
+---
+--- ```vim
--- set ul=0
--- ```
--- But you can also get Vi compatibility by including the 'u' flag in
@@ -7208,7 +7324,8 @@ vim.bo.udf = vim.bo.undofile
--- Also see `undo-two-ways`.
--- Set to -1 for no undo at all. You might want to do this only for the
--- current buffer:
---- ```
+---
+--- ```vim
--- setlocal ul=-1
--- ```
--- This helps when you run out of memory for a single change.
@@ -7280,8 +7397,9 @@ vim.go.ut = vim.go.updatetime
--- For example, when editing assembly language files where statements
--- start in the 9th column and comments in the 41st, it may be useful
--- to use the following:
---- ```
---- :set varsofttabstop=8,32,8
+---
+--- ```vim
+--- set varsofttabstop=8,32,8
--- ```
--- This will set soft tabstops with 8 and 8 + 32 spaces, and 8 more
--- for every column thereafter.
@@ -7298,8 +7416,9 @@ vim.bo.vsts = vim.bo.varsofttabstop
--- A list of the number of spaces that a <Tab> in the file counts for,
--- separated by commas. Each value corresponds to one tab, with the
--- final value applying to all subsequent tabs. For example:
---- ```
---- :set vartabstop=4,20,10,8
+---
+--- ```vim
+--- set vartabstop=4,20,10,8
--- ```
--- This will make the first tab 4 spaces wide, the second 20 spaces,
--- the third 10 spaces, and all following tabs 8 spaces.
@@ -7315,15 +7434,15 @@ vim.bo.vts = vim.bo.vartabstop
--- Sets the verbosity level. Also set by `-V` and `:verbose`.
---
---- Tracing of options in Lua scripts is activated at level 1; Lua scripts
---- are not traced with verbose=0, for performance.
+--- Tracing of assignments to options, mappings, etc. in Lua scripts is
+--- enabled at level 1; Lua scripts are not traced when 'verbose' is 0,
+--- for performance.
---
--- If greater than or equal to a given level, Nvim produces the following
--- messages:
---
--- Level Messages ~
--- ----------------------------------------------------------------------
---- 1 Lua assignments to options, mappings, etc.
--- 2 When a file is ":source"'ed, or `shada` file is read or written.
--- 3 UI info, terminal capabilities.
--- 4 Shell commands.
@@ -7454,8 +7573,9 @@ vim.go.warn = vim.o.warn
--- [ <Left> Insert and Replace
--- ] <Right> Insert and Replace
--- For example:
---- ```
---- :set ww=<,>,[,]
+---
+--- ```vim
+--- set ww=<,>,[,]
--- ```
--- allows wrap only when cursor keys are used.
--- When the movement keys are used in combination with a delete or change
@@ -7484,8 +7604,9 @@ vim.go.ww = vim.go.whichwrap
--- <Esc> can be used, but hitting it twice in a row will still exit
--- command-line as a failsafe measure.
--- Although 'wc' is a number option, you can set it to a special key:
---- ```
---- :set wc=<Tab>
+---
+--- ```vim
+--- set wc=<Tab>
--- ```
---
---
@@ -7500,9 +7621,10 @@ vim.go.wc = vim.go.wildchar
--- keys suitable for this option by looking at `ex-edit-index`. Normally
--- you'll never actually type 'wildcharm', just use it in mappings that
--- automatically invoke completion mode, e.g.:
---- ```
---- :set wcm=<C-Z>
---- :cnoremap ss so $vim/sessions/*.vim<C-Z>
+---
+--- ```vim
+--- set wcm=<C-Z>
+--- cnoremap ss so $vim/sessions/*.vim<C-Z>
--- ```
--- Then after typing :ss you can use CTRL-P & CTRL-N.
---
@@ -7519,8 +7641,9 @@ vim.go.wcm = vim.go.wildcharm
--- The pattern is used like with `:autocmd`, see `autocmd-pattern`.
--- Also see 'suffixes'.
--- Example:
---- ```
---- :set wildignore=*.o,*.obj
+---
+--- ```vim
+--- set wildignore=*.o,*.obj
--- ```
--- The use of `:set+=` and `:set-=` is preferred when adding or removing
--- a pattern from the list. This avoids problems when a future version
@@ -7576,9 +7699,10 @@ vim.go.wic = vim.go.wildignorecase
---
--- If you want <Left> and <Right> to move the cursor instead of selecting
--- a different match, use this:
---- ```
---- :cnoremap <Left> <Space><BS><Left>
---- :cnoremap <Right> <Space><BS><Right>
+---
+--- ```vim
+--- cnoremap <Left> <Space><BS><Left>
+--- cnoremap <Right> <Space><BS><Right>
--- ```
---
--- `hl-WildMenu` highlights the current match.
@@ -7621,24 +7745,29 @@ vim.go.wmnu = vim.go.wildmenu
--- current buffer).
---
--- Examples:
---- ```
---- :set wildmode=full
+---
+--- ```vim
+--- set wildmode=full
--- ```
--- Complete first full match, next match, etc. (the default)
---- ```
---- :set wildmode=longest,full
+---
+--- ```vim
+--- set wildmode=longest,full
--- ```
--- Complete longest common string, then each full match
---- ```
---- :set wildmode=list:full
+---
+--- ```vim
+--- set wildmode=list:full
--- ```
--- List all matches and complete each full match
---- ```
---- :set wildmode=list,full
+---
+--- ```vim
+--- set wildmode=list,full
--- ```
--- List all matches without completing, then each full match
---- ```
---- :set wildmode=longest,list
+---
+--- ```vim
+--- set wildmode=longest,list
--- ```
--- Complete longest common string, then list alternatives.
--- More info here: `cmdline-completion`.
@@ -7776,7 +7905,8 @@ vim.wo.wfw = vim.wo.winfixwidth
--- that ":all" will create only two windows. To avoid "vim -o 1 2 3 4"
--- to create only two windows, set the option after startup is done,
--- using the `VimEnter` event:
---- ```
+---
+--- ```vim
--- au VimEnter * set winheight=999
--- ```
--- Minimum value is 1.
@@ -7806,7 +7936,8 @@ vim.go.wh = vim.go.winheight
--- message area cannot be overridden.
---
--- Example: show a different color for non-current windows:
---- ```
+---
+--- ```vim
--- set winhighlight=Normal:MyNormal,NormalNC:MyNormalNC
--- ```
---
@@ -7876,9 +8007,10 @@ vim.go.wiw = vim.go.winwidth
--- The line will be broken in the middle of a word if necessary. See
--- 'linebreak' to get the break at a word boundary.
--- To make scrolling horizontally a bit more useful, try this:
---- ```
---- :set sidescroll=5
---- :set listchars+=precedes:<,extends:>
+---
+--- ```vim
+--- set sidescroll=5
+--- set listchars+=precedes:<,extends:>
--- ```
--- See 'sidescroll', 'listchars' and `wrap-off`.
--- This option can't be set from a `modeline` when the 'diff' option is
diff --git a/runtime/lua/vim/_meta/re.lua b/runtime/lua/vim/_meta/re.lua
new file mode 100644
index 0000000000..14c94c7824
--- /dev/null
+++ b/runtime/lua/vim/_meta/re.lua
@@ -0,0 +1,54 @@
+--- @meta
+error('Cannot require a meta file')
+
+-- Documentations and Lua types for vim.re (vendored re.lua, lpeg-1.1.0)
+-- https://www.inf.puc-rio.br/~roberto/lpeg/re.html
+--
+-- Copyright © 2007-2023 Lua.org, PUC-Rio.
+-- See 'lpeg.html' for license
+
+--- @brief
+--- The `vim.re` module provides a conventional regex-like syntax for pattern usage
+--- within LPeg |vim.lpeg|.
+---
+--- See https://www.inf.puc-rio.br/~roberto/lpeg/re.html for the original
+--- documentation including regex syntax and more concrete examples.
+
+--- Compiles the given {string} and returns an equivalent LPeg pattern. The given string may define
+--- either an expression or a grammar. The optional {defs} table provides extra Lua values to be used
+--- by the pattern.
+--- @param string string
+--- @param defs? table
+--- @return vim.lpeg.Pattern
+function vim.re.compile(string, defs) end
+
+--- Searches the given {pattern} in the given {subject}. If it finds a match, returns the index
+--- where this occurrence starts and the index where it ends. Otherwise, returns nil.
+---
+--- An optional numeric argument {init} makes the search starts at that position in the subject
+--- string. As usual in Lua libraries, a negative value counts from the end.
+--- @param subject string
+--- @param pattern vim.lpeg.Pattern|string
+--- @param init? integer
+--- @return integer|nil the index where the occurrence starts, nil if no match
+--- @return integer|nil the index where the occurrence ends, nil if no match
+function vim.re.find(subject, pattern, init) end
+
+--- Does a global substitution, replacing all occurrences of {pattern} in the given {subject} by
+--- {replacement}.
+--- @param subject string
+--- @param pattern vim.lpeg.Pattern|string
+--- @param replacement string
+--- @return string
+function vim.re.gsub(subject, pattern, replacement) end
+
+--- Matches the given {pattern} against the given {subject}, returning all captures.
+--- @param subject string
+--- @param pattern vim.lpeg.Pattern|string
+--- @param init? integer
+--- @return integer|vim.lpeg.Capture|nil
+--- @see vim.lpeg.match()
+function vim.re.match(subject, pattern, init) end
+
+--- Updates the pre-defined character classes to the current locale.
+function vim.re.updatelocale() end
diff --git a/runtime/lua/vim/_meta/regex.lua b/runtime/lua/vim/_meta/regex.lua
index 58aa2be8c2..595ad96319 100644
--- a/runtime/lua/vim/_meta/regex.lua
+++ b/runtime/lua/vim/_meta/regex.lua
@@ -2,8 +2,6 @@
-- luacheck: no unused args
---- @defgroup vim.regex
----
--- @brief Vim regexes can be used directly from Lua. Currently they only allow
--- matching within a single line.
@@ -14,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 05e5b2b871..ac25547212 100644
--- a/runtime/lua/vim/_meta/vimfn.lua
+++ b/runtime/lua/vim/_meta/vimfn.lua
@@ -582,7 +582,7 @@ function vim.fn.bufloaded(buf) end
--- echo bufname("file2") " name of buffer where "file2" matches.
--- <
---
---- @param buf? any
+--- @param buf? integer|string
--- @return string
function vim.fn.bufname(buf) end
@@ -599,7 +599,7 @@ function vim.fn.bufname(buf) end
--- number necessarily exist, because ":bwipeout" may have removed
--- them. Use bufexists() to test for the existence of a buffer.
---
---- @param buf? any
+--- @param buf? integer|string
--- @param create? any
--- @return integer
function vim.fn.bufnr(buf, create) end
@@ -1024,6 +1024,8 @@ function vim.fn.complete_check() end
--- no item is selected when using the <Up> or
--- <Down> keys)
--- inserted Inserted string. [NOT IMPLEMENTED YET]
+--- preview_winid Info floating preview window id.
+--- preview_bufnr Info floating preview buffer id.
---
--- *complete_info_mode*
--- mode values are:
@@ -1707,6 +1709,7 @@ function vim.fn.exepath(expr) end
--- echo exists("*strftime")
--- echo exists("*s:MyFunc")
--- echo exists("*MyFunc")
+--- echo exists("*v:lua.Func")
--- echo exists("bufcount")
--- echo exists(":Make")
--- echo exists("#CursorHold")
@@ -1841,7 +1844,13 @@ function vim.fn.exp(expr) end
---
--- @param string string
--- @param nosuf? boolean
---- @param list? any
+--- @param list? nil|false
+--- @return string
+function vim.fn.expand(string, nosuf, list) end
+
+--- @param string string
+--- @param nosuf boolean
+--- @param list true|number|string|table
--- @return string|string[]
function vim.fn.expand(string, nosuf, list) end
@@ -2300,6 +2309,45 @@ function vim.fn.foldtext() end
--- @return string
function vim.fn.foldtextresult(lnum) end
+--- {expr1} must be a |List|, |String|, |Blob| or |Dictionary|.
+--- For each item in {expr1} execute {expr2}. {expr1} is not
+--- modified; its values may be, as with |:lockvar| 1. |E741|
+--- See |map()| and |filter()| to modify {expr1}.
+---
+--- {expr2} must be a |string| or |Funcref|.
+---
+--- If {expr2} is a |string|, inside {expr2} |v:val| has the value
+--- of the current item. For a |Dictionary| |v:key| has the key
+--- of the current item and for a |List| |v:key| has the index of
+--- the current item. For a |Blob| |v:key| has the index of the
+--- current byte. For a |String| |v:key| has the index of the
+--- current character.
+--- Examples: >vim
+--- call foreach(mylist, 'let used[v:val] = v:true')
+--- <This records the items that are in the {expr1} list.
+---
+--- Note that {expr2} is the result of expression and is then used
+--- as a command. Often it is good to use a |literal-string| to
+--- avoid having to double backslashes.
+---
+--- If {expr2} is a |Funcref| it must take two arguments:
+--- 1. the key or the index of the current item.
+--- 2. the value of the current item.
+--- With a lambda you don't get an error if it only accepts one
+--- argument.
+--- If the function returns a value, it is ignored.
+---
+--- Returns {expr1} in all cases.
+--- When an error is encountered while executing {expr2} no
+--- further items in {expr1} are processed.
+--- When {expr2} is a Funcref errors inside a function are ignored,
+--- unless it was defined with the "abort" flag.
+---
+--- @param expr1 any
+--- @param expr2 any
+--- @return any
+function vim.fn.foreach(expr1, expr2) end
+
--- Get the full command name from a short abbreviated command
--- name; see |20.2| for details on command abbreviations.
---
@@ -2514,6 +2562,8 @@ function vim.fn.getbufinfo(buf) end
--- bufnr Buffer number.
--- changed TRUE if the buffer is modified.
--- changedtick Number of changes made to the buffer.
+--- command TRUE if the buffer belongs to the
+--- command-line window |cmdwin|.
--- hidden TRUE if the buffer is hidden.
--- lastused Timestamp in seconds, like
--- |localtime()|, when the buffer was
@@ -2729,7 +2779,7 @@ function vim.fn.getchar() end
--- 32 mouse double click
--- 64 mouse triple click
--- 96 mouse quadruple click (== 32 + 64)
---- 128 command (Macintosh only)
+--- 128 command (Mac) or super
--- Only the modifiers that have not been included in the
--- character itself are obtained. Thus Shift-a results in "A"
--- without a modifier. Returns 0 if no modifiers are used.
@@ -2887,6 +2937,7 @@ function vim.fn.getcmdwintype() end
--- help help subjects
--- highlight highlight groups
--- history |:history| suboptions
+--- keymap keyboard mappings
--- locale locale names (as output of locale -a)
--- mapclear buffer argument
--- mapping mapping name
@@ -3134,8 +3185,13 @@ function vim.fn.getjumplist(winnr, tabnr) end
--- <To get lines from another buffer see |getbufline()| and
--- |getbufoneline()|
---
+--- @param lnum integer|string
+--- @param end_? nil|false
+--- @return string
+function vim.fn.getline(lnum, end_) end
+
--- @param lnum integer
---- @param end_? any
+--- @param end_ true|number|string|table
--- @return string|string[]
function vim.fn.getline(lnum, end_) end
@@ -3433,7 +3489,12 @@ function vim.fn.getqflist(what) end
--- If {regname} is not specified, |v:register| is used.
---
--- @param regname? string
---- @param list? any
+--- @param list? nil|false
+--- @return string
+function vim.fn.getreg(regname, list) end
+
+--- @param regname string
+--- @param list true|number|string|table
--- @return string|string[]
function vim.fn.getreg(regname, list) end
@@ -3464,6 +3525,62 @@ function vim.fn.getreg(regname, list) end
--- @return table
function vim.fn.getreginfo(regname) end
+--- 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. 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 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
+--- (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 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 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>
+--- \ <Cmd>echom getregion(
+--- \ getpos('v'), getpos('.'), #{ type: mode() })<CR>
+--- <
+---
+--- @param pos1 table
+--- @param pos2 table
+--- @param opts? table
+--- @return string[]
+function vim.fn.getregion(pos1, pos2, opts) end
+
--- The result is a String, which is type of register {regname}.
--- The value will be one of:
--- "v" for |charwise| text
@@ -4135,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
@@ -4678,8 +4795,7 @@ function vim.fn.join(list, sep) end
--- Vim value. In the following cases it will output
--- |msgpack-special-dict|:
--- 1. Dictionary contains duplicate key.
---- 2. Dictionary contains empty key.
---- 3. String contains NUL byte. Two special dictionaries: for
+--- 2. String contains NUL byte. Two special dictionaries: for
--- dictionary and for string will be emitted in case string
--- with NUL byte was a dictionary key.
---
@@ -5065,7 +5181,14 @@ function vim.fn.map(expr1, expr2) end
--- @param name string
--- @param mode? string
--- @param abbr? boolean
---- @param dict? boolean
+--- @param dict? false
+--- @return string
+function vim.fn.maparg(name, mode, abbr, dict) end
+
+--- @param name string
+--- @param mode string
+--- @param abbr boolean
+--- @param dict true
--- @return string|table<string,any>
function vim.fn.maparg(name, mode, abbr, dict) end
@@ -5150,6 +5273,12 @@ function vim.fn.maplist() end
--- @return any
function vim.fn.mapnew(expr1, expr2) end
+--- @param mode string
+--- @param abbr? any
+--- @param dict? any
+--- @return any
+function vim.fn.mapset(mode, abbr, dict) end
+
--- Restore a mapping from a dictionary, possibly returned by
--- |maparg()| or |maplist()|. A buffer mapping, when dict.buffer
--- is true, is set on the current buffer; it is up to the caller
@@ -5185,11 +5314,9 @@ function vim.fn.mapnew(expr1, expr2) end
--- call mapset(d)
--- endfor
---
---- @param mode string
---- @param abbr? any
---- @param dict? any
+--- @param dict any
--- @return any
-function vim.fn.mapset(mode, abbr, dict) end
+function vim.fn.mapset(dict) end
--- When {expr} is a |List| then this returns the index of the
--- first item where {pat} matches. Each item is used as a
@@ -5243,6 +5370,7 @@ function vim.fn.mapset(mode, abbr, dict) end
--- Note that when {count} is added the way {start} works changes,
--- see above.
---
+--- *match-pattern*
--- See |pattern| for the patterns that are accepted.
--- The 'ignorecase' option is used to set the ignore-caseness of
--- the pattern. 'smartcase' is NOT used. The matching is always
@@ -5383,6 +5511,57 @@ function vim.fn.matchaddpos(group, pos, priority, id, dict) end
--- @return any
function vim.fn.matcharg(nr) end
+--- Returns the |List| of matches in lines from {lnum} to {end} in
+--- buffer {buf} where {pat} matches.
+---
+--- {lnum} and {end} can either be a line number or the string "$"
+--- to refer to the last line in {buf}.
+---
+--- The {dict} argument supports following items:
+--- submatches include submatch information (|/\(|)
+---
+--- For each match, a |Dict| with the following items is returned:
+--- byteidx starting byte index of the match
+--- lnum line number where there is a match
+--- text matched string
+--- Note that there can be multiple matches in a single line.
+---
+--- This function works only for loaded buffers. First call
+--- |bufload()| if needed.
+---
+--- See |match-pattern| for information about the effect of some
+--- option settings on the pattern.
+---
+--- When {buf} is not a valid buffer, the buffer is not loaded or
+--- {lnum} or {end} is not valid then an error is given and an
+--- empty |List| is returned.
+---
+--- Examples: >vim
+--- " Assuming line 3 in buffer 5 contains "a"
+--- :echo matchbufline(5, '\<\k\+\>', 3, 3)
+--- [{'lnum': 3, 'byteidx': 0, 'text': 'a'}]
+--- " Assuming line 4 in buffer 10 contains "tik tok"
+--- :echo matchbufline(10, '\<\k\+\>', 1, 4)
+--- [{'lnum': 4, 'byteidx': 0, 'text': 'tik'}, {'lnum': 4, 'byteidx': 4, 'text': 'tok'}]
+--- <
+--- If {submatch} is present and is v:true, then submatches like
+--- "\1", "\2", etc. are also returned. Example: >vim
+--- " Assuming line 2 in buffer 2 contains "acd"
+--- :echo matchbufline(2, '\(a\)\?\(b\)\?\(c\)\?\(.*\)', 2, 2
+--- \ {'submatches': v:true})
+--- [{'lnum': 2, 'byteidx': 0, 'text': 'acd', 'submatches': ['a', '', 'c', 'd', '', '', '', '', '']}]
+--- <The "submatches" List always contains 9 items. If a submatch
+--- is not found, then an empty string is returned for that
+--- submatch.
+---
+--- @param buf string|integer
+--- @param pat string
+--- @param lnum string|integer
+--- @param end_ string|integer
+--- @param dict? table
+--- @return any
+function vim.fn.matchbufline(buf, pat, lnum, end_, dict) end
+
--- Deletes a match with ID {id} previously defined by |matchadd()|
--- or one of the |:match| commands. Returns 0 if successful,
--- otherwise -1. See example for |matchadd()|. All matches can
@@ -5552,6 +5731,44 @@ function vim.fn.matchlist(expr, pat, start, count) end
--- @return any
function vim.fn.matchstr(expr, pat, start, count) end
+--- Returns the |List| of matches in {list} where {pat} matches.
+--- {list} is a |List| of strings. {pat} is matched against each
+--- string in {list}.
+---
+--- The {dict} argument supports following items:
+--- submatches include submatch information (|/\(|)
+---
+--- For each match, a |Dict| with the following items is returned:
+--- byteidx starting byte index of the match.
+--- idx index in {list} of the match.
+--- text matched string
+--- submatches a List of submatches. Present only if
+--- "submatches" is set to v:true in {dict}.
+---
+--- See |match-pattern| for information about the effect of some
+--- option settings on the pattern.
+---
+--- Example: >vim
+--- :echo matchstrlist(['tik tok'], '\<\k\+\>')
+--- [{'idx': 0, 'byteidx': 0, 'text': 'tik'}, {'idx': 0, 'byteidx': 4, 'text': 'tok'}]
+--- :echo matchstrlist(['a', 'b'], '\<\k\+\>')
+--- [{'idx': 0, 'byteidx': 0, 'text': 'a'}, {'idx': 1, 'byteidx': 0, 'text': 'b'}]
+--- <
+--- If "submatches" is present and is v:true, then submatches like
+--- "\1", "\2", etc. are also returned. Example: >vim
+--- :echo matchstrlist(['acd'], '\(a\)\?\(b\)\?\(c\)\?\(.*\)',
+--- \ #{submatches: v:true})
+--- [{'idx': 0, 'byteidx': 0, 'text': 'acd', 'submatches': ['a', '', 'c', 'd', '', '', '', '', '']}]
+--- <The "submatches" List always contains 9 items. If a submatch
+--- is not found, then an empty string is returned for that
+--- submatch.
+---
+--- @param list string[]
+--- @param pat string
+--- @param dict? table
+--- @return any
+function vim.fn.matchstrlist(list, pat, dict) end
+
--- Same as |matchstr()|, but return the matched string, the start
--- position and the end position of the match. Example: >vim
--- echo matchstrpos("testing", "ing")
@@ -5583,7 +5800,7 @@ function vim.fn.matchstrpos(expr, pat, start, count) end
--- it returns the maximum of all values in the Dictionary.
--- If {expr} is neither a List nor a Dictionary, or one of the
--- items in {expr} cannot be used as a Number this results in
---- an error. An empty |List| or |Dictionary| results in zero.
+--- an error. An empty |List| or |Dictionary| results in zero.
---
--- @param expr any
--- @return any
@@ -5828,8 +6045,9 @@ function vim.fn.mkdir(name, flags, prot) end
--- the leading character(s).
--- Also see |visualmode()|.
---
+--- @param expr? any
--- @return any
-function vim.fn.mode() end
+function vim.fn.mode(expr) end
--- Convert a list of Vimscript objects to msgpack. Returned value is a
--- |readfile()|-style list. When {type} contains "B", a |Blob| is
@@ -5922,7 +6140,6 @@ function vim.fn.msgpackdump(list, type) end
--- are binary strings).
--- 2. String with NUL byte inside.
--- 3. Duplicate key.
---- 4. Empty key.
--- ext |List| with two values: first is a signed integer
--- representing extension type. Second is
--- |readfile()|-style list of strings.
@@ -6158,9 +6375,9 @@ function vim.fn.prevnonblank(lnum) end
--- <This limits the length of the text used from "line" to
--- "width" bytes.
---
---- If the argument to be formatted is specified using a posional
---- argument specifier, and a '*' is used to indicate that a
---- number argument is to be used to specify the width or
+--- If the argument to be formatted is specified using a
+--- positional argument specifier, and a '*' is used to indicate
+--- that a number argument is to be used to specify the width or
--- precision, the argument(s) to be used must also be specified
--- using a {n$} positional argument specifier. See |printf-$|.
---
@@ -7969,7 +8186,7 @@ function vim.fn.setqflist(list, action, what) end
---
--- @param regname string
--- @param value any
---- @param options? table
+--- @param options? string
--- @return any
function vim.fn.setreg(regname, value, options) end
@@ -9311,7 +9528,12 @@ function vim.fn.strwidth(string) end
--- A line break is included as a newline character.
---
--- @param nr integer
---- @param list? integer
+--- @param list? nil
+--- @return string
+function vim.fn.submatch(nr, list) end
+
+--- @param nr integer
+--- @param list integer
--- @return string|string[]
function vim.fn.submatch(nr, list) end
@@ -9527,7 +9749,7 @@ function vim.fn.synIDtrans(synID) end
---
--- @param lnum integer
--- @param col integer
---- @return {[1]: integer, [2]: string, [3]: integer}[]
+--- @return {[1]: integer, [2]: string, [3]: integer}
function vim.fn.synconcealed(lnum, col) end
--- Return a |List|, which is the stack of syntax items at the
diff --git a/runtime/lua/vim/_meta/vvars.lua b/runtime/lua/vim/_meta/vvars.lua
new file mode 100644
index 0000000000..ee6d8ddf35
--- /dev/null
+++ b/runtime/lua/vim/_meta/vvars.lua
@@ -0,0 +1,779 @@
+--- @meta _
+-- THIS FILE IS GENERATED
+-- DO NOT EDIT
+error('Cannot require a meta file')
+
+--- @class vim.v
+vim.v = ...
+
+--- The command line arguments Vim was invoked with. This is a
+--- list of strings. The first item is the Vim command.
+--- See `v:progpath` for the command with full path.
+--- @type string[]
+vim.v.argv = ...
+
+--- Argument for evaluating 'formatexpr' and used for the typed
+--- character when using <expr> in an abbreviation `:map-<expr>`.
+--- It is also used by the `InsertCharPre` and `InsertEnter` events.
+--- @type any
+vim.v.char = ...
+
+--- The name of the character encoding of a file to be converted.
+--- Only valid while evaluating the 'charconvert' option.
+--- @type string
+vim.v.charconvert_from = ...
+
+--- The name of the character encoding of a file after conversion.
+--- Only valid while evaluating the 'charconvert' option.
+--- @type string
+vim.v.charconvert_to = ...
+
+--- The extra arguments ("++p", "++enc=", "++ff=") given to a file
+--- read/write command. This is set before an autocommand event
+--- for a file read/write command is triggered. There is a
+--- leading space to make it possible to append this variable
+--- directly after the read/write command. Note: "+cmd" isn't
+--- included here, because it will be executed anyway.
+--- @type string
+vim.v.cmdarg = ...
+
+--- Set like v:cmdarg for a file read/write command. When a "!"
+--- was used the value is 1, otherwise it is 0. Note that this
+--- can only be used in autocommands. For user commands `<bang>`
+--- can be used.
+--- @type integer
+vim.v.cmdbang = ...
+
+--- The current locale setting for collation order of the runtime
+--- environment. This allows Vim scripts to be aware of the
+--- current locale encoding. Technical: it's the value of
+--- LC_COLLATE. When not using a locale the value is "C".
+--- This variable can not be set directly, use the `:language`
+--- command.
+--- See `multi-lang`.
+--- @type string
+vim.v.collate = ...
+
+--- Dictionary containing the most recent `complete-items` after
+--- `CompleteDone`. Empty if the completion failed, or after
+--- leaving and re-entering insert mode.
+--- Note: Plugins can modify the value to emulate the builtin
+--- `CompleteDone` event behavior.
+--- @type any
+vim.v.completed_item = ...
+
+--- The count given for the last Normal mode command. Can be used
+--- to get the count before a mapping. Read-only. Example:
+---
+--- ```vim
+--- :map _x :<C-U>echo "the count is " .. v:count<CR>
+--- ```
+---
+--- Note: The <C-U> is required to remove the line range that you
+--- get when typing ':' after a count.
+--- When there are two counts, as in "3d2w", they are multiplied,
+--- just like what happens in the command, "d6w" for the example.
+--- Also used for evaluating the 'formatexpr' option.
+--- @type integer
+vim.v.count = ...
+
+--- Just like "v:count", but defaults to one when no count is
+--- used.
+--- @type integer
+vim.v.count1 = ...
+
+--- The current locale setting for characters of the runtime
+--- environment. This allows Vim scripts to be aware of the
+--- current locale encoding. Technical: it's the value of
+--- LC_CTYPE. When not using a locale the value is "C".
+--- This variable can not be set directly, use the `:language`
+--- command.
+--- See `multi-lang`.
+--- @type any
+vim.v.ctype = ...
+
+--- Normally zero. When a deadly signal is caught it's set to
+--- one. When multiple signals are caught the number increases.
+--- Can be used in an autocommand to check if Vim didn't
+--- terminate normally.
+--- Example:
+---
+--- ```vim
+--- :au VimLeave * if v:dying | echo "\nAAAAaaaarrrggghhhh!!!\n" | endif
+--- ```
+---
+--- Note: if another deadly signal is caught when v:dying is one,
+--- VimLeave autocommands will not be executed.
+--- @type integer
+vim.v.dying = ...
+
+--- Number of screen cells that can be used for an `:echo` message
+--- in the last screen line before causing the `hit-enter-prompt`.
+--- Depends on 'showcmd', 'ruler' and 'columns'. You need to
+--- check 'cmdheight' for whether there are full-width lines
+--- available above the last line.
+--- @type integer
+vim.v.echospace = ...
+
+--- Last given error message.
+--- Modifiable (can be set).
+--- Example:
+---
+--- ```vim
+--- let v:errmsg = ""
+--- silent! next
+--- if v:errmsg != ""
+--- " ... handle error
+--- ```
+--- @type string
+vim.v.errmsg = ...
+
+--- Errors found by assert functions, such as `assert_true()`.
+--- This is a list of strings.
+--- The assert functions append an item when an assert fails.
+--- The return value indicates this: a one is returned if an item
+--- was added to v:errors, otherwise zero is returned.
+--- To remove old results make it empty:
+---
+--- ```vim
+--- let v:errors = []
+--- ```
+---
+--- If v:errors is set to anything but a list it is made an empty
+--- list by the assert function.
+--- @type string[]
+vim.v.errors = ...
+
+--- Dictionary of event data for the current `autocommand`. Valid
+--- only during the event lifetime; storing or passing v:event is
+--- invalid! Copy it instead:
+---
+--- ```vim
+--- au TextYankPost * let g:foo = deepcopy(v:event)
+--- ```
+---
+--- Keys vary by event; see the documentation for the specific
+--- event, e.g. `DirChanged` or `TextYankPost`.
+--- KEY DESCRIPTION ~
+--- abort Whether the event triggered during
+--- an aborting condition (e.g. `c_Esc` or
+--- `c_CTRL-C` for `CmdlineLeave`).
+--- chan `channel-id`
+--- cmdlevel Level of cmdline.
+--- cmdtype Type of cmdline, `cmdline-char`.
+--- cwd Current working directory.
+--- inclusive Motion is `inclusive`, else exclusive.
+--- scope Event-specific scope name.
+--- operator Current `operator`. Also set for Ex
+--- commands (unlike `v:operator`). For
+--- example if `TextYankPost` is triggered
+--- by the `:yank` Ex command then
+--- `v:event.operator` is "y".
+--- regcontents Text stored in the register as a
+--- `readfile()`-style list of lines.
+--- regname Requested register (e.g "x" for "xyy)
+--- or the empty string for an unnamed
+--- operation.
+--- regtype Type of register as returned by
+--- `getregtype()`.
+--- visual Selection is visual (as opposed to,
+--- e.g., via motion).
+--- completed_item Current selected complete item on
+--- `CompleteChanged`, Is `{}` when no complete
+--- item selected.
+--- height Height of popup menu on `CompleteChanged`
+--- width Width of popup menu on `CompleteChanged`
+--- row Row count of popup menu on `CompleteChanged`,
+--- relative to screen.
+--- col Col count of popup menu on `CompleteChanged`,
+--- relative to screen.
+--- size Total number of completion items on
+--- `CompleteChanged`.
+--- scrollbar Is `v:true` if popup menu have scrollbar, or
+--- `v:false` if not.
+--- changed_window Is `v:true` if the event fired while
+--- changing window (or tab) on `DirChanged`.
+--- status Job status or exit code, -1 means "unknown". `TermClose`
+--- @type any
+vim.v.event = ...
+
+--- The value of the exception most recently caught and not
+--- finished. See also `v:throwpoint` and `throw-variables`.
+--- Example:
+---
+--- ```vim
+--- try
+--- throw "oops"
+--- catch /.*/
+--- echo "caught " .. v:exception
+--- endtry
+--- ```
+---
+--- Output: "caught oops".
+--- @type string
+vim.v.exception = ...
+
+--- Exit code, or `v:null` before invoking the `VimLeavePre`
+--- and `VimLeave` autocmds. See `:q`, `:x` and `:cquit`.
+--- Example:
+---
+--- ```vim
+--- :au VimLeave * echo "Exit value is " .. v:exiting
+--- ```
+--- @type any
+vim.v.exiting = ...
+
+--- Special value used to put "false" in JSON and msgpack. See
+--- `json_encode()`. This value is converted to "v:false" when used
+--- as a String (e.g. in `expr5` with string concatenation
+--- operator) and to zero when used as a Number (e.g. in `expr5`
+--- or `expr7` when used with numeric operators). Read-only.
+--- @type boolean
+vim.v['false'] = ...
+
+--- What should happen after a `FileChangedShell` event was
+--- triggered. Can be used in an autocommand to tell Vim what to
+--- do with the affected buffer:
+--- reload Reload the buffer (does not work if
+--- the file was deleted).
+--- edit Reload the buffer and detect the
+--- values for options such as
+--- 'fileformat', 'fileencoding', 'binary'
+--- (does not work if the file was
+--- deleted).
+--- ask Ask the user what to do, as if there
+--- was no autocommand. Except that when
+--- only the timestamp changed nothing
+--- will happen.
+--- <empty> Nothing, the autocommand should do
+--- everything that needs to be done.
+--- The default is empty. If another (invalid) value is used then
+--- Vim behaves like it is empty, there is no warning message.
+--- @type string
+vim.v.fcs_choice = ...
+
+--- The reason why the `FileChangedShell` event was triggered.
+--- Can be used in an autocommand to decide what to do and/or what
+--- to set v:fcs_choice to. Possible values:
+--- deleted file no longer exists
+--- conflict file contents, mode or timestamp was
+--- changed and buffer is modified
+--- changed file contents has changed
+--- mode mode of file changed
+--- time only file timestamp changed
+--- @type string
+vim.v.fcs_reason = ...
+
+--- When evaluating 'includeexpr': the file name that was
+--- detected. Empty otherwise.
+--- @type string
+vim.v.fname = ...
+
+--- The name of the diff (patch) file. Only valid while
+--- evaluating 'patchexpr'.
+--- @type string
+vim.v.fname_diff = ...
+
+--- The name of the input file. Valid while evaluating:
+--- option used for ~
+--- 'charconvert' file to be converted
+--- 'diffexpr' original file
+--- 'patchexpr' original file
+--- And set to the swap file name for `SwapExists`.
+--- @type string
+vim.v.fname_in = ...
+
+--- The name of the new version of the file. Only valid while
+--- evaluating 'diffexpr'.
+--- @type string
+vim.v.fname_new = ...
+
+--- The name of the output file. Only valid while
+--- evaluating:
+--- option used for ~
+--- 'charconvert' resulting converted file [1]
+--- 'diffexpr' output of diff
+--- 'patchexpr' resulting patched file
+--- [1] When doing conversion for a write command (e.g., ":w
+--- file") it will be equal to v:fname_in. When doing conversion
+--- for a read command (e.g., ":e file") it will be a temporary
+--- file and different from v:fname_in.
+--- @type string
+vim.v.fname_out = ...
+
+--- Used for 'foldtext': dashes representing foldlevel of a closed
+--- fold.
+--- Read-only in the `sandbox`. `fold-foldtext`
+--- @type string
+vim.v.folddashes = ...
+
+--- Used for 'foldtext': last line of closed fold.
+--- Read-only in the `sandbox`. `fold-foldtext`
+--- @type integer
+vim.v.foldend = ...
+
+--- Used for 'foldtext': foldlevel of closed fold.
+--- Read-only in the `sandbox`. `fold-foldtext`
+--- @type integer
+vim.v.foldlevel = ...
+
+--- Used for 'foldtext': first line of closed fold.
+--- Read-only in the `sandbox`. `fold-foldtext`
+--- @type integer
+vim.v.foldstart = ...
+
+--- Variable that indicates whether search highlighting is on.
+--- Setting it makes sense only if 'hlsearch' is enabled. Setting
+--- this variable to zero acts like the `:nohlsearch` command,
+--- setting it to one acts like
+---
+--- ```vim
+--- let &hlsearch = &hlsearch
+--- ```
+---
+--- Note that the value is restored when returning from a
+--- function. `function-search-undo`.
+--- @type integer
+vim.v.hlsearch = ...
+
+--- Used for the `InsertEnter` and `InsertChange` autocommand
+--- events. Values:
+--- i Insert mode
+--- r Replace mode
+--- v Virtual Replace mode
+--- @type string
+vim.v.insertmode = ...
+
+--- Key of the current item of a `Dictionary`. Only valid while
+--- evaluating the expression used with `map()` and `filter()`.
+--- Read-only.
+--- @type string
+vim.v.key = ...
+
+--- The current locale setting for messages of the runtime
+--- environment. This allows Vim scripts to be aware of the
+--- current language. Technical: it's the value of LC_MESSAGES.
+--- The value is system dependent.
+--- This variable can not be set directly, use the `:language`
+--- command.
+--- It can be different from `v:ctype` when messages are desired
+--- in a different language than what is used for character
+--- encoding. See `multi-lang`.
+--- @type string
+vim.v.lang = ...
+
+--- The current locale setting for time messages of the runtime
+--- environment. This allows Vim scripts to be aware of the
+--- current language. Technical: it's the value of LC_TIME.
+--- This variable can not be set directly, use the `:language`
+--- command. See `multi-lang`.
+--- @type string
+vim.v.lc_time = ...
+
+--- Line number for the 'foldexpr' `fold-expr`, 'formatexpr',
+--- 'indentexpr' and 'statuscolumn' expressions, tab page number
+--- for 'guitablabel' and 'guitabtooltip'. Only valid while one of
+--- these expressions is being evaluated. Read-only when in the
+--- `sandbox`.
+--- @type integer
+vim.v.lnum = ...
+
+--- Prefix for calling Lua functions from expressions.
+--- See `v:lua-call` for more information.
+--- @type any
+vim.v.lua = ...
+
+--- Maximum line length. Depending on where it is used it can be
+--- screen columns, characters or bytes. The value currently is
+--- 2147483647 on all systems.
+--- @type integer
+vim.v.maxcol = ...
+
+--- Column number for a mouse click obtained with `getchar()`.
+--- This is the screen column number, like with `virtcol()`. The
+--- value is zero when there was no mouse button click.
+--- @type integer
+vim.v.mouse_col = ...
+
+--- Line number for a mouse click obtained with `getchar()`.
+--- This is the text line number, not the screen line number. The
+--- value is zero when there was no mouse button click.
+--- @type integer
+vim.v.mouse_lnum = ...
+
+--- Window number for a mouse click obtained with `getchar()`.
+--- First window has number 1, like with `winnr()`. The value is
+--- zero when there was no mouse button click.
+--- @type integer
+vim.v.mouse_win = ...
+
+--- `window-ID` for a mouse click obtained with `getchar()`.
+--- The value is zero when there was no mouse button click.
+--- @type integer
+vim.v.mouse_winid = ...
+
+--- Dictionary containing msgpack types used by `msgpackparse()`
+--- and `msgpackdump()`. All types inside dictionary are fixed
+--- (not editable) empty lists. To check whether some list is one
+--- of msgpack types, use `is` operator.
+--- @type any
+vim.v.msgpack_types = ...
+
+--- Special value used to put "null" in JSON and NIL in msgpack.
+--- See `json_encode()`. This value is converted to "v:null" when
+--- used as a String (e.g. in `expr5` with string concatenation
+--- operator) and to zero when used as a Number (e.g. in `expr5`
+--- or `expr7` when used with numeric operators). Read-only.
+--- In some places `v:null` can be used for a List, Dict, etc.
+--- that is not set. That is slightly different than an empty
+--- List, Dict, etc.
+--- @type vim.NIL
+vim.v.null = ...
+
+--- Maximum value of a number.
+--- @type integer
+vim.v.numbermax = ...
+
+--- Minimum value of a number (negative).
+--- @type integer
+vim.v.numbermin = ...
+
+--- Number of bits in a Number. This is normally 64, but on some
+--- systems it may be 32.
+--- @type integer
+vim.v.numbersize = ...
+
+--- List of file names that is loaded from the `shada` file on
+--- startup. These are the files that Vim remembers marks for.
+--- The length of the List is limited by the ' argument of the
+--- 'shada' option (default is 100).
+--- When the `shada` file is not used the List is empty.
+--- Also see `:oldfiles` and `c_#<`.
+--- The List can be modified, but this has no effect on what is
+--- stored in the `shada` file later. If you use values other
+--- than String this will cause trouble.
+--- @type string[]
+vim.v.oldfiles = ...
+
+--- The last operator given in Normal mode. This is a single
+--- character except for commands starting with <g> or <z>,
+--- in which case it is two characters. Best used alongside
+--- `v:prevcount` and `v:register`. Useful if you want to cancel
+--- Operator-pending mode and then use the operator, e.g.:
+---
+--- ```vim
+--- :omap O <Esc>:call MyMotion(v:operator)<CR>
+--- ```
+---
+--- The value remains set until another operator is entered, thus
+--- don't expect it to be empty.
+--- v:operator is not set for `:delete`, `:yank` or other Ex
+--- commands.
+--- Read-only.
+--- @type string
+vim.v.operator = ...
+
+--- Command used to set the option. Valid while executing an
+--- `OptionSet` autocommand.
+--- value option was set via ~
+--- "setlocal" `:setlocal` or `:let l:xxx`
+--- "setglobal" `:setglobal` or `:let g:xxx`
+--- "set" `:set` or `:let`
+--- "modeline" `modeline`
+--- @type string
+vim.v.option_command = ...
+
+--- New value of the option. Valid while executing an `OptionSet`
+--- autocommand.
+--- @type any
+vim.v.option_new = ...
+
+--- Old value of the option. Valid while executing an `OptionSet`
+--- autocommand. Depending on the command used for setting and the
+--- kind of option this is either the local old value or the
+--- global old value.
+--- @type any
+vim.v.option_old = ...
+
+--- Old global value of the option. Valid while executing an
+--- `OptionSet` autocommand.
+--- @type any
+vim.v.option_oldglobal = ...
+
+--- Old local value of the option. Valid while executing an
+--- `OptionSet` autocommand.
+--- @type any
+vim.v.option_oldlocal = ...
+
+--- Scope of the set command. Valid while executing an
+--- `OptionSet` autocommand. Can be either "global" or "local"
+--- @type string
+vim.v.option_type = ...
+
+--- The count given for the last but one Normal mode command.
+--- This is the v:count value of the previous command. Useful if
+--- you want to cancel Visual or Operator-pending mode and then
+--- use the count, e.g.:
+---
+--- ```vim
+--- :vmap % <Esc>:call MyFilter(v:prevcount)<CR>
+--- ```
+---
+--- Read-only.
+--- @type integer
+vim.v.prevcount = ...
+
+--- Normally zero. Set to one after using ":profile start".
+--- See `profiling`.
+--- @type integer
+vim.v.profiling = ...
+
+--- The name by which Nvim was invoked (with path removed).
+--- Read-only.
+--- @type string
+vim.v.progname = ...
+
+--- Absolute path to the current running Nvim.
+--- Read-only.
+--- @type string
+vim.v.progpath = ...
+
+--- The name of the register in effect for the current normal mode
+--- command (regardless of whether that command actually used a
+--- register). Or for the currently executing normal mode mapping
+--- (use this in custom commands that take a register).
+--- If none is supplied it is the default register '"', unless
+--- 'clipboard' contains "unnamed" or "unnamedplus", then it is
+--- "*" or '+'.
+--- Also see `getreg()` and `setreg()`
+--- @type string
+vim.v.register = ...
+
+--- Relative line number for the 'statuscolumn' expression.
+--- Read-only.
+--- @type integer
+vim.v.relnum = ...
+
+--- String describing the script or function that caused the
+--- screen to scroll up. It's only set when it is empty, thus the
+--- first reason is remembered. It is set to "Unknown" for a
+--- typed command.
+--- This can be used to find out why your script causes the
+--- hit-enter prompt.
+--- @type any
+vim.v.scrollstart = ...
+
+--- Search direction: 1 after a forward search, 0 after a
+--- backward search. It is reset to forward when directly setting
+--- the last search pattern, see `quote/`.
+--- Note that the value is restored when returning from a
+--- function. `function-search-undo`.
+--- Read-write.
+--- @type integer
+vim.v.searchforward = ...
+
+--- Primary listen-address of Nvim, the first item returned by
+--- `serverlist()`. Usually this is the named pipe created by Nvim
+--- at `startup` or given by `--listen` (or the deprecated
+--- `$NVIM_LISTEN_ADDRESS` env var).
+---
+--- See also `serverstart()` `serverstop()`.
+--- Read-only.
+---
+--- *$NVIM*
+--- $NVIM is set by `terminal` and `jobstart()`, and is thus
+--- a hint that the current environment is a subprocess of Nvim.
+--- Example:
+---
+--- ```vim
+--- if $NVIM
+--- echo nvim_get_chan_info(v:parent)
+--- endif
+--- ```
+---
+--- Note the contents of $NVIM may change in the future.
+--- @type string
+vim.v.servername = ...
+
+--- Result of the last shell command. When non-zero, the last
+--- shell command had an error. When zero, there was no problem.
+--- This only works when the shell returns the error code to Vim.
+--- The value -1 is often used when the command could not be
+--- executed. Read-only.
+--- Example:
+---
+--- ```vim
+--- !mv foo bar
+--- if v:shell_error
+--- echo 'could not rename "foo" to "bar"!'
+--- endif
+--- ```
+--- @type integer
+vim.v.shell_error = ...
+
+--- Last given status message.
+--- Modifiable (can be set).
+--- @type string
+vim.v.statusmsg = ...
+
+--- `channel-id` corresponding to stderr. The value is always 2;
+--- use this variable to make your code more descriptive.
+--- Unlike stdin and stdout (see `stdioopen()`), stderr is always
+--- open for writing. Example:
+---
+--- ```vim
+--- :call chansend(v:stderr, "error: toaster empty\n")
+--- ```
+--- @type integer
+vim.v.stderr = ...
+
+--- `SwapExists` autocommands can set this to the selected choice
+--- for handling an existing swapfile:
+--- 'o' Open read-only
+--- 'e' Edit anyway
+--- 'r' Recover
+--- 'd' Delete swapfile
+--- 'q' Quit
+--- 'a' Abort
+--- The value should be a single-character string. An empty value
+--- results in the user being asked, as would happen when there is
+--- no SwapExists autocommand. The default is empty.
+--- @type string
+vim.v.swapchoice = ...
+
+--- Normal mode command to be executed after a file has been
+--- opened. Can be used for a `SwapExists` autocommand to have
+--- another Vim open the file and jump to the right place. For
+--- example, when jumping to a tag the value is ":tag tagname\r".
+--- For ":edit +cmd file" the value is ":cmd\r".
+--- @type string
+vim.v.swapcommand = ...
+
+--- Name of the swapfile found.
+--- Only valid during `SwapExists` event.
+--- Read-only.
+--- @type string
+vim.v.swapname = ...
+
+--- Value of `Blob` type. Read-only. See: `type()`
+--- @type integer
+vim.v.t_blob = ...
+
+--- Value of `Boolean` type. Read-only. See: `type()`
+--- @type integer
+vim.v.t_bool = ...
+
+--- Value of `Dictionary` type. Read-only. See: `type()`
+--- @type integer
+vim.v.t_dict = ...
+
+--- Value of `Float` type. Read-only. See: `type()`
+--- @type integer
+vim.v.t_float = ...
+
+--- Value of `Funcref` type. Read-only. See: `type()`
+--- @type integer
+vim.v.t_func = ...
+
+--- Value of `List` type. Read-only. See: `type()`
+--- @type integer
+vim.v.t_list = ...
+
+--- Value of `Number` type. Read-only. See: `type()`
+--- @type integer
+vim.v.t_number = ...
+
+--- Value of `String` type. Read-only. See: `type()`
+--- @type integer
+vim.v.t_string = ...
+
+--- The value of the most recent OSC or DCS control sequence
+--- sent from a process running in the embedded `terminal`.
+--- This can be read in a `TermRequest` event handler to respond
+--- to queries from embedded applications.
+--- @type string
+vim.v.termrequest = ...
+
+--- The value of the most recent OSC or DCS control sequence
+--- received by Nvim from the terminal. This can be read in a
+--- `TermResponse` event handler after querying the terminal using
+--- another escape sequence.
+--- @type string
+vim.v.termresponse = ...
+
+--- Must be set before using `test_garbagecollect_now()`.
+--- @type any
+vim.v.testing = ...
+
+--- Full filename of the last loaded or saved session file.
+--- Empty when no session file has been saved. See `:mksession`.
+--- Modifiable (can be set).
+--- @type any
+vim.v.this_session = ...
+
+--- The point where the exception most recently caught and not
+--- finished was thrown. Not set when commands are typed. See
+--- also `v:exception` and `throw-variables`.
+--- Example:
+---
+--- ```vim
+--- try
+--- throw "oops"
+--- catch /.*/
+--- echo "Exception from" v:throwpoint
+--- endtry
+--- ```
+---
+--- Output: "Exception from test.vim, line 2"
+--- @type any
+vim.v.throwpoint = ...
+
+--- Special value used to put "true" in JSON and msgpack. See
+--- `json_encode()`. This value is converted to "v:true" when used
+--- as a String (e.g. in `expr5` with string concatenation
+--- operator) and to one when used as a Number (e.g. in `expr5` or
+--- `expr7` when used with numeric operators). Read-only.
+--- @type boolean
+vim.v['true'] = ...
+
+--- Value of the current item of a `List` or `Dictionary`. Only
+--- valid while evaluating the expression used with `map()` and
+--- `filter()`. Read-only.
+--- @type any
+vim.v.val = ...
+
+--- Vim version number: major version times 100 plus minor
+--- version. Vim 5.0 is 500, Vim 5.1 is 501.
+--- Read-only.
+--- Use `has()` to check the Nvim (not Vim) version:
+---
+--- ```vim
+--- :if has("nvim-0.2.1")
+--- ```
+--- @type integer
+vim.v.version = ...
+
+--- 0 during startup, 1 just before `VimEnter`.
+--- Read-only.
+--- @type integer
+vim.v.vim_did_enter = ...
+
+--- Virtual line number for the 'statuscolumn' expression.
+--- Negative when drawing the status column for virtual lines, zero
+--- when drawing an actual buffer line, and positive when drawing
+--- the wrapped part of a buffer line.
+--- Read-only.
+--- @type integer
+vim.v.virtnum = ...
+
+--- Last given warning message.
+--- Modifiable (can be set).
+--- @type string
+vim.v.warningmsg = ...
+
+--- Application-specific window "handle" which may be set by any
+--- attached UI. Defaults to zero.
+--- Note: For Nvim `windows` use `winnr()` or `win_getid()`, see
+--- `window-ID`.
+--- @type integer
+vim.v.windowid = ...
diff --git a/runtime/lua/vim/_options.lua b/runtime/lua/vim/_options.lua
index b83a8dd4b1..13ad6cc58f 100644
--- a/runtime/lua/vim/_options.lua
+++ b/runtime/lua/vim/_options.lua
@@ -1,12 +1,10 @@
----@defgroup lua-vimscript
+--- @brief Nvim Lua provides an interface or "bridge" to Vimscript variables and
+--- functions, and editor commands and options.
---
----@brief Nvim Lua provides an interface or "bridge" to Vimscript variables and
----functions, and editor commands and options.
----
----Objects passed over this bridge are COPIED (marshalled): there are no
----"references". |lua-guide-variables| For example, using \`vim.fn.remove()\` on
----a Lua list copies the list object to Vimscript and does NOT modify the Lua
----list:
+--- Objects passed over this bridge are COPIED (marshalled): there are no
+--- "references". |lua-guide-variables| For example, using `vim.fn.remove()` on
+--- a Lua list copies the list object to Vimscript and does NOT modify the Lua
+--- list:
---
--- ```lua
--- local list = { 1, 2, 3 }
@@ -14,86 +12,85 @@
--- vim.print(list) --> "{ 1, 2, 3 }"
--- ```
----@addtogroup lua-vimscript
----@brief <pre>help
----vim.call({func}, {...}) *vim.call()*
---- Invokes |vim-function| or |user-function| {func} with arguments {...}.
---- See also |vim.fn|.
---- Equivalent to: >lua
---- vim.fn[func]({...})
----<
----vim.cmd({command})
---- See |vim.cmd()|.
----
----vim.fn.{func}({...}) *vim.fn*
---- Invokes |vim-function| or |user-function| {func} with arguments {...}.
---- To call autoload functions, use the syntax: >lua
---- vim.fn['some\#function']({...})
----<
---- Unlike vim.api.|nvim_call_function()| this converts directly between Vim
---- objects and Lua objects. If the Vim function returns a float, it will be
---- represented directly as a Lua number. Empty lists and dictionaries both
---- are represented by an empty table.
----
---- Note: |v:null| values as part of the return value is represented as
---- |vim.NIL| special value
----
---- Note: vim.fn keys are generated lazily, thus `pairs(vim.fn)` only
---- enumerates functions that were called at least once.
----
---- Note: The majority of functions cannot run in |api-fast| callbacks with some
---- undocumented exceptions which are allowed.
----
---- *lua-vim-variables*
----The Vim editor global dictionaries |g:| |w:| |b:| |t:| |v:| can be accessed
----from Lua conveniently and idiomatically by referencing the `vim.*` Lua tables
----described below. In this way you can easily read and modify global Vimscript
----variables from Lua.
----
----Example: >lua
----
---- vim.g.foo = 5 -- Set the g:foo Vimscript variable.
---- print(vim.g.foo) -- Get and print the g:foo Vimscript variable.
---- vim.g.foo = nil -- Delete (:unlet) the Vimscript variable.
---- vim.b[2].foo = 6 -- Set b:foo for buffer 2
----<
----
----Note that setting dictionary fields directly will not write them back into
----Nvim. This is because the index into the namespace simply returns a copy.
----Instead the whole dictionary must be written as one. This can be achieved by
----creating a short-lived temporary.
----
----Example: >lua
----
---- vim.g.my_dict.field1 = 'value' -- Does not work
----
---- local my_dict = vim.g.my_dict --
---- my_dict.field1 = 'value' -- Instead do
---- vim.g.my_dict = my_dict --
----
----vim.g *vim.g*
---- Global (|g:|) editor variables.
---- Key with no value returns `nil`.
----
----vim.b *vim.b*
---- Buffer-scoped (|b:|) variables for the current buffer.
---- Invalid or unset key returns `nil`. Can be indexed with
---- an integer to access variables for a specific buffer.
----
----vim.w *vim.w*
---- Window-scoped (|w:|) variables for the current window.
---- Invalid or unset key returns `nil`. Can be indexed with
---- an integer to access variables for a specific window.
----
----vim.t *vim.t*
---- Tabpage-scoped (|t:|) variables for the current tabpage.
---- Invalid or unset key returns `nil`. Can be indexed with
---- an integer to access variables for a specific tabpage.
----
----vim.v *vim.v*
---- |v:| variables.
---- Invalid or unset key returns `nil`.
----</pre>
+--- @brief <pre>help
+--- vim.call({func}, {...}) *vim.call()*
+--- Invokes |vim-function| or |user-function| {func} with arguments {...}.
+--- See also |vim.fn|.
+--- Equivalent to: >lua
+--- vim.fn[func]({...})
+--- <
+--- vim.cmd({command})
+--- See |vim.cmd()|.
+---
+--- vim.fn.{func}({...}) *vim.fn*
+--- Invokes |vim-function| or |user-function| {func} with arguments {...}.
+--- To call autoload functions, use the syntax: >lua
+--- vim.fn['some#function']({...})
+--- <
+--- Unlike vim.api.|nvim_call_function()| this converts directly between Vim
+--- objects and Lua objects. If the Vim function returns a float, it will be
+--- represented directly as a Lua number. Empty lists and dictionaries both
+--- are represented by an empty table.
+---
+--- Note: |v:null| values as part of the return value is represented as
+--- |vim.NIL| special value
+---
+--- Note: vim.fn keys are generated lazily, thus `pairs(vim.fn)` only
+--- enumerates functions that were called at least once.
+---
+--- Note: The majority of functions cannot run in |api-fast| callbacks with some
+--- undocumented exceptions which are allowed.
+---
+--- *lua-vim-variables*
+--- The Vim editor global dictionaries |g:| |w:| |b:| |t:| |v:| can be accessed
+--- from Lua conveniently and idiomatically by referencing the `vim.*` Lua tables
+--- described below. In this way you can easily read and modify global Vimscript
+--- variables from Lua.
+---
+--- Example: >lua
+---
+--- vim.g.foo = 5 -- Set the g:foo Vimscript variable.
+--- print(vim.g.foo) -- Get and print the g:foo Vimscript variable.
+--- vim.g.foo = nil -- Delete (:unlet) the Vimscript variable.
+--- vim.b[2].foo = 6 -- Set b:foo for buffer 2
+--- <
+---
+--- Note that setting dictionary fields directly will not write them back into
+--- Nvim. This is because the index into the namespace simply returns a copy.
+--- Instead the whole dictionary must be written as one. This can be achieved by
+--- creating a short-lived temporary.
+---
+--- Example: >lua
+---
+--- vim.g.my_dict.field1 = 'value' -- Does not work
+---
+--- local my_dict = vim.g.my_dict --
+--- my_dict.field1 = 'value' -- Instead do
+--- vim.g.my_dict = my_dict --
+---
+--- vim.g *vim.g*
+--- Global (|g:|) editor variables.
+--- Key with no value returns `nil`.
+---
+--- vim.b *vim.b*
+--- Buffer-scoped (|b:|) variables for the current buffer.
+--- Invalid or unset key returns `nil`. Can be indexed with
+--- an integer to access variables for a specific buffer.
+---
+--- vim.w *vim.w*
+--- Window-scoped (|w:|) variables for the current window.
+--- Invalid or unset key returns `nil`. Can be indexed with
+--- an integer to access variables for a specific window.
+---
+--- vim.t *vim.t*
+--- Tabpage-scoped (|t:|) variables for the current tabpage.
+--- Invalid or unset key returns `nil`. Can be indexed with
+--- an integer to access variables for a specific tabpage.
+---
+--- vim.v *vim.v*
+--- |v:| variables.
+--- Invalid or unset key returns `nil`.
+--- </pre>
local api = vim.api
@@ -108,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)
@@ -126,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
@@ -142,8 +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)
@@ -205,31 +206,30 @@ local function new_win_opt_accessor(winid, bufnr)
})
end
----@addtogroup lua-vimscript
----@brief <pre>help
----` ` *lua-options*
---- *lua-vim-options*
---- *lua-vim-set*
---- *lua-vim-setlocal*
+--- @brief <pre>help
+--- *lua-options*
+--- *lua-vim-options*
+--- *lua-vim-set*
+--- *lua-vim-setlocal*
---
----Vim options can be accessed through |vim.o|, which behaves like Vimscript
----|:set|.
+--- Vim options can be accessed through |vim.o|, which behaves like Vimscript
+--- |:set|.
---
---- Examples: ~
+--- Examples: ~
---
---- To set a boolean toggle:
---- Vimscript: `set number`
---- Lua: `vim.o.number = true`
+--- To set a boolean toggle:
+--- Vimscript: `set number`
+--- Lua: `vim.o.number = true`
---
---- To set a string value:
---- Vimscript: `set wildignore=*.o,*.a,__pycache__`
---- Lua: `vim.o.wildignore = '*.o,*.a,__pycache__'`
+--- To set a string value:
+--- Vimscript: `set wildignore=*.o,*.a,__pycache__`
+--- Lua: `vim.o.wildignore = '*.o,*.a,__pycache__'`
---
----Similarly, there is |vim.bo| and |vim.wo| for setting buffer-scoped and
----window-scoped options. Note that this must NOT be confused with
----|local-options| and |:setlocal|. There is also |vim.go| that only accesses the
----global value of a |global-local| option, see |:setglobal|.
----</pre>
+--- Similarly, there is |vim.bo| and |vim.wo| for setting buffer-scoped and
+--- window-scoped options. Note that this must NOT be confused with
+--- |local-options| and |:setlocal|. There is also |vim.go| that only accesses the
+--- global value of a |global-local| option, see |:setglobal|.
+--- </pre>
--- Get or set |options|. Like `:set`. Invalid key is an error.
---
@@ -276,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:
---
@@ -292,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.
@@ -310,18 +310,21 @@ vim.bo = new_buf_opt_accessor()
--- ```
vim.wo = new_win_opt_accessor()
----@brief [[
--- vim.opt, vim.opt_local and vim.opt_global implementation
---
--- To be used as helpers for working with options within neovim.
--- For information on how to use, see :help vim.opt
----
----@brief ]]
--- 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
@@ -332,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
@@ -360,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
@@ -388,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
@@ -415,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
@@ -425,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
@@ -474,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 = {}
@@ -487,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, ','))
@@ -516,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)
@@ -541,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)
@@ -590,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)
@@ -618,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
@@ -636,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,
@@ -644,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)
@@ -739,74 +767,74 @@ local function create_option_accessor(scope)
})
end
----@addtogroup lua-vimscript
----@brief <pre>help
----` ` *vim.opt_local*
---- *vim.opt_global*
---- *vim.opt*
----
----
----A special interface |vim.opt| exists for conveniently interacting with list-
----and map-style option from Lua: It allows accessing them as Lua tables and
----offers object-oriented method for adding and removing entries.
----
---- Examples: ~
----
---- The following methods of setting a list-style option are equivalent:
---- In Vimscript: >vim
---- set wildignore=*.o,*.a,__pycache__
----<
---- In Lua using `vim.o`: >lua
---- vim.o.wildignore = '*.o,*.a,__pycache__'
----<
---- In Lua using `vim.opt`: >lua
---- vim.opt.wildignore = { '*.o', '*.a', '__pycache__' }
----<
---- To replicate the behavior of |:set+=|, use: >lua
----
---- vim.opt.wildignore:append { "*.pyc", "node_modules" }
----<
---- To replicate the behavior of |:set^=|, use: >lua
----
---- vim.opt.wildignore:prepend { "new_first_value" }
----<
---- To replicate the behavior of |:set-=|, use: >lua
----
---- vim.opt.wildignore:remove { "node_modules" }
----<
---- The following methods of setting a map-style option are equivalent:
---- In Vimscript: >vim
---- set listchars=space:_,tab:>~
----<
---- In Lua using `vim.o`: >lua
---- vim.o.listchars = 'space:_,tab:>~'
----<
---- In Lua using `vim.opt`: >lua
---- vim.opt.listchars = { space = '_', tab = '>~' }
----<
----
----Note that |vim.opt| returns an `Option` object, not the value of the option,
----which is accessed through |vim.opt:get()|:
----
---- Examples: ~
----
---- The following methods of getting a list-style option are equivalent:
---- In Vimscript: >vim
---- echo wildignore
----<
---- In Lua using `vim.o`: >lua
---- print(vim.o.wildignore)
----<
---- In Lua using `vim.opt`: >lua
---- vim.print(vim.opt.wildignore:get())
----<
----
----In any of the above examples, to replicate the behavior |:setlocal|, use
----`vim.opt_local`. Additionally, to replicate the behavior of |:setglobal|, use
----`vim.opt_global`.
----</pre>
-
---- @diagnostic disable-next-line:unused-local used for gen_vimdoc
+--- @brief <pre>help
+--- *vim.opt_local*
+--- *vim.opt_global*
+--- *vim.opt*
+---
+---
+--- A special interface |vim.opt| exists for conveniently interacting with list-
+--- and map-style option from Lua: It allows accessing them as Lua tables and
+--- offers object-oriented method for adding and removing entries.
+---
+--- Examples: ~
+---
+--- The following methods of setting a list-style option are equivalent:
+--- In Vimscript: >vim
+--- set wildignore=*.o,*.a,__pycache__
+--- <
+--- In Lua using `vim.o`: >lua
+--- vim.o.wildignore = '*.o,*.a,__pycache__'
+--- <
+--- In Lua using `vim.opt`: >lua
+--- vim.opt.wildignore = { '*.o', '*.a', '__pycache__' }
+--- <
+--- To replicate the behavior of |:set+=|, use: >lua
+---
+--- vim.opt.wildignore:append { "*.pyc", "node_modules" }
+--- <
+--- To replicate the behavior of |:set^=|, use: >lua
+---
+--- vim.opt.wildignore:prepend { "new_first_value" }
+--- <
+--- To replicate the behavior of |:set-=|, use: >lua
+---
+--- vim.opt.wildignore:remove { "node_modules" }
+--- <
+--- The following methods of setting a map-style option are equivalent:
+--- In Vimscript: >vim
+--- set listchars=space:_,tab:>~
+--- <
+--- In Lua using `vim.o`: >lua
+--- vim.o.listchars = 'space:_,tab:>~'
+--- <
+--- In Lua using `vim.opt`: >lua
+--- vim.opt.listchars = { space = '_', tab = '>~' }
+--- <
+---
+--- Note that |vim.opt| returns an `Option` object, not the value of the option,
+--- which is accessed through |vim.opt:get()|:
+---
+--- Examples: ~
+---
+--- The following methods of getting a list-style option are equivalent:
+--- In Vimscript: >vim
+--- echo wildignore
+--- <
+--- In Lua using `vim.o`: >lua
+--- print(vim.o.wildignore)
+--- <
+--- In Lua using `vim.opt`: >lua
+--- vim.print(vim.opt.wildignore:get())
+--- <
+---
+--- In any of the above examples, to replicate the behavior |:setlocal|, use
+--- `vim.opt_local`. Additionally, to replicate the behavior of |:setglobal|, use
+--- `vim.opt_global`.
+--- </pre>
+
+--- @nodoc
+--- @class vim.Option
local Option = {} -- luacheck: no unused
--- Returns a Lua-representation of the option. Boolean, number and string
@@ -856,9 +884,7 @@ local Option = {} -- luacheck: no unused
--- print("J is enabled!")
--- end
--- ```
----
---@return string|integer|boolean|nil value of option
----@diagnostic disable-next-line:unused-local used for gen_vimdoc
function Option:get() end
--- Append a value to string-style options. See |:set+=|
@@ -869,7 +895,6 @@ function Option:get() end
--- vim.opt.formatoptions:append('j')
--- vim.opt.formatoptions = vim.opt.formatoptions + 'j'
--- ```
----
---@param value string Value to append
---@diagnostic disable-next-line:unused-local used for gen_vimdoc
function Option:append(value) end -- luacheck: no unused
@@ -882,7 +907,6 @@ function Option:append(value) end -- luacheck: no unused
--- vim.opt.wildignore:prepend('*.o')
--- vim.opt.wildignore = vim.opt.wildignore ^ '*.o'
--- ```
----
---@param value string Value to prepend
---@diagnostic disable-next-line:unused-local used for gen_vimdoc
function Option:prepend(value) end -- luacheck: no unused
@@ -895,7 +919,6 @@ function Option:prepend(value) end -- luacheck: no unused
--- vim.opt.wildignore:remove('*.pyc')
--- vim.opt.wildignore = vim.opt.wildignore - '*.pyc'
--- ```
----
---@param value string Value to remove
---@diagnostic disable-next-line:unused-local used for gen_vimdoc
function Option:remove(value) end -- luacheck: no unused
diff --git a/runtime/lua/vim/_system.lua b/runtime/lua/vim/_system.lua
index 9279febddf..e97a5fc6c3 100644
--- a/runtime/lua/vim/_system.lua
+++ b/runtime/lua/vim/_system.lua
@@ -1,6 +1,6 @@
local uv = vim.uv
---- @class SystemOpts
+--- @class vim.SystemOpts
--- @field stdin? string|string[]|true
--- @field stdout? fun(err:string?, data: string?)|false
--- @field stderr? fun(err:string?, data: string?)|false
@@ -61,7 +61,7 @@ end
--- @field wait fun(self: vim.SystemObj, timeout?: integer): vim.SystemCompleted
--- @field kill fun(self: vim.SystemObj, signal: integer|string)
--- @field write fun(self: vim.SystemObj, data?: string|string[])
---- @field is_closing fun(self: vim.SystemObj): boolean?
+--- @field is_closing fun(self: vim.SystemObj): boolean
local SystemObj = {}
--- @param state vim.SystemState
@@ -94,14 +94,14 @@ function SystemObj:wait(timeout)
local done = vim.wait(timeout or state.timeout or MAX_TIMEOUT, function()
return state.result ~= nil
- end)
+ end, nil, true)
if not done then
-- Send sigkill since this cannot be caught
self:_timeout(SIG.KILL)
vim.wait(timeout or state.timeout or MAX_TIMEOUT, function()
return state.result ~= nil
- end)
+ end, nil, true)
end
return state.result
@@ -140,7 +140,7 @@ end
--- @return boolean
function SystemObj:is_closing()
local handle = self._state.handle
- return handle == nil or handle:is_closing()
+ return handle == nil or handle:is_closing() or false
end
---@param output fun(err:string?, data: string?)|false
@@ -302,7 +302,7 @@ end
--- Run a system command
---
--- @param cmd string[]
---- @param opts? SystemOpts
+--- @param opts? vim.SystemOpts
--- @param on_exit? fun(out: vim.SystemCompleted)
--- @return vim.SystemObj
function M.run(cmd, opts, on_exit)
diff --git a/runtime/lua/vim/_watch.lua b/runtime/lua/vim/_watch.lua
index 43fce3bf7f..97c5481ad1 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,110 +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)
+ 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 = {}
-
- 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
+ --- 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 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
@@ -158,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?
@@ -174,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
@@ -190,34 +218,96 @@ 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()
+ 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,
})
- 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 99448982b4..d5075d7d3d 100644
--- a/runtime/lua/vim/diagnostic.lua
+++ b/runtime/lua/vim/diagnostic.lua
@@ -2,15 +2,260 @@ local api, if_nil = vim.api, vim.F.if_nil
local M = {}
----@enum DiagnosticSeverity
+--- [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
+---
+--- 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
+---
+--- 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
+---
+--- 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 (private) vim.diagnostic.OptsResolved
+--- @field float vim.diagnostic.Opts.Float
+--- @field update_in_insert boolean
+--- @field underline vim.diagnostic.Opts.Underline
+--- @field virtual_text vim.diagnostic.Opts.VirtualText
+--- @field signs vim.diagnostic.Opts.Signs
+--- @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}
+---
+--- 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
+---
+--- 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
+---
+--- 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>
+
+--- @nodoc
+--- @enum vim.diagnostic.Severity
M.severity = {
ERROR = 1,
WARN = 2,
INFO = 3,
HINT = 4,
+ [1] = 'ERROR',
+ [2] = 'WARN',
+ [3] = 'INFO',
+ [4] = 'HINT',
}
-vim.tbl_add_reverse_lookup(M.severity)
+--- @alias vim.diagnostic.SeverityInt 1|2|3|4
+
+--- See |diagnostic-severity| and |vim.diagnostic.get()|
+--- @alias vim.diagnostic.SeverityFilter vim.diagnostic.Severity|vim.diagnostic.Severity[]|{min:vim.diagnostic.Severity,max:vim.diagnostic.Severity}
-- Mappings from qflist/loclist error types to severities
M.severity.E = M.severity.ERROR
@@ -18,6 +263,7 @@ M.severity.W = M.severity.WARN
M.severity.I = M.severity.INFO
M.severity.N = M.severity.HINT
+--- @type vim.diagnostic.Opts
local global_diagnostic_options = {
signs = true,
underline = true,
@@ -27,6 +273,12 @@ local global_diagnostic_options = {
severity_sort = false,
}
+--- @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)
+
+--- @nodoc
+--- @type table<string,vim.diagnostic.Handler>
M.handlers = setmetatable({}, {
__newindex = function(t, name, handler)
vim.validate({ handler = { handler, 't' } })
@@ -39,6 +291,9 @@ M.handlers = setmetatable({}, {
-- Metatable that automatically creates an empty table when assigning to a missing key
local bufnr_and_namespace_cacher_mt = {
+ --- @param t table<integer,table>
+ --- @param bufnr integer
+ --- @return table
__index = function(t, bufnr)
assert(bufnr > 0, 'Invalid buffer number')
t[bufnr] = {}
@@ -46,10 +301,13 @@ local bufnr_and_namespace_cacher_mt = {
end,
}
-local diagnostic_cache
+-- bufnr -> ns -> Diagnostic[]
+local diagnostic_cache = {} --- @type table<integer,table<integer,vim.Diagnostic[]>>
do
local group = api.nvim_create_augroup('DiagnosticBufWipeout', {})
- diagnostic_cache = setmetatable({}, {
+ setmetatable(diagnostic_cache, {
+ --- @param t table<integer,vim.Diagnostic[]>
+ --- @param bufnr integer
__index = function(t, bufnr)
assert(bufnr > 0, 'Invalid buffer number')
api.nvim_create_autocmd('BufWipeout', {
@@ -65,61 +323,93 @@ do
})
end
+--- @class (private) vim.diagnostic._extmark
+--- @field [1] integer id
+--- @field [2] integer start
+--- @field [3] integer end
+--- @field [4] table details
+
+--- @type table<integer,table<integer,vim.diagnostic._extmark[]>>
local diagnostic_cache_extmarks = setmetatable({}, bufnr_and_namespace_cacher_mt)
+
+--- @type table<integer,true>
local diagnostic_attached_buffers = {}
+
+--- @type table<integer,true|table<integer,true>>
local diagnostic_disabled = {}
+
+--- @type table<integer,table<integer,table>>
local bufs_waiting_to_update = setmetatable({}, bufnr_and_namespace_cacher_mt)
+--- @class vim.diagnostic.NS
+--- @field name string
+--- @field opts vim.diagnostic.Opts
+--- @field user_data table
+--- @field disabled? boolean
+
+--- @type table<integer,vim.diagnostic.NS>
local all_namespaces = {}
+---@param severity string|vim.diagnostic.Severity
+---@return vim.diagnostic.Severity?
local function to_severity(severity)
if type(severity) == 'string' then
- return assert(
- M.severity[string.upper(severity)],
- string.format('Invalid severity: %s', severity)
- )
+ assert(M.severity[string.upper(severity)], string.format('Invalid severity: %s', severity))
+ return M.severity[string.upper(severity)]
end
return severity
end
+--- @param severity vim.diagnostic.SeverityFilter
+--- @param diagnostics vim.Diagnostic[]
+--- @return vim.Diagnostic[]
local function filter_by_severity(severity, diagnostics)
if not severity then
return diagnostics
end
if type(severity) ~= 'table' then
- severity = to_severity(severity)
+ severity = assert(to_severity(severity))
+ --- @param t vim.Diagnostic
return vim.tbl_filter(function(t)
return t.severity == severity
end, diagnostics)
end
if severity.min or severity.max then
+ --- @cast severity {min:vim.diagnostic.Severity,max:vim.diagnostic.Severity}
local min_severity = to_severity(severity.min) or M.severity.HINT
local max_severity = to_severity(severity.max) or M.severity.ERROR
+ --- @param t vim.Diagnostic
return vim.tbl_filter(function(t)
return t.severity <= min_severity and t.severity >= max_severity
end, diagnostics)
end
- local severities = {}
+ --- @cast severity vim.diagnostic.Severity[]
+
+ local severities = {} --- @type table<vim.diagnostic.Severity,true>
for _, s in ipairs(severity) do
- severities[to_severity(s)] = true
+ severities[assert(to_severity(s))] = true
end
+ --- @param t vim.Diagnostic
return vim.tbl_filter(function(t)
return severities[t.severity]
end, diagnostics)
end
+--- @param bufnr integer
+--- @return integer
local function count_sources(bufnr)
- local seen = {}
+ local seen = {} --- @type table<string,true>
local count = 0
for _, namespace_diagnostics in pairs(diagnostic_cache[bufnr]) do
for _, diagnostic in ipairs(namespace_diagnostics) do
- if diagnostic.source and not seen[diagnostic.source] then
- seen[diagnostic.source] = true
+ local source = diagnostic.source
+ if source and not seen[source] then
+ seen[source] = true
count = count + 1
end
end
@@ -127,51 +417,65 @@ local function count_sources(bufnr)
return count
end
+--- @param diagnostics vim.Diagnostic[]
+--- @return vim.Diagnostic[]
local function prefix_source(diagnostics)
+ --- @param d vim.Diagnostic
return vim.tbl_map(function(d)
if not d.source then
return d
end
- local t = vim.deepcopy(d)
+ local t = vim.deepcopy(d, true)
t.message = string.format('%s: %s', d.source, d.message)
return t
end, diagnostics)
end
+--- @param diagnostics vim.Diagnostic[]
+--- @return vim.Diagnostic[]
local function reformat_diagnostics(format, diagnostics)
vim.validate({
format = { format, 'f' },
diagnostics = { diagnostics, 't' },
})
- local formatted = vim.deepcopy(diagnostics)
+ local formatted = vim.deepcopy(diagnostics, true)
for _, diagnostic in ipairs(formatted) do
diagnostic.message = format(diagnostic)
end
return formatted
end
+--- @param option string
+--- @param namespace integer?
+--- @return table
local function enabled_value(option, namespace)
local ns = namespace and M.get_namespace(namespace) or {}
if ns.opts and type(ns.opts[option]) == 'table' then
return ns.opts[option]
end
- if type(global_diagnostic_options[option]) == 'table' then
- return global_diagnostic_options[option]
+ local global_opt = global_diagnostic_options[option]
+ if type(global_opt) == 'table' then
+ return global_opt
end
return {}
end
+--- @param option string
+--- @param value any?
+--- @param namespace integer?
+--- @param bufnr integer
+--- @return any
local function resolve_optional_value(option, value, namespace, bufnr)
if not value then
return false
elseif value == true then
return enabled_value(option, namespace)
elseif type(value) == 'function' then
- local val = value(namespace, bufnr)
+ local val = value(namespace, bufnr) --- @type any
if val == true then
return enabled_value(option, namespace)
else
@@ -179,15 +483,18 @@ local function resolve_optional_value(option, value, namespace, bufnr)
end
elseif type(value) == 'table' then
return value
- else
- error('Unexpected option type: ' .. vim.inspect(value))
end
+ error('Unexpected option type: ' .. vim.inspect(value))
end
+--- @param opts vim.diagnostic.Opts?
+--- @param namespace integer?
+--- @param bufnr integer
+--- @return vim.diagnostic.OptsResolved
local function get_resolved_options(opts, namespace, bufnr)
local ns = namespace and M.get_namespace(namespace) or {}
-- Do not use tbl_deep_extend so that an empty table can be used to reset to default values
- local resolved = vim.tbl_extend('keep', opts or {}, ns.opts or {}, global_diagnostic_options)
+ local resolved = vim.tbl_extend('keep', opts or {}, ns.opts or {}, global_diagnostic_options) --- @type table<string,any>
for k in pairs(global_diagnostic_options) do
if resolved[k] ~= nil then
resolved[k] = resolve_optional_value(k, resolved[k], namespace, bufnr)
@@ -204,9 +511,11 @@ local diagnostic_severities = {
[M.severity.HINT] = { ctermfg = 7, guifg = 'LightGrey' },
}
--- Make a map from DiagnosticSeverity -> Highlight Name
+--- Make a map from vim.diagnostic.Severity -> Highlight Name
+--- @param base_name string
+--- @return table<vim.diagnostic.SeverityInt,string>
local function make_highlight_map(base_name)
- local result = {}
+ local result = {} --- @type table<vim.diagnostic.SeverityInt,string>
for k in pairs(diagnostic_severities) do
local name = M.severity[k]
name = name:sub(1, 1) .. name:sub(2):lower()
@@ -216,35 +525,13 @@ local function make_highlight_map(base_name)
return result
end
+-- TODO(lewis6991): these highlight maps can only be indexed with an integer, however there usage
+-- implies they can be indexed with any vim.diagnostic.Severity
local virtual_text_highlight_map = make_highlight_map('VirtualText')
local underline_highlight_map = make_highlight_map('Underline')
local floating_highlight_map = make_highlight_map('Floating')
local sign_highlight_map = make_highlight_map('Sign')
----@private
-local define_default_signs = (function()
- local signs_defined = false
- return function()
- if signs_defined then
- return
- end
-
- for severity, sign_hl_name in pairs(sign_highlight_map) do
- if vim.tbl_isempty(vim.fn.sign_getdefined(sign_hl_name)) then
- local severity_name = M.severity[severity]
- vim.fn.sign_define(sign_hl_name, {
- text = (severity_name or 'U'):sub(1, 1),
- texthl = sign_hl_name,
- linehl = '',
- numhl = '',
- })
- end
- end
-
- signs_defined = true
- end
-end)()
-
local function get_bufnr(bufnr)
if not bufnr or bufnr == 0 then
return api.nvim_get_current_buf()
@@ -252,12 +539,14 @@ local function get_bufnr(bufnr)
return bufnr
end
+--- @param diagnostics vim.Diagnostic[]
+--- @return table<integer,vim.Diagnostic[]>
local function diagnostic_lines(diagnostics)
if not diagnostics then
return {}
end
- local diagnostics_by_line = {}
+ local diagnostics_by_line = {} --- @type table<integer,vim.Diagnostic[]>
for _, diagnostic in ipairs(diagnostics) do
local line_diagnostics = diagnostics_by_line[diagnostic.lnum]
if not line_diagnostics then
@@ -269,6 +558,9 @@ local function diagnostic_lines(diagnostics)
return diagnostics_by_line
end
+--- @param namespace integer
+--- @param bufnr integer
+--- @param diagnostics vim.Diagnostic[]
local function set_diagnostic_cache(namespace, bufnr, diagnostics)
for _, diagnostic in ipairs(diagnostics) do
assert(diagnostic.lnum, 'Diagnostic line number is required')
@@ -283,10 +575,12 @@ local function set_diagnostic_cache(namespace, bufnr, diagnostics)
diagnostic_cache[bufnr][namespace] = diagnostics
end
+--- @param bufnr integer
+--- @param last integer
local function restore_extmarks(bufnr, last)
for ns, extmarks in pairs(diagnostic_cache_extmarks[bufnr]) do
local extmarks_current = api.nvim_buf_get_extmarks(bufnr, ns, 0, -1, { details = true })
- local found = {}
+ local found = {} --- @type table<integer,true>
for _, extmark in ipairs(extmarks_current) do
-- nvim_buf_set_lines will move any extmark to the line after the last
-- nvim_buf_set_text will move any extmark to the last line
@@ -304,6 +598,8 @@ local function restore_extmarks(bufnr, last)
end
end
+--- @param namespace integer
+--- @param bufnr? integer
local function save_extmarks(namespace, bufnr)
bufnr = get_bufnr(bufnr)
if not diagnostic_attached_buffers[bufnr] then
@@ -321,6 +617,7 @@ local function save_extmarks(namespace, bufnr)
api.nvim_buf_get_extmarks(bufnr, namespace, 0, -1, { details = true })
end
+--- @type table<string,true>
local registered_autocmds = {}
local function make_augroup_key(namespace, bufnr)
@@ -328,6 +625,8 @@ local function make_augroup_key(namespace, bufnr)
return string.format('DiagnosticInsertLeave:%s:%s', bufnr, ns.name)
end
+--- @param namespace integer
+--- @param bufnr integer
local function execute_scheduled_display(namespace, bufnr)
local args = bufs_waiting_to_update[bufnr][namespace]
if not args then
@@ -343,6 +642,9 @@ end
--- Table of autocmd events to fire the update for displaying new diagnostic information
local insert_leave_auto_cmds = { 'InsertLeave', 'CursorHoldI' }
+--- @param namespace integer
+--- @param bufnr integer
+--- @param args any[]
local function schedule_display(namespace, bufnr, args)
bufs_waiting_to_update[bufnr][namespace] = args
@@ -361,6 +663,8 @@ local function schedule_display(namespace, bufnr, args)
end
end
+--- @param namespace integer
+--- @param bufnr integer
local function clear_scheduled_display(namespace, bufnr)
local key = make_augroup_key(namespace, bufnr)
@@ -370,6 +674,10 @@ local function clear_scheduled_display(namespace, bufnr)
end
end
+--- @param bufnr integer?
+--- @param opts vim.diagnostic.GetOpts?
+--- @param clamp boolean
+--- @return vim.Diagnostic[]
local function get_diagnostics(bufnr, opts, clamp)
opts = opts or {}
@@ -377,16 +685,21 @@ local function get_diagnostics(bufnr, opts, clamp)
local diagnostics = {}
-- Memoized results of buf_line_count per bufnr
+ --- @type table<integer,integer>
local buf_line_count = setmetatable({}, {
+ --- @param t table<integer,integer>
+ --- @param k integer
+ --- @return integer
__index = function(t, k)
t[k] = api.nvim_buf_line_count(k)
return rawget(t, k)
end,
})
+ ---@param b integer
+ ---@param d vim.Diagnostic
local function add(b, d)
if not opts.lnum or d.lnum == opts.lnum then
- d = vim.deepcopy(d)
if clamp and api.nvim_buf_is_loaded(b) then
local line_count = buf_line_count[b] - 1
if
@@ -397,8 +710,9 @@ local function get_diagnostics(bufnr, opts, clamp)
or d.col < 0
or d.end_col < 0
then
+ d = vim.deepcopy(d, true)
d.lnum = math.max(math.min(d.lnum, line_count), 0)
- d.end_lnum = math.max(math.min(d.end_lnum, line_count), 0)
+ d.end_lnum = math.max(math.min(assert(d.end_lnum), line_count), 0)
d.col = math.max(d.col, 0)
d.end_col = math.max(d.end_col, 0)
end
@@ -407,6 +721,8 @@ local function get_diagnostics(bufnr, opts, clamp)
end
end
+ --- @param buf integer
+ --- @param diags vim.Diagnostic[]
local function add_all_diags(buf, diags)
for _, diagnostic in pairs(diags) do
add(buf, diagnostic)
@@ -440,18 +756,20 @@ local function get_diagnostics(bufnr, opts, clamp)
return diagnostics
end
+--- @param loclist boolean
+--- @param opts vim.diagnostic.setqflist.Opts|vim.diagnostic.setloclist.Opts?
local function set_list(loclist, opts)
opts = opts or {}
- local open = vim.F.if_nil(opts.open, true)
+ local open = if_nil(opts.open, true)
local title = opts.title or 'Diagnostics'
local winnr = opts.winnr or 0
- local bufnr
+ local bufnr --- @type integer?
if loclist then
bufnr = api.nvim_win_get_buf(winnr)
end
-- Don't clamp line numbers since the quickfix list can already handle line
-- numbers beyond the end of the buffer
- local diagnostics = get_diagnostics(bufnr, opts, false)
+ local diagnostics = get_diagnostics(bufnr, opts --[[@as vim.diagnostic.GetOpts]], false)
local items = M.toqflist(diagnostics)
if loclist then
vim.fn.setloclist(winnr, {}, ' ', { title = title, items = items })
@@ -463,10 +781,16 @@ local function set_list(loclist, opts)
end
end
+--- @param position {[1]: integer, [2]: integer}
+--- @param search_forward boolean
+--- @param bufnr integer
+--- @param opts vim.diagnostic.GotoOpts
+--- @param namespace integer
+--- @return vim.Diagnostic?
local function next_diagnostic(position, search_forward, bufnr, opts, namespace)
position[1] = position[1] - 1
bufnr = get_bufnr(bufnr)
- local wrap = vim.F.if_nil(opts.wrap, true)
+ local wrap = if_nil(opts.wrap, true)
local line_count = api.nvim_buf_line_count(bufnr)
local diagnostics =
get_diagnostics(bufnr, vim.tbl_extend('keep', opts, { namespace = namespace }), true)
@@ -483,6 +807,7 @@ local function next_diagnostic(position, search_forward, bufnr, opts, namespace)
end
if line_diagnostics[lnum] and not vim.tbl_isempty(line_diagnostics[lnum]) then
local line_length = #api.nvim_buf_get_lines(bufnr, lnum, lnum + 1, true)[1]
+ --- @type function, function
local sort_diagnostics, is_next
if search_forward then
sort_diagnostics = function(a, b)
@@ -501,7 +826,9 @@ local function next_diagnostic(position, search_forward, bufnr, opts, namespace)
end
table.sort(line_diagnostics[lnum], sort_diagnostics)
if i == 0 then
- for _, v in pairs(line_diagnostics[lnum]) do
+ for _, v in
+ pairs(line_diagnostics[lnum] --[[@as table<string,any>]])
+ do
if is_next(v) then
return v
end
@@ -513,10 +840,12 @@ local function next_diagnostic(position, search_forward, bufnr, opts, namespace)
end
end
+--- @param opts vim.diagnostic.GotoOpts?
+--- @param pos {[1]:integer,[2]:integer}|false
local function diagnostic_move_pos(opts, pos)
opts = opts or {}
- local float = vim.F.if_nil(opts.float, true)
+ local float = if_nil(opts.float, true)
local win_id = opts.win_id or api.nvim_get_current_win()
if not pos then
@@ -566,76 +895,18 @@ 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 table|nil 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:
---- <pre>lua
---- function(diagnostic)
---- if diagnostic.severity == vim.diagnostic.severity.ERROR then
---- return string.format("E: %s", diagnostic.message)
---- end
---- return diagnostic.message
---- end
---- </pre>
---- - signs: (default true) Use signs for diagnostics. 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.
---- - 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|nil Update the options for the given namespace. When omitted, update the
---- global diagnostic options.
+---@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 },
namespace = { namespace, 'n', true },
})
- local t
+ local t --- @type vim.diagnostic.Opts
if namespace then
local ns = M.get_namespace(namespace)
t = ns.opts
@@ -645,10 +916,12 @@ function M.config(opts, namespace)
if not opts then
-- Return current config
- return vim.deepcopy(t)
+ return vim.deepcopy(t, true)
end
- for k, v in pairs(opts) do
+ for k, v in
+ pairs(opts --[[@as table<any,any>]])
+ do
t[k] = v
end
@@ -671,8 +944,8 @@ end
---
---@param namespace integer The diagnostic namespace
---@param bufnr integer Buffer number
----@param diagnostics table A list of diagnostic items |diagnostic-structure|
----@param opts table|nil 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' },
@@ -698,6 +971,7 @@ function M.set(namespace, bufnr, diagnostics, opts)
api.nvim_exec_autocmds('DiagnosticChanged', {
modeline = false,
buffer = bufnr,
+ -- TODO(lewis6991): should this be deepcopy()'d like they are in vim.diagnostic.get()
data = { diagnostics = diagnostics },
})
end
@@ -705,11 +979,11 @@ end
--- Get namespace metadata.
---
---@param namespace integer Diagnostic namespace
----@return 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
- local name
+ local name --- @type string?
for k, v in pairs(api.nvim_get_namespaces()) do
if namespace == v then
name = k
@@ -730,48 +1004,56 @@ end
--- Get current diagnostic namespaces.
---
----@return table 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)
+ return vim.deepcopy(all_namespaces, true)
end
----@class Diagnostic
----@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
----@field severity? DiagnosticSeverity
----@field message string
----@field source? string
----@field code? string
----@field _tags? { deprecated: boolean, unnecessary: boolean}
----@field user_data? any arbitrary data plugins can add
-
--- 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|nil Buffer number to get diagnostics from. Use 0 for
---- current buffer or nil for all buffers.
----@param opts table|nil 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 Diagnostic[] table A list of diagnostic items |diagnostic-structure|. Keys `bufnr`, `end_lnum`, `end_col`, and `severity` are guaranteed to be present.
+---@param bufnr integer? Buffer number to get diagnostics from. Use 0 for
+--- 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 },
opts = { opts, 't', true },
})
- return get_diagnostics(bufnr, opts, false)
+ return vim.deepcopy(get_diagnostics(bufnr, opts, false), true)
+end
+
+--- Get current diagnostics count.
+---
+---@param bufnr? integer Buffer number to get diagnostics from. Use 0 for
+--- current buffer or nil for all buffers.
+---@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 },
+ opts = { opts, 't', true },
+ })
+
+ local diagnostics = get_diagnostics(bufnr, opts, false)
+ local count = {} --- @type table<integer,integer>
+ for i = 1, #diagnostics do
+ local severity = diagnostics[i].severity --[[@as integer]]
+ count[severity] = (count[severity] or 0) + 1
+ end
+ return count
end
--- Get the previous diagnostic closest to the cursor position.
---
----@param opts nil|table See |vim.diagnostic.goto_next()|
----@return Diagnostic|nil Previous diagnostic
+---@param opts? vim.diagnostic.GotoOpts
+---@return vim.Diagnostic? : Previous diagnostic
function M.get_prev(opts)
opts = opts or {}
@@ -784,9 +1066,9 @@ end
--- Return the position of the previous diagnostic in the current buffer.
---
----@param opts table|nil 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
@@ -797,15 +1079,15 @@ function M.get_prev_pos(opts)
end
--- Move to the previous diagnostic in the current buffer.
----@param opts table|nil 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 table|nil See |vim.diagnostic.goto_next()|
----@return Diagnostic|nil Next diagnostic
+---@param opts? vim.diagnostic.GotoOpts
+---@return vim.Diagnostic? : Next diagnostic
function M.get_next(opts)
opts = opts or {}
@@ -818,9 +1100,9 @@ end
--- Return the position of the next diagnostic in the current buffer.
---
----@param opts table|nil See |vim.diagnostic.goto_next()|
----@return table|false Next diagnostic position as a (row, col) tuple or false if no next
---- diagnostic.
+---@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)
if not next then
@@ -830,22 +1112,49 @@ 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 table|nil Configuration table with the following keys:
---- - namespace: (number) 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)
- return diagnostic_move_pos(opts, M.get_next_pos(opts))
+ diagnostic_move_pos(opts, M.get_next_pos(opts))
end
M.handlers.signs = {
@@ -868,11 +1177,9 @@ M.handlers.signs = {
diagnostics = filter_by_severity(opts.signs.severity, diagnostics)
end
- define_default_signs()
-
-- 10 is the default sign priority when none is explicitly specified
local priority = opts.signs and opts.signs.priority or 10
- local get_priority
+ local get_priority --- @type function
if opts.severity_sort then
if type(opts.severity_sort) == 'table' and opts.severity_sort.reverse then
get_priority = function(severity)
@@ -890,22 +1197,85 @@ M.handlers.signs = {
end
local ns = M.get_namespace(namespace)
- if not ns.user_data.sign_group then
- ns.user_data.sign_group = string.format('vim.diagnostic.%s', ns.name)
+ if not ns.user_data.sign_ns then
+ ns.user_data.sign_ns =
+ api.nvim_create_namespace(string.format('%s/diagnostic/signs', ns.name))
end
- local sign_group = ns.user_data.sign_group
+ -- 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 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]
+ if sign then
+ local severity = M.severity[v:upper()]
+ vim.deprecate(
+ 'Defining diagnostic signs with :sign-define or sign_define()',
+ 'vim.diagnostic.config()',
+ '0.12',
+ nil,
+ false
+ )
+
+ if not opts.signs.text then
+ opts.signs.text = {}
+ end
+
+ if not opts.signs.numhl then
+ opts.signs.numhl = {}
+ end
+
+ if not opts.signs.linehl then
+ opts.signs.linehl = {}
+ end
+
+ if opts.signs.text[severity] == nil then
+ opts.signs.text[severity] = sign.text or ''
+ end
+
+ if opts.signs.numhl[severity] == nil then
+ opts.signs.numhl[severity] = sign.numhl
+ end
+
+ if opts.signs.linehl[severity] == nil then
+ opts.signs.linehl[severity] = sign.linehl
+ end
+ end
+ end
+ end
+
+ local text = {} ---@type table<vim.diagnostic.Severity|string, string>
+ for k in pairs(M.severity) do
+ if opts.signs.text and opts.signs.text[k] then
+ text[k] = opts.signs.text[k]
+ elseif type(k) == 'string' and not text[k] then
+ text[k] = string.sub(k, 1, 1):upper()
+ end
+ end
+
+ local numhl = opts.signs.numhl or {}
+ local linehl = opts.signs.linehl or {}
+
for _, diagnostic in ipairs(diagnostics) do
- vim.fn.sign_place(0, sign_group, sign_highlight_map[diagnostic.severity], bufnr, {
- priority = get_priority(diagnostic.severity),
- lnum = diagnostic.lnum + 1,
- })
+ if api.nvim_buf_is_loaded(diagnostic.bufnr) then
+ api.nvim_buf_set_extmark(bufnr, ns.user_data.sign_ns, diagnostic.lnum, 0, {
+ sign_text = text[diagnostic.severity] or text[M.severity[diagnostic.severity]] or 'U',
+ sign_hl_group = sign_highlight_map[diagnostic.severity],
+ number_hl_group = numhl[diagnostic.severity],
+ line_hl_group = linehl[diagnostic.severity],
+ priority = get_priority(diagnostic.severity),
+ })
+ end
end
end,
+
+ --- @param namespace integer
+ --- @param bufnr integer
hide = function(namespace, bufnr)
local ns = M.get_namespace(namespace)
- if ns.user_data.sign_group and api.nvim_buf_is_valid(bufnr) then
- vim.fn.sign_unplace(ns.user_data.sign_group, { buffer = bufnr })
+ if ns.user_data.sign_ns and api.nvim_buf_is_valid(bufnr) then
+ api.nvim_buf_clear_namespace(bufnr, ns.user_data.sign_ns, 0, -1)
end
end,
}
@@ -936,15 +1306,18 @@ M.handlers.underline = {
local ns = M.get_namespace(namespace)
if not ns.user_data.underline_ns then
- ns.user_data.underline_ns = api.nvim_create_namespace('')
+ ns.user_data.underline_ns =
+ api.nvim_create_namespace(string.format('%s/diagnostic/underline', ns.name))
end
local underline_ns = ns.user_data.underline_ns
for _, diagnostic in ipairs(diagnostics) do
- local higroup = underline_highlight_map[diagnostic.severity]
+ --- @type string?
+ local higroup = underline_highlight_map[assert(diagnostic.severity)]
if higroup == nil then
-- Default to error if we don't have a highlight associated
+ -- TODO(lewis6991): this is always nil since underline_highlight_map only has integer keys
higroup = underline_highlight_map.Error
end
@@ -1000,7 +1373,7 @@ M.handlers.virtual_text = {
return
end
- local severity
+ local severity --- @type vim.diagnostic.SeverityFilter?
if opts.virtual_text then
if opts.virtual_text.format then
diagnostics = reformat_diagnostics(opts.virtual_text.format, diagnostics)
@@ -1018,7 +1391,8 @@ M.handlers.virtual_text = {
local ns = M.get_namespace(namespace)
if not ns.user_data.virt_text_ns then
- ns.user_data.virt_text_ns = api.nvim_create_namespace('')
+ ns.user_data.virt_text_ns =
+ api.nvim_create_namespace(string.format('%s/diagnostic/virtual_text', ns.name))
end
local virt_text_ns = ns.user_data.virt_text_ns
@@ -1057,7 +1431,9 @@ M.handlers.virtual_text = {
--- Exported for backward compatibility with
--- vim.lsp.diagnostic.get_virtual_text_chunks_for_line(). When that function is eventually removed,
--- this can be made local.
----@private
+--- @private
+--- @param line_diags table<integer,vim.Diagnostic>
+--- @param opts vim.diagnostic.Opts.VirtualText
function M._get_virt_text_chunks(line_diags, opts)
if #line_diags == 0 then
return nil
@@ -1107,10 +1483,10 @@ end
--- To hide diagnostics and prevent them from re-displaying, use
--- |vim.diagnostic.disable()|.
---
----@param namespace integer|nil Diagnostic namespace. When omitted, hide
---- diagnostics from all namespaces.
----@param bufnr integer|nil Buffer number, or 0 for current buffer. When
---- omitted, hide diagnostics in all buffers.
+---@param namespace integer? Diagnostic namespace. When omitted, hide
+--- diagnostics from all namespaces.
+---@param bufnr integer? Buffer number, or 0 for current buffer. When
+--- omitted, hide diagnostics in all buffers.
function M.hide(namespace, bufnr)
vim.validate({
namespace = { namespace, 'n', true },
@@ -1132,11 +1508,11 @@ end
--- Check whether diagnostics are disabled in a given buffer.
---
----@param bufnr integer|nil Buffer number, or 0 for current buffer.
----@param namespace integer|nil Diagnostic namespace. When omitted, checks if
---- all diagnostics are disabled in {bufnr}.
---- Otherwise, only checks if diagnostics from
---- {namespace} are disabled.
+---@param bufnr integer? Buffer number, or 0 for current buffer.
+---@param namespace integer? Diagnostic namespace. When omitted, checks if
+--- all diagnostics are disabled in {bufnr}.
+--- Otherwise, only checks if diagnostics from
+--- {namespace} are disabled.
---@return boolean
function M.is_disabled(bufnr, namespace)
bufnr = get_bufnr(bufnr)
@@ -1153,17 +1529,17 @@ end
--- Display diagnostics for the given namespace and buffer.
---
----@param namespace integer|nil Diagnostic namespace. When omitted, show
---- diagnostics from all namespaces.
----@param bufnr integer|nil Buffer number, or 0 for current buffer. When omitted, show
---- diagnostics in all buffers.
----@param diagnostics table|nil The diagnostics to display. When omitted, use the
+---@param namespace integer? Diagnostic namespace. When omitted, show
+--- diagnostics from all namespaces.
+---@param bufnr integer? Buffer number, or 0 for current buffer. When omitted, show
+--- diagnostics in all buffers.
+---@param diagnostics vim.Diagnostic[]? The diagnostics to display. When omitted, use the
--- saved diagnostics for the given namespace and
--- buffer. This can be used to display a list of diagnostics
--- without saving them or to display only a subset of
--- diagnostics. May not be used when {namespace}
--- or {bufnr} is nil.
----@param opts table|nil 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 },
@@ -1202,24 +1578,24 @@ function M.show(namespace, bufnr, diagnostics, opts)
diagnostics = diagnostics or get_diagnostics(bufnr, { namespace = namespace }, true)
- if not diagnostics or vim.tbl_isempty(diagnostics) then
+ if vim.tbl_isempty(diagnostics) then
return
end
- opts = get_resolved_options(opts, namespace, bufnr)
+ local opts_res = get_resolved_options(opts, namespace, bufnr)
- if opts.update_in_insert then
+ if opts_res.update_in_insert then
clear_scheduled_display(namespace, bufnr)
else
local mode = api.nvim_get_mode()
if string.sub(mode.mode, 1, 1) == 'i' then
- schedule_display(namespace, bufnr, opts)
+ schedule_display(namespace, bufnr, opts_res)
return
end
end
- if vim.F.if_nil(opts.severity_sort, false) then
- if type(opts.severity_sort) == 'table' and opts.severity_sort.reverse then
+ if if_nil(opts_res.severity_sort, false) then
+ if type(opts_res.severity_sort) == 'table' and opts_res.severity_sort.reverse then
table.sort(diagnostics, function(a, b)
return a.severity < b.severity
end)
@@ -1231,61 +1607,23 @@ function M.show(namespace, bufnr, diagnostics, opts)
end
for handler_name, handler in pairs(M.handlers) do
- if handler.show and opts[handler_name] then
- handler.show(namespace, bufnr, diagnostics, opts)
+ if handler.show and opts_res[handler_name] then
+ handler.show(namespace, bufnr, diagnostics, opts_res)
end
end
end
--- Show diagnostics in a floating window.
---
----@param opts table|nil 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()|.
----@return integer|nil, integer|nil: ({float_bufnr}, {win_id})
+---@param opts vim.diagnostic.Opts.Float?
+---@return integer? float_bufnr
+---@return integer? win_id
function M.open_float(opts, ...)
-- Support old (bufnr, opts) signature
- local bufnr
+ local bufnr --- @type integer?
if opts == nil or type(opts) == 'number' then
bufnr = opts
- opts = ...
+ opts = ... --- @type vim.diagnostic.Opts.Float
else
vim.validate({
opts = { opts, 't', true },
@@ -1307,16 +1645,17 @@ function M.open_float(opts, ...)
end
local scope = ({ l = 'line', c = 'cursor', b = 'buffer' })[opts.scope] or opts.scope or 'line'
- local lnum, col
+ local lnum, col --- @type integer, integer
+ local opts_pos = opts.pos
if scope == 'line' or scope == 'cursor' then
- if not opts.pos then
+ if not opts_pos then
local pos = api.nvim_win_get_cursor(0)
lnum = pos[1] - 1
col = pos[2]
- elseif type(opts.pos) == 'number' then
- lnum = opts.pos
- elseif type(opts.pos) == 'table' then
- lnum, col = unpack(opts.pos)
+ elseif type(opts_pos) == 'number' then
+ lnum = opts_pos
+ elseif type(opts_pos) == 'table' then
+ lnum, col = opts_pos[1], opts_pos[2]
else
error("Invalid value for option 'pos'")
end
@@ -1324,15 +1663,17 @@ function M.open_float(opts, ...)
error("Invalid value for option 'scope'")
end
- local diagnostics = get_diagnostics(bufnr, opts, true)
+ local diagnostics = get_diagnostics(bufnr, opts --[[@as vim.diagnostic.GetOpts]], true)
if scope == 'line' then
+ --- @param d vim.Diagnostic
diagnostics = vim.tbl_filter(function(d)
return d.lnum == lnum
end, diagnostics)
elseif scope == 'cursor' then
-- LSP servers can send diagnostics with `end_col` past the length of the line
local line_length = #api.nvim_buf_get_lines(bufnr, lnum, lnum + 1, true)[1]
+ --- @param d vim.Diagnostic
diagnostics = vim.tbl_filter(function(d)
return d.lnum == lnum
and math.min(d.col, line_length - 1) <= col
@@ -1344,7 +1685,7 @@ function M.open_float(opts, ...)
return
end
- local severity_sort = vim.F.if_nil(opts.severity_sort, global_diagnostic_options.severity_sort)
+ local severity_sort = if_nil(opts.severity_sort, global_diagnostic_options.severity_sort)
if severity_sort then
if type(severity_sort) == 'table' and severity_sort.reverse then
table.sort(diagnostics, function(a, b)
@@ -1357,8 +1698,8 @@ function M.open_float(opts, ...)
end
end
- local lines = {}
- local highlights = {}
+ local lines = {} --- @type string[]
+ local highlights = {} --- @type table[]
local header = if_nil(opts.header, 'Diagnostics:')
if header then
vim.validate({
@@ -1393,7 +1734,7 @@ function M.open_float(opts, ...)
return string.format('%d. ', i)
end)
- local prefix, prefix_hl_group
+ local prefix, prefix_hl_group --- @type string?, string?
if prefix_opt then
vim.validate({
prefix = {
@@ -1413,7 +1754,7 @@ function M.open_float(opts, ...)
return diagnostic.code and string.format(' [%s]', diagnostic.code) or ''
end)
- local suffix, suffix_hl_group
+ local suffix, suffix_hl_group --- @type string?, string?
if suffix_opt then
vim.validate({
suffix = {
@@ -1430,15 +1771,18 @@ function M.open_float(opts, ...)
end
for i, diagnostic in ipairs(diagnostics) do
- if prefix_opt and type(prefix_opt) == 'function' then
- prefix, prefix_hl_group = prefix_opt(diagnostic, i, #diagnostics)
- prefix, prefix_hl_group = prefix or '', prefix_hl_group or 'NormalFloat'
+ if type(prefix_opt) == 'function' then
+ --- @cast prefix_opt fun(...): string?, string?
+ local prefix0, prefix_hl_group0 = prefix_opt(diagnostic, i, #diagnostics)
+ prefix, prefix_hl_group = prefix0 or '', prefix_hl_group0 or 'NormalFloat'
end
- if suffix_opt and type(suffix_opt) == 'function' then
- suffix, suffix_hl_group = suffix_opt(diagnostic, i, #diagnostics)
- suffix, suffix_hl_group = suffix or '', suffix_hl_group or 'NormalFloat'
+ if type(suffix_opt) == 'function' then
+ --- @cast suffix_opt fun(...): string?, string?
+ local suffix0, suffix_hl_group0 = suffix_opt(diagnostic, i, #diagnostics)
+ suffix, suffix_hl_group = suffix0 or '', suffix_hl_group0 or 'NormalFloat'
end
- local hiname = floating_highlight_map[diagnostic.severity]
+ --- @type string?
+ local hiname = floating_highlight_map[assert(diagnostic.severity)]
local message_lines = vim.split(diagnostic.message, '\n')
for j = 1, #message_lines do
local pre = j == 1 and prefix or string.rep(' ', #prefix)
@@ -1462,7 +1806,7 @@ function M.open_float(opts, ...)
if not opts.focus_id then
opts.focus_id = scope
end
- local float_bufnr, winnr = require('vim.lsp.util').open_floating_preview(lines, 'plaintext', opts)
+ local float_bufnr, winnr = vim.lsp.util.open_floating_preview(lines, 'plaintext', opts)
for i, hl in ipairs(highlights) do
local line = lines[i]
local prefix_len = hl.prefix and hl.prefix.length or 0
@@ -1486,10 +1830,10 @@ end
--- simply remove diagnostic decorations in a way that they can be
--- re-displayed, use |vim.diagnostic.hide()|.
---
----@param namespace integer|nil Diagnostic namespace. When omitted, remove
---- diagnostics from all namespaces.
----@param bufnr integer|nil Remove diagnostics for the given buffer. When omitted,
---- diagnostics are removed for all buffers.
+---@param namespace integer? Diagnostic namespace. When omitted, remove
+--- diagnostics from all namespaces.
+---@param bufnr integer? Remove diagnostics for the given buffer. When omitted,
+--- diagnostics are removed for all buffers.
function M.reset(namespace, bufnr)
vim.validate({
namespace = { namespace, 'n', true },
@@ -1516,34 +1860,63 @@ 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 table|nil 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
+
--- Add buffer diagnostics to the location list.
---
----@param opts table|nil 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
--- Disable diagnostics in the given buffer.
---
----@param bufnr integer|nil Buffer number, or 0 for current buffer. When
---- omitted, disable diagnostics in all buffers.
----@param namespace integer|nil Only disable diagnostics for the given namespace.
+---@param bufnr integer? Buffer number, or 0 for current buffer. When
+--- omitted, disable diagnostics in all buffers.
+---@param namespace integer? Only disable diagnostics for the given namespace.
function M.disable(bufnr, namespace)
vim.validate({ bufnr = { bufnr, 'n', true }, namespace = { namespace, 'n', true } })
if bufnr == nil then
@@ -1578,9 +1951,9 @@ end
--- Enable diagnostics in the given buffer.
---
----@param bufnr integer|nil Buffer number, or 0 for current buffer. When
---- omitted, enable diagnostics in all buffers.
----@param namespace integer|nil Only enable diagnostics for the given namespace.
+---@param bufnr integer? Buffer number, or 0 for current buffer. When
+--- omitted, enable diagnostics in all buffers.
+---@param namespace integer? Only enable diagnostics for the given namespace.
function M.enable(bufnr, namespace)
vim.validate({ bufnr = { bufnr, 'n', true }, namespace = { namespace, 'n', true } })
if bufnr == nil then
@@ -1614,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"
@@ -1626,14 +1998,14 @@ end
---
---@param str string String to parse diagnostics from.
---@param pat string Lua pattern with capture groups.
----@param groups table 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|nil Table of default values for any fields not listed in {groups}.
---- When omitted, numeric values default to 0 and "severity" defaults to
---- ERROR.
----@return Diagnostic|nil: |diagnostic-structure| or `nil` if {pat} fails to match {str}.
+---@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?: |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' },
@@ -1643,14 +2015,16 @@ function M.match(str, pat, groups, severity_map, defaults)
defaults = { defaults, 't', true },
})
+ --- @type table<string,vim.diagnostic.Severity>
severity_map = severity_map or M.severity
- local diagnostic = {}
- local matches = { string.match(str, pat) }
+ local matches = { str:match(pat) } --- @type any[]
if vim.tbl_isempty(matches) then
return
end
+ local diagnostic = {} --- @type type<string,any>
+
for i, match in ipairs(matches) do
local field = groups[i]
if field == 'severity' then
@@ -1658,10 +2032,10 @@ function M.match(str, pat, groups, severity_map, defaults)
elseif field == 'lnum' or field == 'end_lnum' or field == 'col' or field == 'end_col' then
match = assert(tonumber(match)) - 1
end
- diagnostic[field] = match
+ diagnostic[field] = match --- @type any
end
- diagnostic = vim.tbl_extend('keep', diagnostic, defaults or {})
+ diagnostic = vim.tbl_extend('keep', diagnostic, defaults or {}) --- @type vim.Diagnostic
diagnostic.severity = diagnostic.severity or M.severity.ERROR
diagnostic.col = diagnostic.col or 0
diagnostic.end_lnum = diagnostic.end_lnum or diagnostic.lnum
@@ -1679,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 table 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 = {
@@ -1690,7 +2064,7 @@ function M.toqflist(diagnostics)
},
})
- local list = {}
+ local list = {} --- @type table[]
for _, v in ipairs(diagnostics) do
local item = {
bufnr = v.bufnr,
@@ -1720,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 Diagnostic[] array of |diagnostic-structure|
+---@return vim.Diagnostic[]
function M.fromqflist(list)
vim.validate({
list = {
@@ -1730,7 +2104,7 @@ function M.fromqflist(list)
},
})
- local diagnostics = {}
+ local diagnostics = {} --- @type vim.Diagnostic[]
for _, item in ipairs(list) do
if item.valid == 1 then
local lnum = math.max(0, item.lnum - 1)
@@ -1738,7 +2112,7 @@ function M.fromqflist(list)
local end_lnum = item.end_lnum > 0 and (item.end_lnum - 1) or lnum
local end_col = item.end_col > 0 and (item.end_col - 1) or col
local severity = item.type ~= '' and M.severity[item.type] or M.severity.ERROR
- table.insert(diagnostics, {
+ diagnostics[#diagnostics + 1] = {
bufnr = item.bufnr,
lnum = lnum,
col = col,
@@ -1746,7 +2120,7 @@ function M.fromqflist(list)
end_col = end_col,
severity = severity,
message = item.text,
- })
+ }
end
end
return diagnostics
diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua
index c6200f16bb..fba76f93b2 100644
--- a/runtime/lua/vim/filetype.lua
+++ b/runtime/lua/vim/filetype.lua
@@ -267,6 +267,7 @@ local extension = {
crdpro = 'chordpro',
cho = 'chordpro',
chordpro = 'chordpro',
+ ck = 'chuck',
eni = 'cl',
icl = 'clean',
cljx = 'clojure',
@@ -341,7 +342,7 @@ local extension = {
decl = detect.decl,
dec = detect.decl,
dcl = detect_seq(detect.decl, 'clean'),
- def = 'def',
+ def = detect.def,
desc = 'desc',
directory = 'desktop',
desktop = 'desktop',
@@ -363,6 +364,8 @@ local extension = {
d = detect.dtrace,
dts = 'dts',
dtsi = 'dts',
+ dtso = 'dts',
+ its = 'dts',
dylan = 'dylan',
intr = 'dylanintr',
lid = 'dylanlid',
@@ -674,8 +677,6 @@ local extension = {
mmp = 'mmp',
mms = detect.mms,
DEF = 'modula2',
- m2 = 'modula2',
- mi = 'modula2',
lm3 = 'modula3',
mojo = 'mojo',
['🔥'] = 'mojo', -- 🙄
@@ -695,6 +696,7 @@ local extension = {
msql = 'msql',
mu = 'mupad',
mush = 'mush',
+ mustache = 'mustache',
mysql = 'mysql',
n1ql = 'n1ql',
nql = 'n1ql',
@@ -908,6 +910,7 @@ local extension = {
sed = 'sed',
sexp = 'sexplib',
bash = detect.bash,
+ bats = detect.bash,
ebuild = detect.bash,
eclass = detect.bash,
env = detect.sh,
@@ -1341,10 +1344,12 @@ local filename = {
['.hintrc'] = 'jsonc',
['.jsfmtrc'] = 'jsonc',
['.jshintrc'] = 'jsonc',
+ ['.luaurc'] = 'jsonc',
['.swrc'] = 'jsonc',
['.justfile'] = 'just',
Kconfig = 'kconfig',
['Kconfig.debug'] = 'kconfig',
+ ['Config.in'] = 'kconfig',
['lftp.conf'] = 'lftp',
['.lftprc'] = 'lftp',
['/.libao'] = 'libao',
@@ -1393,6 +1398,7 @@ local filename = {
octaverc = 'octave',
['octave.conf'] = 'octave',
opam = 'opam',
+ ['pacman.log'] = 'pacmanlog',
['/etc/pam.conf'] = 'pamconf',
['pam_env.conf'] = 'pamenv',
['.pam_environment'] = 'pamenv',
@@ -1654,6 +1660,11 @@ local pattern = {
['.*/dtrace/.*%.d'] = 'dtrace',
['.*esmtprc'] = 'esmtprc',
['.*Eterm/.*%.cfg'] = 'eterm',
+ ['.*s6.*/up'] = 'execline',
+ ['.*s6.*/down'] = 'execline',
+ ['.*s6.*/run'] = 'execline',
+ ['.*s6.*/finish'] = 'execline',
+ ['s6%-.*'] = 'execline',
['[a-zA-Z0-9].*Dict'] = detect.foam,
['[a-zA-Z0-9].*Dict%..*'] = detect.foam,
['[a-zA-Z].*Properties'] = detect.foam,
@@ -1730,6 +1741,7 @@ local pattern = {
['[jt]sconfig.*%.json'] = 'jsonc',
['[jJ]ustfile'] = 'just',
['Kconfig%..*'] = starsetf('kconfig'),
+ ['Config%.in%..*'] = starsetf('kconfig'),
['.*%.[Ss][Uu][Bb]'] = 'krl',
['lilo%.conf.*'] = starsetf('lilo'),
['.*/etc/logcheck/.*%.d.*/.*'] = starsetf('logcheck'),
@@ -1903,7 +1915,7 @@ local pattern = {
['.*baseq[2-3]/.*%.cfg'] = 'quake',
['.*quake[1-3]/.*%.cfg'] = 'quake',
['.*id1/.*%.cfg'] = 'quake',
- ['.*/queries/.*%.scm'] = 'query', -- tree-sitter queries (Neovim only)
+ ['.*/queries/.*%.scm'] = 'query', -- treesitter queries (Neovim only)
['.*,v'] = 'rcs',
['%.reminders.*'] = starsetf('remind'),
['[rR]akefile.*'] = starsetf('ruby'),
@@ -2070,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
@@ -2260,16 +2273,31 @@ 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.
---
--- The filetype can be detected using one of three methods:
---- 1. Using an existing buffer
---- 2. Using only a file name
---- 3. Using only file contents
+--- 1. Using an existing buffer
+--- 2. Using only a file name
+--- 3. Using only file contents
---
--- Of these, option 1 provides the most accurate result as it uses both the buffer's filename and
--- (optionally) the buffer contents. Options 2 and 3 can be used without an existing buffer, but
@@ -2295,18 +2323,6 @@ end
---
---@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/filetype/detect.lua b/runtime/lua/vim/filetype/detect.lua
index 9d0f1bd3ab..3db4f2bcdc 100644
--- a/runtime/lua/vim/filetype/detect.lua
+++ b/runtime/lua/vim/filetype/detect.lua
@@ -419,6 +419,55 @@ function M.dtrace(_, bufnr)
return 'd'
end
+--- @param bufnr integer
+--- @return boolean
+local function is_modula2(bufnr)
+ return matchregex(nextnonblank(bufnr, 1), [[\<MODULE\s\+\w\+\s*\%(\[.*]\s*\)\=;\|^\s*(\*]])
+end
+
+--- @param bufnr integer
+--- @return string, fun(b: integer)
+local function modula2(bufnr)
+ local dialect = vim.g.modula2_default_dialect or 'pim'
+ local extension = vim.g.modula2_default_extension or ''
+
+ -- ignore unknown dialects or badly formatted tags
+ for _, line in ipairs(getlines(bufnr, 1, 200)) do
+ local matched_dialect, matched_extension = line:match('%(%*!m2(%w+)%+(%w+)%*%)')
+ if not matched_dialect then
+ matched_dialect = line:match('%(%*!m2(%w+)%*%)')
+ end
+ if matched_dialect then
+ if vim.tbl_contains({ 'iso', 'pim', 'r10' }, matched_dialect) then
+ dialect = matched_dialect
+ end
+ if vim.tbl_contains({ 'gm2' }, matched_extension) then
+ extension = matched_extension
+ end
+ break
+ end
+ end
+
+ return 'modula2',
+ function(b)
+ vim.api.nvim_buf_call(b, function()
+ fn['modula2#SetDialect'](dialect, extension)
+ end)
+ end
+end
+
+--- @type vim.filetype.mapfn
+function M.def(_, bufnr)
+ if vim.g.filetype_def == 'modula2' or is_modula2(bufnr) then
+ return modula2(bufnr)
+ end
+
+ if vim.g.filetype_def then
+ return vim.g.filetype_def
+ end
+ return 'def'
+end
+
--- @type vim.filetype.mapfn
function M.e(_, bufnr)
if vim.g.filetype_euphoria then
@@ -906,14 +955,16 @@ end
--- Determine if *.mod is ABB RAPID, LambdaProlog, Modula-2, Modsim III or go.mod
--- @type vim.filetype.mapfn
function M.mod(path, bufnr)
+ if vim.g.filetype_mod == 'modula2' or is_modula2(bufnr) then
+ return modula2(bufnr)
+ end
+
if vim.g.filetype_mod then
return vim.g.filetype_mod
elseif matchregex(path, [[\c\<go\.mod$]]) then
return 'gomod'
elseif is_lprolog(bufnr) then
return 'lprolog'
- elseif matchregex(nextnonblank(bufnr, 1), [[\%(\<MODULE\s\+\w\+\s*;\|^\s*(\*\)]]) then
- return 'modula2'
elseif is_rapid(bufnr) then
return 'rapid'
end
@@ -1530,12 +1581,26 @@ function M.v(_, bufnr)
-- Filetype was already detected
return
end
+ if vim.g.filetype_v then
+ return vim.g.filetype_v
+ end
+ local in_comment = 0
for _, line in ipairs(getlines(bufnr, 1, 200)) do
- if not line:find('^%s*/') then
- if findany(line, { ';%s*$', ';%s*/' }) then
- return 'verilog'
- elseif findany(line, { '%.%s*$', '%.%s*%(%*' }) then
+ if line:find('^%s*/%*') then
+ in_comment = 1
+ end
+ if in_comment == 1 then
+ if line:find('%*/') then
+ in_comment = 0
+ end
+ elseif not line:find('^%s*//') then
+ if
+ line:find('%.%s*$') and not line:find('/[/*]')
+ or line:find('%(%*') and not line:find('/[/*].*%(%*')
+ then
return 'coq'
+ elseif findany(line, { ';%s*$', ';%s*/[/*]' }) then
+ return 'verilog'
end
end
end
@@ -1661,6 +1726,7 @@ local patterns_hashbang = {
['^\\%(rexx\\|regina\\)\\>'] = { 'rexx', { vim_regex = true } },
['^janet\\>'] = { 'janet', { vim_regex = true } },
['^dart\\>'] = { 'dart', { vim_regex = true } },
+ ['^execlineb\\>'] = { 'execline', { vim_regex = true } },
}
---@private
diff --git a/runtime/lua/vim/fs.lua b/runtime/lua/vim/fs.lua
index 22612a7255..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,9 +326,16 @@ 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
+--- backslash (\) characters are converted to forward slashes (/). Environment
--- variables are also expanded.
---
--- Examples:
@@ -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/glob.lua b/runtime/lua/vim/glob.lua
new file mode 100644
index 0000000000..ad4a915a94
--- /dev/null
+++ b/runtime/lua/vim/glob.lua
@@ -0,0 +1,84 @@
+local lpeg = vim.lpeg
+local P, S, V, R, B = lpeg.P, lpeg.S, lpeg.V, lpeg.R, lpeg.B
+local C, Cc, Ct, Cf = lpeg.C, lpeg.Cc, lpeg.Ct, lpeg.Cf
+
+local M = {}
+
+local pathsep = P('/')
+
+--- Parses a raw glob into an |lua-lpeg| pattern.
+---
+--- This uses glob semantics from LSP 3.17.0: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#pattern
+---
+--- Glob patterns can have the following syntax:
+--- - `*` to match one or more characters in a path segment
+--- - `?` to match on one character in a path segment
+--- - `**` to match any number of path segments, including none
+--- - `{}` to group conditions (e.g. `*.{ts,js}` matches TypeScript and JavaScript files)
+--- - `[]` to declare a range of characters to match in a path segment (e.g., `example.[0-9]` to match on `example.0`, `example.1`, …)
+--- - `[!...]` to negate a range of characters to match in a path segment (e.g., `example.[!0-9]` to match on `example.a`, `example.b`, but not `example.0`)
+---
+---@param pattern string The raw glob pattern
+---@return vim.lpeg.Pattern pattern An |lua-lpeg| representation of the pattern
+function M.to_lpeg(pattern)
+ local function class(inv, ranges)
+ local patt = R(unpack(vim.tbl_map(table.concat, ranges)))
+ if inv == '!' then
+ patt = P(1) - patt
+ end
+ return patt
+ end
+
+ local function add(acc, a)
+ return acc + a
+ end
+
+ local function mul(acc, m)
+ return acc * m
+ end
+
+ local function star(stars, after)
+ return (-after * (P(1) - pathsep)) ^ #stars * after
+ end
+
+ local function dstar(after)
+ return (-after * P(1)) ^ 0 * after
+ end
+
+ local p = P({
+ 'Pattern',
+ Pattern = V('Elem') ^ -1 * V('End'),
+ Elem = Cf(
+ (V('DStar') + V('Star') + V('Ques') + V('Class') + V('CondList') + V('Literal'))
+ * (V('Elem') + V('End')),
+ mul
+ ),
+ DStar = (B(pathsep) + -B(P(1)))
+ * P('**')
+ * (pathsep * (V('Elem') + V('End')) + V('End'))
+ / dstar,
+ Star = C(P('*') ^ 1) * (V('Elem') + V('End')) / star,
+ Ques = P('?') * Cc(P(1) - pathsep),
+ Class = P('[')
+ * C(P('!') ^ -1)
+ * Ct(Ct(C(P(1)) * P('-') * C(P(1) - P(']'))) ^ 1 * P(']'))
+ / class,
+ CondList = P('{') * Cf(V('Cond') * (P(',') * V('Cond')) ^ 0, add) * P('}'),
+ -- TODO: '*' inside a {} condition is interpreted literally but should probably have the same
+ -- wildcard semantics it usually has.
+ -- Fixing this is non-trivial because '*' should match non-greedily up to "the rest of the
+ -- pattern" which in all other cases is the entire succeeding part of the pattern, but at the end of a {}
+ -- condition means "everything after the {}" where several other options separated by ',' may
+ -- exist in between that should not be matched by '*'.
+ Cond = Cf((V('Ques') + V('Class') + V('CondList') + (V('Literal') - S(',}'))) ^ 1, mul)
+ + Cc(P(0)),
+ Literal = P(1) / P,
+ End = P(-1) * Cc(P(-1)),
+ })
+
+ local lpeg_pattern = p:match(pattern) --[[@as vim.lpeg.Pattern?]]
+ assert(lpeg_pattern, 'Invalid glob')
+ return lpeg_pattern
+end
+
+return M
diff --git a/runtime/lua/vim/health.lua b/runtime/lua/vim/health.lua
index 6e47a22d03..f6f7abef8f 100644
--- a/runtime/lua/vim/health.lua
+++ b/runtime/lua/vim/health.lua
@@ -1,8 +1,8 @@
local M = {}
-local s_output = {}
+local s_output = {} ---@type string[]
--- Returns the fold text of the current healthcheck section
+--- Returns the fold text of the current healthcheck section
function M.foldtext()
local foldtext = vim.fn.foldtext()
@@ -36,12 +36,13 @@ function M.foldtext()
return vim.b.failedchecks[foldtext] and '+WE' .. foldtext:sub(4) or foldtext
end
--- From a path return a list [{name}, {func}, {type}] representing a healthcheck
+--- @param path string path to search for the healthcheck
+--- @return string[] { name, func, type } representing a healthcheck
local function filepath_to_healthcheck(path)
path = vim.fs.normalize(path)
- local name
- local func
- local filetype
+ local name --- @type string
+ local func --- @type string
+ local filetype --- @type string
if path:find('vim$') then
name = vim.fs.basename(path):gsub('%.vim$', '')
func = 'health#' .. name .. '#check'
@@ -50,10 +51,10 @@ local function filepath_to_healthcheck(path)
local subpath = path:gsub('.*lua/', '')
if vim.fs.basename(subpath) == 'health.lua' then
-- */health.lua
- name = vim.fs.dirname(subpath)
+ name = assert(vim.fs.dirname(subpath))
else
-- */health/init.lua
- name = vim.fs.dirname(vim.fs.dirname(subpath))
+ name = assert(vim.fs.dirname(assert(vim.fs.dirname(subpath))))
end
name = name:gsub('/', '.')
@@ -63,11 +64,12 @@ local function filepath_to_healthcheck(path)
return { name, func, filetype }
end
--- Returns { {name, func, type}, ... } representing healthchecks
+--- @param plugin_names string
+--- @return table<any,string[]> { {name, func, type}, ... } representing healthchecks
local function get_healthcheck_list(plugin_names)
- local healthchecks = {}
- plugin_names = vim.split(plugin_names, ' ')
- for _, p in pairs(plugin_names) do
+ local healthchecks = {} --- @type table<any,string[]>
+ local plugin_names_list = vim.split(plugin_names, ' ')
+ for _, p in pairs(plugin_names_list) do
-- support vim/lsp/health{/init/}.lua as :checkhealth vim.lsp
p = p:gsub('%.', '/')
@@ -83,7 +85,7 @@ local function get_healthcheck_list(plugin_names)
if vim.tbl_count(paths) == 0 then
healthchecks[#healthchecks + 1] = { p, '', '' } -- healthcheck not found
else
- local unique_paths = {}
+ local unique_paths = {} --- @type table<string, boolean>
for _, v in pairs(paths) do
unique_paths[v] = true
end
@@ -100,10 +102,11 @@ local function get_healthcheck_list(plugin_names)
return healthchecks
end
--- Returns {name: [func, type], ..} representing healthchecks
+--- @param plugin_names string
+--- @return table<string, string[]> {name: [func, type], ..} representing healthchecks
local function get_healthcheck(plugin_names)
local health_list = get_healthcheck_list(plugin_names)
- local healthchecks = {}
+ local healthchecks = {} --- @type table<string, string[]>
for _, c in pairs(health_list) do
if c[1] ~= 'vim' then
healthchecks[c[1]] = { c[2], c[3] }
@@ -113,7 +116,11 @@ local function get_healthcheck(plugin_names)
return healthchecks
end
--- Indents lines *except* line 1 of a string if it contains newlines.
+--- Indents lines *except* line 1 of a string if it contains newlines.
+---
+--- @param s string
+--- @param columns integer
+--- @return string
local function indent_after_line1(s, columns)
local lines = vim.split(s, '\n')
local indent = string.rep(' ', columns)
@@ -123,13 +130,20 @@ local function indent_after_line1(s, columns)
return table.concat(lines, '\n')
end
--- Changes ':h clipboard' to ':help |clipboard|'.
+--- Changes ':h clipboard' to ':help |clipboard|'.
+---
+--- @param s string
+--- @return string
local function help_to_link(s)
return vim.fn.substitute(s, [[\v:h%[elp] ([^|][^"\r\n ]+)]], [[:help |\1|]], [[g]])
end
--- Format a message for a specific report item.
--- Variable args: Optional advice (string or list)
+--- Format a message for a specific report item.
+---
+--- @param status string
+--- @param msg string
+--- @param ... string|string[] Optional advice
+--- @return string
local function format_report_message(status, msg, ...)
local output = '- ' .. status
if status ~= '' then
@@ -159,42 +173,54 @@ local function format_report_message(status, msg, ...)
return help_to_link(output)
end
+--- @param output string
local function collect_output(output)
vim.list_extend(s_output, vim.split(output, '\n'))
end
--- Starts a new report.
+--- Starts a new report.
+---
+--- @param name string
function M.start(name)
local input = string.format('\n%s ~', name)
collect_output(input)
end
--- Reports a message in the current section.
+--- Reports a message in the current section.
+---
+--- @param msg string
function M.info(msg)
local input = format_report_message('', msg)
collect_output(input)
end
--- Reports a successful healthcheck.
+--- Reports a successful healthcheck.
+---
+--- @param msg string
function M.ok(msg)
local input = format_report_message('OK', msg)
collect_output(input)
end
--- Reports a health warning.
--- ...: Optional advice (string or table)
+--- Reports a health warning.
+---
+--- @param msg string
+--- @param ... string|string[] Optional advice
function M.warn(msg, ...)
local input = format_report_message('WARNING', msg, ...)
collect_output(input)
end
--- Reports a failed healthcheck.
--- ...: Optional advice (string or table)
+--- Reports a failed healthcheck.
+---
+--- @param msg string
+--- @param ... string|string[] Optional advice
function M.error(msg, ...)
local input = format_report_message('ERROR', msg, ...)
collect_output(input)
end
+--- @param type string
local function deprecate(type)
local before = string.format('vim.health.report_%s()', type)
local after = string.format('vim.health.%s()', type)
@@ -206,27 +232,149 @@ local function deprecate(type)
vim.print('Running healthchecks...')
end
+--- @deprecated
+--- @param name string
function M.report_start(name)
deprecate('start')
M.start(name)
end
+
+--- @deprecated
+--- @param msg string
function M.report_info(msg)
deprecate('info')
M.info(msg)
end
+
+--- @deprecated
+--- @param msg string
function M.report_ok(msg)
deprecate('ok')
M.ok(msg)
end
+
+--- @deprecated
+--- @param msg string
function M.report_warn(msg, ...)
deprecate('warn')
M.warn(msg, ...)
end
+
+--- @deprecated
+--- @param msg string
function M.report_error(msg, ...)
deprecate('error')
M.error(msg, ...)
end
+function M.provider_disabled(provider)
+ local loaded_var = 'loaded_' .. provider .. '_provider'
+ local v = vim.g[loaded_var]
+ if v == 0 then
+ M.info('Disabled (' .. loaded_var .. '=' .. v .. ').')
+ return true
+ end
+ return false
+end
+
+-- Handler for s:system() function.
+local function system_handler(self, _, data, event)
+ if event == 'stderr' then
+ if self.add_stderr_to_output then
+ self.output = self.output .. table.concat(data, '')
+ else
+ self.stderr = self.stderr .. table.concat(data, '')
+ end
+ elseif event == 'stdout' then
+ self.output = self.output .. table.concat(data, '')
+ end
+end
+
+-- Attempts to construct a shell command from an args list.
+-- Only for display, to help users debug a failed command.
+local function shellify(cmd)
+ if type(cmd) ~= 'table' then
+ return cmd
+ end
+ local escaped = {}
+ for i, v in ipairs(cmd) do
+ if v:match('[^A-Za-z_/.-]') then
+ escaped[i] = vim.fn.shellescape(v)
+ else
+ escaped[i] = v
+ end
+ end
+ return table.concat(escaped, ' ')
+end
+
+function M.cmd_ok(cmd)
+ local out = vim.fn.system(cmd)
+ return vim.v.shell_error == 0, out
+end
+
+--- Run a system command and timeout after 30 seconds.
+---
+--- @param cmd table List of command arguments to execute
+--- @param args? table Optional arguments:
+--- - stdin (string): Data to write to the job's stdin
+--- - stderr (boolean): Append stderr to stdout
+--- - ignore_error (boolean): If true, ignore error output
+--- - timeout (number): Number of seconds to wait before timing out (default 30)
+function M.system(cmd, args)
+ args = args or {}
+ local stdin = args.stdin or ''
+ local stderr = vim.F.if_nil(args.stderr, false)
+ local ignore_error = vim.F.if_nil(args.ignore_error, false)
+
+ local shell_error_code = 0
+ local opts = {
+ add_stderr_to_output = stderr,
+ output = '',
+ stderr = '',
+ on_stdout = system_handler,
+ on_stderr = system_handler,
+ on_exit = function(_, data)
+ shell_error_code = data
+ end,
+ }
+ local jobid = vim.fn.jobstart(cmd, opts)
+
+ if jobid < 1 then
+ local message =
+ string.format('Command error (job=%d): %s (in %s)', jobid, shellify(cmd), vim.loop.cwd())
+ error(message)
+ return opts.output, 1
+ end
+
+ if stdin:find('^%s$') then
+ vim.fn.chansend(jobid, stdin)
+ end
+
+ local res = vim.fn.jobwait({ jobid }, vim.F.if_nil(args.timeout, 30) * 1000)
+ if res[1] == -1 then
+ error('Command timed out: ' .. shellify(cmd))
+ vim.fn.jobstop(jobid)
+ elseif shell_error_code ~= 0 and not ignore_error then
+ local emsg = string.format(
+ 'Command error (job=%d, exit code %d): %s (in %s)',
+ jobid,
+ shell_error_code,
+ shellify(cmd),
+ vim.loop.cwd()
+ )
+ if opts.output:find('%S') then
+ emsg = string.format('%s\noutput: %s', emsg, opts.output)
+ end
+ if opts.stderr:find('%S') then
+ emsg = string.format('%s\nstderr: %s', emsg, opts.stderr)
+ end
+ error(emsg)
+ end
+
+ -- return opts.output
+ return vim.trim(vim.fn.system(cmd)), shell_error_code
+end
+
local path2name = function(path)
if path:match('%.lua$') then
-- Lua: transform "../lua/vim/lsp/health.lua" into "vim.lsp"
@@ -251,7 +399,7 @@ local path2name = function(path)
end
local PATTERNS = { '/autoload/health/*.vim', '/lua/**/**/health.lua', '/lua/**/**/health/init.lua' }
--- :checkhealth completion function used by ex_getln.c get_healthcheck_names()
+--- :checkhealth completion function used by cmdexpand.c get_healthcheck_names()
M._complete = function()
local names = vim.tbl_flatten(vim.tbl_map(function(pattern)
return vim.tbl_map(path2name, vim.api.nvim_get_runtime_file(pattern, true))
@@ -266,16 +414,25 @@ M._complete = function()
return vim.tbl_keys(unique)
end
--- Runs the specified healthchecks.
--- Runs all discovered healthchecks if plugin_names is empty.
-function M._check(plugin_names)
+--- Runs the specified healthchecks.
+--- Runs all discovered healthchecks if plugin_names is empty.
+---
+--- @param mods string command modifiers that affect splitting a window.
+--- @param plugin_names string glob of plugin names, split on whitespace. For example, using
+--- `:checkhealth vim.* nvim` will healthcheck `vim.lsp`, `vim.treesitter`
+--- and `nvim` modules.
+function M._check(mods, plugin_names)
local healthchecks = plugin_names == '' and get_healthcheck('*') or get_healthcheck(plugin_names)
- -- Create buffer and open in a tab, unless this is the default buffer when Nvim starts.
local emptybuf = vim.fn.bufnr('$') == 1 and vim.fn.getline(1) == '' and 1 == vim.fn.line('$')
- local mod = emptybuf and 'buffer' or 'tab sbuffer'
+
+ -- When no command modifiers are used:
+ -- - If the current buffer is empty, open healthcheck directly.
+ -- - If not specified otherwise open healthcheck in a tab.
+ local buf_cmd = #mods > 0 and (mods .. ' sbuffer') or emptybuf and 'buffer' or 'tab sbuffer'
+
local bufnr = vim.api.nvim_create_buf(true, true)
- vim.cmd(mod .. ' ' .. bufnr)
+ vim.cmd(buf_cmd .. ' ' .. bufnr)
if vim.fn.bufexists('health://') == 1 then
vim.cmd.bwipe('health://')
@@ -283,7 +440,8 @@ function M._check(plugin_names)
vim.cmd.file('health://')
vim.cmd.setfiletype('checkhealth')
- if healthchecks == nil or next(healthchecks) == nil then
+ -- This should only happen when doing `:checkhealth vim`
+ if next(healthchecks) == nil then
vim.fn.setline(1, 'ERROR: No healthchecks found.')
return
end
@@ -319,7 +477,7 @@ function M._check(plugin_names)
local header = { string.rep('=', 78), name .. ': ' .. func, '' }
-- remove empty line after header from report_start
if s_output[1] == '' then
- local tmp = {}
+ local tmp = {} ---@type string[]
for i = 2, #s_output do
tmp[#tmp + 1] = s_output[i]
end
@@ -339,4 +497,11 @@ function M._check(plugin_names)
vim.print('')
end
+local fn_bool = function(key)
+ return function(...)
+ return vim.fn[key](...) == 1
+ end
+end
+M.executable = fn_bool('executable')
+
return M
diff --git a/runtime/lua/vim/highlight.lua b/runtime/lua/vim/highlight.lua
index fc2fd43c97..effe280dee 100644
--- a/runtime/lua/vim/highlight.lua
+++ b/runtime/lua/vim/highlight.lua
@@ -1,4 +1,4 @@
----@defgroup vim.highlight
+---@brief
---
--- Nvim includes a function for highlighting a selection on yank.
---
@@ -19,18 +19,19 @@
--- ```vim
--- au TextYankPost * silent! lua vim.highlight.on_yank {on_visual=false}
--- ```
+---
local api = vim.api
local M = {}
--- Table with default priorities used for highlighting:
---- - `syntax`: `50`, used for standard syntax highlighting
---- - `treesitter`: `100`, used for tree-sitter-based highlighting
---- - `semantic_tokens`: `125`, used for LSP semantic token highlighting
---- - `diagnostics`: `150`, used for code analysis such as diagnostics
---- - `user`: `200`, used for user-triggered highlights such as LSP document
---- symbols or `on_yank` autocommands
+--- - `syntax`: `50`, used for standard syntax highlighting
+--- - `treesitter`: `100`, used for treesitter-based highlighting
+--- - `semantic_tokens`: `125`, used for LSP semantic token highlighting
+--- - `diagnostics`: `150`, used for code analysis such as diagnostics
+--- - `user`: `200`, used for user-triggered highlights such as LSP document
+--- symbols or `on_yank` autocommands
M.priorities = {
syntax = 50,
treesitter = 100,
@@ -55,6 +56,7 @@ function M.range(bufnr, ns, higroup, start, finish, opts)
local regtype = opts.regtype or 'v'
local inclusive = opts.inclusive or false
local priority = opts.priority or M.priorities.user
+ local scoped = opts._scoped or false
-- TODO: in case of 'v', 'V' (not block), this should calculate equivalent
-- bounds (row, col, end_row, end_col) as multiline regions are natively
@@ -72,12 +74,14 @@ function M.range(bufnr, ns, higroup, start, finish, opts)
end_col = cols[2],
priority = priority,
strict = false,
+ scoped = scoped,
})
end
end
local yank_ns = api.nvim_create_namespace('hlyank')
local yank_timer
+local yank_cancel
--- Highlight the yanked text
---
@@ -120,24 +124,29 @@ function M.on_yank(opts)
local higroup = opts.higroup or 'IncSearch'
local timeout = opts.timeout or 150
- local bufnr = api.nvim_get_current_buf()
- api.nvim_buf_clear_namespace(bufnr, yank_ns, 0, -1)
+ local bufnr = vim.api.nvim_get_current_buf()
+ local winid = vim.api.nvim_get_current_win()
if yank_timer then
yank_timer:close()
+ yank_cancel()
end
M.range(bufnr, yank_ns, higroup, "'[", "']", {
regtype = event.regtype,
inclusive = event.inclusive,
priority = opts.priority or M.priorities.user,
+ _scoped = true,
})
+ vim.api.nvim_win_add_ns(winid, yank_ns)
- yank_timer = vim.defer_fn(function()
+ yank_cancel = function()
yank_timer = nil
- if api.nvim_buf_is_valid(bufnr) then
- api.nvim_buf_clear_namespace(bufnr, yank_ns, 0, -1)
- end
- end, timeout)
+ yank_cancel = nil
+ pcall(vim.api.nvim_buf_clear_namespace, bufnr, yank_ns, 0, -1)
+ pcall(vim.api.nvim_win_remove_ns, winid, yank_ns)
+ end
+
+ yank_timer = vim.defer_fn(yank_cancel, timeout)
end
return M
diff --git a/runtime/lua/vim/iter.lua b/runtime/lua/vim/iter.lua
index 874bdfb437..a37b7f7858 100644
--- a/runtime/lua/vim/iter.lua
+++ b/runtime/lua/vim/iter.lua
@@ -1,7 +1,7 @@
----@defgroup vim.iter
+--- @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,10 +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
@@ -76,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
@@ -112,6 +119,35 @@ local function sanitize(t)
return t
end
+--- Flattens a single list-like table. Errors if it attempts to flatten a
+--- dict-like table
+---@param v table table which should be flattened
+---@param max_depth number depth to which the table should be flattened
+---@param depth number current iteration depth
+---@param result table output table that contains flattened result
+---@return table|nil flattened table if it can be flattened, otherwise nil
+local function flatten(v, max_depth, depth, result)
+ if depth < max_depth and type(v) == 'table' then
+ local i = 0
+ for _ in pairs(v) do
+ i = i + 1
+
+ if v[i] == nil then
+ -- short-circuit: this is not a list like table
+ return nil
+ end
+
+ if flatten(v[i], max_depth, depth + 1, result) == nil then
+ return nil
+ end
+ end
+ else
+ result[#result + 1] = v
+ end
+
+ return result
+end
+
--- Determine if the current iterator stage should continue.
---
--- If any arguments are passed to this function, then return those arguments
@@ -152,11 +188,11 @@ end
--- local bufs = vim.iter(vim.api.nvim_list_bufs()):filter(vim.api.nvim_buf_is_loaded)
--- ```
---
----@param f function(...):bool Takes all values returned from the previous stage
---- in the pipeline and returns false or nil if the
---- current iterator element should be removed.
+---@param f fun(...):boolean Takes all values returned from the previous stage
+--- 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 ...
@@ -165,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
@@ -179,6 +215,55 @@ function ListIter.filter(self, f)
return self
end
+--- Flattens a |list-iterator|, un-nesting nested values up to the given {depth}.
+--- Errors if it attempts to flatten a dict-like value.
+---
+--- Examples:
+---
+--- ```lua
+--- vim.iter({ 1, { 2 }, { { 3 } } }):flatten():totable()
+--- -- { 1, 2, { 3 } }
+---
+--- vim.iter({1, { { a = 2 } }, { 3 } }):flatten():totable()
+--- -- { 1, { a = 2 }, 3 }
+---
+--- vim.iter({ 1, { { a = 2 } }, { 3 } }):flatten(math.huge):totable()
+--- -- error: attempt to flatten a dict-like table
+--- ```
+---
+---@param depth? number Depth to which |list-iterator| should be flattened
+--- (defaults to 1)
+---@return Iter
+---@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(depth)
+ depth = depth or 1
+ local inc = self._head < self._tail and 1 or -1
+ local target = {}
+
+ for i = self._head, self._tail - inc, inc do
+ local flattened = flatten(self._table[i], depth, 0, {})
+
+ -- exit early if we try to flatten a dict-like table
+ if flattened == nil then
+ error('flatten() requires a list-like table')
+ end
+
+ for _, v in pairs(flattened) do
+ target[#target + 1] = v
+ end
+ end
+
+ self._head = 1
+ self._tail = #target + 1
+ self._table = target
+ return self
+end
+
--- Maps the items of an iterator pipeline to the values returned by `f`.
---
--- If the map function returns nil, the value is filtered from the iterator.
@@ -195,13 +280,13 @@ end
--- -- { 6, 12 }
--- ```
---
----@param f function(...):any Mapping function. Takes all values returned from
---- the previous stage in the pipeline as arguments
---- and returns one or more new values, which are used
---- in the next pipeline stage. Nil return values
---- are filtered from the output.
+---@param f fun(...):any Mapping function. Takes all values returned from
+--- the previous stage in the pipeline as arguments
+--- and returns one or more new values, which are used
+--- 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
@@ -245,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
@@ -263,10 +348,10 @@ end
---
--- For functions with side effects. To modify the values in the iterator, use |Iter:map()|.
---
----@param f function(...) 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)
+---@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(f)
local function fn(...)
if select(1, ...) ~= nil then
f(...)
@@ -278,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]))
@@ -311,7 +396,7 @@ end
---
---
---@return table
-function Iter.totable(self)
+function Iter:totable()
local t = {}
while true do
@@ -326,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
@@ -356,6 +441,18 @@ function ListIter.totable(self)
return self._table
end
+--- Collect the iterator into a delimited string.
+---
+--- Each element in the iterator is joined into a string separated by {delim}.
+---
+--- Consumes the iterator.
+---
+--- @param delim string Delimiter
+--- @return string
+function Iter:join(delim)
+ return table.concat(self:totable(), delim)
+end
+
--- Folds ("reduces") an iterator into a single value.
---
--- Examples:
@@ -375,9 +472,9 @@ end
---@generic A
---
---@param init A Initial value of the accumulator.
----@param f function(acc:A, ...):A Accumulation function.
+---@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
@@ -394,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
@@ -420,13 +517,13 @@ end
--- ```
---
---@return any
-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
@@ -448,13 +545,12 @@ end
--- ```
---
---@return Iter
-function Iter.rev(self)
+function Iter:rev()
error('rev() requires a list-like table')
- return self
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
@@ -477,12 +573,12 @@ end
--- ```
---
---@return any
-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
@@ -509,9 +605,9 @@ end
--- -- 12
---
--- ```
----
+---@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)
@@ -555,13 +651,15 @@ end
---
---@see Iter.find
---
+---@param f any
---@return any
-function Iter.rfind(self, f) -- luacheck: no unused args
+---@diagnostic disable-next-line: unused-local
+function Iter:rfind(f) -- luacheck: no unused args
error('rfind() requires a list-like table')
end
---@private
-function ListIter.rfind(self, f) -- luacheck: no unused args
+function ListIter:rfind(f)
if type(f) ~= 'function' then
local val = f
f = function(v)
@@ -580,6 +678,41 @@ function ListIter.rfind(self, f) -- luacheck: no unused args
self._head = self._tail
end
+--- Transforms an iterator to yield only the first n values.
+---
+--- Example:
+---
+--- ```lua
+--- local it = vim.iter({ 1, 2, 3, 4 }):take(2)
+--- it:next()
+--- -- 1
+--- it:next()
+--- -- 2
+--- it:next()
+--- -- nil
+--- ```
+---
+---@param n integer
+---@return Iter
+function Iter:take(n)
+ local next = self.next
+ local i = 0
+ self.next = function()
+ if i < n then
+ i = i + 1
+ return next(self)
+ end
+ end
+ return self
+end
+
+---@private
+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
+end
+
--- "Pops" a value from a |list-iterator| (gets the last value and decrements the tail).
---
--- Example:
@@ -593,11 +726,12 @@ end
--- ```
---
---@return any
-function Iter.nextback(self) -- luacheck: no unused args
+function Iter:nextback()
error('nextback() requires a list-like table')
end
-function ListIter.nextback(self)
+--- @nodoc
+function ListIter:nextback()
if self._head ~= self._tail then
local inc = self._head < self._tail and 1 or -1
self._tail = self._tail - inc
@@ -622,11 +756,12 @@ end
--- ```
---
---@return any
-function Iter.peekback(self) -- luacheck: no unused args
+function Iter:peekback()
error('peekback() requires a list-like table')
end
-function ListIter.peekback(self)
+---@nodoc
+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]
@@ -647,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
@@ -655,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
@@ -678,13 +813,13 @@ end
---
---@param n number Number of values to skip.
---@return Iter
-function Iter.skipback(self, n) -- luacheck: no unused args
+---@diagnostic disable-next-line: unused-local
+function Iter:skipback(n) -- luacheck: no unused args
error('skipback() requires a list-like table')
- return self
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
@@ -709,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
@@ -731,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
@@ -744,22 +879,22 @@ end
---@param first number
---@param last number
---@return Iter
-function Iter.slice(self, first, last) -- luacheck: no unused args
+---@diagnostic disable-next-line: unused-local
+function Iter:slice(first, last) -- luacheck: no unused args
error('slice() requires a list-like table')
- return self
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
--- Returns true if any of the items in the iterator match the given predicate.
---
----@param pred function(...):bool 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)
+---@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(pred)
local any = false
--- Use a closure to handle var args returned from iterator
@@ -780,10 +915,10 @@ end
--- Returns true if all items in the iterator match the given predicate.
---
----@param pred function(...):bool 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)
+---@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(pred)
local all = true
local function fn(...)
@@ -818,7 +953,7 @@ end
--- ```
---
---@return any
-function Iter.last(self)
+function Iter:last()
local last = self:next()
local cur = self:next()
while cur do
@@ -829,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
@@ -865,7 +1000,7 @@ end
--- ```
---
---@return Iter
-function Iter.enumerate(self)
+function Iter:enumerate()
local i = 0
return self:map(function(...)
i = i + 1
@@ -874,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]
@@ -978,9 +1113,9 @@ end
---
---@see |Iter:filter()|
---
----@param f function(...):bool Filter function. Accepts the current iterator or table values as
---- arguments and returns true if those values should be kept in the
---- final table
+---@param f fun(...):boolean Filter function. Accepts the current iterator or table values as
+--- arguments and returns true if those values should be kept in the
+--- final table
---@param src table|function Table or iterator function to filter
---@return table
function M.filter(f, src, ...)
@@ -996,18 +1131,17 @@ end
---
---@see |Iter:map()|
---
----@param f function(...):?any Map function. Accepts the current iterator or table values as
---- arguments and returns one or more new values. Nil values are removed
---- from the final table.
+---@param f fun(...): any? Map function. Accepts the current iterator or table values as
+--- arguments and returns one or more new values. Nil values are removed
+--- from the final table.
---@param src table|function Table or iterator function to filter
---@return table
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/keymap.lua b/runtime/lua/vim/keymap.lua
index bdea95f9ab..84e9b4197d 100644
--- a/runtime/lua/vim/keymap.lua
+++ b/runtime/lua/vim/keymap.lua
@@ -44,7 +44,7 @@ function keymap.set(mode, lhs, rhs, opts)
opts = { opts, 't', true },
})
- opts = vim.deepcopy(opts or {})
+ opts = vim.deepcopy(opts or {}, true)
---@cast mode string[]
mode = type(mode) == 'string' and { mode } or mode
@@ -90,6 +90,8 @@ end
--- vim.keymap.del({'n', 'i', 'v'}, '<leader>w', { buffer = 5 })
--- ```
---
+---@param modes string|string[]
+---@param lhs string
---@param opts table|nil A table of optional arguments:
--- - "buffer": (integer|boolean) Remove a mapping from the given buffer.
--- When `0` or `true`, use the current buffer.
diff --git a/runtime/lua/vim/loader.lua b/runtime/lua/vim/loader.lua
index ee01111337..d3d8948654 100644
--- a/runtime/lua/vim/loader.lua
+++ b/runtime/lua/vim/loader.lua
@@ -1,24 +1,46 @@
+local fs = vim.fs -- "vim.fs" is a dependency, so must be loaded early.
local uv = vim.uv
-local uri_encode = vim.uri_encode
+local uri_encode = vim.uri_encode --- @type function
--- @type (fun(modename: string): fun()|string)[]
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?}?>
@@ -28,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 = {},
@@ -63,7 +85,7 @@ function Loader.get_hash(path)
end
local function normalize(path)
- return vim.fs.normalize(path, { expand_env = false })
+ return fs.normalize(path, { expand_env = false })
end
--- Gets the rtp excluding after directories.
@@ -189,7 +211,6 @@ function Loader.loader_lib(modname)
local sysname = uv.os_uname().sysname:lower() or ''
local is_win = sysname:find('win', 1, true) and not sysname:find('darwin', 1, true)
local ret = M.find(modname, { patterns = is_win and { '.dll' } or { '.so' } })[1]
- ---@type function?, string?
if ret then
-- Making function name in Lua 5.1 (see src/loadlib.c:mkfuncname) is
-- a) strip prefix up to and including the first dash, if any
@@ -207,15 +228,13 @@ end
--- `loadfile` using the cache
--- Note this has the mode and env arguments which is supported by LuaJIT and is 5.1 compatible.
---@param filename? string
----@param mode? "b"|"t"|"bt"
+---@param _mode? "b"|"t"|"bt"
---@param env? table
---@return function?, string? error_message
---@private
--- luacheck: ignore 312
-function Loader.loadfile(filename, mode, env)
+function Loader.loadfile(filename, _mode, env)
-- ignore mode, since we byte-compile the Lua source files
- mode = nil
- return Loader.load(normalize(filename), { mode = mode, env = env })
+ return Loader.load(normalize(filename), { env = env })
end
--- Checks whether two cache hashes are the same based on:
@@ -272,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|nil) 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[] (list) 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 {}
@@ -308,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
@@ -432,7 +442,7 @@ end
function Loader.lsmod(path)
if not Loader._indexed[path] then
Loader._indexed[path] = {}
- for name, t in vim.fs.dir(path .. '/lua') do
+ for name, t in fs.dir(path .. '/lua') do
local modpath = path .. '/lua/' .. name
-- HACK: type is not always returned due to a bug in luv
t = t or Loader.get_hash(modpath).type
@@ -474,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 261a3aa5de..d5c376ba44 100644
--- a/runtime/lua/vim/lsp.lua
+++ b/runtime/lua/vim/lsp.lua
@@ -1,39 +1,37 @@
----@diagnostic disable: invisible
-local default_handlers = require('vim.lsp.handlers')
-local log = require('vim.lsp.log')
-local lsp_rpc = require('vim.lsp.rpc')
-local protocol = require('vim.lsp.protocol')
-local ms = protocol.Methods
-local util = require('vim.lsp.util')
-local sync = require('vim.lsp.sync')
-local semantic_tokens = require('vim.lsp.semantic_tokens')
-
local api = vim.api
-local nvim_err_writeln, nvim_buf_get_lines, nvim_command, nvim_exec_autocmds =
- api.nvim_err_writeln, api.nvim_buf_get_lines, api.nvim_command, api.nvim_exec_autocmds
-local uv = vim.uv
local tbl_isempty, tbl_extend = vim.tbl_isempty, vim.tbl_extend
local validate = vim.validate
local if_nil = vim.F.if_nil
-local lsp = {
- protocol = protocol,
-
- handlers = default_handlers,
-
- buf = require('vim.lsp.buf'),
- diagnostic = require('vim.lsp.diagnostic'),
- codelens = require('vim.lsp.codelens'),
- inlay_hint = require('vim.lsp.inlay_hint'),
- semantic_tokens = semantic_tokens,
- util = util,
+local lsp = vim._defer_require('vim.lsp', {
+ _changetracking = ..., --- @module 'vim.lsp._changetracking'
+ _completion = ..., --- @module 'vim.lsp._completion'
+ _dynamic = ..., --- @module 'vim.lsp._dynamic'
+ _snippet_grammar = ..., --- @module 'vim.lsp._snippet_grammar'
+ _tagfunc = ..., --- @module 'vim.lsp._tagfunc'
+ _watchfiles = ..., --- @module 'vim.lsp._watchfiles'
+ buf = ..., --- @module 'vim.lsp.buf'
+ client = ..., --- @module 'vim.lsp.client'
+ codelens = ..., --- @module 'vim.lsp.codelens'
+ diagnostic = ..., --- @module 'vim.lsp.diagnostic'
+ handlers = ..., --- @module 'vim.lsp.handlers'
+ inlay_hint = ..., --- @module 'vim.lsp.inlay_hint'
+ log = ..., --- @module 'vim.lsp.log'
+ protocol = ..., --- @module 'vim.lsp.protocol'
+ rpc = ..., --- @module 'vim.lsp.rpc'
+ semantic_tokens = ..., --- @module 'vim.lsp.semantic_tokens'
+ util = ..., --- @module 'vim.lsp.util'
+})
- -- Allow raw RPC access.
- rpc = lsp_rpc,
+local log = lsp.log
+local protocol = lsp.protocol
+local ms = protocol.Methods
+local util = lsp.util
+local changetracking = lsp._changetracking
- -- Export these directly from rpc.
- rpc_response_error = lsp_rpc.rpc_response_error,
-}
+-- Export these directly from rpc.
+---@nodoc
+lsp.rpc_response_error = lsp.rpc.rpc_response_error
-- maps request name to the required server_capability in the client.
lsp._request_name_to_capability = {
@@ -69,14 +67,6 @@ lsp._request_name_to_capability = {
-- TODO improve handling of scratch buffers with LSP attached.
---- Concatenates and writes a list of strings to the Vim error buffer.
----
----@param ... string List to write to the buffer
-local function err_message(...)
- nvim_err_writeln(table.concat(vim.tbl_flatten({ ... })))
- nvim_command('redraw')
-end
-
--- Returns the buffer number for the given {bufnr}.
---
---@param bufnr (integer|nil) Buffer number to resolve. Defaults to current buffer
@@ -102,57 +92,28 @@ function lsp._unsupported_method(method)
return msg
end
---- Checks whether a given path is a directory.
----
----@param filename (string) path to check
----@return boolean # true if {filename} exists and is a directory, false otherwise
-local function is_dir(filename)
- validate({ filename = { filename, 's' } })
- local stat = uv.fs_stat(filename)
- return stat and stat.type == 'directory' or false
-end
-
local wait_result_reason = { [-1] = 'timeout', [-2] = 'interrupted', [-3] = 'error' }
-local valid_encodings = {
- ['utf-8'] = 'utf-8',
- ['utf-16'] = 'utf-16',
- ['utf-32'] = 'utf-32',
- ['utf8'] = 'utf-8',
- ['utf16'] = 'utf-16',
- ['utf32'] = 'utf-32',
- UTF8 = 'utf-8',
- UTF16 = 'utf-16',
- UTF32 = 'utf-32',
-}
-
local format_line_ending = {
['unix'] = '\n',
['dos'] = '\r\n',
['mac'] = '\r',
}
+---@private
---@param bufnr (number)
---@return string
-local function buf_get_line_ending(bufnr)
+function lsp._buf_get_line_ending(bufnr)
return format_line_ending[vim.bo[bufnr].fileformat] or '\n'
end
-local client_index = 0
---- Returns a new, unused client id.
----
----@return integer client_id
-local function next_client_id()
- client_index = client_index + 1
- return client_index
-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' },
@@ -182,136 +143,36 @@ local function for_each_buffer_client(bufnr, fn, restrict_client_ids)
end
end
+local client_errors_base = table.maxn(lsp.rpc.client_errors)
+local client_errors_offset = 0
+
+local function client_error(name)
+ client_errors_offset = client_errors_offset + 1
+ 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|.
--- Can be used to look up the string from a the number or the number
--- from the string.
--- @nodoc
lsp.client_errors = tbl_extend(
'error',
- lsp_rpc.client_errors,
- vim.tbl_add_reverse_lookup({
- BEFORE_INIT_CALLBACK_ERROR = table.maxn(lsp_rpc.client_errors) + 1,
- ON_INIT_CALLBACK_ERROR = table.maxn(lsp_rpc.client_errors) + 2,
- ON_ATTACH_ERROR = table.maxn(lsp_rpc.client_errors) + 3,
- })
+ lsp.rpc.client_errors,
+ client_error('BEFORE_INIT_CALLBACK_ERROR'),
+ client_error('ON_INIT_CALLBACK_ERROR'),
+ client_error('ON_ATTACH_ERROR'),
+ client_error('ON_EXIT_CALLBACK_ERROR')
)
---- Normalizes {encoding} to valid LSP encoding names.
----
----@param encoding (string) Encoding to normalize
----@return string # normalized encoding name
-local function validate_encoding(encoding)
- validate({
- encoding = { encoding, 's' },
- })
- return valid_encodings[encoding:lower()]
- or error(
- string.format(
- "Invalid offset encoding %q. Must be one of: 'utf-8', 'utf-16', 'utf-32'",
- encoding
- )
- )
-end
-
----@internal
---- Parses a command invocation into the command itself and its args. If there
---- are no arguments, an empty table is returned as the second argument.
----
----@param input string[]
----@return string command, string[] args #the command and arguments
-function lsp._cmd_parts(input)
- validate({
- cmd = {
- input,
- function()
- return vim.tbl_islist(input)
- end,
- 'list',
- },
- })
-
- local cmd = input[1]
- local cmd_args = {}
- -- Don't mutate our input.
- for i, v in ipairs(input) do
- validate({ ['cmd argument'] = { v, 's' } })
- if i > 1 then
- table.insert(cmd_args, v)
- end
- end
- return cmd, cmd_args
-end
-
---- Augments a validator function with support for optional (nil) values.
----
----@param fn (fun(v): boolean) The original validator function; should return a
----bool.
----@return fun(v): boolean # The augmented function. Also returns true if {v} is
----`nil`.
-local function optional_validator(fn)
- return function(v)
- return v == nil or fn(v)
- end
-end
-
---- Validates a client configuration as given to |vim.lsp.start_client()|.
----
----@param config (lsp.ClientConfig)
----@return (string|fun(dispatchers:table):table) Command
----@return string[] Arguments
----@return string Encoding.
-local function validate_client_config(config)
- validate({
- config = { config, 't' },
- })
- validate({
- handlers = { config.handlers, 't', true },
- capabilities = { config.capabilities, 't', true },
- cmd_cwd = { config.cmd_cwd, optional_validator(is_dir), 'directory' },
- cmd_env = { config.cmd_env, 't', true },
- detached = { config.detached, 'b', true },
- name = { config.name, 's', true },
- on_error = { config.on_error, 'f', true },
- on_exit = { config.on_exit, 'f', true },
- on_init = { config.on_init, 'f', true },
- settings = { config.settings, 't', true },
- commands = { config.commands, 't', true },
- before_init = { config.before_init, 'f', true },
- offset_encoding = { config.offset_encoding, 's', true },
- flags = { config.flags, 't', true },
- get_language_id = { config.get_language_id, 'f', true },
- })
- assert(
- (
- not config.flags
- or not config.flags.debounce_text_changes
- or type(config.flags.debounce_text_changes) == 'number'
- ),
- 'flags.debounce_text_changes must be a number with the debounce time in milliseconds'
- )
-
- local cmd, cmd_args --- @type (string|fun(dispatchers:table):table), string[]
- local config_cmd = config.cmd
- if type(config_cmd) == 'function' then
- cmd = config_cmd
- else
- cmd, cmd_args = lsp._cmd_parts(config_cmd)
- end
- local offset_encoding = valid_encodings.UTF16
- if config.offset_encoding then
- offset_encoding = validate_encoding(config.offset_encoding)
- end
-
- return cmd, cmd_args, offset_encoding
-end
-
+---@private
--- Returns full text of buffer {bufnr} as a string.
---
---@param bufnr (number) Buffer handle, or 0 for current.
---@return string # Buffer text as string.
-local function buf_get_full_text(bufnr)
- local line_ending = buf_get_line_ending(bufnr)
- local text = table.concat(nvim_buf_get_lines(bufnr, 0, -1, true), line_ending)
+function lsp._buf_get_full_text(bufnr)
+ local line_ending = lsp._buf_get_line_ending(bufnr)
+ local text = table.concat(api.nvim_buf_get_lines(bufnr, 0, -1, true), line_ending)
if vim.bo[bufnr].eol then
text = text .. line_ending
end
@@ -327,484 +188,26 @@ end
---@param fn (T) Function to run
---@return T
local function once(fn)
- local value --- @type any
+ local value --- @type function
local ran = false
return function(...)
if not ran then
- value = fn(...)
+ value = fn(...) --- @type function
ran = true
end
return value
end
end
-local changetracking = {}
-do
- ---@private
- ---
- --- LSP has 3 different sync modes:
- --- - None (Servers will read the files themselves when needed)
- --- - Full (Client sends the full buffer content on updates)
- --- - Incremental (Client sends only the changed parts)
- ---
- --- Changes are tracked per buffer.
- --- A buffer can have multiple clients attached and each client needs to send the changes
- --- To minimize the amount of changesets to compute, computation is grouped:
- ---
- --- None: One group for all clients
- --- Full: One group for all clients
- --- Incremental: One group per `offset_encoding`
- ---
- --- Sending changes can be debounced per buffer. To simplify the implementation the
- --- smallest debounce interval is used and we don't group clients by different intervals.
- ---
- --- @class CTGroup
- --- @field sync_kind integer TextDocumentSyncKind, considers config.flags.allow_incremental_sync
- --- @field offset_encoding "utf-8"|"utf-16"|"utf-32"
- ---
- --- @class CTBufferState
- --- @field name string name of the buffer
- --- @field lines string[] snapshot of buffer lines from last didChange
- --- @field lines_tmp string[]
- --- @field pending_changes table[] List of debounced changes in incremental sync mode
- --- @field timer nil|uv.uv_timer_t uv_timer
- --- @field last_flush nil|number uv.hrtime of the last flush/didChange-notification
- --- @field needs_flush boolean true if buffer updates haven't been sent to clients/servers yet
- --- @field refs integer how many clients are using this group
- ---
- --- @class CTGroupState
- --- @field buffers table<integer, CTBufferState>
- --- @field debounce integer debounce duration in ms
- --- @field clients table<integer, table> clients using this state. {client_id, client}
-
- ---@param group CTGroup
- ---@return string
- local function group_key(group)
- if group.sync_kind == protocol.TextDocumentSyncKind.Incremental then
- return tostring(group.sync_kind) .. '\0' .. group.offset_encoding
- end
- return tostring(group.sync_kind)
- end
-
- ---@private
- ---@type table<CTGroup, CTGroupState>
- local state_by_group = setmetatable({}, {
- __index = function(tbl, k)
- return rawget(tbl, group_key(k))
- end,
- __newindex = function(tbl, k, v)
- rawset(tbl, group_key(k), v)
- end,
- })
-
- ---@return CTGroup
- local function get_group(client)
- local allow_inc_sync = if_nil(client.config.flags.allow_incremental_sync, true)
- local change_capability = vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'change')
- local sync_kind = change_capability or protocol.TextDocumentSyncKind.None
- if not allow_inc_sync and change_capability == protocol.TextDocumentSyncKind.Incremental then
- sync_kind = protocol.TextDocumentSyncKind.Full
- end
- return {
- sync_kind = sync_kind,
- offset_encoding = client.offset_encoding,
- }
- end
-
- ---@param state CTBufferState
- local function incremental_changes(state, encoding, bufnr, firstline, lastline, new_lastline)
- local prev_lines = state.lines
- local curr_lines = state.lines_tmp
-
- local changed_lines = nvim_buf_get_lines(bufnr, firstline, new_lastline, true)
- for i = 1, firstline do
- curr_lines[i] = prev_lines[i]
- end
- for i = firstline + 1, new_lastline do
- curr_lines[i] = changed_lines[i - firstline]
- end
- for i = lastline + 1, #prev_lines do
- curr_lines[i - lastline + new_lastline] = prev_lines[i]
- end
- if tbl_isempty(curr_lines) then
- -- Can happen when deleting the entire contents of a buffer, see https://github.com/neovim/neovim/issues/16259.
- curr_lines[1] = ''
- end
-
- local line_ending = buf_get_line_ending(bufnr)
- local incremental_change = sync.compute_diff(
- state.lines,
- curr_lines,
- firstline,
- lastline,
- new_lastline,
- encoding,
- line_ending
- )
-
- -- Double-buffering of lines tables is used to reduce the load on the garbage collector.
- -- At this point the prev_lines table is useless, but its internal storage has already been allocated,
- -- so let's keep it around for the next didChange event, in which it will become the next
- -- curr_lines table. Note that setting elements to nil doesn't actually deallocate slots in the
- -- internal storage - it merely marks them as free, for the GC to deallocate them.
- for i in ipairs(prev_lines) do
- prev_lines[i] = nil
- end
- state.lines = curr_lines
- state.lines_tmp = prev_lines
-
- return incremental_change
- end
-
- ---@private
- function changetracking.init(client, bufnr)
- assert(client.offset_encoding, 'lsp client must have an offset_encoding')
- local group = get_group(client)
- local state = state_by_group[group]
- if state then
- state.debounce = math.min(state.debounce, client.config.flags.debounce_text_changes or 150)
- state.clients[client.id] = client
- else
- state = {
- buffers = {},
- debounce = client.config.flags.debounce_text_changes or 150,
- clients = {
- [client.id] = client,
- },
- }
- state_by_group[group] = state
- end
- local buf_state = state.buffers[bufnr]
- if buf_state then
- buf_state.refs = buf_state.refs + 1
- else
- buf_state = {
- name = api.nvim_buf_get_name(bufnr),
- lines = {},
- lines_tmp = {},
- pending_changes = {},
- needs_flush = false,
- refs = 1,
- }
- state.buffers[bufnr] = buf_state
- if group.sync_kind == protocol.TextDocumentSyncKind.Incremental then
- buf_state.lines = nvim_buf_get_lines(bufnr, 0, -1, true)
- end
- end
- end
-
- ---@private
- function changetracking._get_and_set_name(client, bufnr, name)
- local state = state_by_group[get_group(client)] or {}
- local buf_state = (state.buffers or {})[bufnr]
- local old_name = buf_state.name
- buf_state.name = name
- return old_name
- end
-
- ---@private
- function changetracking.reset_buf(client, bufnr)
- changetracking.flush(client, bufnr)
- local state = state_by_group[get_group(client)]
- if not state then
- return
- end
- assert(state.buffers, 'CTGroupState must have buffers')
- local buf_state = state.buffers[bufnr]
- buf_state.refs = buf_state.refs - 1
- assert(buf_state.refs >= 0, 'refcount on buffer state must not get negative')
- if buf_state.refs == 0 then
- state.buffers[bufnr] = nil
- changetracking._reset_timer(buf_state)
- end
- end
-
- ---@private
- function changetracking.reset(client)
- local state = state_by_group[get_group(client)]
- if not state then
- return
- end
- state.clients[client.id] = nil
- if vim.tbl_count(state.clients) == 0 then
- for _, buf_state in pairs(state.buffers) do
- changetracking._reset_timer(buf_state)
- end
- state.buffers = {}
- end
- end
-
- -- Adjust debounce time by taking time of last didChange notification into
- -- consideration. If the last didChange happened more than `debounce` time ago,
- -- debounce can be skipped and otherwise maybe reduced.
- --
- -- This turns the debounce into a kind of client rate limiting
- --
- ---@param debounce integer
- ---@param buf_state CTBufferState
- ---@return number
- local function next_debounce(debounce, buf_state)
- if debounce == 0 then
- return 0
- end
- local ns_to_ms = 0.000001
- if not buf_state.last_flush then
- return debounce
- end
- local now = uv.hrtime()
- local ms_since_last_flush = (now - buf_state.last_flush) * ns_to_ms
- return math.max(debounce - ms_since_last_flush, 0)
- end
-
- ---@param bufnr integer
- ---@param sync_kind integer protocol.TextDocumentSyncKind
- ---@param state CTGroupState
- ---@param buf_state CTBufferState
- local function send_changes(bufnr, sync_kind, state, buf_state)
- if not buf_state.needs_flush then
- return
- end
- buf_state.last_flush = uv.hrtime()
- buf_state.needs_flush = false
-
- if not api.nvim_buf_is_valid(bufnr) then
- buf_state.pending_changes = {}
- return
- end
-
- local changes
- if sync_kind == protocol.TextDocumentSyncKind.None then
- return
- elseif sync_kind == protocol.TextDocumentSyncKind.Incremental then
- changes = buf_state.pending_changes
- buf_state.pending_changes = {}
- else
- changes = {
- { text = buf_get_full_text(bufnr) },
- }
- end
- local uri = vim.uri_from_bufnr(bufnr)
- for _, client in pairs(state.clients) do
- if not client.is_stopped() and lsp.buf_is_attached(bufnr, client.id) then
- client.notify(ms.textDocument_didChange, {
- textDocument = {
- uri = uri,
- version = util.buf_versions[bufnr],
- },
- contentChanges = changes,
- })
- end
- end
- end
-
- ---@private
- function changetracking.send_changes(bufnr, firstline, lastline, new_lastline)
- local groups = {} ---@type table<string,CTGroup>
- for _, client in pairs(lsp.get_clients({ bufnr = bufnr })) do
- local group = get_group(client)
- groups[group_key(group)] = group
- end
- for _, group in pairs(groups) do
- local state = state_by_group[group]
- if not state then
- error(
- string.format(
- 'changetracking.init must have been called for all LSP clients. group=%s states=%s',
- vim.inspect(group),
- vim.inspect(vim.tbl_keys(state_by_group))
- )
- )
- end
- local buf_state = state.buffers[bufnr]
- buf_state.needs_flush = true
- changetracking._reset_timer(buf_state)
- local debounce = next_debounce(state.debounce, buf_state)
- if group.sync_kind == protocol.TextDocumentSyncKind.Incremental then
- -- This must be done immediately and cannot be delayed
- -- The contents would further change and startline/endline may no longer fit
- local changes = incremental_changes(
- buf_state,
- group.offset_encoding,
- bufnr,
- firstline,
- lastline,
- new_lastline
- )
- table.insert(buf_state.pending_changes, changes)
- end
- if debounce == 0 then
- send_changes(bufnr, group.sync_kind, state, buf_state)
- else
- local timer = assert(uv.new_timer(), 'Must be able to create timer')
- buf_state.timer = timer
- timer:start(
- debounce,
- 0,
- vim.schedule_wrap(function()
- changetracking._reset_timer(buf_state)
- send_changes(bufnr, group.sync_kind, state, buf_state)
- end)
- )
- end
- end
- end
-
- ---@private
- function changetracking._reset_timer(buf_state)
- local timer = buf_state.timer
- if timer then
- buf_state.timer = nil
- if not timer:is_closing() then
- timer:stop()
- timer:close()
- end
- end
- end
-
- --- Flushes any outstanding change notification.
- ---@private
- function changetracking.flush(client, bufnr)
- local group = get_group(client)
- local state = state_by_group[group]
- if not state then
- return
- end
- if bufnr then
- local buf_state = state.buffers[bufnr] or {}
- changetracking._reset_timer(buf_state)
- send_changes(bufnr, group.sync_kind, state, buf_state)
- else
- for buf, buf_state in pairs(state.buffers) do
- changetracking._reset_timer(buf_state)
- send_changes(buf, group.sync_kind, state, buf_state)
- end
- end
- end
-end
-
---- Default handler for the 'textDocument/didOpen' LSP notification.
----
----@param bufnr integer Number of the buffer, or 0 for current
----@param client table Client object
-local function text_document_did_open_handler(bufnr, client)
- changetracking.init(client, bufnr)
- if not vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'openClose') then
- return
- end
- if not api.nvim_buf_is_loaded(bufnr) then
- return
- end
- local filetype = vim.bo[bufnr].filetype
-
- local params = {
- textDocument = {
- version = 0,
- uri = vim.uri_from_bufnr(bufnr),
- languageId = client.config.get_language_id(bufnr, filetype),
- text = buf_get_full_text(bufnr),
- },
- }
- client.notify(ms.textDocument_didOpen, params)
- util.buf_versions[bufnr] = params.textDocument.version
-
- -- Next chance we get, we should re-do the diagnostics
- vim.schedule(function()
- -- Protect against a race where the buffer disappears
- -- between `did_open_handler` and the scheduled function firing.
- if api.nvim_buf_is_valid(bufnr) then
- local namespace = vim.lsp.diagnostic.get_namespace(client.id)
- vim.diagnostic.show(namespace, bufnr)
- end
- 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.
+--- @class vim.lsp.start.Opts
+--- @inlinedoc
---
---- - Members
---- - {id} (number): The id allocated to the client.
+--- 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
---
---- - {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|.
----
---- - {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): copy 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.
-function lsp.client()
- error()
-end
-
---- @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
@@ -824,8 +227,7 @@ end
--- See |vim.lsp.start_client()| for all available options. The most important are:
---
--- - `name` arbitrary name for the LSP client. Should be unique per language server.
---- - `cmd` command (in list form) used to start the language server. Must be absolute, or found on
---- `$PATH`. Shell constructs like `~` are not expanded.
+--- - `cmd` command string[] or function, described at |vim.lsp.start_client()|.
--- - `root_dir` path to the project root. By default this is used to decide if an existing client
--- should be re-used. The example above uses |vim.fs.find()| and |vim.fs.dirname()| to detect the
--- root by traversing the file system upwards starting from the current directory until either
@@ -844,30 +246,18 @@ end
--- Either use |:au|, |nvim_create_autocmd()| or put the call in a
--- `ftplugin/<filetype_name>.lua` (See |ftplugin-name|)
---
----@param config table Same configuration as documented in |vim.lsp.start_client()|
----@param opts (nil|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|nil 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
or function(client, conf)
- return client.config.root_dir == conf.root_dir and client.name == conf.name
+ return client.root_dir == conf.root_dir and client.name == conf.name
end
- if not config.name and type(config.cmd) == 'table' then
- config.name = config.cmd[1] and vim.fs.basename(config.cmd[1]) or nil
- end
- local bufnr = opts.bufnr
- if bufnr == nil or bufnr == 0 then
- bufnr = api.nvim_get_current_buf()
- end
+
+ local bufnr = resolve_bufnr(opts.bufnr)
+
for _, clients in ipairs({ uninitialized_clients, lsp.get_clients() }) do
for _, client in pairs(clients) do
if reuse_client(client, config) then
@@ -876,10 +266,13 @@ function lsp.start(config, opts)
end
end
end
+
local client_id = lsp.start_client(config)
- if client_id == nil then
- return nil -- lsp.start_client will have printed an error
+
+ if not client_id then
+ return -- lsp.start_client will have printed an error
end
+
lsp.buf_attach_client(bufnr, client_id)
return client_id
end
@@ -890,9 +283,11 @@ end
---@return string
function lsp.status()
local percentage = nil
- local messages = {}
+ local messages = {} --- @type string[]
for _, client in ipairs(vim.lsp.get_clients()) do
+ --- @diagnostic disable-next-line:no-unknown
for progress in client.progress do
+ --- @cast progress {token: lsp.ProgressToken, value: lsp.LSPAny}
local value = progress.value
if type(value) == 'table' and value.kind then
local message = value.message and (value.title .. ': ' .. value.message) or value.title
@@ -913,12 +308,15 @@ function lsp.status()
end
-- Determines whether the given option can be set by `set_defaults`.
+---@param bufnr integer
+---@param option string
+---@return boolean
local function is_empty_or_default(bufnr, option)
if vim.bo[bufnr][option] == '' then
return true
end
- local info = vim.api.nvim_get_option_info2(option, { buf = bufnr })
+ local info = api.nvim_get_option_info2(option, { buf = bufnr })
local scriptinfo = vim.tbl_filter(function(e)
return e.sid == info.last_set_sid
end, vim.fn.getscriptinfo())
@@ -931,7 +329,8 @@ 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
client.supports_method(ms.textDocument_definition) and is_empty_or_default(bufnr, 'tagfunc')
@@ -964,818 +363,151 @@ function lsp._set_defaults(client, bufnr)
end
end
---- @class lsp.ClientConfig
---- @field cmd (string[]|fun(dispatchers: table):table)
---- @field cmd_cwd string
---- @field cmd_env (table)
---- @field detached boolean
---- @field workspace_folders (table)
---- @field capabilities lsp.ClientCapabilities
---- @field handlers table<string,function>
---- @field settings table
---- @field commands table
---- @field init_options table
---- @field name string
---- @field get_language_id fun(bufnr: integer, filetype: string): string
---- @field offset_encoding string
---- @field on_error fun(code: integer)
---- @field before_init function
---- @field on_init function
---- @field on_exit fun(code: integer, signal: integer, client_id: integer)
---- @field on_attach fun(client: lsp.Client, bufnr: integer)
---- @field trace 'off'|'messages'|'verbose'|nil
---- @field flags table
---- @field root_dir string
-
--- 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 a list of
---- strings treated like |jobstart()|. The command must launch the language server
---- process. `cmd` can also be a function that creates an RPC client.
---- The function receives a dispatchers table and must return a table with the
---- functions `request`, `notify`, `is_closing` and `terminate`
---- See |vim.lsp.rpc.request()| and |vim.lsp.rpc.notify()|
---- For TCP there is a built-in 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:
---- <pre>
---- { PORT = 8080; HOST = "0.0.0.0"; }
---- </pre>
----
---- - 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. Most language servers expect to be sent client specified settings after
---- initialization. Nvim does not make this assumption. A
---- `workspace/didChangeConfiguration` notification should be sent
---- to the server during on_init.
----
---- - 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
---- fully initialized. Use `on_init` to do any actions once
---- the client has been initialized.
-function lsp.start_client(config)
- local cmd, cmd_args, offset_encoding = validate_client_config(config)
-
- config.flags = config.flags or {}
- config.settings = config.settings or {}
-
- -- By default, get_language_id just returns the exact filetype it is passed.
- -- It is possible to pass in something that will calculate a different filetype,
- -- to be sent by the client.
- config.get_language_id = config.get_language_id or function(_, filetype)
- return filetype
- end
-
- local client_id = next_client_id()
-
- local handlers = config.handlers or {}
- local name = config.name or tostring(client_id)
- local log_prefix = string.format('LSP[%s]', name)
-
- local dispatch = {}
-
- --- Returns the handler associated with an LSP method.
- --- Returns the default handler if the user hasn't set a custom one.
- ---
- ---@param method (string) LSP method name
- ---@return lsp-handler|nil The handler for the given method, if defined, or the default from |vim.lsp.handlers|
- local function resolve_handler(method)
- return handlers[method] or default_handlers[method]
- end
-
- ---@private
- --- Handles a notification sent by an LSP server by invoking the
- --- corresponding handler.
- ---
- ---@param method (string) LSP method name
- ---@param params (table) The parameters for that method.
- function dispatch.notification(method, params)
- if log.trace() then
- log.trace('notification', method, params)
- end
- local handler = resolve_handler(method)
- if handler then
- -- Method name is provided here for convenience.
- handler(nil, params, { method = method, client_id = client_id })
- end
- end
-
- ---@private
- --- Handles a request from an LSP server by invoking the corresponding handler.
- ---
- ---@param method (string) LSP method name
- ---@param params (table) The parameters for that method
- function dispatch.server_request(method, params)
- if log.trace() then
- log.trace('server_request', method, params)
- end
- local handler = resolve_handler(method)
- if handler then
- if log.trace() then
- log.trace('server_request: found handler for', method)
- end
- return handler(nil, params, { method = method, client_id = client_id })
- end
- if log.warn() then
- log.warn('server_request: no handler found for', method)
- end
- return nil, lsp.rpc_response_error(protocol.ErrorCodes.MethodNotFound)
+--- Reset defaults set by `set_defaults`.
+--- Must only be called if the last client attached to a buffer exits.
+local function reset_defaults(bufnr)
+ if vim.bo[bufnr].tagfunc == 'v:lua.vim.lsp.tagfunc' then
+ vim.bo[bufnr].tagfunc = nil
end
-
- --- Logs the given error to the LSP log and to the error buffer.
- --- @param code integer Error code
- --- @param err any Error arguments
- local function write_error(code, err)
- if log.error() then
- log.error(log_prefix, 'on_error', { code = lsp.client_errors[code], err = err })
- end
- err_message(log_prefix, ': Error ', lsp.client_errors[code], ': ', vim.inspect(err))
- end
-
- ---@private
- --- Invoked when the client operation throws an error.
- ---
- ---@param code (integer) Error code
- ---@param err (...) 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 a human-friendly name.
- function dispatch.on_error(code, err)
- write_error(code, err)
- if config.on_error then
- local status, usererr = pcall(config.on_error, code, err)
- if not status then
- local _ = log.error() and log.error(log_prefix, 'user on_error failed', { err = usererr })
- err_message(log_prefix, ' user on_error failed: ', tostring(usererr))
- end
- end
+ if vim.bo[bufnr].omnifunc == 'v:lua.vim.lsp.omnifunc' then
+ vim.bo[bufnr].omnifunc = nil
end
-
- --- Reset defaults set by `set_defaults`.
- --- Must only be called if the last client attached to a buffer exits.
- local function reset_defaults(bufnr)
- if vim.bo[bufnr].tagfunc == 'v:lua.vim.lsp.tagfunc' then
- vim.bo[bufnr].tagfunc = nil
- end
- if vim.bo[bufnr].omnifunc == 'v:lua.vim.lsp.omnifunc' then
- vim.bo[bufnr].omnifunc = nil
- end
- if vim.bo[bufnr].formatexpr == 'v:lua.vim.lsp.formatexpr()' then
- vim.bo[bufnr].formatexpr = nil
- end
- api.nvim_buf_call(bufnr, function()
- local keymap = vim.fn.maparg('K', 'n', false, true)
- if keymap and keymap.callback == vim.lsp.buf.hover then
- vim.keymap.del('n', 'K', { buffer = bufnr })
- end
- end)
+ if vim.bo[bufnr].formatexpr == 'v:lua.vim.lsp.formatexpr()' then
+ vim.bo[bufnr].formatexpr = nil
end
-
- ---@private
- --- Invoked on client exit.
- ---
- ---@param code (integer) exit code of the process
- ---@param signal (integer) the signal used to terminate (if any)
- function dispatch.on_exit(code, signal)
- if config.on_exit then
- pcall(config.on_exit, code, signal, client_id)
+ api.nvim_buf_call(bufnr, function()
+ local keymap = vim.fn.maparg('K', 'n', false, true)
+ if keymap and keymap.callback == vim.lsp.buf.hover then
+ vim.keymap.del('n', 'K', { buffer = bufnr })
end
+ end)
+end
- local client = active_clients[client_id] and active_clients[client_id]
- or uninitialized_clients[client_id]
-
- for bufnr, client_ids in pairs(all_buffer_active_clients) do
- if client_ids[client_id] then
- vim.schedule(function()
- if client and client.attached_buffers[bufnr] then
- nvim_exec_autocmds('LspDetach', {
- buffer = bufnr,
- modeline = false,
- data = { client_id = client_id },
- })
- end
-
- local namespace = vim.lsp.diagnostic.get_namespace(client_id)
- vim.diagnostic.reset(namespace, bufnr)
-
- client_ids[client_id] = nil
- if vim.tbl_isempty(client_ids) then
- reset_defaults(bufnr)
- end
- end)
- end
+--- @param client vim.lsp.Client
+local function on_client_init(client)
+ local id = client.id
+ uninitialized_clients[id] = nil
+ -- Only assign after initialized.
+ active_clients[id] = client
+ -- If we had been registered before we start, then send didOpen This can
+ -- happen if we attach to buffers before initialize finishes or if
+ -- someone restarts a client.
+ for bufnr, client_ids in pairs(all_buffer_active_clients) do
+ if client_ids[id] then
+ client.on_attach(bufnr)
end
-
- -- Schedule the deletion of the client object so that it exists in the execution of LspDetach
- -- autocommands
- vim.schedule(function()
- active_clients[client_id] = nil
- uninitialized_clients[client_id] = nil
-
- -- Client can be absent if executable starts, but initialize fails
- -- init/attach won't have happened
- if client then
- changetracking.reset(client)
- end
- if code ~= 0 or (signal ~= 0 and signal ~= 15) then
- local msg = string.format(
- 'Client %s quit with exit code %s and signal %s. Check log for errors: %s',
- name,
- code,
- signal,
- lsp.get_log_path()
- )
- vim.notify(msg, vim.log.levels.WARN)
- end
- end)
- end
-
- -- Start the RPC client.
- local rpc
- if type(cmd) == 'function' then
- rpc = cmd(dispatch)
- else
- rpc = lsp_rpc.start(cmd, cmd_args, dispatch, {
- cwd = config.cmd_cwd,
- env = config.cmd_env,
- detached = config.detached,
- })
end
+end
- -- Return nil if client fails to start
- if not rpc then
- return
- end
-
- ---@class lsp.Client
- local client = {
- id = client_id,
- name = name,
- rpc = rpc,
- offset_encoding = offset_encoding,
- config = config,
- attached_buffers = {},
-
- handlers = handlers,
- commands = config.commands or {},
-
- --- @type table<integer,{ type: string, bufnr: integer, method: string}>
- requests = {},
-
- --- Contains $/progress report messages.
- --- They have the format {token: integer|string, value: any}
- --- For "work done progress", value will be one of:
- --- - lsp.WorkDoneProgressBegin,
- --- - lsp.WorkDoneProgressReport (extended with title from Begin)
- --- - lsp.WorkDoneProgressEnd (extended with title from Begin)
- progress = vim.ringbuf(50),
-
- --- @type lsp.ServerCapabilities
- server_capabilities = {},
-
- ---@deprecated use client.progress instead
- messages = { name = name, messages = {}, progress = {}, status = {} },
- dynamic_capabilities = require('vim.lsp._dynamic').new(client_id),
- }
-
- ---@type table<string|integer, string> title of unfinished progress sequences by token
- client.progress.pending = {}
-
- --- @type lsp.ClientCapabilities
- client.config.capabilities = config.capabilities or protocol.make_client_capabilities()
-
- -- Store the uninitialized_clients for cleanup in case we exit before initialize finishes.
- uninitialized_clients[client_id] = client
-
- local function initialize()
- local valid_traces = {
- off = 'off',
- messages = 'messages',
- verbose = 'verbose',
- }
-
- local workspace_folders --- @type table[]?
- local root_uri --- @type string?
- local root_path --- @type string?
- if config.workspace_folders or config.root_dir then
- if config.root_dir and not config.workspace_folders then
- workspace_folders = {
- {
- uri = vim.uri_from_fname(config.root_dir),
- name = string.format('%s', config.root_dir),
- },
- }
- else
- workspace_folders = config.workspace_folders
- end
- root_uri = workspace_folders[1].uri
- root_path = vim.uri_to_fname(root_uri)
- else
- workspace_folders = nil
- root_uri = nil
- root_path = nil
- end
+--- @param code integer
+--- @param signal integer
+--- @param client_id integer
+local function on_client_exit(code, signal, client_id)
+ local client = active_clients[client_id] or uninitialized_clients[client_id]
- local initialize_params = {
- -- The process Id of the parent process that started the server. Is null if
- -- the process has not been started by another process. If the parent
- -- process is not alive then the server should exit (see exit notification)
- -- its process.
- processId = uv.os_getpid(),
- -- Information about the client
- -- since 3.15.0
- clientInfo = {
- name = 'Neovim',
- version = tostring(vim.version()),
- },
- -- The rootPath of the workspace. Is null if no folder is open.
- --
- -- @deprecated in favour of rootUri.
- rootPath = root_path or vim.NIL,
- -- The rootUri of the workspace. Is null if no folder is open. If both
- -- `rootPath` and `rootUri` are set `rootUri` wins.
- rootUri = root_uri or vim.NIL,
- -- The workspace folders configured in the client when the server starts.
- -- This property is only available if the client supports workspace folders.
- -- It can be `null` if the client supports workspace folders but none are
- -- configured.
- workspaceFolders = workspace_folders or vim.NIL,
- -- User provided initialization options.
- initializationOptions = config.init_options,
- -- The capabilities provided by the client (editor or tool)
- capabilities = config.capabilities,
- -- The initial trace setting. If omitted trace is disabled ("off").
- -- trace = "off" | "messages" | "verbose";
- trace = valid_traces[config.trace] or 'off',
- }
- if config.before_init then
- local status, err = pcall(config.before_init, initialize_params, config)
- if not status then
- write_error(lsp.client_errors.BEFORE_INIT_CALLBACK_ERROR, err)
- end
- end
-
- --- @param method string
- --- @param opts? {bufnr?: number}
- client.supports_method = function(method, opts)
- opts = opts or {}
- local required_capability = lsp._request_name_to_capability[method]
- -- if we don't know about the method, assume that the client supports it.
- if not required_capability then
- return true
- end
- if vim.tbl_get(client.server_capabilities, unpack(required_capability)) then
- return true
- else
- if client.dynamic_capabilities:supports_registration(method) then
- return client.dynamic_capabilities:supports(method, opts)
+ for bufnr, client_ids in pairs(all_buffer_active_clients) do
+ if client_ids[client_id] then
+ vim.schedule(function()
+ if client and client.attached_buffers[bufnr] then
+ api.nvim_exec_autocmds('LspDetach', {
+ buffer = bufnr,
+ modeline = false,
+ data = { client_id = client_id },
+ })
end
- return false
- end
- end
- local _ = log.trace() and log.trace(log_prefix, 'initialize_params', initialize_params)
- rpc.request('initialize', initialize_params, function(init_err, result)
- assert(not init_err, tostring(init_err))
- assert(result, 'server sent empty result')
- rpc.notify('initialized', vim.empty_dict())
- client.initialized = true
- uninitialized_clients[client_id] = nil
- client.workspace_folders = workspace_folders
-
- -- These are the cleaned up capabilities we use for dynamically deciding
- -- when to send certain events to clients.
- client.server_capabilities =
- assert(result.capabilities, "initialize result doesn't contain capabilities")
- client.server_capabilities = protocol.resolve_capabilities(client.server_capabilities)
-
- if client.server_capabilities.positionEncoding then
- client.offset_encoding = client.server_capabilities.positionEncoding
- end
-
- if next(config.settings) then
- client.notify(ms.workspace_didChangeConfiguration, { settings = config.settings })
- end
-
- if config.on_init then
- local status, err = pcall(config.on_init, client, result)
- if not status then
- write_error(lsp.client_errors.ON_INIT_CALLBACK_ERROR, err)
- end
- end
- local _ = log.info()
- and log.info(
- log_prefix,
- 'server_capabilities',
- { server_capabilities = client.server_capabilities }
- )
+ local namespace = vim.lsp.diagnostic.get_namespace(client_id)
+ vim.diagnostic.reset(namespace, bufnr)
- -- Only assign after initialized.
- active_clients[client_id] = client
- -- If we had been registered before we start, then send didOpen This can
- -- happen if we attach to buffers before initialize finishes or if
- -- someone restarts a client.
- for bufnr, client_ids in pairs(all_buffer_active_clients) do
- if client_ids[client_id] then
- client._on_attach(bufnr)
+ client_ids[client_id] = nil
+ if vim.tbl_isempty(client_ids) then
+ reset_defaults(bufnr)
end
- end
- end)
- end
-
- ---@nodoc
- --- Sends a request to the server.
- ---
- --- This is a thin wrapper around {client.rpc.request} with some additional
- --- checks for capabilities and handler availability.
- ---
- ---@param method string LSP method name.
- ---@param params table|nil LSP request params.
- ---@param handler lsp-handler|nil Response |lsp-handler| for this method.
- ---@param bufnr integer Buffer handle (0 for current).
- ---@return boolean status, integer|nil request_id {status} is a bool indicating
- ---whether the request was successful. If it is `false`, then it will
- ---always be `false` (the client has shutdown). If it was
- ---successful, then it will return {request_id} as the
- ---second result. You can use this with `client.cancel_request(request_id)`
- ---to cancel the-request.
- ---@see |vim.lsp.buf_request_all()|
- function client.request(method, params, handler, bufnr)
- if not handler then
- handler = assert(
- resolve_handler(method),
- string.format('not found: %q request handler for client %q.', method, client.name)
- )
- end
- -- Ensure pending didChange notifications are sent so that the server doesn't operate on a stale state
- changetracking.flush(client, bufnr)
- local version = util.buf_versions[bufnr]
- bufnr = resolve_bufnr(bufnr)
- if log.debug() then
- log.debug(log_prefix, 'client.request', client_id, method, params, handler, bufnr)
- end
- local success, request_id = rpc.request(method, params, function(err, result)
- local context = {
- method = method,
- client_id = client_id,
- bufnr = bufnr,
- params = params,
- version = version,
- }
- handler(err, result, context)
- end, function(request_id)
- local request = client.requests[request_id]
- request.type = 'complete'
- nvim_exec_autocmds('LspRequest', {
- buffer = api.nvim_buf_is_valid(bufnr) and bufnr or nil,
- modeline = false,
- data = { client_id = client_id, request_id = request_id, request = request },
- })
- client.requests[request_id] = nil
- end)
-
- if success and request_id then
- local request = { type = 'pending', bufnr = bufnr, method = method }
- client.requests[request_id] = request
- nvim_exec_autocmds('LspRequest', {
- buffer = bufnr,
- modeline = false,
- data = { client_id = client_id, request_id = request_id, request = request },
- })
- end
-
- return success, request_id
- end
-
- ---@private
- --- Sends a request to the server and synchronously waits for the response.
- ---
- --- This is a wrapper around {client.request}
- ---
- ---@param method (string) LSP method name.
- ---@param params (table) LSP request params.
- ---@param timeout_ms (integer|nil) Maximum time in milliseconds to wait for
- --- a result. Defaults to 1000
- ---@param bufnr (integer) Buffer handle (0 for current).
- ---@return {err: lsp.ResponseError|nil, result:any}|nil, string|nil err # 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`.
- ---@see |vim.lsp.buf_request_sync()|
- function client.request_sync(method, params, timeout_ms, bufnr)
- local request_result = nil
- local function _sync_handler(err, result)
- request_result = { err = err, result = result }
- end
-
- local success, request_id = client.request(method, params, _sync_handler, bufnr)
- if not success then
- return nil
- end
-
- local wait_result, reason = vim.wait(timeout_ms or 1000, function()
- return request_result ~= nil
- end, 10)
-
- if not wait_result then
- if request_id then
- client.cancel_request(request_id)
- end
- return nil, wait_result_reason[reason]
- end
- return request_result
- end
-
- ---@nodoc
- --- Sends a notification to an LSP server.
- ---
- ---@param method string LSP method name.
- ---@param params table|nil LSP request params.
- ---@return boolean status true if the notification was successful.
- ---If it is false, then it will always be false
- ---(the client has shutdown).
- function client.notify(method, params)
- if method ~= ms.textDocument_didChange then
- changetracking.flush(client)
- end
-
- local client_active = rpc.notify(method, params)
-
- if client_active then
- vim.schedule(function()
- nvim_exec_autocmds('LspNotify', {
- modeline = false,
- data = {
- client_id = client.id,
- method = method,
- params = params,
- },
- })
end)
end
-
- return client_active
- end
-
- ---@nodoc
- --- Cancels a request with a given request id.
- ---
- ---@param id (integer) id of request to cancel
- ---@return boolean status true if notification was successful. false otherwise
- ---@see |vim.lsp.client.notify()|
- function client.cancel_request(id)
- validate({ id = { id, 'n' } })
- local request = client.requests[id]
- if request and request.type == 'pending' then
- request.type = 'cancel'
- nvim_exec_autocmds('LspRequest', {
- buffer = request.bufnr,
- modeline = false,
- data = { client_id = client_id, request_id = id, request = request },
- })
- end
- return rpc.notify(ms.dollar_cancelRequest, { id = id })
end
- -- Track this so that we can escalate automatically if we've already tried a
- -- graceful shutdown
- local graceful_shutdown_failed = false
-
- ---@nodoc
- --- 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.
- ---
- ---@param force boolean|nil
- function client.stop(force)
- if rpc.is_closing() then
- return
- end
- if force or not client.initialized or graceful_shutdown_failed then
- rpc.terminate()
- return
- end
- -- Sending a signal after a process has exited is acceptable.
- rpc.request(ms.shutdown, nil, function(err, _)
- if err == nil then
- rpc.notify(ms.exit)
- else
- -- If there was an error in the shutdown request, then term to be safe.
- rpc.terminate()
- graceful_shutdown_failed = true
- end
- end)
- end
+ local name = client.name or 'unknown'
- ---@private
- --- Checks whether a client is stopped.
- ---
- ---@return boolean # true if client is stopped or in the process of being
- ---stopped; false otherwise
- function client.is_stopped()
- return rpc.is_closing()
- end
-
- ---@private
- --- Execute a lsp command, either via client command function (if available)
- --- or via workspace/executeCommand (if supported by the server)
- ---
- ---@param command lsp.Command
- ---@param context? {bufnr: integer}
- ---@param handler? lsp-handler only called if a server command
- function client._exec_cmd(command, context, handler)
- context = vim.deepcopy(context or {})
- context.bufnr = context.bufnr or api.nvim_get_current_buf()
- context.client_id = client.id
- local cmdname = command.command
- local fn = client.commands[cmdname] or lsp.commands[cmdname]
- if fn then
- fn(command, context)
- return
- end
+ -- Schedule the deletion of the client object so that it exists in the execution of LspDetach
+ -- autocommands
+ vim.schedule(function()
+ active_clients[client_id] = nil
+ uninitialized_clients[client_id] = nil
- local command_provider = client.server_capabilities.executeCommandProvider
- local commands = type(command_provider) == 'table' and command_provider.commands or {}
- if not vim.list_contains(commands, cmdname) then
- vim.notify_once(
- string.format(
- 'Language server `%s` does not support command `%s`. This command may require a client extension.',
- client.name,
- cmdname
- ),
- vim.log.levels.WARN
+ -- Client can be absent if executable starts, but initialize fails
+ -- init/attach won't have happened
+ if client then
+ changetracking.reset(client)
+ end
+ if code ~= 0 or (signal ~= 0 and signal ~= 15) then
+ local msg = string.format(
+ 'Client %s quit with exit code %s and signal %s. Check log for errors: %s',
+ name,
+ code,
+ signal,
+ lsp.get_log_path()
)
- return
+ vim.notify(msg, vim.log.levels.WARN)
end
- -- Not using command directly to exclude extra properties,
- -- see https://github.com/python-lsp/python-lsp-server/issues/146
- local params = {
- command = command.command,
- arguments = command.arguments,
- }
- client.request(ms.workspace_executeCommand, params, handler, context.bufnr)
- end
-
- ---@private
- --- Runs the on_attach function from the client's config if it was defined.
- ---@param bufnr integer Buffer number
- function client._on_attach(bufnr)
- text_document_did_open_handler(bufnr, client)
-
- lsp._set_defaults(client, bufnr)
+ end)
+end
- nvim_exec_autocmds('LspAttach', {
- buffer = bufnr,
- modeline = false,
- data = { client_id = client.id },
- })
+--- Starts and initializes a client with the given configuration.
+--- @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)
+ local client = require('vim.lsp.client').create(config)
- if config.on_attach then
- local status, err = pcall(config.on_attach, client, bufnr)
- if not status then
- write_error(lsp.client_errors.ON_ATTACH_ERROR, err)
- end
- end
+ if not client then
+ return
+ end
- -- schedule the initialization of semantic tokens to give the above
- -- on_attach and LspAttach callbacks the ability to schedule wrap the
- -- opt-out (deleting the semanticTokensProvider from capabilities)
- vim.schedule(function()
- if vim.tbl_get(client.server_capabilities, 'semanticTokensProvider', 'full') then
- semantic_tokens.start(bufnr, client.id)
- end
- end)
+ --- @diagnostic disable-next-line: invisible
+ table.insert(client._on_init_cbs, on_client_init)
+ --- @diagnostic disable-next-line: invisible
+ table.insert(client._on_exit_cbs, on_client_exit)
- client.attached_buffers[bufnr] = true
- end
+ -- Store the uninitialized_clients for cleanup in case we exit before initialize finishes.
+ uninitialized_clients[client.id] = client
- initialize()
+ client:initialize()
- return client_id
+ return client.id
end
----@private
----@fn text_document_did_change_handler(_, bufnr, changedtick, firstline, lastline, new_lastline, old_byte_size, old_utf32_size, old_utf16_size)
--- Notify all attached clients that a buffer has changed.
-local text_document_did_change_handler
-do
- text_document_did_change_handler = function(
- _,
- bufnr,
- changedtick,
- firstline,
- lastline,
- new_lastline
- )
- -- Detach (nvim_buf_attach) via returning True to on_lines if no clients are attached
- if tbl_isempty(all_buffer_active_clients[bufnr] or {}) then
- return true
- end
- util.buf_versions[bufnr] = changedtick
- changetracking.send_changes(bufnr, firstline, lastline, new_lastline)
+---@param _ integer
+---@param bufnr integer
+---@param changedtick integer
+---@param firstline integer
+---@param lastline integer
+---@param new_lastline integer
+---@return true?
+local function text_document_did_change_handler(
+ _,
+ bufnr,
+ changedtick,
+ firstline,
+ lastline,
+ new_lastline
+)
+ -- Detach (nvim_buf_attach) via returning True to on_lines if no clients are attached
+ if tbl_isempty(all_buffer_active_clients[bufnr] or {}) then
+ return true
end
+ util.buf_versions[bufnr] = changedtick
+ changetracking.send_changes(bufnr, firstline, lastline, new_lastline)
end
---Buffer lifecycle handler for textDocument/didSave
+--- @param bufnr integer
local function text_document_did_save_handler(bufnr)
bufnr = resolve_bufnr(bufnr)
local uri = vim.uri_from_bufnr(bufnr)
- local text = once(buf_get_full_text)
+ local text = once(lsp._buf_get_full_text)
for _, client in ipairs(lsp.get_clients({ bufnr = bufnr })) do
local name = api.nvim_buf_get_name(bufnr)
local old_name = changetracking._get_and_set_name(client, bufnr, name)
@@ -1789,15 +521,15 @@ local function text_document_did_save_handler(bufnr)
textDocument = {
version = 0,
uri = uri,
- languageId = client.config.get_language_id(bufnr, vim.bo[bufnr].filetype),
- text = buf_get_full_text(bufnr),
+ languageId = client.get_language_id(bufnr, vim.bo[bufnr].filetype),
+ text = lsp._buf_get_full_text(bufnr),
},
})
util.buf_versions[bufnr] = 0
end
local save_capability = vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'save')
if save_capability then
- local included_text
+ local included_text --- @type string?
if type(save_capability) == 'table' and save_capability.includeText then
included_text = text(bufnr)
end
@@ -1826,8 +558,7 @@ function lsp.buf_attach_client(bufnr, client_id)
})
bufnr = resolve_bufnr(bufnr)
if not api.nvim_buf_is_loaded(bufnr) then
- local _ = log.warn()
- and log.warn(string.format('buf_attach_client called on unloaded buffer (id: %d): ', bufnr))
+ log.warn(string.format('buf_attach_client called on unloaded buffer (id: %d): ', bufnr))
return false
end
local buffer_client_ids = all_buffer_active_clients[bufnr]
@@ -1884,7 +615,7 @@ function lsp.buf_attach_client(bufnr, client_id)
if vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'openClose') then
client.notify(ms.textDocument_didClose, params)
end
- text_document_did_open_handler(bufnr, client)
+ client:_text_document_did_open_handler(bufnr)
end
end,
on_detach = function()
@@ -1916,7 +647,7 @@ function lsp.buf_attach_client(bufnr, client_id)
-- Send didOpen for the client if it is initialized. If it isn't initialized
-- then it will send didOpen on initialize.
if client then
- client._on_attach(bufnr)
+ client:_on_attach(bufnr)
end
return true
end
@@ -1946,7 +677,7 @@ function lsp.buf_detach_client(bufnr, client_id)
return
end
- nvim_exec_autocmds('LspDetach', {
+ api.nvim_exec_autocmds('LspDetach', {
buffer = bufnr,
modeline = false,
data = { client_id = client_id },
@@ -1985,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
@@ -2001,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
@@ -2011,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 }
@@ -2026,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
@@ -2068,7 +803,7 @@ end
---@private
---@deprecated
function lsp.get_active_clients(filter)
- -- TODO: add vim.deprecate call after 0.10 is out for removal in 0.12
+ vim.deprecate('vim.lsp.get_active_clients()', 'vim.lsp.get_clients()', '0.12')
return lsp.get_clients(filter)
end
@@ -2087,12 +822,12 @@ api.nvim_create_autocmd('VimLeavePre', {
client.stop()
end
- local timeouts = {}
+ local timeouts = {} --- @type table<integer,integer>
local max_timeout = 0
local send_kill = false
for client_id, client in pairs(active_clients) do
- local timeout = if_nil(client.config.flags.exit_timeout, false)
+ local timeout = if_nil(client.flags.exit_timeout, false)
if timeout then
send_kill = true
timeouts[client_id] = timeout
@@ -2134,7 +869,7 @@ api.nvim_create_autocmd('VimLeavePre', {
---@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 handler? lsp-handler See |lsp-handler|
+---@param handler? lsp.Handler See |lsp-handler|
--- If nil, follows resolution strategy defined in |lsp-handler-configuration|
---
---@return table<integer, integer> client_request_ids Map of client-id:request-id pairs
@@ -2152,7 +887,7 @@ function lsp.buf_request(bufnr, method, params, handler)
bufnr = resolve_bufnr(bufnr)
local method_supported = false
local clients = lsp.get_clients({ bufnr = bufnr })
- local client_request_ids = {}
+ local client_request_ids = {} --- @type table<integer,integer>
for _, client in ipairs(clients) do
if client.supports_method(method, { bufnr = bufnr }) then
method_supported = true
@@ -2169,7 +904,7 @@ function lsp.buf_request(bufnr, method, params, handler)
-- if has client but no clients support the given method, notify the user
if next(clients) and not method_supported then
vim.notify(lsp._unsupported_method(method), vim.log.levels.ERROR)
- nvim_command('redraw')
+ vim.cmd.redraw()
return {}, function() end
end
@@ -2194,7 +929,7 @@ end
--- a `client_id:result` map.
---@return function cancel Function that cancels all requests.
function lsp.buf_request_all(bufnr, method, params, handler)
- local results = {}
+ local results = {} --- @type table<integer,{error:string, result:any}>
local result_count = 0
local expected_result_count = 0
@@ -2225,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
@@ -2287,21 +1021,24 @@ end
--- - findstart=0: column where the completion starts, or -2 or -3
--- - findstart=1: list of matches (actually just calls |complete()|)
function lsp.omnifunc(findstart, base)
- if log.debug() then
- log.debug('omnifunc.findstart', { findstart = findstart, base = base })
- end
- return require('vim.lsp._completion').omnifunc(findstart, base)
+ log.debug('omnifunc.findstart', { findstart = findstart, base = 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})'``.
+--- 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
@@ -2324,6 +1061,7 @@ function lsp.formatexpr(opts)
local params = util.make_formatting_params()
local end_line = vim.fn.getline(end_lnum) --[[@as string]]
local end_col = util._str_utfindex_enc(end_line, nil, client.offset_encoding)
+ --- @cast params +lsp.DocumentRangeFormattingParams
params.range = {
start = {
line = start_lnum - 1,
@@ -2337,7 +1075,7 @@ function lsp.formatexpr(opts)
local response =
client.request_sync(ms.textDocument_rangeFormatting, params, timeout_ms, bufnr)
if response and response.result then
- lsp.util.apply_text_edits(response.result, 0, client.offset_encoding)
+ lsp.util.apply_text_edits(response.result, bufnr, client.offset_encoding)
return 0
end
end
@@ -2359,7 +1097,7 @@ end
---
---@return table[] tags A list of matching tags
function lsp.tagfunc(pattern, flags)
- return require('vim.lsp.tagfunc')(pattern, flags)
+ return vim.lsp._tagfunc(pattern, flags)
end
---Checks whether a client is stopped.
@@ -2372,13 +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)
- local result = {}
+ vim.deprecate('vim.lsp.buf_get_clients()', 'vim.lsp.get_clients()', '0.12')
+ 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
@@ -2428,11 +1167,16 @@ end
--- buffer number as arguments.
---@deprecated use lsp.get_clients({ bufnr = bufnr }) with regular loop
function lsp.for_each_buffer_client(bufnr, fn)
+ vim.deprecate(
+ 'vim.lsp.for_each_buffer_client()',
+ 'lsp.get_clients({ bufnr = bufnr }) with regular loop',
+ '0.12'
+ )
return for_each_buffer_client(bufnr, fn)
end
--- Function to manage overriding defaults for LSP handlers.
----@param handler (function) See |lsp-handler|
+---@param handler (lsp.Handler) See |lsp-handler|
---@param override_config (table) Table containing the keys to override behavior of the {handler}
function lsp.with(handler, override_config)
return function(err, result, ctx, config)
@@ -2497,6 +1241,7 @@ end
--- arguments?: any[]
---
--- The second argument is the `ctx` of |lsp-handler|
+--- @type table<string,function>
lsp.commands = setmetatable({}, {
__newindex = function(tbl, key, value)
assert(type(key) == 'string', 'The key for commands in `vim.lsp.commands` must be a string')
diff --git a/runtime/lua/vim/lsp/_changetracking.lua b/runtime/lua/vim/lsp/_changetracking.lua
new file mode 100644
index 0000000000..b2be53269f
--- /dev/null
+++ b/runtime/lua/vim/lsp/_changetracking.lua
@@ -0,0 +1,373 @@
+local protocol = require('vim.lsp.protocol')
+local sync = require('vim.lsp.sync')
+local util = require('vim.lsp.util')
+
+local api = vim.api
+local uv = vim.uv
+
+local M = {}
+
+--- LSP has 3 different sync modes:
+--- - None (Servers will read the files themselves when needed)
+--- - Full (Client sends the full buffer content on updates)
+--- - Incremental (Client sends only the changed parts)
+---
+--- Changes are tracked per buffer.
+--- A buffer can have multiple clients attached and each client needs to send the changes
+--- To minimize the amount of changesets to compute, computation is grouped:
+---
+--- None: One group for all clients
+--- Full: One group for all clients
+--- Incremental: One group per `offset_encoding`
+---
+--- Sending changes can be debounced per buffer. To simplify the implementation the
+--- smallest debounce interval is used and we don't group clients by different intervals.
+---
+--- @class vim.lsp.CTGroup
+--- @field sync_kind integer TextDocumentSyncKind, considers config.flags.allow_incremental_sync
+--- @field offset_encoding "utf-8"|"utf-16"|"utf-32"
+---
+--- @class vim.lsp.CTBufferState
+--- @field name string name of the buffer
+--- @field lines string[] snapshot of buffer lines from last didChange
+--- @field lines_tmp string[]
+--- @field pending_changes table[] List of debounced changes in incremental sync mode
+--- @field timer uv.uv_timer_t? uv_timer
+--- @field last_flush nil|number uv.hrtime of the last flush/didChange-notification
+--- @field needs_flush boolean true if buffer updates haven't been sent to clients/servers yet
+--- @field refs integer how many clients are using this group
+---
+--- @class vim.lsp.CTGroupState
+--- @field buffers table<integer,vim.lsp.CTBufferState>
+--- @field debounce integer debounce duration in ms
+--- @field clients table<integer, table> clients using this state. {client_id, client}
+
+---@param group vim.lsp.CTGroup
+---@return string
+local function group_key(group)
+ if group.sync_kind == protocol.TextDocumentSyncKind.Incremental then
+ return tostring(group.sync_kind) .. '\0' .. group.offset_encoding
+ end
+ return tostring(group.sync_kind)
+end
+
+---@type table<vim.lsp.CTGroup,vim.lsp.CTGroupState>
+local state_by_group = setmetatable({}, {
+ __index = function(tbl, k)
+ return rawget(tbl, group_key(k))
+ end,
+ __newindex = function(tbl, k, v)
+ rawset(tbl, group_key(k), v)
+ end,
+})
+
+---@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
+ local change_capability = vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'change')
+ local sync_kind = change_capability or protocol.TextDocumentSyncKind.None
+ if not allow_inc_sync and change_capability == protocol.TextDocumentSyncKind.Incremental then
+ sync_kind = protocol.TextDocumentSyncKind.Full --[[@as integer]]
+ end
+ return {
+ sync_kind = sync_kind,
+ offset_encoding = client.offset_encoding,
+ }
+end
+
+---@param state vim.lsp.CTBufferState
+---@param encoding string
+---@param bufnr integer
+---@param firstline integer
+---@param lastline integer
+---@param new_lastline integer
+---@return lsp.TextDocumentContentChangeEvent
+local function incremental_changes(state, encoding, bufnr, firstline, lastline, new_lastline)
+ local prev_lines = state.lines
+ local curr_lines = state.lines_tmp
+
+ local changed_lines = api.nvim_buf_get_lines(bufnr, firstline, new_lastline, true)
+ for i = 1, firstline do
+ curr_lines[i] = prev_lines[i]
+ end
+ for i = firstline + 1, new_lastline do
+ curr_lines[i] = changed_lines[i - firstline]
+ end
+ for i = lastline + 1, #prev_lines do
+ curr_lines[i - lastline + new_lastline] = prev_lines[i]
+ end
+ if vim.tbl_isempty(curr_lines) then
+ -- Can happen when deleting the entire contents of a buffer, see https://github.com/neovim/neovim/issues/16259.
+ curr_lines[1] = ''
+ end
+
+ local line_ending = vim.lsp._buf_get_line_ending(bufnr)
+ local incremental_change = sync.compute_diff(
+ state.lines,
+ curr_lines,
+ firstline,
+ lastline,
+ new_lastline,
+ encoding,
+ line_ending
+ )
+
+ -- Double-buffering of lines tables is used to reduce the load on the garbage collector.
+ -- At this point the prev_lines table is useless, but its internal storage has already been allocated,
+ -- so let's keep it around for the next didChange event, in which it will become the next
+ -- curr_lines table. Note that setting elements to nil doesn't actually deallocate slots in the
+ -- internal storage - it merely marks them as free, for the GC to deallocate them.
+ for i in ipairs(prev_lines) do
+ prev_lines[i] = nil
+ end
+ state.lines = curr_lines
+ state.lines_tmp = prev_lines
+
+ return incremental_change
+end
+
+---@param client vim.lsp.Client
+---@param bufnr integer
+function M.init(client, bufnr)
+ assert(client.offset_encoding, 'lsp client must have an offset_encoding')
+ local group = get_group(client)
+ local state = state_by_group[group]
+ if state then
+ state.debounce = math.min(state.debounce, client.flags.debounce_text_changes or 150)
+ state.clients[client.id] = client
+ else
+ state = {
+ buffers = {},
+ debounce = client.flags.debounce_text_changes or 150,
+ clients = {
+ [client.id] = client,
+ },
+ }
+ state_by_group[group] = state
+ end
+ local buf_state = state.buffers[bufnr]
+ if buf_state then
+ buf_state.refs = buf_state.refs + 1
+ else
+ buf_state = {
+ name = api.nvim_buf_get_name(bufnr),
+ lines = {},
+ lines_tmp = {},
+ pending_changes = {},
+ needs_flush = false,
+ refs = 1,
+ }
+ state.buffers[bufnr] = buf_state
+ if group.sync_kind == protocol.TextDocumentSyncKind.Incremental then
+ buf_state.lines = api.nvim_buf_get_lines(bufnr, 0, -1, true)
+ end
+ end
+end
+
+--- @param client vim.lsp.Client
+--- @param bufnr integer
+--- @param name string
+--- @return string
+function M._get_and_set_name(client, bufnr, name)
+ local state = state_by_group[get_group(client)] or {}
+ local buf_state = (state.buffers or {})[bufnr]
+ local old_name = buf_state.name
+ buf_state.name = name
+ return old_name
+end
+
+---@param buf_state vim.lsp.CTBufferState
+local function reset_timer(buf_state)
+ local timer = buf_state.timer
+ if timer then
+ buf_state.timer = nil
+ if not timer:is_closing() then
+ timer:stop()
+ timer:close()
+ end
+ end
+end
+
+--- @param client vim.lsp.Client
+--- @param bufnr integer
+function M.reset_buf(client, bufnr)
+ M.flush(client, bufnr)
+ local state = state_by_group[get_group(client)]
+ if not state then
+ return
+ end
+ assert(state.buffers, 'CTGroupState must have buffers')
+ local buf_state = state.buffers[bufnr]
+ buf_state.refs = buf_state.refs - 1
+ assert(buf_state.refs >= 0, 'refcount on buffer state must not get negative')
+ if buf_state.refs == 0 then
+ state.buffers[bufnr] = nil
+ reset_timer(buf_state)
+ end
+end
+
+--- @param client vim.lsp.Client
+function M.reset(client)
+ local state = state_by_group[get_group(client)]
+ if not state then
+ return
+ end
+ state.clients[client.id] = nil
+ if vim.tbl_count(state.clients) == 0 then
+ for _, buf_state in pairs(state.buffers) do
+ reset_timer(buf_state)
+ end
+ state.buffers = {}
+ end
+end
+
+-- Adjust debounce time by taking time of last didChange notification into
+-- consideration. If the last didChange happened more than `debounce` time ago,
+-- debounce can be skipped and otherwise maybe reduced.
+--
+-- This turns the debounce into a kind of client rate limiting
+--
+---@param debounce integer
+---@param buf_state vim.lsp.CTBufferState
+---@return number
+local function next_debounce(debounce, buf_state)
+ if debounce == 0 then
+ return 0
+ end
+ local ns_to_ms = 0.000001
+ if not buf_state.last_flush then
+ return debounce
+ end
+ local now = uv.hrtime()
+ local ms_since_last_flush = (now - buf_state.last_flush) * ns_to_ms
+ return math.max(debounce - ms_since_last_flush, 0)
+end
+
+---@param bufnr integer
+---@param sync_kind integer protocol.TextDocumentSyncKind
+---@param state vim.lsp.CTGroupState
+---@param buf_state vim.lsp.CTBufferState
+local function send_changes(bufnr, sync_kind, state, buf_state)
+ if not buf_state.needs_flush then
+ return
+ end
+ buf_state.last_flush = uv.hrtime()
+ buf_state.needs_flush = false
+
+ if not api.nvim_buf_is_valid(bufnr) then
+ buf_state.pending_changes = {}
+ return
+ end
+
+ local changes --- @type lsp.TextDocumentContentChangeEvent[]
+ if sync_kind == protocol.TextDocumentSyncKind.None then
+ return
+ elseif sync_kind == protocol.TextDocumentSyncKind.Incremental then
+ changes = buf_state.pending_changes
+ buf_state.pending_changes = {}
+ else
+ changes = {
+ { text = vim.lsp._buf_get_full_text(bufnr) },
+ }
+ end
+ local uri = vim.uri_from_bufnr(bufnr)
+ for _, client in pairs(state.clients) do
+ if not client.is_stopped() and vim.lsp.buf_is_attached(bufnr, client.id) then
+ client.notify(protocol.Methods.textDocument_didChange, {
+ textDocument = {
+ uri = uri,
+ version = util.buf_versions[bufnr],
+ },
+ contentChanges = changes,
+ })
+ end
+ end
+end
+
+--- @param bufnr integer
+--- @param firstline integer
+--- @param lastline integer
+--- @param new_lastline integer
+--- @param group vim.lsp.CTGroup
+local function send_changes_for_group(bufnr, firstline, lastline, new_lastline, group)
+ local state = state_by_group[group]
+ if not state then
+ error(
+ string.format(
+ 'changetracking.init must have been called for all LSP clients. group=%s states=%s',
+ vim.inspect(group),
+ vim.inspect(vim.tbl_keys(state_by_group))
+ )
+ )
+ end
+ local buf_state = state.buffers[bufnr]
+ buf_state.needs_flush = true
+ reset_timer(buf_state)
+ local debounce = next_debounce(state.debounce, buf_state)
+ if group.sync_kind == protocol.TextDocumentSyncKind.Incremental then
+ -- This must be done immediately and cannot be delayed
+ -- The contents would further change and startline/endline may no longer fit
+ local changes = incremental_changes(
+ buf_state,
+ group.offset_encoding,
+ bufnr,
+ firstline,
+ lastline,
+ new_lastline
+ )
+ table.insert(buf_state.pending_changes, changes)
+ end
+ if debounce == 0 then
+ send_changes(bufnr, group.sync_kind, state, buf_state)
+ else
+ local timer = assert(uv.new_timer(), 'Must be able to create timer')
+ buf_state.timer = timer
+ timer:start(
+ debounce,
+ 0,
+ vim.schedule_wrap(function()
+ reset_timer(buf_state)
+ send_changes(bufnr, group.sync_kind, state, buf_state)
+ end)
+ )
+ end
+end
+
+--- @param bufnr integer
+--- @param firstline integer
+--- @param lastline integer
+--- @param new_lastline integer
+function M.send_changes(bufnr, firstline, lastline, new_lastline)
+ local groups = {} ---@type table<string,vim.lsp.CTGroup>
+ for _, client in pairs(vim.lsp.get_clients({ bufnr = bufnr })) do
+ local group = get_group(client)
+ groups[group_key(group)] = group
+ end
+ for _, group in pairs(groups) do
+ send_changes_for_group(bufnr, firstline, lastline, new_lastline, group)
+ end
+end
+
+--- Flushes any outstanding change notification.
+---@param client vim.lsp.Client
+---@param bufnr? integer
+function M.flush(client, bufnr)
+ local group = get_group(client)
+ local state = state_by_group[group]
+ if not state then
+ return
+ end
+ if bufnr then
+ local buf_state = state.buffers[bufnr] or {}
+ reset_timer(buf_state)
+ send_changes(bufnr, group.sync_kind, state, buf_state)
+ else
+ for buf, buf_state in pairs(state.buffers) do
+ reset_timer(buf_state)
+ send_changes(buf, group.sync_kind, state, buf_state)
+ end
+ end
+end
+
+return M
diff --git a/runtime/lua/vim/lsp/_completion.lua b/runtime/lua/vim/lsp/_completion.lua
index 7a607d6c13..a169f96565 100644
--- a/runtime/lua/vim/lsp/_completion.lua
+++ b/runtime/lua/vim/lsp/_completion.lua
@@ -4,11 +4,21 @@ local lsp = vim.lsp
local protocol = lsp.protocol
local ms = protocol.Methods
+--- @alias vim.lsp.CompletionResult lsp.CompletionList | lsp.CompletionItem[]
+
+-- TODO(mariasolos): Remove this declaration once we figure out a better way to handle
+-- literal/anonymous types (see https://github.com/neovim/neovim/pull/27542/files#r1495259331).
+--- @class lsp.ItemDefaults
+--- @field editRange lsp.Range | { insert: lsp.Range, replace: lsp.Range } | nil
+--- @field insertTextFormat lsp.InsertTextFormat?
+--- @field insertTextMode lsp.InsertTextMode?
+--- @field data any
+
---@param input string unparsed snippet
---@return string parsed snippet
local function parse_snippet(input)
local ok, parsed = pcall(function()
- return require('vim.lsp._snippet_grammar').parse(input)
+ return vim.lsp._snippet_grammar.parse(input)
end)
return ok and tostring(parsed) or input
end
@@ -37,19 +47,49 @@ local function get_completion_word(item)
return item.label
end
----@param result lsp.CompletionList|lsp.CompletionItem[]
+--- Applies the given defaults to the completion item, modifying it in place.
+---
+--- @param item lsp.CompletionItem
+--- @param defaults lsp.ItemDefaults?
+local function apply_defaults(item, defaults)
+ if not defaults then
+ return
+ end
+
+ item.insertTextFormat = item.insertTextFormat or defaults.insertTextFormat
+ item.insertTextMode = item.insertTextMode or defaults.insertTextMode
+ item.data = item.data or defaults.data
+ if defaults.editRange then
+ local textEdit = item.textEdit or {}
+ item.textEdit = textEdit
+ textEdit.newText = textEdit.newText or item.textEditText or item.insertText
+ if defaults.editRange.start then
+ textEdit.range = textEdit.range or defaults.editRange
+ elseif defaults.editRange.insert then
+ textEdit.insert = defaults.editRange.insert
+ textEdit.replace = defaults.editRange.replace
+ end
+ end
+end
+
+---@param result vim.lsp.CompletionResult
---@return lsp.CompletionItem[]
local function get_items(result)
if result.items then
+ for _, item in ipairs(result.items) do
+ ---@diagnostic disable-next-line: param-type-mismatch
+ apply_defaults(item, result.itemDefaults)
+ end
return result.items
+ else
+ return result
end
- return result
end
--- Turns the result of a `textDocument/completion` request into vim-compatible
--- |complete-items|.
---
----@param result lsp.CompletionList|lsp.CompletionItem[] Result of `textDocument/completion`
+---@param result vim.lsp.CompletionResult Result of `textDocument/completion`
---@param prefix string prefix to filter the completion items
---@return table[]
---@see complete-items
@@ -130,7 +170,7 @@ end
---@param lnum integer 0-indexed line number
---@param client_start_boundary integer 0-indexed word boundary
---@param server_start_boundary? integer 0-indexed word boundary, based on textEdit.range.start.character
----@param result lsp.CompletionList|lsp.CompletionItem[]
+---@param result vim.lsp.CompletionResult
---@param encoding string
---@return table[] matches
---@return integer? server_start_boundary
@@ -206,7 +246,7 @@ function M.omnifunc(findstart, base)
local params = util.make_position_params(win, client.offset_encoding)
client.request(ms.textDocument_completion, params, function(err, result)
if err then
- require('vim.lsp.log').warn(err.message)
+ vim.lsp.log.warn(err.message)
end
if result and vim.fn.mode() == 'i' then
local matches
diff --git a/runtime/lua/vim/lsp/_dynamic.lua b/runtime/lua/vim/lsp/_dynamic.lua
index 04040e8e28..819b03a63a 100644
--- a/runtime/lua/vim/lsp/_dynamic.lua
+++ b/runtime/lua/vim/lsp/_dynamic.lua
@@ -1,4 +1,4 @@
-local wf = require('vim.lsp._watchfiles')
+local glob = vim.glob
--- @class lsp.DynamicCapabilities
--- @field capabilities table<string, lsp.Registration[]>
@@ -6,6 +6,7 @@ local wf = require('vim.lsp._watchfiles')
local M = {}
--- @param client_id number
+--- @return lsp.DynamicCapabilities
function M.new(client_id)
return setmetatable({
capabilities = {},
@@ -18,12 +19,12 @@ function M:supports_registration(method)
if not client then
return false
end
- local capability = vim.tbl_get(client.config.capabilities, unpack(vim.split(method, '/')))
+ local capability = vim.tbl_get(client.capabilities, unpack(vim.split(method, '/')))
return type(capability) == 'table' and capability.dynamicRegistration
end
--- @param registrations lsp.Registration[]
---- @private
+--- @package
function M:register(registrations)
-- remove duplicates
self:unregister(registrations)
@@ -37,7 +38,7 @@ function M:register(registrations)
end
--- @param unregisterations lsp.Unregistration[]
---- @private
+--- @package
function M:unregister(unregisterations)
for _, unreg in ipairs(unregisterations) do
local method = unreg.method
@@ -55,9 +56,9 @@ function M:unregister(unregisterations)
end
--- @param method string
---- @param opts? {bufnr?: number}
+--- @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()
@@ -69,15 +70,15 @@ function M:get(method, opts)
if not documentSelector then
return reg
end
- if M.match(opts.bufnr, documentSelector) then
+ if self:match(opts.bufnr, documentSelector) then
return reg
end
end
end
--- @param method string
---- @param opts? {bufnr?: number}
---- @private
+--- @param opts? {bufnr: integer?}
+--- @package
function M:supports(method, opts)
return self:get(method, opts) ~= nil
end
@@ -85,19 +86,23 @@ end
--- @param bufnr number
--- @param documentSelector lsp.DocumentSelector
--- @private
-function M.match(bufnr, documentSelector)
- local ft = vim.bo[bufnr].filetype
+function M:match(bufnr, documentSelector)
+ local client = vim.lsp.get_client_by_id(self.client_id)
+ if not client then
+ return false
+ end
+ local language = client.get_language_id(bufnr, vim.bo[bufnr].filetype)
local uri = vim.uri_from_bufnr(bufnr)
local fname = vim.uri_to_fname(uri)
for _, filter in ipairs(documentSelector) do
local matches = true
- if filter.language and ft ~= filter.language then
+ if filter.language and language ~= filter.language then
matches = false
end
if matches and filter.scheme and not vim.startswith(uri, filter.scheme .. ':') then
matches = false
end
- if matches and filter.pattern and not wf._match(filter.pattern, fname) then
+ if matches and filter.pattern and not glob.to_lpeg(filter.pattern):match(fname) then
matches = false
end
if matches then
diff --git a/runtime/lua/vim/lsp/_meta.lua b/runtime/lua/vim/lsp/_meta.lua
index acf799264e..be3222828d 100644
--- a/runtime/lua/vim/lsp/_meta.lua
+++ b/runtime/lua/vim/lsp/_meta.lua
@@ -1,22 +1,16 @@
---@meta
error('Cannot require a meta file')
----@alias lsp-handler fun(err: lsp.ResponseError|nil, result: any, context: lsp.HandlerContext, config: table|nil): any?
+---@alias lsp.Handler fun(err: lsp.ResponseError?, result: any, context: lsp.HandlerContext, config?: table): ...any
---@class lsp.HandlerContext
---@field method string
---@field client_id integer
---@field bufnr? integer
---@field params? any
+---@field version? integer
---@class lsp.ResponseError
---@field code integer
---@field message string
---@field data string|number|boolean|table[]|table|nil
-
---- @class lsp.DocumentFilter
---- @field language? string
---- @field scheme? string
---- @field pattern? string
-
---- @alias lsp.RegisterOptions any | lsp.StaticRegistrationOptions | lsp.TextDocumentRegistrationOptions
diff --git a/runtime/lua/vim/lsp/_meta/protocol.lua b/runtime/lua/vim/lsp/_meta/protocol.lua
index 72b0f00f65..a5da5ac6b7 100644
--- a/runtime/lua/vim/lsp/_meta/protocol.lua
+++ b/runtime/lua/vim/lsp/_meta/protocol.lua
@@ -1,7 +1,11 @@
--[[
-This file is autogenerated from scripts/gen_lsp.lua
+THIS FILE IS GENERATED by scripts/gen_lsp.lua
+DO NOT EDIT MANUALLY
+
+Based on LSP protocol 3.18
+
Regenerate:
-nvim -l scripts/gen_lsp.lua gen --version 3.18 --runtime/lua/vim/lsp/_meta/protocol.lua
+nvim -l scripts/gen_lsp.lua gen --version 3.18
--]]
---@meta
@@ -9,111 +13,132 @@ error('Cannot require a meta file')
---@alias lsp.null nil
---@alias uinteger integer
----@alias lsp.decimal number
+---@alias decimal number
---@alias lsp.DocumentUri string
---@alias lsp.URI string
----@alias lsp.LSPObject table<string, lsp.LSPAny>
----@alias lsp.LSPArray lsp.LSPAny[]
----@alias lsp.LSPAny lsp.LSPObject|lsp.LSPArray|string|number|boolean|nil
---@class lsp.ImplementationParams: lsp.TextDocumentPositionParams, lsp.WorkDoneProgressParams, lsp.PartialResultParams
---Represents a location inside a resource, such as a line
---inside a text file.
---@class lsp.Location
+---
---@field uri lsp.DocumentUri
+---
---@field range lsp.Range
----@class lsp.ImplementationRegistrationOptions: lsp.TextDocumentRegistrationOptions, lsp.StaticRegistrationOptions
+---@class lsp.ImplementationRegistrationOptions: lsp.TextDocumentRegistrationOptions, lsp.ImplementationOptions, lsp.StaticRegistrationOptions
---@class lsp.TypeDefinitionParams: lsp.TextDocumentPositionParams, lsp.WorkDoneProgressParams, lsp.PartialResultParams
----@class lsp.TypeDefinitionRegistrationOptions: lsp.TextDocumentRegistrationOptions, lsp.StaticRegistrationOptions
+---@class lsp.TypeDefinitionRegistrationOptions: lsp.TextDocumentRegistrationOptions, lsp.TypeDefinitionOptions, lsp.StaticRegistrationOptions
---A workspace folder inside a client.
---@class lsp.WorkspaceFolder
+---
---The associated URI for this workspace folder.
---@field uri lsp.URI
+---
---The name of the workspace folder. Used to refer to this
---workspace folder in the user interface.
---@field name string
---The parameters of a `workspace/didChangeWorkspaceFolders` notification.
---@class lsp.DidChangeWorkspaceFoldersParams
+---
---The actual workspace folder change event.
---@field event lsp.WorkspaceFoldersChangeEvent
---The parameters of a configuration request.
---@class lsp.ConfigurationParams
+---
---@field items lsp.ConfigurationItem[]
---Parameters for a {@link DocumentColorRequest}.
----@class lsp.DocumentColorParams
+---@class lsp.DocumentColorParams: lsp.WorkDoneProgressParams, lsp.PartialResultParams
+---
---The text document.
---@field textDocument lsp.TextDocumentIdentifier
---Represents a color range from a document.
---@class lsp.ColorInformation
+---
---The range in the document where this color appears.
---@field range lsp.Range
+---
---The actual color value for this color range.
---@field color lsp.Color
----@class lsp.DocumentColorRegistrationOptions: lsp.TextDocumentRegistrationOptions, lsp.StaticRegistrationOptions
+---@class lsp.DocumentColorRegistrationOptions: lsp.TextDocumentRegistrationOptions, lsp.DocumentColorOptions, lsp.StaticRegistrationOptions
---Parameters for a {@link ColorPresentationRequest}.
----@class lsp.ColorPresentationParams
+---@class lsp.ColorPresentationParams: lsp.WorkDoneProgressParams, lsp.PartialResultParams
+---
---The text document.
---@field textDocument lsp.TextDocumentIdentifier
+---
---The color to request presentations for.
---@field color lsp.Color
+---
---The range where the color would be inserted. Serves as a context.
---@field range lsp.Range
---@class lsp.ColorPresentation
+---
---The label of this color presentation. It will be shown on the color
---picker header. By default this is also the text that is inserted when selecting
---this color presentation.
---@field label string
+---
---An {@link TextEdit edit} which is applied to a document when selecting
---this presentation for the color. When `falsy` the {@link ColorPresentation.label label}
---is used.
---@field textEdit? lsp.TextEdit
+---
---An optional array of additional {@link TextEdit text edits} that are applied when
---selecting this color presentation. Edits must not overlap with the main {@link ColorPresentation.textEdit edit} nor with themselves.
---@field additionalTextEdits? lsp.TextEdit[]
---@class lsp.WorkDoneProgressOptions
+---
---@field workDoneProgress? boolean
---General text document registration options.
---@class lsp.TextDocumentRegistrationOptions
+---
---A document selector to identify the scope of the registration. If set to null
---the document selector provided on the client side will be used.
---@field documentSelector lsp.DocumentSelector|lsp.null
---Parameters for a {@link FoldingRangeRequest}.
----@class lsp.FoldingRangeParams
+---@class lsp.FoldingRangeParams: lsp.WorkDoneProgressParams, lsp.PartialResultParams
+---
---The text document.
---@field textDocument lsp.TextDocumentIdentifier
---Represents a folding range. To be valid, start and end line must be bigger than zero and smaller
---than the number of lines in the document. Clients are free to ignore invalid ranges.
---@class lsp.FoldingRange
+---
---The zero-based start line of the range to fold. The folded area starts after the line's last character.
---To be valid, the end must be zero or larger and smaller than the number of lines in the document.
---@field startLine uinteger
+---
---The zero-based character offset from where the folded range starts. If not defined, defaults to the length of the start line.
---@field startCharacter? uinteger
+---
---The zero-based end line of the range to fold. The folded area ends with the line's last character.
---To be valid, the end must be zero or larger and smaller than the number of lines in the document.
---@field endLine uinteger
+---
---The zero-based character offset before the folded range ends. If not defined, defaults to the length of the end line.
---@field endCharacter? uinteger
+---
---Describes the kind of the folding range such as `comment' or 'region'. The kind
---is used to categorize folding ranges and used by commands like 'Fold all comments'.
---See {@link FoldingRangeKind} for an enumeration of standardized kinds.
---@field kind? lsp.FoldingRangeKind
+---
---The text that the client should show when the specified range is
---collapsed. If not defined or not supported by the client, a default
---will be chosen by the client.
@@ -121,34 +146,40 @@ error('Cannot require a meta file')
---@since 3.17.0
---@field collapsedText? string
----@class lsp.FoldingRangeRegistrationOptions: lsp.TextDocumentRegistrationOptions, lsp.StaticRegistrationOptions
+---@class lsp.FoldingRangeRegistrationOptions: lsp.TextDocumentRegistrationOptions, lsp.FoldingRangeOptions, lsp.StaticRegistrationOptions
---@class lsp.DeclarationParams: lsp.TextDocumentPositionParams, lsp.WorkDoneProgressParams, lsp.PartialResultParams
----@class lsp.DeclarationRegistrationOptions: lsp.DeclarationOptions, lsp.StaticRegistrationOptions
+---@class lsp.DeclarationRegistrationOptions: lsp.DeclarationOptions, lsp.TextDocumentRegistrationOptions, lsp.StaticRegistrationOptions
---A parameter literal used in selection range requests.
----@class lsp.SelectionRangeParams
+---@class lsp.SelectionRangeParams: lsp.WorkDoneProgressParams, lsp.PartialResultParams
+---
---The text document.
---@field textDocument lsp.TextDocumentIdentifier
+---
---The positions inside the text document.
---@field positions lsp.Position[]
---A selection range represents a part of a selection hierarchy. A selection range
---may have a parent selection range that contains it.
---@class lsp.SelectionRange
+---
---The {@link Range range} of this selection range.
---@field range lsp.Range
+---
---The parent selection range containing this range. Therefore `parent.range` must contain `this.range`.
---@field parent? lsp.SelectionRange
----@class lsp.SelectionRangeRegistrationOptions: lsp.SelectionRangeOptions, lsp.StaticRegistrationOptions
+---@class lsp.SelectionRangeRegistrationOptions: lsp.SelectionRangeOptions, lsp.TextDocumentRegistrationOptions, lsp.StaticRegistrationOptions
---@class lsp.WorkDoneProgressCreateParams
+---
---The token to be used to report progress.
---@field token lsp.ProgressToken
---@class lsp.WorkDoneProgressCancelParams
+---
---The token to be used to report progress.
---@field token lsp.ProgressToken
@@ -162,21 +193,29 @@ error('Cannot require a meta file')
---
---@since 3.16.0
---@class lsp.CallHierarchyItem
+---
---The name of this item.
---@field name string
+---
---The kind of this item.
---@field kind lsp.SymbolKind
+---
---Tags for this item.
---@field tags? lsp.SymbolTag[]
+---
---More detail for this item, e.g. the signature of a function.
---@field detail? string
+---
---The resource identifier of this item.
---@field uri lsp.DocumentUri
+---
---The range enclosing this symbol not including leading/trailing whitespace but everything else, e.g. comments and code.
---@field range lsp.Range
+---
---The range that should be selected and revealed when this symbol is being picked, e.g. the name of a function.
---Must be contained by the {@link CallHierarchyItem.range `range`}.
---@field selectionRange lsp.Range
+---
---A data entry field that is preserved between a call hierarchy prepare and
---incoming calls or outgoing calls requests.
---@field data? lsp.LSPAny
@@ -184,20 +223,23 @@ error('Cannot require a meta file')
---Call hierarchy options used during static or dynamic registration.
---
---@since 3.16.0
----@class lsp.CallHierarchyRegistrationOptions: lsp.TextDocumentRegistrationOptions, lsp.StaticRegistrationOptions
+---@class lsp.CallHierarchyRegistrationOptions: lsp.TextDocumentRegistrationOptions, lsp.CallHierarchyOptions, lsp.StaticRegistrationOptions
---The parameter of a `callHierarchy/incomingCalls` request.
---
---@since 3.16.0
----@class lsp.CallHierarchyIncomingCallsParams
+---@class lsp.CallHierarchyIncomingCallsParams: lsp.WorkDoneProgressParams, lsp.PartialResultParams
+---
---@field item lsp.CallHierarchyItem
---Represents an incoming call, e.g. a caller of a method or constructor.
---
---@since 3.16.0
---@class lsp.CallHierarchyIncomingCall
+---
---The item that makes the call.
---@field from lsp.CallHierarchyItem
+---
---The ranges at which the calls appear. This is relative to the caller
---denoted by {@link CallHierarchyIncomingCall.from `this.from`}.
---@field fromRanges lsp.Range[]
@@ -205,64 +247,78 @@ error('Cannot require a meta file')
---The parameter of a `callHierarchy/outgoingCalls` request.
---
---@since 3.16.0
----@class lsp.CallHierarchyOutgoingCallsParams
+---@class lsp.CallHierarchyOutgoingCallsParams: lsp.WorkDoneProgressParams, lsp.PartialResultParams
+---
---@field item lsp.CallHierarchyItem
---Represents an outgoing call, e.g. calling a getter from a method or a method from a constructor etc.
---
---@since 3.16.0
---@class lsp.CallHierarchyOutgoingCall
+---
---The item that is called.
---@field to lsp.CallHierarchyItem
+---
---The range at which this item is called. This is the range relative to the caller, e.g the item
---passed to {@link CallHierarchyItemProvider.provideCallHierarchyOutgoingCalls `provideCallHierarchyOutgoingCalls`}
---and not {@link CallHierarchyOutgoingCall.to `this.to`}.
---@field fromRanges lsp.Range[]
---@since 3.16.0
----@class lsp.SemanticTokensParams
+---@class lsp.SemanticTokensParams: lsp.WorkDoneProgressParams, lsp.PartialResultParams
+---
---The text document.
---@field textDocument lsp.TextDocumentIdentifier
---@since 3.16.0
---@class lsp.SemanticTokens
+---
---An optional result id. If provided and clients support delta updating
---the client will include the result id in the next semantic token request.
---A server can then instead of computing all semantic tokens again simply
---send a delta.
---@field resultId? string
+---
---The actual tokens.
---@field data uinteger[]
---@since 3.16.0
---@class lsp.SemanticTokensPartialResult
+---
---@field data uinteger[]
---@since 3.16.0
----@class lsp.SemanticTokensRegistrationOptions: lsp.TextDocumentRegistrationOptions, lsp.StaticRegistrationOptions
+---@class lsp.SemanticTokensRegistrationOptions: lsp.TextDocumentRegistrationOptions, lsp.SemanticTokensOptions, lsp.StaticRegistrationOptions
---@since 3.16.0
----@class lsp.SemanticTokensDeltaParams
+---@class lsp.SemanticTokensDeltaParams: lsp.WorkDoneProgressParams, lsp.PartialResultParams
+---
---The text document.
---@field textDocument lsp.TextDocumentIdentifier
+---
---The result id of a previous response. The result Id can either point to a full response
---or a delta response depending on what was received last.
---@field previousResultId string
---@since 3.16.0
---@class lsp.SemanticTokensDelta
+---
---@field resultId? string
+---
---The semantic token edits to transform a previous result into a new result.
---@field edits lsp.SemanticTokensEdit[]
---@since 3.16.0
---@class lsp.SemanticTokensDeltaPartialResult
+---
---@field edits lsp.SemanticTokensEdit[]
---@since 3.16.0
----@class lsp.SemanticTokensRangeParams
+---@class lsp.SemanticTokensRangeParams: lsp.WorkDoneProgressParams, lsp.PartialResultParams
+---
---The text document.
---@field textDocument lsp.TextDocumentIdentifier
+---
---The range the semantic tokens are requested for.
---@field range lsp.Range
@@ -270,17 +326,21 @@ error('Cannot require a meta file')
---
---@since 3.16.0
---@class lsp.ShowDocumentParams
+---
---The uri to show.
---@field uri lsp.URI
+---
---Indicates to show the resource in an external program.
---To show, for example, `https://code.visualstudio.com/`
---in the default WEB browser set `external` to `true`.
---@field external? boolean
+---
---An optional property to indicate whether the editor
---showing the document should take focus or not.
---Clients might ignore this property if an external
---program is started.
---@field takeFocus? boolean
+---
---An optional selection range if the document is a text
---document. Clients might ignore the property if an
---external program is started or the file is not a text
@@ -291,6 +351,7 @@ error('Cannot require a meta file')
---
---@since 3.16.0
---@class lsp.ShowDocumentResult
+---
---A boolean indicating if the show was successful.
---@field success boolean
@@ -300,21 +361,24 @@ error('Cannot require a meta file')
---
---@since 3.16.0
---@class lsp.LinkedEditingRanges
+---
---A list of ranges that can be edited together. The ranges must have
---identical length and contain identical text content. The ranges cannot overlap.
---@field ranges lsp.Range[]
+---
---An optional word pattern (regular expression) that describes valid contents for
---the given ranges. If no pattern is provided, the client configuration's word
---pattern will be used.
---@field wordPattern? string
----@class lsp.LinkedEditingRangeRegistrationOptions: lsp.TextDocumentRegistrationOptions, lsp.StaticRegistrationOptions
+---@class lsp.LinkedEditingRangeRegistrationOptions: lsp.TextDocumentRegistrationOptions, lsp.LinkedEditingRangeOptions, lsp.StaticRegistrationOptions
---The parameters sent in notifications/requests for user-initiated creation of
---files.
---
---@since 3.16.0
---@class lsp.CreateFilesParams
+---
---An array of all files/folders created in this operation.
---@field files lsp.FileCreate[]
@@ -331,8 +395,10 @@ error('Cannot require a meta file')
---cause failure of the operation. How the client recovers from the failure is described by
---the client capability: `workspace.workspaceEdit.failureHandling`
---@class lsp.WorkspaceEdit
+---
---Holds changes to existing resources.
---@field changes? table<lsp.DocumentUri, lsp.TextEdit[]>
+---
---Depending on the client capability `workspace.workspaceEdit.resourceOperations` document changes
---are either an array of `TextDocumentEdit`s to express changes to n different text documents
---where each text document edit addresses a specific version of a text document. Or it can contain
@@ -343,7 +409,8 @@ error('Cannot require a meta file')
---
---If a client neither supports `documentChanges` nor `workspace.workspaceEdit.resourceOperations` then
---only plain `TextEdit`s using the `changes` property are supported.
----@field documentChanges? lsp.TextDocumentEdit|lsp.CreateFile|lsp.RenameFile|lsp.DeleteFile[]
+---@field documentChanges? (lsp.TextDocumentEdit|lsp.CreateFile|lsp.RenameFile|lsp.DeleteFile)[]
+---
---A map of change annotations that can be referenced in `AnnotatedTextEdit`s or create, rename and
---delete file / folder operations.
---
@@ -356,6 +423,7 @@ error('Cannot require a meta file')
---
---@since 3.16.0
---@class lsp.FileOperationRegistrationOptions
+---
---The actual filters.
---@field filters lsp.FileOperationFilter[]
@@ -364,6 +432,7 @@ error('Cannot require a meta file')
---
---@since 3.16.0
---@class lsp.RenameFilesParams
+---
---An array of all files/folders renamed in this operation. When a folder is renamed, only
---the folder will be included, and not its children.
---@field files lsp.FileRename[]
@@ -373,6 +442,7 @@ error('Cannot require a meta file')
---
---@since 3.16.0
---@class lsp.DeleteFilesParams
+---
---An array of all files/folders deleted in this operation.
---@field files lsp.FileDelete[]
@@ -382,17 +452,21 @@ error('Cannot require a meta file')
---
---@since 3.16.0
---@class lsp.Moniker
+---
---The scheme of the moniker. For example tsc or .Net
---@field scheme string
+---
---The identifier of the moniker. The value is opaque in LSIF however
---schema owners are allowed to define the structure if they want.
---@field identifier string
+---
---The scope in which the moniker is unique
---@field unique lsp.UniquenessLevel
+---
---The moniker kind if known.
---@field kind? lsp.MonikerKind
----@class lsp.MonikerRegistrationOptions: lsp.TextDocumentRegistrationOptions
+---@class lsp.MonikerRegistrationOptions: lsp.TextDocumentRegistrationOptions, lsp.MonikerOptions
---The parameter of a `textDocument/prepareTypeHierarchy` request.
---
@@ -401,23 +475,31 @@ error('Cannot require a meta file')
---@since 3.17.0
---@class lsp.TypeHierarchyItem
+---
---The name of this item.
---@field name string
+---
---The kind of this item.
---@field kind lsp.SymbolKind
+---
---Tags for this item.
---@field tags? lsp.SymbolTag[]
+---
---More detail for this item, e.g. the signature of a function.
---@field detail? string
+---
---The resource identifier of this item.
---@field uri lsp.DocumentUri
+---
---The range enclosing this symbol not including leading/trailing whitespace
---but everything else, e.g. comments and code.
---@field range lsp.Range
+---
---The range that should be selected and revealed when this symbol is being
---picked, e.g. the name of a function. Must be contained by the
---{@link TypeHierarchyItem.range `range`}.
---@field selectionRange lsp.Range
+---
---A data entry field that is preserved between a type hierarchy prepare and
---supertypes or subtypes requests. It could also be used to identify the
---type hierarchy in the server, helping improve the performance on
@@ -427,28 +509,33 @@ error('Cannot require a meta file')
---Type hierarchy options used during static or dynamic registration.
---
---@since 3.17.0
----@class lsp.TypeHierarchyRegistrationOptions: lsp.TextDocumentRegistrationOptions, lsp.StaticRegistrationOptions
+---@class lsp.TypeHierarchyRegistrationOptions: lsp.TextDocumentRegistrationOptions, lsp.TypeHierarchyOptions, lsp.StaticRegistrationOptions
---The parameter of a `typeHierarchy/supertypes` request.
---
---@since 3.17.0
----@class lsp.TypeHierarchySupertypesParams
+---@class lsp.TypeHierarchySupertypesParams: lsp.WorkDoneProgressParams, lsp.PartialResultParams
+---
---@field item lsp.TypeHierarchyItem
---The parameter of a `typeHierarchy/subtypes` request.
---
---@since 3.17.0
----@class lsp.TypeHierarchySubtypesParams
+---@class lsp.TypeHierarchySubtypesParams: lsp.WorkDoneProgressParams, lsp.PartialResultParams
+---
---@field item lsp.TypeHierarchyItem
---A parameter literal used in inline value requests.
---
---@since 3.17.0
----@class lsp.InlineValueParams
+---@class lsp.InlineValueParams: lsp.WorkDoneProgressParams
+---
---The text document.
---@field textDocument lsp.TextDocumentIdentifier
+---
---The document range for which inline values should be computed.
---@field range lsp.Range
+---
---Additional information about the context in which inline values were
---requested.
---@field context lsp.InlineValueContext
@@ -456,14 +543,16 @@ error('Cannot require a meta file')
---Inline value options used during static or dynamic registration.
---
---@since 3.17.0
----@class lsp.InlineValueRegistrationOptions: lsp.InlineValueOptions, lsp.StaticRegistrationOptions
+---@class lsp.InlineValueRegistrationOptions: lsp.InlineValueOptions, lsp.TextDocumentRegistrationOptions, lsp.StaticRegistrationOptions
---A parameter literal used in inlay hint requests.
---
---@since 3.17.0
----@class lsp.InlayHintParams
+---@class lsp.InlayHintParams: lsp.WorkDoneProgressParams
+---
---The text document.
---@field textDocument lsp.TextDocumentIdentifier
+---
---The document range for which inlay hints should be computed.
---@field range lsp.Range
@@ -471,36 +560,47 @@ error('Cannot require a meta file')
---
---@since 3.17.0
---@class lsp.InlayHint
+---
---The position of this hint.
+---
+---If multiple hints have the same position, they will be shown in the order
+---they appear in the response.
---@field position lsp.Position
+---
---The label of this hint. A human readable string or an array of
---InlayHintLabelPart label parts.
---
---*Note* that neither the string nor the label part can be empty.
---@field label string|lsp.InlayHintLabelPart[]
+---
---The kind of this hint. Can be omitted in which case the client
---should fall back to a reasonable default.
---@field kind? lsp.InlayHintKind
+---
---Optional text edits that are performed when accepting this inlay hint.
---
---*Note* that edits are expected to change the document so that the inlay
---hint (or its nearest variant) is now part of the document and the inlay
---hint itself is now obsolete.
---@field textEdits? lsp.TextEdit[]
+---
---The tooltip text when you hover over this item.
---@field tooltip? string|lsp.MarkupContent
+---
---Render padding before the hint.
---
---Note: Padding should use the editor's background color, not the
---background color of the hint itself. That means padding can be used
---to visually align/separate an inlay hint.
---@field paddingLeft? boolean
+---
---Render padding after the hint.
---
---Note: Padding should use the editor's background color, not the
---background color of the hint itself. That means padding can be used
---to visually align/separate an inlay hint.
---@field paddingRight? boolean
+---
---A data entry field that is preserved on an inlay hint between
---a `textDocument/inlayHint` and a `inlayHint/resolve` request.
---@field data? lsp.LSPAny
@@ -508,16 +608,19 @@ error('Cannot require a meta file')
---Inlay hint options used during static or dynamic registration.
---
---@since 3.17.0
----@class lsp.InlayHintRegistrationOptions: lsp.InlayHintOptions, lsp.StaticRegistrationOptions
+---@class lsp.InlayHintRegistrationOptions: lsp.InlayHintOptions, lsp.TextDocumentRegistrationOptions, lsp.StaticRegistrationOptions
---Parameters of the document diagnostic request.
---
---@since 3.17.0
----@class lsp.DocumentDiagnosticParams
+---@class lsp.DocumentDiagnosticParams: lsp.WorkDoneProgressParams, lsp.PartialResultParams
+---
---The text document.
---@field textDocument lsp.TextDocumentIdentifier
+---
---The additional identifier provided during registration.
---@field identifier? string
+---
---The result id of a previous response if provided.
---@field previousResultId? string
@@ -525,25 +628,29 @@ error('Cannot require a meta file')
---
---@since 3.17.0
---@class lsp.DocumentDiagnosticReportPartialResult
+---
---@field relatedDocuments table<lsp.DocumentUri, lsp.FullDocumentDiagnosticReport|lsp.UnchangedDocumentDiagnosticReport>
---Cancellation data returned from a diagnostic request.
---
---@since 3.17.0
---@class lsp.DiagnosticServerCancellationData
+---
---@field retriggerRequest boolean
---Diagnostic registration options.
---
---@since 3.17.0
----@class lsp.DiagnosticRegistrationOptions: lsp.TextDocumentRegistrationOptions, lsp.StaticRegistrationOptions
+---@class lsp.DiagnosticRegistrationOptions: lsp.TextDocumentRegistrationOptions, lsp.DiagnosticOptions, lsp.StaticRegistrationOptions
---Parameters of the workspace diagnostic request.
---
---@since 3.17.0
----@class lsp.WorkspaceDiagnosticParams
+---@class lsp.WorkspaceDiagnosticParams: lsp.WorkDoneProgressParams, lsp.PartialResultParams
+---
---The additional identifier provided during registration.
---@field identifier? string
+---
---The currently known diagnostic reports with their
---previous result ids.
---@field previousResultIds lsp.PreviousResultId[]
@@ -552,20 +659,24 @@ error('Cannot require a meta file')
---
---@since 3.17.0
---@class lsp.WorkspaceDiagnosticReport
+---
---@field items lsp.WorkspaceDocumentDiagnosticReport[]
---A partial result for a workspace diagnostic report.
---
---@since 3.17.0
---@class lsp.WorkspaceDiagnosticReportPartialResult
+---
---@field items lsp.WorkspaceDocumentDiagnosticReport[]
---The params sent in an open notebook document notification.
---
---@since 3.17.0
---@class lsp.DidOpenNotebookDocumentParams
+---
---The notebook document that got opened.
---@field notebookDocument lsp.NotebookDocument
+---
---The text documents that represent the content
---of a notebook cell.
---@field cellTextDocuments lsp.TextDocumentItem[]
@@ -574,11 +685,13 @@ error('Cannot require a meta file')
---
---@since 3.17.0
---@class lsp.DidChangeNotebookDocumentParams
+---
---The notebook document that did change. The version number points
---to the version after all provided changes have been applied. If
---only the text document content of a cell changes the notebook version
---doesn't necessarily have to change.
---@field notebookDocument lsp.VersionedNotebookDocumentIdentifier
+---
---The actual changes to the notebook document.
---
---The changes describe single state changes to the notebook document.
@@ -598,6 +711,7 @@ error('Cannot require a meta file')
---
---@since 3.17.0
---@class lsp.DidSaveNotebookDocumentParams
+---
---The notebook document that got saved.
---@field notebookDocument lsp.NotebookDocumentIdentifier
@@ -605,8 +719,10 @@ error('Cannot require a meta file')
---
---@since 3.17.0
---@class lsp.DidCloseNotebookDocumentParams
+---
---The notebook document that got closed.
---@field notebookDocument lsp.NotebookDocumentIdentifier
+---
---The text documents that represent the content
---of a notebook cell that got closed.
---@field cellTextDocuments lsp.TextDocumentIdentifier[]
@@ -614,56 +730,71 @@ error('Cannot require a meta file')
---A parameter literal used in inline completion requests.
---
---@since 3.18.0
+---@proposed
---@class lsp.InlineCompletionParams: lsp.TextDocumentPositionParams, lsp.WorkDoneProgressParams
+---
---Additional information about the context in which inline completions were
---requested.
---@field context lsp.InlineCompletionContext
---Represents a collection of {@link InlineCompletionItem inline completion items} to be presented in the editor.
+---
+---@since 3.18.0
+---@proposed
---@class lsp.InlineCompletionList
+---
---The inline completion items
---@field items lsp.InlineCompletionItem[]
---An inline completion item represents a text snippet that is proposed inline to complete text that is being typed.
---
---@since 3.18.0
+---@proposed
---@class lsp.InlineCompletionItem
+---
---The text to replace the range with. Must be set.
----@field insertText string
----The format of the insert text. The format applies to the `insertText`. If omitted defaults to `InsertTextFormat.PlainText`.
----@field insertTextFormat? lsp.InsertTextFormat
+---@field insertText string|lsp.StringValue
+---
---A text that is used to decide if this inline completion should be shown. When `falsy` the {@link InlineCompletionItem.insertText} is used.
---@field filterText? string
+---
---The range to replace. Must begin and end on the same line.
---@field range? lsp.Range
+---
---An optional {@link Command} that is executed *after* inserting this completion.
---@field command? lsp.Command
---Inline completion options used during static or dynamic registration.
---
---@since 3.18.0
----@class lsp.InlineCompletionRegistrationOptions: lsp.InlineCompletionOptions, lsp.StaticRegistrationOptions
+---@proposed
+---@class lsp.InlineCompletionRegistrationOptions: lsp.InlineCompletionOptions, lsp.TextDocumentRegistrationOptions, lsp.StaticRegistrationOptions
---@class lsp.RegistrationParams
+---
---@field registrations lsp.Registration[]
---@class lsp.UnregistrationParams
+---
---@field unregisterations lsp.Unregistration[]
----@class lsp.InitializeParams: lsp._InitializeParams
+---@class lsp.InitializeParams: lsp._InitializeParams, lsp.WorkspaceFoldersInitializeParams
---The result returned from an initialize request.
---@class lsp.InitializeResult
+---
---The capabilities the language server provides.
---@field capabilities lsp.ServerCapabilities
+---
---Information about the server.
---
---@since 3.15.0
----@field serverInfo? anonym1
+---@field serverInfo? lsp._anonym1.serverInfo
---The data type of the ResponseError if the
---initialize request fails.
---@class lsp.InitializeError
+---
---Indicates whether the client execute the following retry logic:
---(1) show the message provided by the ResponseError to the user
---(2) user selects retry or cancel
@@ -674,49 +805,62 @@ error('Cannot require a meta file')
---The parameters of a change configuration notification.
---@class lsp.DidChangeConfigurationParams
+---
---The actual changed settings
---@field settings lsp.LSPAny
---@class lsp.DidChangeConfigurationRegistrationOptions
+---
---@field section? string|string[]
---The parameters of a notification message.
---@class lsp.ShowMessageParams
+---
---The message type. See {@link MessageType}
---@field type lsp.MessageType
+---
---The actual message.
---@field message string
---@class lsp.ShowMessageRequestParams
+---
---The message type. See {@link MessageType}
---@field type lsp.MessageType
+---
---The actual message.
---@field message string
+---
---The message action items to present.
---@field actions? lsp.MessageActionItem[]
---@class lsp.MessageActionItem
+---
---A short title like 'Retry', 'Open Log' etc.
---@field title string
---The log message parameters.
---@class lsp.LogMessageParams
+---
---The message type. See {@link MessageType}
---@field type lsp.MessageType
+---
---The actual message.
---@field message string
---The parameters sent in an open text document notification
---@class lsp.DidOpenTextDocumentParams
+---
---The document that was opened.
---@field textDocument lsp.TextDocumentItem
---The change text document notification's parameters.
---@class lsp.DidChangeTextDocumentParams
+---
---The document that did change. The version number points
---to the version after all provided content changes have
---been applied.
---@field textDocument lsp.VersionedTextDocumentIdentifier
+---
---The actual content changes. The content changes describe single state changes
---to the document. So if there are two content changes c1 (at array index 0) and
---c2 (at array index 1) for a document in state S then c1 moves the document from
@@ -732,64 +876,78 @@ error('Cannot require a meta file')
---Describe options to be used when registered for text document change events.
---@class lsp.TextDocumentChangeRegistrationOptions: lsp.TextDocumentRegistrationOptions
+---
---How documents are synced to the server.
---@field syncKind lsp.TextDocumentSyncKind
---The parameters sent in a close text document notification
---@class lsp.DidCloseTextDocumentParams
+---
---The document that was closed.
---@field textDocument lsp.TextDocumentIdentifier
---The parameters sent in a save text document notification
---@class lsp.DidSaveTextDocumentParams
+---
---The document that was saved.
---@field textDocument lsp.TextDocumentIdentifier
+---
---Optional the content when saved. Depends on the includeText value
---when the save notification was requested.
---@field text? string
---Save registration options.
----@class lsp.TextDocumentSaveRegistrationOptions: lsp.TextDocumentRegistrationOptions
+---@class lsp.TextDocumentSaveRegistrationOptions: lsp.TextDocumentRegistrationOptions, lsp.SaveOptions
---The parameters sent in a will save text document notification.
---@class lsp.WillSaveTextDocumentParams
+---
---The document that will be saved.
---@field textDocument lsp.TextDocumentIdentifier
+---
---The 'TextDocumentSaveReason'.
---@field reason lsp.TextDocumentSaveReason
---A text edit applicable to a text document.
---@class lsp.TextEdit
+---
---The range of the text document to be manipulated. To insert
---text into a document create a range where start === end.
---@field range lsp.Range
+---
---The string to be inserted. For delete operations use an
---empty string.
---@field newText string
---The watched files change notification's parameters.
---@class lsp.DidChangeWatchedFilesParams
+---
---The actual file events.
---@field changes lsp.FileEvent[]
---Describe options to be used when registered for text document change events.
---@class lsp.DidChangeWatchedFilesRegistrationOptions
+---
---The watchers to register.
---@field watchers lsp.FileSystemWatcher[]
---The publish diagnostic notification's parameters.
---@class lsp.PublishDiagnosticsParams
+---
---The URI for which diagnostic information is reported.
---@field uri lsp.DocumentUri
+---
---Optional the version number of the document the diagnostics are published for.
---
---@since 3.15.0
---@field version? integer
+---
---An array of diagnostic information items.
---@field diagnostics lsp.Diagnostic[]
---Completion parameters
---@class lsp.CompletionParams: lsp.TextDocumentPositionParams, lsp.WorkDoneProgressParams, lsp.PartialResultParams
+---
---The completion context. This is only available it the client specifies
---to send this using the client capability `textDocument.completion.contextSupport === true`
---@field context? lsp.CompletionContext
@@ -797,6 +955,7 @@ error('Cannot require a meta file')
---A completion item represents a text snippet that is
---proposed to complete text that is being typed.
---@class lsp.CompletionItem
+---
---The label of this completion item.
---
---The label property is also by default the text that
@@ -805,39 +964,49 @@ error('Cannot require a meta file')
---If label details are provided the label itself should
---be an unqualified name of the completion item.
---@field label string
+---
---Additional details for the label
---
---@since 3.17.0
---@field labelDetails? lsp.CompletionItemLabelDetails
+---
---The kind of this completion item. Based of the kind
---an icon is chosen by the editor.
---@field kind? lsp.CompletionItemKind
+---
---Tags for this completion item.
---
---@since 3.15.0
---@field tags? lsp.CompletionItemTag[]
+---
---A human-readable string with additional information
---about this item, like type or symbol information.
---@field detail? string
+---
---A human-readable string that represents a doc-comment.
---@field documentation? string|lsp.MarkupContent
+---
---Indicates if this item is deprecated.
---@deprecated Use `tags` instead.
---@field deprecated? boolean
+---
---Select this item when showing.
---
---*Note* that only one completion item can be selected and that the
---tool / client decides which item that is. The rule is that the *first*
---item of those that match best is selected.
---@field preselect? boolean
+---
---A string that should be used when comparing this item
---with other items. When `falsy` the {@link CompletionItem.label label}
---is used.
---@field sortText? string
+---
---A string that should be used when filtering a set of
---completion items. When `falsy` the {@link CompletionItem.label label}
---is used.
---@field filterText? string
+---
---A string that should be inserted into a document when selecting
---this completion. When `falsy` the {@link CompletionItem.label label}
---is used.
@@ -850,6 +1019,7 @@ error('Cannot require a meta file')
---recommended to use `textEdit` instead since it avoids additional client
---side interpretation.
---@field insertText? string
+---
---The format of the insert text. The format applies to both the
---`insertText` property and the `newText` property of a provided
---`textEdit`. If omitted defaults to `InsertTextFormat.PlainText`.
@@ -857,12 +1027,14 @@ error('Cannot require a meta file')
---Please note that the insertTextFormat doesn't apply to
---`additionalTextEdits`.
---@field insertTextFormat? lsp.InsertTextFormat
+---
---How whitespace and indentation is handled during completion
---item insertion. If not provided the clients default value depends on
---the `textDocument.completion.insertTextMode` client capability.
---
---@since 3.16.0
---@field insertTextMode? lsp.InsertTextMode
+---
---An {@link TextEdit edit} which is applied to a document when selecting
---this completion. When an edit is provided the value of
---{@link CompletionItem.insertText insertText} is ignored.
@@ -884,6 +1056,7 @@ error('Cannot require a meta file')
---
---@since 3.16.0 additional type `InsertReplaceEdit`
---@field textEdit? lsp.TextEdit|lsp.InsertReplaceEdit
+---
---The edit text used if the completion item is part of a CompletionList and
---CompletionList defines an item default for the text edit range.
---
@@ -895,6 +1068,7 @@ error('Cannot require a meta file')
---
---@since 3.17.0
---@field textEditText? string
+---
---An optional array of additional {@link TextEdit text edits} that are applied when
---selecting this completion. Edits must not overlap (including the same insert position)
---with the main {@link CompletionItem.textEdit edit} nor with themselves.
@@ -903,14 +1077,17 @@ error('Cannot require a meta file')
---(for example adding an import statement at the top of the file if the completion item will
---insert an unqualified type).
---@field additionalTextEdits? lsp.TextEdit[]
+---
---An optional set of characters that when pressed while this completion is active will accept it first and
---then type that character. *Note* that all commit characters should have `length=1` and that superfluous
---characters will be ignored.
---@field commitCharacters? string[]
+---
---An optional {@link Command command} that is executed *after* inserting this completion. *Note* that
---additional modifications to the current document should be described with the
---{@link CompletionItem.additionalTextEdits additionalTextEdits}-property.
---@field command? lsp.Command
+---
---A data entry field that is preserved on a completion item between a
---{@link CompletionRequest} and a {@link CompletionResolveRequest}.
---@field data? lsp.LSPAny
@@ -918,11 +1095,13 @@ error('Cannot require a meta file')
---Represents a collection of {@link CompletionItem completion items} to be presented
---in the editor.
---@class lsp.CompletionList
+---
---This list it not complete. Further typing results in recomputing this list.
---
---Recomputed lists have all their items replaced (not appended) in the
---incomplete completion sessions.
---@field isIncomplete boolean
+---
---In many cases the items of an actual completion result share the same
---value for properties like `commitCharacters` or the range of a text
---edit. A completion list can therefore define item defaults which will
@@ -936,29 +1115,33 @@ error('Cannot require a meta file')
---capability.
---
---@since 3.17.0
----@field itemDefaults? anonym3
+---@field itemDefaults? lsp._anonym2.itemDefaults
+---
---The completion items.
---@field items lsp.CompletionItem[]
---Registration options for a {@link CompletionRequest}.
----@class lsp.CompletionRegistrationOptions: lsp.TextDocumentRegistrationOptions
+---@class lsp.CompletionRegistrationOptions: lsp.TextDocumentRegistrationOptions, lsp.CompletionOptions
---Parameters for a {@link HoverRequest}.
---@class lsp.HoverParams: lsp.TextDocumentPositionParams, lsp.WorkDoneProgressParams
---The result of a hover request.
---@class lsp.Hover
+---
---The hover's content
---@field contents lsp.MarkupContent|lsp.MarkedString|lsp.MarkedString[]
+---
---An optional range inside the text document that is used to
---visualize the hover, e.g. by changing the background color.
---@field range? lsp.Range
---Registration options for a {@link HoverRequest}.
----@class lsp.HoverRegistrationOptions: lsp.TextDocumentRegistrationOptions
+---@class lsp.HoverRegistrationOptions: lsp.TextDocumentRegistrationOptions, lsp.HoverOptions
---Parameters for a {@link SignatureHelpRequest}.
---@class lsp.SignatureHelpParams: lsp.TextDocumentPositionParams, lsp.WorkDoneProgressParams
+---
---The signature help context. This is only available if the client specifies
---to send this using the client capability `textDocument.signatureHelp.contextSupport === true`
---
@@ -969,8 +1152,10 @@ error('Cannot require a meta file')
---callable. There can be multiple signature but only one
---active and only one active parameter.
---@class lsp.SignatureHelp
+---
---One or more signatures.
---@field signatures lsp.SignatureInformation[]
+---
---The active signature. If omitted or the value lies outside the
---range of `signatures` the value defaults to zero or is ignored if
---the `SignatureHelp` has no signatures.
@@ -981,30 +1166,41 @@ error('Cannot require a meta file')
---In future version of the protocol this property might become
---mandatory to better express this.
---@field activeSignature? uinteger
----The active parameter of the active signature. If omitted or the value
----lies outside the range of `signatures[activeSignature].parameters`
----defaults to 0 if the active signature has parameters. If
----the active signature has no parameters it is ignored.
+---
+---The active parameter of the active signature.
+---
+---If `null`, no parameter of the signature is active (for example a named
+---argument that does not match any declared parameters). This is only valid
+---since 3.18.0 and if the client specifies the client capability
+---`textDocument.signatureHelp.noActiveParameterSupport === true`
+---
+---If omitted or the value lies outside the range of
+---`signatures[activeSignature].parameters` defaults to 0 if the active
+---signature has parameters.
+---
+---If the active signature has no parameters it is ignored.
+---
---In future version of the protocol this property might become
----mandatory to better express the active parameter if the
----active signature does have any.
----@field activeParameter? uinteger
+---mandatory (but still nullable) to better express the active parameter if
+---the active signature does have any.
+---@field activeParameter? uinteger|lsp.null
---Registration options for a {@link SignatureHelpRequest}.
----@class lsp.SignatureHelpRegistrationOptions: lsp.TextDocumentRegistrationOptions
+---@class lsp.SignatureHelpRegistrationOptions: lsp.TextDocumentRegistrationOptions, lsp.SignatureHelpOptions
---Parameters for a {@link DefinitionRequest}.
---@class lsp.DefinitionParams: lsp.TextDocumentPositionParams, lsp.WorkDoneProgressParams, lsp.PartialResultParams
---Registration options for a {@link DefinitionRequest}.
----@class lsp.DefinitionRegistrationOptions: lsp.TextDocumentRegistrationOptions
+---@class lsp.DefinitionRegistrationOptions: lsp.TextDocumentRegistrationOptions, lsp.DefinitionOptions
---Parameters for a {@link ReferencesRequest}.
---@class lsp.ReferenceParams: lsp.TextDocumentPositionParams, lsp.WorkDoneProgressParams, lsp.PartialResultParams
+---
---@field context lsp.ReferenceContext
---Registration options for a {@link ReferencesRequest}.
----@class lsp.ReferenceRegistrationOptions: lsp.TextDocumentRegistrationOptions
+---@class lsp.ReferenceRegistrationOptions: lsp.TextDocumentRegistrationOptions, lsp.ReferenceOptions
---Parameters for a {@link DocumentHighlightRequest}.
---@class lsp.DocumentHighlightParams: lsp.TextDocumentPositionParams, lsp.WorkDoneProgressParams, lsp.PartialResultParams
@@ -1013,26 +1209,31 @@ error('Cannot require a meta file')
---special attention. Usually a document highlight is visualized by changing
---the background color of its range.
---@class lsp.DocumentHighlight
+---
---The range this highlight applies to.
---@field range lsp.Range
+---
---The highlight kind, default is {@link DocumentHighlightKind.Text text}.
---@field kind? lsp.DocumentHighlightKind
---Registration options for a {@link DocumentHighlightRequest}.
----@class lsp.DocumentHighlightRegistrationOptions: lsp.TextDocumentRegistrationOptions
+---@class lsp.DocumentHighlightRegistrationOptions: lsp.TextDocumentRegistrationOptions, lsp.DocumentHighlightOptions
---Parameters for a {@link DocumentSymbolRequest}.
----@class lsp.DocumentSymbolParams
+---@class lsp.DocumentSymbolParams: lsp.WorkDoneProgressParams, lsp.PartialResultParams
+---
---The text document.
---@field textDocument lsp.TextDocumentIdentifier
---Represents information about programming constructs like variables, classes,
---interfaces etc.
---@class lsp.SymbolInformation: lsp.BaseSymbolInformation
+---
---Indicates if this symbol is deprecated.
---
---@deprecated Use tags instead
---@field deprecated? boolean
+---
---The location of this symbol. The location's range is used by a tool
---to reveal the location in the editor. If the symbol is selected in the
---tool the range's start information is used to position the cursor. So
@@ -1049,40 +1250,51 @@ error('Cannot require a meta file')
---have two ranges: one that encloses its definition and one that points to
---its most interesting range, e.g. the range of an identifier.
---@class lsp.DocumentSymbol
+---
---The name of this symbol. Will be displayed in the user interface and therefore must not be
---an empty string or a string only consisting of white spaces.
---@field name string
+---
---More detail for this symbol, e.g the signature of a function.
---@field detail? string
+---
---The kind of this symbol.
---@field kind lsp.SymbolKind
+---
---Tags for this document symbol.
---
---@since 3.16.0
---@field tags? lsp.SymbolTag[]
+---
---Indicates if this symbol is deprecated.
---
---@deprecated Use tags instead
---@field deprecated? boolean
+---
---The range enclosing this symbol not including leading/trailing whitespace but everything else
---like comments. This information is typically used to determine if the clients cursor is
---inside the symbol to reveal in the symbol in the UI.
---@field range lsp.Range
+---
---The range that should be selected and revealed when this symbol is being picked, e.g the name of a function.
---Must be contained by the `range`.
---@field selectionRange lsp.Range
+---
---Children of this symbol, e.g. properties of a class.
---@field children? lsp.DocumentSymbol[]
---Registration options for a {@link DocumentSymbolRequest}.
----@class lsp.DocumentSymbolRegistrationOptions: lsp.TextDocumentRegistrationOptions
+---@class lsp.DocumentSymbolRegistrationOptions: lsp.TextDocumentRegistrationOptions, lsp.DocumentSymbolOptions
---The parameters of a {@link CodeActionRequest}.
----@class lsp.CodeActionParams
+---@class lsp.CodeActionParams: lsp.WorkDoneProgressParams, lsp.PartialResultParams
+---
---The document in which the command was invoked.
---@field textDocument lsp.TextDocumentIdentifier
+---
---The range for which the command was invoked.
---@field range lsp.Range
+---
---Context carrying additional information.
---@field context lsp.CodeActionContext
@@ -1091,10 +1303,13 @@ error('Cannot require a meta file')
---an array of arguments which will be passed to the command handler
---function when invoked.
---@class lsp.Command
+---
---Title of the command, like `save`.
---@field title string
+---
---The identifier of the actual command handler.
---@field command string
+---
---Arguments that the command handler should be
---invoked with.
---@field arguments? lsp.LSPAny[]
@@ -1104,14 +1319,18 @@ error('Cannot require a meta file')
---
---A CodeAction must set either `edit` and/or a `command`. If both are supplied, the `edit` is applied first, then the `command` is executed.
---@class lsp.CodeAction
+---
---A short, human-readable, title for this code action.
---@field title string
+---
---The kind of the code action.
---
---Used to filter code actions.
---@field kind? lsp.CodeActionKind
+---
---The diagnostics that this code action resolves.
---@field diagnostics? lsp.Diagnostic[]
+---
---Marks this as a preferred action. Preferred actions are used by the `auto fix` command and can be targeted
---by keybindings.
---
@@ -1120,6 +1339,7 @@ error('Cannot require a meta file')
---
---@since 3.15.0
---@field isPreferred? boolean
+---
---Marks that the code action cannot currently be applied.
---
---Clients should follow the following guidelines regarding disabled code actions:
@@ -1135,13 +1355,16 @@ error('Cannot require a meta file')
--- error message with `reason` in the editor.
---
---@since 3.16.0
----@field disabled? anonym4
+---@field disabled? lsp._anonym4.disabled
+---
---The workspace edit this code action performs.
---@field edit? lsp.WorkspaceEdit
+---
---A command this code action executes. If a code action
---provides an edit and a command, first the edit is
---executed and then the command.
---@field command? lsp.Command
+---
---A data entry field that is preserved on a code action between
---a `textDocument/codeAction` and a `codeAction/resolve` request.
---
@@ -1149,10 +1372,11 @@ error('Cannot require a meta file')
---@field data? lsp.LSPAny
---Registration options for a {@link CodeActionRequest}.
----@class lsp.CodeActionRegistrationOptions: lsp.TextDocumentRegistrationOptions
+---@class lsp.CodeActionRegistrationOptions: lsp.TextDocumentRegistrationOptions, lsp.CodeActionOptions
---The parameters of a {@link WorkspaceSymbolRequest}.
----@class lsp.WorkspaceSymbolParams
+---@class lsp.WorkspaceSymbolParams: lsp.WorkDoneProgressParams, lsp.PartialResultParams
+---
---A query string to filter symbols by. Clients may send an empty
---string here to request all symbols.
---@field query string
@@ -1163,12 +1387,14 @@ error('Cannot require a meta file')
---
---@since 3.17.0
---@class lsp.WorkspaceSymbol: lsp.BaseSymbolInformation
+---
---The location of the symbol. Whether a server is allowed to
---return a location without a range depends on the client
---capability `workspace.symbol.resolveSupport`.
---
---See SymbolInformation#location for more details.
----@field location lsp.Location|anonym5
+---@field location lsp.Location|lsp._anonym5.location
+---
---A data entry field that is preserved on a workspace symbol between a
---workspace symbol request and a workspace symbol resolve request.
---@field data? lsp.LSPAny
@@ -1177,7 +1403,8 @@ error('Cannot require a meta file')
---@class lsp.WorkspaceSymbolRegistrationOptions: lsp.WorkspaceSymbolOptions
---The parameters of a {@link CodeLensRequest}.
----@class lsp.CodeLensParams
+---@class lsp.CodeLensParams: lsp.WorkDoneProgressParams, lsp.PartialResultParams
+---
---The document to request code lens for.
---@field textDocument lsp.TextDocumentIdentifier
@@ -1187,30 +1414,36 @@ error('Cannot require a meta file')
---A code lens is _unresolved_ when no command is associated to it. For performance
---reasons the creation of a code lens and resolving should be done in two stages.
---@class lsp.CodeLens
+---
---The range in which this code lens is valid. Should only span a single line.
---@field range lsp.Range
+---
---The command this code lens represents.
---@field command? lsp.Command
+---
---A data entry field that is preserved on a code lens item between
----a {@link CodeLensRequest} and a [CodeLensResolveRequest]
----(#CodeLensResolveRequest)
+---a {@link CodeLensRequest} and a {@link CodeLensResolveRequest}
---@field data? lsp.LSPAny
---Registration options for a {@link CodeLensRequest}.
----@class lsp.CodeLensRegistrationOptions: lsp.TextDocumentRegistrationOptions
+---@class lsp.CodeLensRegistrationOptions: lsp.TextDocumentRegistrationOptions, lsp.CodeLensOptions
---The parameters of a {@link DocumentLinkRequest}.
----@class lsp.DocumentLinkParams
+---@class lsp.DocumentLinkParams: lsp.WorkDoneProgressParams, lsp.PartialResultParams
+---
---The document to provide document links for.
---@field textDocument lsp.TextDocumentIdentifier
---A document link is a range in a text document that links to an internal or external resource, like another
---text document or a web site.
---@class lsp.DocumentLink
+---
---The range this link applies to.
---@field range lsp.Range
+---
---The uri this link points to. If missing a resolve request is sent later.
---@field target? lsp.URI
+---
---The tooltip text when you hover over this link.
---
---If a tooltip is provided, is will be displayed in a string that includes instructions on how to
@@ -1219,86 +1452,104 @@ error('Cannot require a meta file')
---
---@since 3.15.0
---@field tooltip? string
+---
---A data entry field that is preserved on a document link between a
---DocumentLinkRequest and a DocumentLinkResolveRequest.
---@field data? lsp.LSPAny
---Registration options for a {@link DocumentLinkRequest}.
----@class lsp.DocumentLinkRegistrationOptions: lsp.TextDocumentRegistrationOptions
+---@class lsp.DocumentLinkRegistrationOptions: lsp.TextDocumentRegistrationOptions, lsp.DocumentLinkOptions
---The parameters of a {@link DocumentFormattingRequest}.
----@class lsp.DocumentFormattingParams
+---@class lsp.DocumentFormattingParams: lsp.WorkDoneProgressParams
+---
---The document to format.
---@field textDocument lsp.TextDocumentIdentifier
+---
---The format options.
---@field options lsp.FormattingOptions
---Registration options for a {@link DocumentFormattingRequest}.
----@class lsp.DocumentFormattingRegistrationOptions: lsp.TextDocumentRegistrationOptions
+---@class lsp.DocumentFormattingRegistrationOptions: lsp.TextDocumentRegistrationOptions, lsp.DocumentFormattingOptions
---The parameters of a {@link DocumentRangeFormattingRequest}.
----@class lsp.DocumentRangeFormattingParams
+---@class lsp.DocumentRangeFormattingParams: lsp.WorkDoneProgressParams
+---
---The document to format.
---@field textDocument lsp.TextDocumentIdentifier
+---
---The range to format
---@field range lsp.Range
+---
---The format options
---@field options lsp.FormattingOptions
---Registration options for a {@link DocumentRangeFormattingRequest}.
----@class lsp.DocumentRangeFormattingRegistrationOptions: lsp.TextDocumentRegistrationOptions
+---@class lsp.DocumentRangeFormattingRegistrationOptions: lsp.TextDocumentRegistrationOptions, lsp.DocumentRangeFormattingOptions
---The parameters of a {@link DocumentRangesFormattingRequest}.
---
---@since 3.18.0
---@proposed
----@class lsp.DocumentRangesFormattingParams
+---@class lsp.DocumentRangesFormattingParams: lsp.WorkDoneProgressParams
+---
---The document to format.
---@field textDocument lsp.TextDocumentIdentifier
+---
---The ranges to format
---@field ranges lsp.Range[]
+---
---The format options
---@field options lsp.FormattingOptions
---The parameters of a {@link DocumentOnTypeFormattingRequest}.
---@class lsp.DocumentOnTypeFormattingParams
+---
---The document to format.
---@field textDocument lsp.TextDocumentIdentifier
+---
---The position around which the on type formatting should happen.
---This is not necessarily the exact position where the character denoted
---by the property `ch` got typed.
---@field position lsp.Position
+---
---The character that has been typed that triggered the formatting
---on type request. That is not necessarily the last character that
---got inserted into the document since the client could auto insert
---characters as well (e.g. like automatic brace completion).
---@field ch string
+---
---The formatting options.
---@field options lsp.FormattingOptions
---Registration options for a {@link DocumentOnTypeFormattingRequest}.
----@class lsp.DocumentOnTypeFormattingRegistrationOptions: lsp.TextDocumentRegistrationOptions
+---@class lsp.DocumentOnTypeFormattingRegistrationOptions: lsp.TextDocumentRegistrationOptions, lsp.DocumentOnTypeFormattingOptions
---The parameters of a {@link RenameRequest}.
----@class lsp.RenameParams
+---@class lsp.RenameParams: lsp.WorkDoneProgressParams
+---
---The document to rename.
---@field textDocument lsp.TextDocumentIdentifier
+---
---The position at which this request was sent.
---@field position lsp.Position
+---
---The new name of the symbol. If the given name is not valid the
---request must return a {@link ResponseError} with an
---appropriate message set.
---@field newName string
---Registration options for a {@link RenameRequest}.
----@class lsp.RenameRegistrationOptions: lsp.TextDocumentRegistrationOptions
+---@class lsp.RenameRegistrationOptions: lsp.TextDocumentRegistrationOptions, lsp.RenameOptions
---@class lsp.PrepareRenameParams: lsp.TextDocumentPositionParams, lsp.WorkDoneProgressParams
---The parameters of a {@link ExecuteCommandRequest}.
----@class lsp.ExecuteCommandParams
+---@class lsp.ExecuteCommandParams: lsp.WorkDoneProgressParams
+---
---The identifier of the actual command handler.
---@field command string
+---
---Arguments that the command should be invoked with.
---@field arguments? lsp.LSPAny[]
@@ -1307,10 +1558,12 @@ error('Cannot require a meta file')
---The parameters passed via an apply workspace edit request.
---@class lsp.ApplyWorkspaceEditParams
+---
---An optional label of the workspace edit. This label is
---presented in the user interface for example on an undo
---stack to undo the workspace edit.
---@field label? string
+---
---The edits to apply.
---@field edit lsp.WorkspaceEdit
@@ -1318,34 +1571,42 @@ error('Cannot require a meta file')
---
---@since 3.17 renamed from ApplyWorkspaceEditResponse
---@class lsp.ApplyWorkspaceEditResult
+---
---Indicates whether the edit was applied or not.
---@field applied boolean
+---
---An optional textual description for why the edit was not applied.
---This may be used by the server for diagnostic logging or to provide
---a suitable error for a request that triggered the edit.
---@field failureReason? string
+---
---Depending on the client's failure handling strategy `failedChange` might
---contain the index of the change that failed. This property is only available
---if the client signals a `failureHandlingStrategy` in its client capabilities.
---@field failedChange? uinteger
---@class lsp.WorkDoneProgressBegin
+---
---@field kind "begin"
+---
---Mandatory title of the progress operation. Used to briefly inform about
---the kind of operation being performed.
---
---Examples: "Indexing" or "Linking dependencies".
---@field title string
+---
---Controls if a cancel button should show to allow the user to cancel the
---long running operation. Clients that don't support cancellation are allowed
---to ignore the setting.
---@field cancellable? boolean
+---
---Optional, more detailed associated progress message. Contains
---complementary information to the `title`.
---
---Examples: "3/25 files", "project/src/module2", "node_modules/some_dep".
---If unset, the previous progress message (if any) is still valid.
---@field message? string
+---
---Optional progress percentage to display (value 100 is considered 100%).
---If not provided infinite progress is assumed and clients are allowed
---to ignore the `percentage` value in subsequent in report notifications.
@@ -1355,18 +1616,22 @@ error('Cannot require a meta file')
---@field percentage? uinteger
---@class lsp.WorkDoneProgressReport
+---
---@field kind "report"
+---
---Controls enablement state of a cancel button.
---
---Clients that don't support cancellation or don't support controlling the button's
---enablement state are allowed to ignore the property.
---@field cancellable? boolean
+---
---Optional, more detailed associated progress message. Contains
---complementary information to the `title`.
---
---Examples: "3/25 files", "project/src/module2", "node_modules/some_dep".
---If unset, the previous progress message (if any) is still valid.
---@field message? string
+---
---Optional progress percentage to display (value 100 is considered 100%).
---If not provided infinite progress is assumed and clients are allowed
---to ignore the `percentage` value in subsequent in report notifications.
@@ -1376,41 +1641,53 @@ error('Cannot require a meta file')
---@field percentage? uinteger
---@class lsp.WorkDoneProgressEnd
+---
---@field kind "end"
+---
---Optional, a final message indicating to for example indicate the outcome
---of the operation.
---@field message? string
---@class lsp.SetTraceParams
+---
---@field value lsp.TraceValues
---@class lsp.LogTraceParams
+---
---@field message string
+---
---@field verbose? string
---@class lsp.CancelParams
+---
---The request id to cancel.
---@field id integer|string
---@class lsp.ProgressParams
+---
---The progress token provided by the client or server.
---@field token lsp.ProgressToken
+---
---The progress data.
---@field value lsp.LSPAny
---A parameter literal used in requests to pass a text document and a position inside that
---document.
---@class lsp.TextDocumentPositionParams
+---
---The text document.
---@field textDocument lsp.TextDocumentIdentifier
+---
---The position inside the text document.
---@field position lsp.Position
---@class lsp.WorkDoneProgressParams
+---
---An optional token that a server can use to report work done progress.
---@field workDoneToken? lsp.ProgressToken
---@class lsp.PartialResultParams
+---
---An optional token that a server can use to report partial results (e.g. streaming) to
---the client.
---@field partialResultToken? lsp.ProgressToken
@@ -1418,17 +1695,21 @@ error('Cannot require a meta file')
---Represents the connection of two locations. Provides additional metadata over normal {@link Location locations},
---including an origin range.
---@class lsp.LocationLink
+---
---Span of the origin of this link.
---
---Used as the underlined span for mouse interaction. Defaults to the word range at
---the definition position.
---@field originSelectionRange? lsp.Range
+---
---The target resource identifier of this link.
---@field targetUri lsp.DocumentUri
+---
---The full target range of this link. If the target for example is a symbol then target range is the
---range enclosing this symbol not including leading/trailing whitespace but everything else
---like comments. This information is typically used to highlight the range in the editor.
---@field targetRange lsp.Range
+---
---The range that should be selected and revealed when this link is being followed, e.g the name of a function.
---Must be contained by the `targetRange`. See also `DocumentSymbol#range`
---@field targetSelectionRange lsp.Range
@@ -1445,56 +1726,68 @@ error('Cannot require a meta file')
---}
---```
---@class lsp.Range
+---
---The range's start position.
---@field start lsp.Position
+---
---The range's end position.
---@field end lsp.Position
----@class lsp.ImplementationOptions
+---@class lsp.ImplementationOptions: lsp.WorkDoneProgressOptions
---Static registration options to be returned in the initialize
---request.
---@class lsp.StaticRegistrationOptions
+---
---The id used to register the request. The id can be used to deregister
---the request again. See also Registration#id.
---@field id? string
----@class lsp.TypeDefinitionOptions
+---@class lsp.TypeDefinitionOptions: lsp.WorkDoneProgressOptions
---The workspace folder change event.
---@class lsp.WorkspaceFoldersChangeEvent
+---
---The array of added workspace folders
---@field added lsp.WorkspaceFolder[]
+---
---The array of the removed workspace folders
---@field removed lsp.WorkspaceFolder[]
---@class lsp.ConfigurationItem
+---
---The scope to get the configuration section for.
----@field scopeUri? string
+---@field scopeUri? lsp.URI
+---
---The configuration section asked for.
---@field section? string
---A literal to identify a text document in the client.
---@class lsp.TextDocumentIdentifier
+---
---The text document's uri.
---@field uri lsp.DocumentUri
---Represents a color in RGBA space.
---@class lsp.Color
+---
---The red component of this color in the range [0-1].
---@field red decimal
+---
---The green component of this color in the range [0-1].
---@field green decimal
+---
---The blue component of this color in the range [0-1].
---@field blue decimal
+---
---The alpha component of this color in the range [0-1].
---@field alpha decimal
----@class lsp.DocumentColorOptions
+---@class lsp.DocumentColorOptions: lsp.WorkDoneProgressOptions
----@class lsp.FoldingRangeOptions
+---@class lsp.FoldingRangeOptions: lsp.WorkDoneProgressOptions
----@class lsp.DeclarationOptions
+---@class lsp.DeclarationOptions: lsp.WorkDoneProgressOptions
---Position in a text document expressed as zero-based line and character
---offset. Prior to 3.17 the offsets were always based on a UTF-16 string
@@ -1503,14 +1796,14 @@ error('Cannot require a meta file')
---offset of b is 3 since `𐐀` is represented using two code units in UTF-16.
---Since 3.17 clients and servers can agree on a different string encoding
---representation (e.g. UTF-8). The client announces it's supported encoding
----via the client capability [`general.positionEncodings`](#clientCapabilities).
+---via the client capability [`general.positionEncodings`](https://microsoft.github.io/language-server-protocol/specifications/specification-current/#clientCapabilities).
---The value is an array of position encodings the client supports, with
---decreasing preference (e.g. the encoding at index `0` is the most preferred
---one). To stay backwards compatible the only mandatory encoding is UTF-16
---represented via the string `utf-16`. The server can pick one of the
---encodings offered by the client and signals that encoding back to the
---client via the initialize result's property
----[`capabilities.positionEncoding`](#serverCapabilities). If the string value
+---[`capabilities.positionEncoding`](https://microsoft.github.io/language-server-protocol/specifications/specification-current/#serverCapabilities). If the string value
---`utf-16` is missing from the client's capability `general.positionEncodings`
---servers can safely assume that the client supports UTF-16. If the server
---omits the position encoding in its initialize result the encoding defaults
@@ -1524,11 +1817,13 @@ error('Cannot require a meta file')
---
---@since 3.17.0 - support for negotiated position encoding.
---@class lsp.Position
+---
---Line position in a document (zero-based).
---
---If a line number is greater than the number of lines in a document, it defaults back to the number of lines in the document.
---If a line number is negative, it defaults to 0.
---@field line uinteger
+---
---Character offset on a line in a document (zero-based).
---
---The meaning of this offset is determined by the negotiated
@@ -1538,38 +1833,45 @@ error('Cannot require a meta file')
---line length.
---@field character uinteger
----@class lsp.SelectionRangeOptions
+---@class lsp.SelectionRangeOptions: lsp.WorkDoneProgressOptions
---Call hierarchy options used during static registration.
---
---@since 3.16.0
----@class lsp.CallHierarchyOptions
+---@class lsp.CallHierarchyOptions: lsp.WorkDoneProgressOptions
---@since 3.16.0
----@class lsp.SemanticTokensOptions
+---@class lsp.SemanticTokensOptions: lsp.WorkDoneProgressOptions
+---
---The legend used by the server
---@field legend lsp.SemanticTokensLegend
+---
---Server supports providing semantic tokens for a specific range
---of a document.
----@field range? boolean|anonym6
+---@field range? boolean|lsp._anonym6.range
+---
---Server supports providing semantic tokens for a full document.
----@field full? boolean|anonym7
+---@field full? boolean|lsp._anonym7.full
---@since 3.16.0
---@class lsp.SemanticTokensEdit
+---
---The start offset of the edit.
---@field start uinteger
+---
---The count of elements to remove.
---@field deleteCount uinteger
+---
---The elements to insert.
---@field data? uinteger[]
----@class lsp.LinkedEditingRangeOptions
+---@class lsp.LinkedEditingRangeOptions: lsp.WorkDoneProgressOptions
---Represents information on a file/folder create.
---
---@since 3.16.0
---@class lsp.FileCreate
+---
---A file:// URI for the location of the file/folder being created.
---@field uri string
@@ -1578,40 +1880,52 @@ error('Cannot require a meta file')
---So the creator of a TextDocumentEdit doesn't need to sort the array of edits or do any
---kind of ordering. However the edits must be non overlapping.
---@class lsp.TextDocumentEdit
+---
---The text document to change.
---@field textDocument lsp.OptionalVersionedTextDocumentIdentifier
+---
---The edits to be applied.
---
---@since 3.16.0 - support for AnnotatedTextEdit. This is guarded using a
---client capability.
----@field edits lsp.TextEdit|lsp.AnnotatedTextEdit[]
+---@field edits (lsp.TextEdit|lsp.AnnotatedTextEdit)[]
---Create file operation.
---@class lsp.CreateFile: lsp.ResourceOperation
+---
---A create
---@field kind "create"
+---
---The resource to create.
---@field uri lsp.DocumentUri
+---
---Additional options
---@field options? lsp.CreateFileOptions
---Rename file operation
---@class lsp.RenameFile: lsp.ResourceOperation
+---
---A rename
---@field kind "rename"
+---
---The old (existing) location.
---@field oldUri lsp.DocumentUri
+---
---The new location.
---@field newUri lsp.DocumentUri
+---
---Rename options.
---@field options? lsp.RenameFileOptions
---Delete file operation
---@class lsp.DeleteFile: lsp.ResourceOperation
+---
---A delete
---@field kind "delete"
+---
---The file to delete.
---@field uri lsp.DocumentUri
+---
---Delete options.
---@field options? lsp.DeleteFileOptions
@@ -1619,12 +1933,15 @@ error('Cannot require a meta file')
---
---@since 3.16.0
---@class lsp.ChangeAnnotation
+---
---A human-readable string describing the actual change. The string
---is rendered prominent in the user interface.
---@field label string
+---
---A flag which indicates that user confirmation is needed
---before applying the change.
---@field needsConfirmation? boolean
+---
---A human-readable string which is rendered less prominent in
---the user interface.
---@field description? string
@@ -1634,8 +1951,10 @@ error('Cannot require a meta file')
---
---@since 3.16.0
---@class lsp.FileOperationFilter
+---
---A Uri scheme like `file` or `untitled`.
---@field scheme? string
+---
---The actual file operation pattern.
---@field pattern lsp.FileOperationPattern
@@ -1643,8 +1962,10 @@ error('Cannot require a meta file')
---
---@since 3.16.0
---@class lsp.FileRename
+---
---A file:// URI for the original location of the file/folder being renamed.
---@field oldUri string
+---
---A file:// URI for the new location of the file/folder being renamed.
---@field newUri string
@@ -1652,20 +1973,23 @@ error('Cannot require a meta file')
---
---@since 3.16.0
---@class lsp.FileDelete
+---
---A file:// URI for the location of the file/folder being deleted.
---@field uri string
----@class lsp.MonikerOptions
+---@class lsp.MonikerOptions: lsp.WorkDoneProgressOptions
---Type hierarchy options used during static registration.
---
---@since 3.17.0
----@class lsp.TypeHierarchyOptions
+---@class lsp.TypeHierarchyOptions: lsp.WorkDoneProgressOptions
---@since 3.17.0
---@class lsp.InlineValueContext
+---
---The stack frame (as a DAP Id) where the execution has stopped.
---@field frameId integer
+---
---The document range where execution has stopped.
---Typically the end position of the range denotes the line where the inline values are shown.
---@field stoppedLocation lsp.Range
@@ -1674,8 +1998,10 @@ error('Cannot require a meta file')
---
---@since 3.17.0
---@class lsp.InlineValueText
+---
---The document range for which the inline value applies.
---@field range lsp.Range
+---
---The text of the inline value.
---@field text string
@@ -1685,11 +2011,14 @@ error('Cannot require a meta file')
---
---@since 3.17.0
---@class lsp.InlineValueVariableLookup
+---
---The document range for which the inline value applies.
---The range is used to extract the variable name from the underlying document.
---@field range lsp.Range
+---
---If specified the name of the variable to look up.
---@field variableName? string
+---
---How to perform the lookup.
---@field caseSensitiveLookup boolean
@@ -1699,28 +2028,33 @@ error('Cannot require a meta file')
---
---@since 3.17.0
---@class lsp.InlineValueEvaluatableExpression
+---
---The document range for which the inline value applies.
---The range is used to extract the evaluatable expression from the underlying document.
---@field range lsp.Range
+---
---If specified the expression overrides the extracted expression.
---@field expression? string
---Inline value options used during static registration.
---
---@since 3.17.0
----@class lsp.InlineValueOptions
+---@class lsp.InlineValueOptions: lsp.WorkDoneProgressOptions
---An inlay hint label part allows for interactive and composite labels
---of inlay hints.
---
---@since 3.17.0
---@class lsp.InlayHintLabelPart
+---
---The value of this label part.
---@field value string
+---
---The tooltip text when you hover over this label part. Depending on
---the client capability `inlayHint.resolveSupport` clients might resolve
---this property late using the resolve request.
---@field tooltip? string|lsp.MarkupContent
+---
---An optional source code location that represents this
---label part.
---
@@ -1733,6 +2067,7 @@ error('Cannot require a meta file')
---Depending on the client capability `inlayHint.resolveSupport` clients
---might resolve this property late using the resolve request.
---@field location? lsp.Location
+---
---An optional command for this label part.
---
---Depending on the client capability `inlayHint.resolveSupport` clients
@@ -1762,15 +2097,18 @@ error('Cannot require a meta file')
---*Please Note* that clients might sanitize the return markdown. A client could decide to
---remove HTML from the markdown to avoid script execution.
---@class lsp.MarkupContent
+---
---The type of the Markup
---@field kind lsp.MarkupKind
+---
---The content itself
---@field value string
---Inlay hint options used during static registration.
---
---@since 3.17.0
----@class lsp.InlayHintOptions
+---@class lsp.InlayHintOptions: lsp.WorkDoneProgressOptions
+---
---The server provides support to resolve additional
---information for an inlay hint item.
---@field resolveProvider? boolean
@@ -1779,6 +2117,7 @@ error('Cannot require a meta file')
---
---@since 3.17.0
---@class lsp.RelatedFullDocumentDiagnosticReport: lsp.FullDocumentDiagnosticReport
+---
---Diagnostics of related documents. This information is useful
---in programming languages where code in a file A can generate
---diagnostics in a file B which A depends on. An example of
@@ -1792,6 +2131,7 @@ error('Cannot require a meta file')
---
---@since 3.17.0
---@class lsp.RelatedUnchangedDocumentDiagnosticReport: lsp.UnchangedDocumentDiagnosticReport
+---
---Diagnostics of related documents. This information is useful
---in programming languages where code in a file A can generate
---diagnostics in a file B which A depends on. An example of
@@ -1805,12 +2145,15 @@ error('Cannot require a meta file')
---
---@since 3.17.0
---@class lsp.FullDocumentDiagnosticReport
+---
---A full document diagnostic report.
---@field kind "full"
+---
---An optional result id. If provided it will
---be sent on the next diagnostic request for the
---same document.
---@field resultId? string
+---
---The actual items.
---@field items lsp.Diagnostic[]
@@ -1819,11 +2162,13 @@ error('Cannot require a meta file')
---
---@since 3.17.0
---@class lsp.UnchangedDocumentDiagnosticReport
+---
---A document diagnostic report indicating
---no changes to the last result. A server can
---only return `unchanged` if result ids are
---provided.
---@field kind "unchanged"
+---
---A result id which will be sent on the next
---diagnostic request for the same document.
---@field resultId string
@@ -1831,15 +2176,18 @@ error('Cannot require a meta file')
---Diagnostic options.
---
---@since 3.17.0
----@class lsp.DiagnosticOptions
+---@class lsp.DiagnosticOptions: lsp.WorkDoneProgressOptions
+---
---An optional identifier under which the diagnostics are
---managed by the client.
---@field identifier? string
+---
---Whether the language has inter file dependencies meaning that
---editing code in one file can result in a different diagnostic
---set in another file. Inter file dependencies are common for
---most programming languages and typically uncommon for linters.
---@field interFileDependencies boolean
+---
---The server provides support for workspace diagnostics as well.
---@field workspaceDiagnostics boolean
@@ -1847,9 +2195,11 @@ error('Cannot require a meta file')
---
---@since 3.17.0
---@class lsp.PreviousResultId
+---
---The URI for which the client knowns a
---result id.
---@field uri lsp.DocumentUri
+---
---The value of the previous result id.
---@field value string
@@ -1857,31 +2207,40 @@ error('Cannot require a meta file')
---
---@since 3.17.0
---@class lsp.NotebookDocument
+---
---The notebook document's uri.
---@field uri lsp.URI
+---
---The type of the notebook.
---@field notebookType string
+---
---The version number of this document (it will increase after each
---change, including undo/redo).
---@field version integer
+---
---Additional metadata stored with the notebook
---document.
---
---Note: should always be an object literal (e.g. LSPObject)
---@field metadata? lsp.LSPObject
+---
---The cells of a notebook.
---@field cells lsp.NotebookCell[]
---An item to transfer a text document from the client to the
---server.
---@class lsp.TextDocumentItem
+---
---The text document's uri.
---@field uri lsp.DocumentUri
+---
---The text document's language identifier.
---@field languageId string
+---
---The version number of this document (it will increase after each
---change, including undo/redo).
---@field version integer
+---
---The content of the opened text document.
---@field text string
@@ -1889,8 +2248,10 @@ error('Cannot require a meta file')
---
---@since 3.17.0
---@class lsp.VersionedNotebookDocumentIdentifier
+---
---The version number of this notebook document.
---@field version integer
+---
---The notebook document's uri.
---@field uri lsp.URI
@@ -1898,64 +2259,97 @@ error('Cannot require a meta file')
---
---@since 3.17.0
---@class lsp.NotebookDocumentChangeEvent
+---
---The changed meta data if any.
---
---Note: should always be an object literal (e.g. LSPObject)
---@field metadata? lsp.LSPObject
+---
---Changes to cells
----@field cells? anonym10
+---@field cells? lsp._anonym8.cells
---A literal to identify a notebook document in the client.
---
---@since 3.17.0
---@class lsp.NotebookDocumentIdentifier
+---
---The notebook document's uri.
---@field uri lsp.URI
---Provides information about the context in which an inline completion was requested.
---
---@since 3.18.0
+---@proposed
---@class lsp.InlineCompletionContext
+---
---Describes how the inline completion was triggered.
---@field triggerKind lsp.InlineCompletionTriggerKind
+---
---Provides information about the currently selected item in the autocomplete widget if it is visible.
---@field selectedCompletionInfo? lsp.SelectedCompletionInfo
+---A string value used as a snippet is a template which allows to insert text
+---and to control the editor cursor when insertion happens.
+---
+---A snippet can define tab stops and placeholders with `$1`, `$2`
+---and `${3:foo}`. `$0` defines the final tab stop, it defaults to
+---the end of the snippet. Variables are defined with `$name` and
+---`${name:default value}`.
+---
+---@since 3.18.0
+---@proposed
+---@class lsp.StringValue
+---
+---The kind of string value.
+---@field kind "snippet"
+---
+---The snippet string.
+---@field value string
+
---Inline completion options used during static registration.
---
---@since 3.18.0
----@class lsp.InlineCompletionOptions
+---@proposed
+---@class lsp.InlineCompletionOptions: lsp.WorkDoneProgressOptions
----General parameters to to register for an notification or to register a provider.
+---General parameters to register for a notification or to register a provider.
---@class lsp.Registration
+---
---The id used to register the request. The id can be used to deregister
---the request again.
---@field id string
+---
---The method / capability to register for.
---@field method string
+---
---Options necessary for the registration.
---@field registerOptions? lsp.LSPAny
---General parameters to unregister a request or notification.
---@class lsp.Unregistration
+---
---The id used to unregister the request or notification. Usually an id
---provided during the register request.
---@field id string
+---
---The method to unregister for.
---@field method string
---The initialize parameters
----@class lsp._InitializeParams
+---@class lsp._InitializeParams: lsp.WorkDoneProgressParams
+---
---The process Id of the parent process that started
---the server.
---
---Is `null` if the process has not been started by another process.
---If the parent process is not alive then the server should exit.
---@field processId integer|lsp.null
+---
---Information about the client
---
---@since 3.15.0
----@field clientInfo? anonym11
+---@field clientInfo? lsp._anonym11.clientInfo
+---
---The locale the client is currently showing the user interface
---in. This must not necessarily be the locale of the operating
---system.
@@ -1965,25 +2359,31 @@ error('Cannot require a meta file')
---
---@since 3.16.0
---@field locale? string
+---
---The rootPath of the workspace. Is null
---if no folder is open.
---
---@deprecated in favour of rootUri.
---@field rootPath? string|lsp.null
+---
---The rootUri of the workspace. Is null if no
---folder is open. If both `rootPath` and `rootUri` are set
---`rootUri` wins.
---
---@deprecated in favour of workspaceFolders.
---@field rootUri lsp.DocumentUri|lsp.null
+---
---The capabilities provided by the client (editor or tool)
---@field capabilities lsp.ClientCapabilities
+---
---User provided initialization options.
---@field initializationOptions? lsp.LSPAny
+---
---The initial trace setting. If omitted trace is disabled ('off').
---@field trace? lsp.TraceValues
---@class lsp.WorkspaceFoldersInitializeParams
+---
---The workspace folders configured in the client when the server starts.
---
---This property is only available if the client supports workspace folders.
@@ -1996,6 +2396,7 @@ error('Cannot require a meta file')
---Defines the capabilities provided by a language
---server.
---@class lsp.ServerCapabilities
+---
---The position encoding the server picked from the encodings offered
---by the client via the client capability `general.positionEncodings`.
---
@@ -2006,125 +2407,167 @@ error('Cannot require a meta file')
---
---@since 3.17.0
---@field positionEncoding? lsp.PositionEncodingKind
+---
---Defines how text documents are synced. Is either a detailed structure
---defining each notification or for backwards compatibility the
---TextDocumentSyncKind number.
---@field textDocumentSync? lsp.TextDocumentSyncOptions|lsp.TextDocumentSyncKind
+---
---Defines how notebook documents are synced.
---
---@since 3.17.0
---@field notebookDocumentSync? lsp.NotebookDocumentSyncOptions|lsp.NotebookDocumentSyncRegistrationOptions
+---
---The server provides completion support.
---@field completionProvider? lsp.CompletionOptions
+---
---The server provides hover support.
---@field hoverProvider? boolean|lsp.HoverOptions
+---
---The server provides signature help support.
---@field signatureHelpProvider? lsp.SignatureHelpOptions
+---
---The server provides Goto Declaration support.
---@field declarationProvider? boolean|lsp.DeclarationOptions|lsp.DeclarationRegistrationOptions
+---
---The server provides goto definition support.
---@field definitionProvider? boolean|lsp.DefinitionOptions
+---
---The server provides Goto Type Definition support.
---@field typeDefinitionProvider? boolean|lsp.TypeDefinitionOptions|lsp.TypeDefinitionRegistrationOptions
+---
---The server provides Goto Implementation support.
---@field implementationProvider? boolean|lsp.ImplementationOptions|lsp.ImplementationRegistrationOptions
+---
---The server provides find references support.
---@field referencesProvider? boolean|lsp.ReferenceOptions
+---
---The server provides document highlight support.
---@field documentHighlightProvider? boolean|lsp.DocumentHighlightOptions
+---
---The server provides document symbol support.
---@field documentSymbolProvider? boolean|lsp.DocumentSymbolOptions
+---
---The server provides code actions. CodeActionOptions may only be
---specified if the client states that it supports
---`codeActionLiteralSupport` in its initial `initialize` request.
---@field codeActionProvider? boolean|lsp.CodeActionOptions
+---
---The server provides code lens.
---@field codeLensProvider? lsp.CodeLensOptions
+---
---The server provides document link support.
---@field documentLinkProvider? lsp.DocumentLinkOptions
+---
---The server provides color provider support.
---@field colorProvider? boolean|lsp.DocumentColorOptions|lsp.DocumentColorRegistrationOptions
+---
---The server provides workspace symbol support.
---@field workspaceSymbolProvider? boolean|lsp.WorkspaceSymbolOptions
+---
---The server provides document formatting.
---@field documentFormattingProvider? boolean|lsp.DocumentFormattingOptions
+---
---The server provides document range formatting.
---@field documentRangeFormattingProvider? boolean|lsp.DocumentRangeFormattingOptions
+---
---The server provides document formatting on typing.
---@field documentOnTypeFormattingProvider? lsp.DocumentOnTypeFormattingOptions
+---
---The server provides rename support. RenameOptions may only be
---specified if the client states that it supports
---`prepareSupport` in its initial `initialize` request.
---@field renameProvider? boolean|lsp.RenameOptions
+---
---The server provides folding provider support.
---@field foldingRangeProvider? boolean|lsp.FoldingRangeOptions|lsp.FoldingRangeRegistrationOptions
+---
---The server provides selection range support.
---@field selectionRangeProvider? boolean|lsp.SelectionRangeOptions|lsp.SelectionRangeRegistrationOptions
+---
---The server provides execute command support.
---@field executeCommandProvider? lsp.ExecuteCommandOptions
+---
---The server provides call hierarchy support.
---
---@since 3.16.0
---@field callHierarchyProvider? boolean|lsp.CallHierarchyOptions|lsp.CallHierarchyRegistrationOptions
+---
---The server provides linked editing range support.
---
---@since 3.16.0
---@field linkedEditingRangeProvider? boolean|lsp.LinkedEditingRangeOptions|lsp.LinkedEditingRangeRegistrationOptions
+---
---The server provides semantic tokens support.
---
---@since 3.16.0
---@field semanticTokensProvider? lsp.SemanticTokensOptions|lsp.SemanticTokensRegistrationOptions
+---
---The server provides moniker support.
---
---@since 3.16.0
---@field monikerProvider? boolean|lsp.MonikerOptions|lsp.MonikerRegistrationOptions
+---
---The server provides type hierarchy support.
---
---@since 3.17.0
---@field typeHierarchyProvider? boolean|lsp.TypeHierarchyOptions|lsp.TypeHierarchyRegistrationOptions
+---
---The server provides inline values.
---
---@since 3.17.0
---@field inlineValueProvider? boolean|lsp.InlineValueOptions|lsp.InlineValueRegistrationOptions
+---
---The server provides inlay hints.
---
---@since 3.17.0
---@field inlayHintProvider? boolean|lsp.InlayHintOptions|lsp.InlayHintRegistrationOptions
+---
---The server has support for pull model diagnostics.
---
---@since 3.17.0
---@field diagnosticProvider? lsp.DiagnosticOptions|lsp.DiagnosticRegistrationOptions
+---
---Inline completion options used during static registration.
---
---@since 3.18.0
+---@proposed
---@field inlineCompletionProvider? boolean|lsp.InlineCompletionOptions
+---
---Workspace specific server capabilities.
----@field workspace? anonym12
+---@field workspace? lsp._anonym12.workspace
+---
---Experimental server capabilities.
---@field experimental? lsp.LSPAny
---A text document identifier to denote a specific version of a text document.
---@class lsp.VersionedTextDocumentIdentifier: lsp.TextDocumentIdentifier
+---
---The version number of this document.
---@field version integer
---Save options.
---@class lsp.SaveOptions
+---
---The client is supposed to include the content on save.
---@field includeText? boolean
---An event describing a file change.
---@class lsp.FileEvent
+---
---The file's uri.
---@field uri lsp.DocumentUri
+---
---The change type.
---@field type lsp.FileChangeType
---@class lsp.FileSystemWatcher
+---
---The glob pattern to watch. See {@link GlobPattern glob pattern} for more detail.
---
---@since 3.17.0 support for relative patterns.
---@field globPattern lsp.GlobPattern
+---
---The kind of events of interest. If omitted it defaults
---to WatchKind.Create | WatchKind.Change | WatchKind.Delete
---which is 7.
@@ -2133,31 +2576,40 @@ error('Cannot require a meta file')
---Represents a diagnostic, such as a compiler error or warning. Diagnostic objects
---are only valid in the scope of a resource.
---@class lsp.Diagnostic
+---
---The range at which the message applies
---@field range lsp.Range
+---
---The diagnostic's severity. Can be omitted. If omitted it is up to the
---client to interpret diagnostics as error, warning, info or hint.
---@field severity? lsp.DiagnosticSeverity
+---
---The diagnostic's code, which usually appear in the user interface.
---@field code? integer|string
+---
---An optional property to describe the error code.
---Requires the code field (above) to be present/not null.
---
---@since 3.16.0
---@field codeDescription? lsp.CodeDescription
+---
---A human-readable string describing the source of this
---diagnostic, e.g. 'typescript' or 'super lint'. It usually
---appears in the user interface.
---@field source? string
+---
---The diagnostic's message. It usually appears in the user interface
---@field message string
+---
---Additional metadata about the diagnostic.
---
---@since 3.15.0
---@field tags? lsp.DiagnosticTag[]
+---
---An array of related diagnostic information, e.g. when symbol-names within
---a scope collide all definitions can be marked via this property.
---@field relatedInformation? lsp.DiagnosticRelatedInformation[]
+---
---A data entry field that is preserved between a `textDocument/publishDiagnostics`
---notification and `textDocument/codeAction` request.
---
@@ -2166,8 +2618,10 @@ error('Cannot require a meta file')
---Contains additional information about the context in which a completion request is triggered.
---@class lsp.CompletionContext
+---
---How the completion was triggered.
---@field triggerKind lsp.CompletionTriggerKind
+---
---The trigger character (a single character) that has trigger code complete.
---Is undefined if `triggerKind !== CompletionTriggerKind.TriggerCharacter`
---@field triggerCharacter? string
@@ -2176,9 +2630,11 @@ error('Cannot require a meta file')
---
---@since 3.17.0
---@class lsp.CompletionItemLabelDetails
+---
---An optional string which is rendered less prominently directly after {@link CompletionItem.label label},
---without any spacing. Should be used for function signatures and type annotations.
---@field detail? string
+---
---An optional string which is rendered less prominently after {@link CompletionItem.detail}. Should be used
---for fully qualified names and file paths.
---@field description? string
@@ -2187,15 +2643,19 @@ error('Cannot require a meta file')
---
---@since 3.16.0
---@class lsp.InsertReplaceEdit
+---
---The string to be inserted.
---@field newText string
+---
---The range if the insert is requested
---@field insert lsp.Range
+---
---The range if the replace is requested.
---@field replace lsp.Range
---Completion options.
----@class lsp.CompletionOptions
+---@class lsp.CompletionOptions: lsp.WorkDoneProgressOptions
+---
---Most tools trigger completion request automatically without explicitly requesting
---it using a keyboard shortcut (e.g. Ctrl+Space). Typically they do so when the user
---starts to type an identifier. For example if the user types `c` in a JavaScript file
@@ -2205,6 +2665,7 @@ error('Cannot require a meta file')
---If code complete should automatically be trigger on characters not being valid inside
---an identifier (for example `.` in JavaScript) list them in `triggerCharacters`.
---@field triggerCharacters? string[]
+---
---The list of all possible characters that commit a completion. This field can be used
---if clients don't support individual commit characters per completion item. See
---`ClientCapabilities.textDocument.completion.completionItem.commitCharactersSupport`
@@ -2214,33 +2675,39 @@ error('Cannot require a meta file')
---
---@since 3.2.0
---@field allCommitCharacters? string[]
+---
---The server provides support to resolve additional
---information for a completion item.
---@field resolveProvider? boolean
+---
---The server supports the following `CompletionItem` specific
---capabilities.
---
---@since 3.17.0
----@field completionItem? anonym13
+---@field completionItem? lsp._anonym13.completionItem
---Hover options.
----@class lsp.HoverOptions
+---@class lsp.HoverOptions: lsp.WorkDoneProgressOptions
---Additional information about the context in which a signature help request was triggered.
---
---@since 3.15.0
---@class lsp.SignatureHelpContext
+---
---Action that caused signature help to be triggered.
---@field triggerKind lsp.SignatureHelpTriggerKind
+---
---Character that caused signature help to be triggered.
---
---This is undefined when `triggerKind !== SignatureHelpTriggerKind.TriggerCharacter`
---@field triggerCharacter? string
+---
---`true` if signature help was already showing when it was triggered.
---
---Retriggers occurs when the signature help is already active and can be caused by actions such as
---typing a trigger character, a cursor move, or document content changes.
---@field isRetrigger boolean
+---
---The currently active `SignatureHelp`.
---
---The `activeSignatureHelp` has its `SignatureHelp.activeSignature` field updated based on
@@ -2251,25 +2718,37 @@ error('Cannot require a meta file')
---can have a label, like a function-name, a doc-comment, and
---a set of parameters.
---@class lsp.SignatureInformation
+---
---The label of this signature. Will be shown in
---the UI.
---@field label string
+---
---The human-readable doc-comment of this signature. Will be shown
---in the UI but can be omitted.
---@field documentation? string|lsp.MarkupContent
+---
---The parameters of this signature.
---@field parameters? lsp.ParameterInformation[]
+---
---The index of the active parameter.
---
----If provided, this is used in place of `SignatureHelp.activeParameter`.
+---If `null`, no parameter of the signature is active (for example a named
+---argument that does not match any declared parameters). This is only valid
+---since 3.18.0 and if the client specifies the client capability
+---`textDocument.signatureHelp.noActiveParameterSupport === true`
+---
+---If provided (or `null`), this is used in place of
+---`SignatureHelp.activeParameter`.
---
---@since 3.16.0
----@field activeParameter? uinteger
+---@field activeParameter? uinteger|lsp.null
---Server Capabilities for a {@link SignatureHelpRequest}.
----@class lsp.SignatureHelpOptions
+---@class lsp.SignatureHelpOptions: lsp.WorkDoneProgressOptions
+---
---List of characters that trigger signature help automatically.
---@field triggerCharacters? string[]
+---
---List of characters that re-trigger signature help.
---
---These trigger characters are only active when signature help is already showing. All trigger characters
@@ -2279,30 +2758,35 @@ error('Cannot require a meta file')
---@field retriggerCharacters? string[]
---Server Capabilities for a {@link DefinitionRequest}.
----@class lsp.DefinitionOptions
+---@class lsp.DefinitionOptions: lsp.WorkDoneProgressOptions
---Value-object that contains additional information when
---requesting references.
---@class lsp.ReferenceContext
+---
---Include the declaration of the current symbol.
---@field includeDeclaration boolean
---Reference options.
----@class lsp.ReferenceOptions
+---@class lsp.ReferenceOptions: lsp.WorkDoneProgressOptions
---Provider options for a {@link DocumentHighlightRequest}.
----@class lsp.DocumentHighlightOptions
+---@class lsp.DocumentHighlightOptions: lsp.WorkDoneProgressOptions
---A base for all symbol information.
---@class lsp.BaseSymbolInformation
+---
---The name of this symbol.
---@field name string
+---
---The kind of this symbol.
---@field kind lsp.SymbolKind
+---
---Tags for this symbol.
---
---@since 3.16.0
---@field tags? lsp.SymbolTag[]
+---
---The name of the symbol containing this symbol. This information is for
---user interface purposes (e.g. to render a qualifier in the user interface
---if necessary). It can't be used to re-infer a hierarchy for the document
@@ -2310,7 +2794,8 @@ error('Cannot require a meta file')
---@field containerName? string
---Provider options for a {@link DocumentSymbolRequest}.
----@class lsp.DocumentSymbolOptions
+---@class lsp.DocumentSymbolOptions: lsp.WorkDoneProgressOptions
+---
---A human-readable string that is shown when multiple outlines trees
---are shown for the same document.
---
@@ -2320,29 +2805,34 @@ error('Cannot require a meta file')
---Contains additional diagnostic information about the context in which
---a {@link CodeActionProvider.provideCodeActions code action} is run.
---@class lsp.CodeActionContext
+---
---An array of diagnostics known on the client side overlapping the range provided to the
---`textDocument/codeAction` request. They are provided so that the server knows which
---errors are currently presented to the user for the given range. There is no guarantee
---that these accurately reflect the error state of the resource. The primary parameter
---to compute code actions is the provided range.
---@field diagnostics lsp.Diagnostic[]
+---
---Requested kind of actions to return.
---
---Actions not of this kind are filtered out by the client before being shown. So servers
---can omit computing them.
---@field only? lsp.CodeActionKind[]
+---
---The reason why code actions were requested.
---
---@since 3.17.0
---@field triggerKind? lsp.CodeActionTriggerKind
---Provider options for a {@link CodeActionRequest}.
----@class lsp.CodeActionOptions
+---@class lsp.CodeActionOptions: lsp.WorkDoneProgressOptions
+---
---CodeActionKinds that this server may return.
---
---The list of kinds may be generic, such as `CodeActionKind.Refactor`, or the server
---may list out every specific kind they provide.
---@field codeActionKinds? lsp.CodeActionKind[]
+---
---The server provides support to resolve additional
---information for a code action.
---
@@ -2350,7 +2840,8 @@ error('Cannot require a meta file')
---@field resolveProvider? boolean
---Server capabilities for a {@link WorkspaceSymbolRequest}.
----@class lsp.WorkspaceSymbolOptions
+---@class lsp.WorkspaceSymbolOptions: lsp.WorkDoneProgressOptions
+---
---The server provides support to resolve additional
---information for a workspace symbol.
---
@@ -2358,39 +2849,47 @@ error('Cannot require a meta file')
---@field resolveProvider? boolean
---Code Lens provider options of a {@link CodeLensRequest}.
----@class lsp.CodeLensOptions
+---@class lsp.CodeLensOptions: lsp.WorkDoneProgressOptions
+---
---Code lens has a resolve provider as well.
---@field resolveProvider? boolean
---Provider options for a {@link DocumentLinkRequest}.
----@class lsp.DocumentLinkOptions
+---@class lsp.DocumentLinkOptions: lsp.WorkDoneProgressOptions
+---
---Document links have a resolve provider as well.
---@field resolveProvider? boolean
---Value-object describing what options formatting should use.
---@class lsp.FormattingOptions
+---
---Size of a tab in spaces.
---@field tabSize uinteger
+---
---Prefer spaces over tabs.
---@field insertSpaces boolean
+---
---Trim trailing whitespace on a line.
---
---@since 3.15.0
---@field trimTrailingWhitespace? boolean
+---
---Insert a newline character at the end of the file if one does not exist.
---
---@since 3.15.0
---@field insertFinalNewline? boolean
+---
---Trim all newlines after the final newline at the end of the file.
---
---@since 3.15.0
---@field trimFinalNewlines? boolean
---Provider options for a {@link DocumentFormattingRequest}.
----@class lsp.DocumentFormattingOptions
+---@class lsp.DocumentFormattingOptions: lsp.WorkDoneProgressOptions
---Provider options for a {@link DocumentRangeFormattingRequest}.
----@class lsp.DocumentRangeFormattingOptions
+---@class lsp.DocumentRangeFormattingOptions: lsp.WorkDoneProgressOptions
+---
---Whether the server supports formatting multiple ranges at once.
---
---@since 3.18.0
@@ -2399,32 +2898,39 @@ error('Cannot require a meta file')
---Provider options for a {@link DocumentOnTypeFormattingRequest}.
---@class lsp.DocumentOnTypeFormattingOptions
+---
---A character on which formatting should be triggered, like `{`.
---@field firstTriggerCharacter string
+---
---More trigger characters.
---@field moreTriggerCharacter? string[]
---Provider options for a {@link RenameRequest}.
----@class lsp.RenameOptions
+---@class lsp.RenameOptions: lsp.WorkDoneProgressOptions
+---
---Renames should be checked and tested before being executed.
---
---@since version 3.12.0
---@field prepareProvider? boolean
---The server capabilities of a {@link ExecuteCommandRequest}.
----@class lsp.ExecuteCommandOptions
+---@class lsp.ExecuteCommandOptions: lsp.WorkDoneProgressOptions
+---
---The commands to be executed on the server
---@field commands string[]
---@since 3.16.0
---@class lsp.SemanticTokensLegend
+---
---The token types a server uses.
---@field tokenTypes string[]
+---
---The token modifiers a server uses.
---@field tokenModifiers string[]
---A text document identifier to optionally denote a specific version of a text document.
---@class lsp.OptionalVersionedTextDocumentIdentifier: lsp.TextDocumentIdentifier
+---
---The version number of this document. If a versioned text document identifier
---is sent from the server to the client and the file is not open in the editor
---(the server has not received an open notification before) the server can send
@@ -2436,13 +2942,16 @@ error('Cannot require a meta file')
---
---@since 3.16.0.
---@class lsp.AnnotatedTextEdit: lsp.TextEdit
+---
---The actual identifier of the change annotation
---@field annotationId lsp.ChangeAnnotationIdentifier
---A generic resource operation.
---@class lsp.ResourceOperation
+---
---The resource operation kind.
---@field kind string
+---
---An optional annotation identifier describing the operation.
---
---@since 3.16.0
@@ -2450,22 +2959,28 @@ error('Cannot require a meta file')
---Options to create a file.
---@class lsp.CreateFileOptions
+---
---Overwrite existing file. Overwrite wins over `ignoreIfExists`
---@field overwrite? boolean
+---
---Ignore if exists.
---@field ignoreIfExists? boolean
---Rename file options
---@class lsp.RenameFileOptions
+---
---Overwrite target if existing. Overwrite wins over `ignoreIfExists`
---@field overwrite? boolean
+---
---Ignores if target exists.
---@field ignoreIfExists? boolean
---Delete file options
---@class lsp.DeleteFileOptions
+---
---Delete the content recursively if a folder is denoted.
---@field recursive? boolean
+---
---Ignore the operation if the file doesn't exist.
---@field ignoreIfNotExists? boolean
@@ -2474,18 +2989,21 @@ error('Cannot require a meta file')
---
---@since 3.16.0
---@class lsp.FileOperationPattern
+---
---The glob pattern to match. Glob patterns can have the following syntax:
---- `*` to match one or more characters in a path segment
---- `?` to match on one character in a path segment
---- `**` to match any number of path segments, including none
----- `{}` to group sub patterns into an OR expression. (e.g. `**​/*.{ts,js}` matches all TypeScript and JavaScript files)
+---- `{}` to group sub patterns into an OR expression. (e.g. `**/*.{ts,js}` matches all TypeScript and JavaScript files)
---- `[]` to declare a range of characters to match in a path segment (e.g., `example.[0-9]` to match on `example.0`, `example.1`, …)
---- `[!...]` to negate a range of characters to match in a path segment (e.g., `example.[!0-9]` to match on `example.a`, `example.b`, but not `example.0`)
---@field glob string
+---
---Whether to match files or folders with this pattern.
---
---Matches both if undefined.
---@field matches? lsp.FileOperationPatternKind
+---
---Additional options used during matching.
---@field options? lsp.FileOperationPatternOptions
@@ -2493,8 +3011,10 @@ error('Cannot require a meta file')
---
---@since 3.17.0
---@class lsp.WorkspaceFullDocumentDiagnosticReport: lsp.FullDocumentDiagnosticReport
+---
---The URI for which diagnostic information is reported.
---@field uri lsp.DocumentUri
+---
---The version number for which the diagnostics are reported.
---If the document is not marked as open `null` can be provided.
---@field version integer|lsp.null
@@ -2503,8 +3023,10 @@ error('Cannot require a meta file')
---
---@since 3.17.0
---@class lsp.WorkspaceUnchangedDocumentDiagnosticReport: lsp.UnchangedDocumentDiagnosticReport
+---
---The URI for which diagnostic information is reported.
---@field uri lsp.DocumentUri
+---
---The version number for which the diagnostics are reported.
---If the document is not marked as open `null` can be provided.
---@field version integer|lsp.null
@@ -2517,15 +3039,19 @@ error('Cannot require a meta file')
---
---@since 3.17.0
---@class lsp.NotebookCell
+---
---The cell's kind
---@field kind lsp.NotebookCellKind
+---
---The URI of the cell's text document
---content.
---@field document lsp.DocumentUri
+---
---Additional metadata stored with the cell.
---
---Note: should always be an object literal (e.g. LSPObject)
---@field metadata? lsp.LSPObject
+---
---Additional execution summary information
---if supported by the client.
---@field executionSummary? lsp.ExecutionSummary
@@ -2535,54 +3061,71 @@ error('Cannot require a meta file')
---
---@since 3.17.0
---@class lsp.NotebookCellArrayChange
+---
---The start oftest of the cell that changed.
---@field start uinteger
+---
---The deleted cells
---@field deleteCount uinteger
+---
---The new cells, if any
---@field cells? lsp.NotebookCell[]
---Describes the currently selected completion item.
---
---@since 3.18.0
+---@proposed
---@class lsp.SelectedCompletionInfo
+---
---The range that will be replaced if this completion item is accepted.
---@field range lsp.Range
+---
---The text the range will be replaced with if this completion is accepted.
---@field text string
---Defines the capabilities provided by the client.
---@class lsp.ClientCapabilities
+---
---Workspace specific client capabilities.
---@field workspace? lsp.WorkspaceClientCapabilities
+---
---Text document specific client capabilities.
---@field textDocument? lsp.TextDocumentClientCapabilities
+---
---Capabilities specific to the notebook document support.
---
---@since 3.17.0
---@field notebookDocument? lsp.NotebookDocumentClientCapabilities
+---
---Window specific client capabilities.
---@field window? lsp.WindowClientCapabilities
+---
---General client capabilities.
---
---@since 3.16.0
---@field general? lsp.GeneralClientCapabilities
+---
---Experimental client capabilities.
---@field experimental? lsp.LSPAny
---@class lsp.TextDocumentSyncOptions
+---
---Open and close notifications are sent to the server. If omitted open close notification should not
---be sent.
---@field openClose? boolean
+---
---Change notifications are sent to the server. See TextDocumentSyncKind.None, TextDocumentSyncKind.Full
---and TextDocumentSyncKind.Incremental. If omitted it defaults to TextDocumentSyncKind.None.
---@field change? lsp.TextDocumentSyncKind
+---
---If present will save notifications are sent to the server. If omitted the notification should not be
---sent.
---@field willSave? boolean
+---
---If present will save wait until requests are sent to the server. If omitted the request should not be
---sent.
---@field willSaveWaitUntil? boolean
+---
---If present save notifications are sent to the server. If omitted the notification should not be
---sent.
---@field save? boolean|lsp.SaveOptions
@@ -2601,8 +3144,10 @@ error('Cannot require a meta file')
---
---@since 3.17.0
---@class lsp.NotebookDocumentSyncOptions
+---
---The notebooks to be synced
----@field notebookSelector anonym15|anonym17[]
+---@field notebookSelector (lsp._anonym14.notebookSelector|lsp._anonym16.notebookSelector)[]
+---
---Whether save notification should be forwarded to
---the server. Will only be honored if mode === `notebook`.
---@field save? boolean
@@ -2613,8 +3158,10 @@ error('Cannot require a meta file')
---@class lsp.NotebookDocumentSyncRegistrationOptions: lsp.NotebookDocumentSyncOptions, lsp.StaticRegistrationOptions
---@class lsp.WorkspaceFoldersServerCapabilities
+---
---The server has support for workspace folders
---@field supported? boolean
+---
---Whether the server wants to receive workspace folder
---change notifications.
---
@@ -2628,16 +3175,22 @@ error('Cannot require a meta file')
---
---@since 3.16.0
---@class lsp.FileOperationOptions
+---
---The server is interested in receiving didCreateFiles notifications.
---@field didCreate? lsp.FileOperationRegistrationOptions
+---
---The server is interested in receiving willCreateFiles requests.
---@field willCreate? lsp.FileOperationRegistrationOptions
+---
---The server is interested in receiving didRenameFiles notifications.
---@field didRename? lsp.FileOperationRegistrationOptions
+---
---The server is interested in receiving willRenameFiles requests.
---@field willRename? lsp.FileOperationRegistrationOptions
+---
---The server is interested in receiving didDeleteFiles file notifications.
---@field didDelete? lsp.FileOperationRegistrationOptions
+---
---The server is interested in receiving willDeleteFiles file requests.
---@field willDelete? lsp.FileOperationRegistrationOptions
@@ -2645,6 +3198,7 @@ error('Cannot require a meta file')
---
---@since 3.16.0
---@class lsp.CodeDescription
+---
---An URI to open with more information about the diagnostic error.
---@field href lsp.URI
@@ -2652,14 +3206,17 @@ error('Cannot require a meta file')
---used to point to code locations that cause or related to a diagnostics, e.g when duplicating
---a symbol in a scope.
---@class lsp.DiagnosticRelatedInformation
+---
---The location of this related diagnostic information.
---@field location lsp.Location
+---
---The message of this related diagnostic information.
---@field message string
---Represents a parameter of a callable-signature. A parameter can
---have a label and a doc-comment.
---@class lsp.ParameterInformation
+---
---The label of this parameter information.
---
---Either a string or an inclusive start and exclusive end offsets within its containing
@@ -2669,6 +3226,7 @@ error('Cannot require a meta file')
---*Note*: a label of type string should be a substring of its containing signature label.
---Its intended use case is to highlight the parameter label part in the `SignatureInformation.label`.
---@field label string|{ [1]: uinteger, [2]: uinteger }
+---
---The human-readable doc-comment of this parameter. Will be shown
---in the UI but can be omitted.
---@field documentation? string|lsp.MarkupContent
@@ -2678,11 +3236,13 @@ error('Cannot require a meta file')
---
---@since 3.17.0
---@class lsp.NotebookCellTextDocumentFilter
+---
---A filter that matches against the notebook
---containing the notebook cell. If a string
---value is provided it matches against the
---notebook type. '*' matches every notebook.
---@field notebook string|lsp.NotebookDocumentFilter
+---
---A language id like `python`.
---
---Will be matched against the language id of the
@@ -2693,178 +3253,235 @@ error('Cannot require a meta file')
---
---@since 3.16.0
---@class lsp.FileOperationPatternOptions
+---
---The pattern should be matched ignoring casing.
---@field ignoreCase? boolean
---@class lsp.ExecutionSummary
+---
---A strict monotonically increasing value
---indicating the execution order of a cell
---inside a notebook.
---@field executionOrder uinteger
+---
---Whether the execution was successful or
---not if known by the client.
---@field success? boolean
---Workspace specific client capabilities.
---@class lsp.WorkspaceClientCapabilities
+---
---The client supports applying batch edits
---to the workspace by supporting the request
---'workspace/applyEdit'
---@field applyEdit? boolean
+---
---Capabilities specific to `WorkspaceEdit`s.
---@field workspaceEdit? lsp.WorkspaceEditClientCapabilities
+---
---Capabilities specific to the `workspace/didChangeConfiguration` notification.
---@field didChangeConfiguration? lsp.DidChangeConfigurationClientCapabilities
+---
---Capabilities specific to the `workspace/didChangeWatchedFiles` notification.
---@field didChangeWatchedFiles? lsp.DidChangeWatchedFilesClientCapabilities
+---
---Capabilities specific to the `workspace/symbol` request.
---@field symbol? lsp.WorkspaceSymbolClientCapabilities
+---
---Capabilities specific to the `workspace/executeCommand` request.
---@field executeCommand? lsp.ExecuteCommandClientCapabilities
+---
---The client has support for workspace folders.
---
---@since 3.6.0
---@field workspaceFolders? boolean
+---
---The client supports `workspace/configuration` requests.
---
---@since 3.6.0
---@field configuration? boolean
+---
---Capabilities specific to the semantic token requests scoped to the
---workspace.
---
---@since 3.16.0.
---@field semanticTokens? lsp.SemanticTokensWorkspaceClientCapabilities
+---
---Capabilities specific to the code lens requests scoped to the
---workspace.
---
---@since 3.16.0.
---@field codeLens? lsp.CodeLensWorkspaceClientCapabilities
+---
---The client has support for file notifications/requests for user operations on files.
---
---Since 3.16.0
---@field fileOperations? lsp.FileOperationClientCapabilities
+---
---Capabilities specific to the inline values requests scoped to the
---workspace.
---
---@since 3.17.0.
---@field inlineValue? lsp.InlineValueWorkspaceClientCapabilities
+---
---Capabilities specific to the inlay hint requests scoped to the
---workspace.
---
---@since 3.17.0.
---@field inlayHint? lsp.InlayHintWorkspaceClientCapabilities
+---
---Capabilities specific to the diagnostic requests scoped to the
---workspace.
---
---@since 3.17.0.
---@field diagnostics? lsp.DiagnosticWorkspaceClientCapabilities
+---
+---Capabilities specific to the folding range requests scoped to the workspace.
+---
+---@since 3.18.0
+---@proposed
+---@field foldingRange? lsp.FoldingRangeWorkspaceClientCapabilities
---Text document specific client capabilities.
---@class lsp.TextDocumentClientCapabilities
+---
---Defines which synchronization capabilities the client supports.
---@field synchronization? lsp.TextDocumentSyncClientCapabilities
+---
---Capabilities specific to the `textDocument/completion` request.
---@field completion? lsp.CompletionClientCapabilities
+---
---Capabilities specific to the `textDocument/hover` request.
---@field hover? lsp.HoverClientCapabilities
+---
---Capabilities specific to the `textDocument/signatureHelp` request.
---@field signatureHelp? lsp.SignatureHelpClientCapabilities
+---
---Capabilities specific to the `textDocument/declaration` request.
---
---@since 3.14.0
---@field declaration? lsp.DeclarationClientCapabilities
+---
---Capabilities specific to the `textDocument/definition` request.
---@field definition? lsp.DefinitionClientCapabilities
+---
---Capabilities specific to the `textDocument/typeDefinition` request.
---
---@since 3.6.0
---@field typeDefinition? lsp.TypeDefinitionClientCapabilities
+---
---Capabilities specific to the `textDocument/implementation` request.
---
---@since 3.6.0
---@field implementation? lsp.ImplementationClientCapabilities
+---
---Capabilities specific to the `textDocument/references` request.
---@field references? lsp.ReferenceClientCapabilities
+---
---Capabilities specific to the `textDocument/documentHighlight` request.
---@field documentHighlight? lsp.DocumentHighlightClientCapabilities
+---
---Capabilities specific to the `textDocument/documentSymbol` request.
---@field documentSymbol? lsp.DocumentSymbolClientCapabilities
+---
---Capabilities specific to the `textDocument/codeAction` request.
---@field codeAction? lsp.CodeActionClientCapabilities
+---
---Capabilities specific to the `textDocument/codeLens` request.
---@field codeLens? lsp.CodeLensClientCapabilities
+---
---Capabilities specific to the `textDocument/documentLink` request.
---@field documentLink? lsp.DocumentLinkClientCapabilities
+---
---Capabilities specific to the `textDocument/documentColor` and the
---`textDocument/colorPresentation` request.
---
---@since 3.6.0
---@field colorProvider? lsp.DocumentColorClientCapabilities
+---
---Capabilities specific to the `textDocument/formatting` request.
---@field formatting? lsp.DocumentFormattingClientCapabilities
+---
---Capabilities specific to the `textDocument/rangeFormatting` request.
---@field rangeFormatting? lsp.DocumentRangeFormattingClientCapabilities
+---
---Capabilities specific to the `textDocument/onTypeFormatting` request.
---@field onTypeFormatting? lsp.DocumentOnTypeFormattingClientCapabilities
+---
---Capabilities specific to the `textDocument/rename` request.
---@field rename? lsp.RenameClientCapabilities
+---
---Capabilities specific to the `textDocument/foldingRange` request.
---
---@since 3.10.0
---@field foldingRange? lsp.FoldingRangeClientCapabilities
+---
---Capabilities specific to the `textDocument/selectionRange` request.
---
---@since 3.15.0
---@field selectionRange? lsp.SelectionRangeClientCapabilities
+---
---Capabilities specific to the `textDocument/publishDiagnostics` notification.
---@field publishDiagnostics? lsp.PublishDiagnosticsClientCapabilities
+---
---Capabilities specific to the various call hierarchy requests.
---
---@since 3.16.0
---@field callHierarchy? lsp.CallHierarchyClientCapabilities
+---
---Capabilities specific to the various semantic token request.
---
---@since 3.16.0
---@field semanticTokens? lsp.SemanticTokensClientCapabilities
+---
---Capabilities specific to the `textDocument/linkedEditingRange` request.
---
---@since 3.16.0
---@field linkedEditingRange? lsp.LinkedEditingRangeClientCapabilities
+---
---Client capabilities specific to the `textDocument/moniker` request.
---
---@since 3.16.0
---@field moniker? lsp.MonikerClientCapabilities
+---
---Capabilities specific to the various type hierarchy requests.
---
---@since 3.17.0
---@field typeHierarchy? lsp.TypeHierarchyClientCapabilities
+---
---Capabilities specific to the `textDocument/inlineValue` request.
---
---@since 3.17.0
---@field inlineValue? lsp.InlineValueClientCapabilities
+---
---Capabilities specific to the `textDocument/inlayHint` request.
---
---@since 3.17.0
---@field inlayHint? lsp.InlayHintClientCapabilities
+---
---Capabilities specific to the diagnostic pull model.
---
---@since 3.17.0
---@field diagnostic? lsp.DiagnosticClientCapabilities
+---
---Client capabilities specific to inline completions.
---
---@since 3.18.0
+---@proposed
---@field inlineCompletion? lsp.InlineCompletionClientCapabilities
---Capabilities specific to the notebook document support.
---
---@since 3.17.0
---@class lsp.NotebookDocumentClientCapabilities
+---
---Capabilities specific to notebook document synchronization
---
---@since 3.17.0
---@field synchronization lsp.NotebookDocumentSyncClientCapabilities
---@class lsp.WindowClientCapabilities
+---
---It indicates whether the client supports server initiated
---progress using the `window/workDoneProgress/create` request.
---
@@ -2875,10 +3492,12 @@ error('Cannot require a meta file')
---
---@since 3.15.0
---@field workDoneProgress? boolean
+---
---Capabilities specific to the showMessage request.
---
---@since 3.16.0
---@field showMessage? lsp.ShowMessageRequestClientCapabilities
+---
---Capabilities specific to the showDocument request.
---
---@since 3.16.0
@@ -2888,21 +3507,25 @@ error('Cannot require a meta file')
---
---@since 3.16.0
---@class lsp.GeneralClientCapabilities
+---
---Client capability that signals how the client
---handles stale requests (e.g. a request
---for which the client will not process the response
---anymore since the information is outdated).
---
---@since 3.17.0
----@field staleRequestSupport? anonym18
+---@field staleRequestSupport? lsp._anonym18.staleRequestSupport
+---
---Client capabilities specific to regular expressions.
---
---@since 3.16.0
---@field regularExpressions? lsp.RegularExpressionsClientCapabilities
+---
---Client capabilities specific to the client's markdown parser.
---
---@since 3.16.0
---@field markdown? lsp.MarkdownClientCapabilities
+---
---The position encodings supported by the client. Client and server
---have to agree on the same position encoding to ensure that offsets
---(e.g. character position in a line) are interpreted the same on both
@@ -2929,25 +3552,31 @@ error('Cannot require a meta file')
---
---@since 3.17.0
---@class lsp.RelativePattern
+---
---A workspace folder or a base URI to which this pattern will be matched
---against relatively.
---@field baseUri lsp.WorkspaceFolder|lsp.URI
+---
---The actual glob pattern;
---@field pattern lsp.Pattern
---@class lsp.WorkspaceEditClientCapabilities
+---
---The client supports versioned document changes in `WorkspaceEdit`s
---@field documentChanges? boolean
+---
---The resource operations the client supports. Clients should at least
---support 'create', 'rename' and 'delete' files and folders.
---
---@since 3.13.0
---@field resourceOperations? lsp.ResourceOperationKind[]
+---
---The failure handling strategy of a client if applying the workspace edit
---fails.
---
---@since 3.13.0
---@field failureHandling? lsp.FailureHandlingKind
+---
---Whether the client normalizes line endings to the client specific
---setting.
---If set to `true` the client will normalize line ending characters
@@ -2956,21 +3585,25 @@ error('Cannot require a meta file')
---
---@since 3.16.0
---@field normalizesLineEndings? boolean
+---
---Whether the client in general supports change annotations on text edits,
---create file, rename file and delete file changes.
---
---@since 3.16.0
----@field changeAnnotationSupport? anonym19
+---@field changeAnnotationSupport? lsp._anonym19.changeAnnotationSupport
---@class lsp.DidChangeConfigurationClientCapabilities
+---
---Did change configuration notification supports dynamic registration.
---@field dynamicRegistration? boolean
---@class lsp.DidChangeWatchedFilesClientCapabilities
+---
---Did change watched files notification supports dynamic registration. Please note
---that the current protocol doesn't support static configuration for file changes
---from the server side.
---@field dynamicRegistration? boolean
+---
---Whether the client has support for {@link RelativePattern relative pattern}
---or not.
---
@@ -2979,29 +3612,35 @@ error('Cannot require a meta file')
---Client capabilities for a {@link WorkspaceSymbolRequest}.
---@class lsp.WorkspaceSymbolClientCapabilities
+---
---Symbol request supports dynamic registration.
---@field dynamicRegistration? boolean
+---
---Specific capabilities for the `SymbolKind` in the `workspace/symbol` request.
----@field symbolKind? anonym20
+---@field symbolKind? lsp._anonym20.symbolKind
+---
---The client supports tags on `SymbolInformation`.
---Clients supporting tags have to handle unknown tags gracefully.
---
---@since 3.16.0
----@field tagSupport? anonym21
+---@field tagSupport? lsp._anonym21.tagSupport
+---
---The client support partial workspace symbols. The client will send the
---request `workspaceSymbol/resolve` to the server to resolve additional
---properties.
---
---@since 3.17.0
----@field resolveSupport? anonym22
+---@field resolveSupport? lsp._anonym22.resolveSupport
---The client capabilities of a {@link ExecuteCommandRequest}.
---@class lsp.ExecuteCommandClientCapabilities
+---
---Execute command supports dynamic registration.
---@field dynamicRegistration? boolean
---@since 3.16.0
---@class lsp.SemanticTokensWorkspaceClientCapabilities
+---
---Whether the client implementation supports a refresh request sent from
---the server to the client.
---
@@ -3013,6 +3652,7 @@ error('Cannot require a meta file')
---@since 3.16.0
---@class lsp.CodeLensWorkspaceClientCapabilities
+---
---Whether the client implementation supports a refresh request sent from the
---server to the client.
---
@@ -3029,18 +3669,25 @@ error('Cannot require a meta file')
---
---@since 3.16.0
---@class lsp.FileOperationClientCapabilities
+---
---Whether the client supports dynamic registration for file requests/notifications.
---@field dynamicRegistration? boolean
+---
---The client has support for sending didCreateFiles notifications.
---@field didCreate? boolean
+---
---The client has support for sending willCreateFiles requests.
---@field willCreate? boolean
+---
---The client has support for sending didRenameFiles notifications.
---@field didRename? boolean
+---
---The client has support for sending willRenameFiles requests.
---@field willRename? boolean
+---
---The client has support for sending didDeleteFiles notifications.
---@field didDelete? boolean
+---
---The client has support for sending willDeleteFiles requests.
---@field willDelete? boolean
@@ -3048,6 +3695,7 @@ error('Cannot require a meta file')
---
---@since 3.17.0
---@class lsp.InlineValueWorkspaceClientCapabilities
+---
---Whether the client implementation supports a refresh request sent from the
---server to the client.
---
@@ -3061,6 +3709,7 @@ error('Cannot require a meta file')
---
---@since 3.17.0
---@class lsp.InlayHintWorkspaceClientCapabilities
+---
---Whether the client implementation supports a refresh request sent from
---the server to the client.
---
@@ -3074,6 +3723,7 @@ error('Cannot require a meta file')
---
---@since 3.17.0
---@class lsp.DiagnosticWorkspaceClientCapabilities
+---
---Whether the client implementation supports a refresh request sent from
---the server to the client.
---
@@ -3083,55 +3733,88 @@ error('Cannot require a meta file')
---change that requires such a calculation.
---@field refreshSupport? boolean
+---Client workspace capabilities specific to folding ranges
+---
+---@since 3.18.0
+---@proposed
+---@class lsp.FoldingRangeWorkspaceClientCapabilities
+---
+---Whether the client implementation supports a refresh request sent from the
+---server to the client.
+---
+---Note that this event is global and will force the client to refresh all
+---folding ranges currently shown. It should be used with absolute care and is
+---useful for situation where a server for example detects a project wide
+---change that requires such a calculation.
+---
+---@since 3.18.0
+---@proposed
+---@field refreshSupport? boolean
+
---@class lsp.TextDocumentSyncClientCapabilities
+---
---Whether text document synchronization supports dynamic registration.
---@field dynamicRegistration? boolean
+---
---The client supports sending will save notifications.
---@field willSave? boolean
+---
---The client supports sending a will save request and
---waits for a response providing text edits which will
---be applied to the document before it is saved.
---@field willSaveWaitUntil? boolean
+---
---The client supports did save notifications.
---@field didSave? boolean
---Completion client capabilities
---@class lsp.CompletionClientCapabilities
+---
---Whether completion supports dynamic registration.
---@field dynamicRegistration? boolean
+---
---The client supports the following `CompletionItem` specific
---capabilities.
----@field completionItem? anonym26
----@field completionItemKind? anonym27
+---@field completionItem? lsp._anonym23.completionItem
+---
+---@field completionItemKind? lsp._anonym27.completionItemKind
+---
---Defines how the client handles whitespace and indentation
---when accepting a completion item that uses multi line
---text in either `insertText` or `textEdit`.
---
---@since 3.17.0
---@field insertTextMode? lsp.InsertTextMode
+---
---The client supports to send additional context information for a
---`textDocument/completion` request.
---@field contextSupport? boolean
+---
---The client supports the following `CompletionList` specific
---capabilities.
---
---@since 3.17.0
----@field completionList? anonym28
+---@field completionList? lsp._anonym28.completionList
---@class lsp.HoverClientCapabilities
+---
---Whether hover supports dynamic registration.
---@field dynamicRegistration? boolean
+---
---Client supports the following content formats for the content
---property. The order describes the preferred format of the client.
---@field contentFormat? lsp.MarkupKind[]
---Client Capabilities for a {@link SignatureHelpRequest}.
---@class lsp.SignatureHelpClientCapabilities
+---
---Whether signature help supports dynamic registration.
---@field dynamicRegistration? boolean
+---
---The client supports the following `SignatureInformation`
---specific properties.
----@field signatureInformation? anonym30
+---@field signatureInformation? lsp._anonym29.signatureInformation
+---
---The client supports to send additional context information for a
---`textDocument/signatureHelp` request. A client that opts into
---contextSupport will also support the `retriggerCharacters` on
@@ -3142,17 +3825,21 @@ error('Cannot require a meta file')
---@since 3.14.0
---@class lsp.DeclarationClientCapabilities
+---
---Whether declaration supports dynamic registration. If this is set to `true`
---the client supports the new `DeclarationRegistrationOptions` return value
---for the corresponding server capability as well.
---@field dynamicRegistration? boolean
+---
---The client supports additional metadata in the form of declaration links.
---@field linkSupport? boolean
---Client Capabilities for a {@link DefinitionRequest}.
---@class lsp.DefinitionClientCapabilities
+---
---Whether definition supports dynamic registration.
---@field dynamicRegistration? boolean
+---
---The client supports additional metadata in the form of definition links.
---
---@since 3.14.0
@@ -3160,10 +3847,12 @@ error('Cannot require a meta file')
---Since 3.6.0
---@class lsp.TypeDefinitionClientCapabilities
+---
---Whether implementation supports dynamic registration. If this is set to `true`
---the client supports the new `TypeDefinitionRegistrationOptions` return value
---for the corresponding server capability as well.
---@field dynamicRegistration? boolean
+---
---The client supports additional metadata in the form of definition links.
---
---Since 3.14.0
@@ -3171,10 +3860,12 @@ error('Cannot require a meta file')
---@since 3.6.0
---@class lsp.ImplementationClientCapabilities
+---
---Whether implementation supports dynamic registration. If this is set to `true`
---the client supports the new `ImplementationRegistrationOptions` return value
---for the corresponding server capability as well.
---@field dynamicRegistration? boolean
+---
---The client supports additional metadata in the form of definition links.
---
---@since 3.14.0
@@ -3182,29 +3873,36 @@ error('Cannot require a meta file')
---Client Capabilities for a {@link ReferencesRequest}.
---@class lsp.ReferenceClientCapabilities
+---
---Whether references supports dynamic registration.
---@field dynamicRegistration? boolean
---Client Capabilities for a {@link DocumentHighlightRequest}.
---@class lsp.DocumentHighlightClientCapabilities
+---
---Whether document highlight supports dynamic registration.
---@field dynamicRegistration? boolean
---Client Capabilities for a {@link DocumentSymbolRequest}.
---@class lsp.DocumentSymbolClientCapabilities
+---
---Whether document symbol supports dynamic registration.
---@field dynamicRegistration? boolean
+---
---Specific capabilities for the `SymbolKind` in the
---`textDocument/documentSymbol` request.
----@field symbolKind? anonym31
+---@field symbolKind? lsp._anonym31.symbolKind
+---
---The client supports hierarchical document symbols.
---@field hierarchicalDocumentSymbolSupport? boolean
+---
---The client supports tags on `SymbolInformation`. Tags are supported on
---`DocumentSymbol` if `hierarchicalDocumentSymbolSupport` is set to true.
---Clients supporting tags have to handle unknown tags gracefully.
---
---@since 3.16.0
----@field tagSupport? anonym32
+---@field tagSupport? lsp._anonym32.tagSupport
+---
---The client supports an additional label presented in the UI when
---registering a document symbol provider.
---
@@ -3213,33 +3911,40 @@ error('Cannot require a meta file')
---The Client Capabilities of a {@link CodeActionRequest}.
---@class lsp.CodeActionClientCapabilities
+---
---Whether code action supports dynamic registration.
---@field dynamicRegistration? boolean
+---
---The client support code action literals of type `CodeAction` as a valid
---response of the `textDocument/codeAction` request. If the property is not
---set the request can only return `Command` literals.
---
---@since 3.8.0
----@field codeActionLiteralSupport? anonym34
+---@field codeActionLiteralSupport? lsp._anonym33.codeActionLiteralSupport
+---
---Whether code action supports the `isPreferred` property.
---
---@since 3.15.0
---@field isPreferredSupport? boolean
+---
---Whether code action supports the `disabled` property.
---
---@since 3.16.0
---@field disabledSupport? boolean
+---
---Whether code action supports the `data` property which is
---preserved between a `textDocument/codeAction` and a
---`codeAction/resolve` request.
---
---@since 3.16.0
---@field dataSupport? boolean
+---
---Whether the client supports resolving additional code action
---properties via a separate `codeAction/resolve` request.
---
---@since 3.16.0
----@field resolveSupport? anonym35
+---@field resolveSupport? lsp._anonym35.resolveSupport
+---
---Whether the client honors the change annotations in
---text edits and resource operations returned via the
---`CodeAction#edit` property by for example presenting
@@ -3251,19 +3956,23 @@ error('Cannot require a meta file')
---The client capabilities of a {@link CodeLensRequest}.
---@class lsp.CodeLensClientCapabilities
+---
---Whether code lens supports dynamic registration.
---@field dynamicRegistration? boolean
---The client capabilities of a {@link DocumentLinkRequest}.
---@class lsp.DocumentLinkClientCapabilities
+---
---Whether document link supports dynamic registration.
---@field dynamicRegistration? boolean
+---
---Whether the client supports the `tooltip` property on `DocumentLink`.
---
---@since 3.15.0
---@field tooltipSupport? boolean
---@class lsp.DocumentColorClientCapabilities
+---
---Whether implementation supports dynamic registration. If this is set to `true`
---the client supports the new `DocumentColorRegistrationOptions` return value
---for the corresponding server capability as well.
@@ -3271,13 +3980,16 @@ error('Cannot require a meta file')
---Client capabilities of a {@link DocumentFormattingRequest}.
---@class lsp.DocumentFormattingClientCapabilities
+---
---Whether formatting supports dynamic registration.
---@field dynamicRegistration? boolean
---Client capabilities of a {@link DocumentRangeFormattingRequest}.
---@class lsp.DocumentRangeFormattingClientCapabilities
+---
---Whether range formatting supports dynamic registration.
---@field dynamicRegistration? boolean
+---
---Whether the client supports formatting multiple ranges at once.
---
---@since 3.18.0
@@ -3286,17 +3998,21 @@ error('Cannot require a meta file')
---Client capabilities of a {@link DocumentOnTypeFormattingRequest}.
---@class lsp.DocumentOnTypeFormattingClientCapabilities
+---
---Whether on type formatting supports dynamic registration.
---@field dynamicRegistration? boolean
---@class lsp.RenameClientCapabilities
+---
---Whether rename supports dynamic registration.
---@field dynamicRegistration? boolean
+---
---Client supports testing for validity of rename operations
---before execution.
---
---@since 3.12.0
---@field prepareSupport? boolean
+---
---Client supports the default behavior result.
---
---The value indicates the default behavior used by the
@@ -3304,6 +4020,7 @@ error('Cannot require a meta file')
---
---@since 3.16.0
---@field prepareSupportDefaultBehavior? lsp.PrepareSupportDefaultBehavior
+---
---Whether the client honors the change annotations in
---text edits and resource operations returned via the
---rename request's workspace edit by for example presenting
@@ -3314,29 +4031,35 @@ error('Cannot require a meta file')
---@field honorsChangeAnnotations? boolean
---@class lsp.FoldingRangeClientCapabilities
+---
---Whether implementation supports dynamic registration for folding range
---providers. If this is set to `true` the client supports the new
---`FoldingRangeRegistrationOptions` return value for the corresponding
---server capability as well.
---@field dynamicRegistration? boolean
+---
---The maximum number of folding ranges that the client prefers to receive
---per document. The value serves as a hint, servers are free to follow the
---limit.
---@field rangeLimit? uinteger
+---
---If set, the client signals that it only supports folding complete lines.
---If set, client will ignore specified `startCharacter` and `endCharacter`
---properties in a FoldingRange.
---@field lineFoldingOnly? boolean
+---
---Specific options for the folding range kind.
---
---@since 3.17.0
----@field foldingRangeKind? anonym36
+---@field foldingRangeKind? lsp._anonym36.foldingRangeKind
+---
---Specific options for the folding range.
---
---@since 3.17.0
----@field foldingRange? anonym37
+---@field foldingRange? lsp._anonym37.foldingRange
---@class lsp.SelectionRangeClientCapabilities
+---
---Whether implementation supports dynamic registration for selection range providers. If this is set to `true`
---the client supports the new `SelectionRangeRegistrationOptions` return value for the corresponding server
---capability as well.
@@ -3344,22 +4067,27 @@ error('Cannot require a meta file')
---The publish diagnostic client capabilities.
---@class lsp.PublishDiagnosticsClientCapabilities
+---
---Whether the clients accepts diagnostics with related information.
---@field relatedInformation? boolean
+---
---Client supports the tag property to provide meta data about a diagnostic.
---Clients supporting tags have to handle unknown tags gracefully.
---
---@since 3.15.0
----@field tagSupport? anonym38
+---@field tagSupport? lsp._anonym38.tagSupport
+---
---Whether the client interprets the version property of the
---`textDocument/publishDiagnostics` notification's parameter.
---
---@since 3.15.0
---@field versionSupport? boolean
+---
---Client supports a codeDescription property
---
---@since 3.16.0
---@field codeDescriptionSupport? boolean
+---
---Whether code action supports the `data` property which is
---preserved between a `textDocument/publishDiagnostics` and
---`textDocument/codeAction` request.
@@ -3369,6 +4097,7 @@ error('Cannot require a meta file')
---@since 3.16.0
---@class lsp.CallHierarchyClientCapabilities
+---
---Whether implementation supports dynamic registration. If this is set to `true`
---the client supports the new `(TextDocumentRegistrationOptions & StaticRegistrationOptions)`
---return value for the corresponding server capability as well.
@@ -3376,10 +4105,12 @@ error('Cannot require a meta file')
---@since 3.16.0
---@class lsp.SemanticTokensClientCapabilities
+---
---Whether implementation supports dynamic registration. If this is set to `true`
---the client supports the new `(TextDocumentRegistrationOptions & StaticRegistrationOptions)`
---return value for the corresponding server capability as well.
---@field dynamicRegistration? boolean
+---
---Which requests the client supports and might send to the server
---depending on the server's capability. Please note that clients might not
---show semantic tokens or degrade some of the user experience if a range
@@ -3388,17 +4119,23 @@ error('Cannot require a meta file')
---`request.range` are both set to true but the server only provides a
---range provider the client might not render a minimap correctly or might
---even decide to not show any semantic tokens at all.
----@field requests anonym41
+---@field requests lsp._anonym39.requests
+---
---The token types that the client supports.
---@field tokenTypes string[]
+---
---The token modifiers that the client supports.
---@field tokenModifiers string[]
+---
---The token formats the clients supports.
---@field formats lsp.TokenFormat[]
+---
---Whether the client supports tokens that can overlap each other.
---@field overlappingTokenSupport? boolean
+---
---Whether the client supports tokens that can span multiple lines.
---@field multilineTokenSupport? boolean
+---
---Whether the client allows the server to actively cancel a
---semantic token request, e.g. supports returning
---LSPErrorCodes.ServerCancelled. If a server does the client
@@ -3406,6 +4143,7 @@ error('Cannot require a meta file')
---
---@since 3.17.0
---@field serverCancelSupport? boolean
+---
---Whether the client uses semantic tokens to augment existing
---syntax tokens. If set to `true` client side created syntax
---tokens and semantic tokens are both used for colorization. If
@@ -3422,6 +4160,7 @@ error('Cannot require a meta file')
---
---@since 3.16.0
---@class lsp.LinkedEditingRangeClientCapabilities
+---
---Whether implementation supports dynamic registration. If this is set to `true`
---the client supports the new `(TextDocumentRegistrationOptions & StaticRegistrationOptions)`
---return value for the corresponding server capability as well.
@@ -3431,6 +4170,7 @@ error('Cannot require a meta file')
---
---@since 3.16.0
---@class lsp.MonikerClientCapabilities
+---
---Whether moniker supports dynamic registration. If this is set to `true`
---the client supports the new `MonikerRegistrationOptions` return value
---for the corresponding server capability as well.
@@ -3438,6 +4178,7 @@ error('Cannot require a meta file')
---@since 3.17.0
---@class lsp.TypeHierarchyClientCapabilities
+---
---Whether implementation supports dynamic registration. If this is set to `true`
---the client supports the new `(TextDocumentRegistrationOptions & StaticRegistrationOptions)`
---return value for the corresponding server capability as well.
@@ -3447,6 +4188,7 @@ error('Cannot require a meta file')
---
---@since 3.17.0
---@class lsp.InlineValueClientCapabilities
+---
---Whether implementation supports dynamic registration for inline value providers.
---@field dynamicRegistration? boolean
@@ -3454,27 +4196,33 @@ error('Cannot require a meta file')
---
---@since 3.17.0
---@class lsp.InlayHintClientCapabilities
+---
---Whether inlay hints support dynamic registration.
---@field dynamicRegistration? boolean
+---
---Indicates which properties a client can resolve lazily on an inlay
---hint.
----@field resolveSupport? anonym42
+---@field resolveSupport? lsp._anonym42.resolveSupport
---Client capabilities specific to diagnostic pull requests.
---
---@since 3.17.0
---@class lsp.DiagnosticClientCapabilities
+---
---Whether implementation supports dynamic registration. If this is set to `true`
---the client supports the new `(TextDocumentRegistrationOptions & StaticRegistrationOptions)`
---return value for the corresponding server capability as well.
---@field dynamicRegistration? boolean
+---
---Whether the clients supports related documents for document diagnostic pulls.
---@field relatedDocumentSupport? boolean
---Client capabilities specific to inline completions.
---
---@since 3.18.0
+---@proposed
---@class lsp.InlineCompletionClientCapabilities
+---
---Whether implementation supports dynamic registration for inline completion providers.
---@field dynamicRegistration? boolean
@@ -3482,23 +4230,27 @@ error('Cannot require a meta file')
---
---@since 3.17.0
---@class lsp.NotebookDocumentSyncClientCapabilities
+---
---Whether implementation supports dynamic registration. If this is
---set to `true` the client supports the new
---`(TextDocumentRegistrationOptions & StaticRegistrationOptions)`
---return value for the corresponding server capability as well.
---@field dynamicRegistration? boolean
+---
---The client supports sending execution summary data per cell.
---@field executionSummarySupport? boolean
---Show message request client capabilities
---@class lsp.ShowMessageRequestClientCapabilities
+---
---Capabilities specific to the `MessageActionItem` type.
----@field messageActionItem? anonym43
+---@field messageActionItem? lsp._anonym43.messageActionItem
---Client capabilities for the showDocument request.
---
---@since 3.16.0
---@class lsp.ShowDocumentClientCapabilities
+---
---The client has support for the showDocument
---request.
---@field support boolean
@@ -3507,8 +4259,10 @@ error('Cannot require a meta file')
---
---@since 3.16.0
---@class lsp.RegularExpressionsClientCapabilities
+---
---The engine's name.
---@field engine string
+---
---The engine's version.
---@field version? string
@@ -3516,10 +4270,13 @@ error('Cannot require a meta file')
---
---@since 3.16.0
---@class lsp.MarkdownClientCapabilities
+---
---The name of the parser.
---@field parser string
+---
---The version of the parser.
---@field version? string
+---
---A list of HTML tags that the client allows / supports in
---Markdown.
---
@@ -3662,18 +4419,13 @@ error('Cannot require a meta file')
---| 1 # Type
---| 2 # Parameter
----Defines whether the insert text in a completion item should be interpreted as
----plain text or a snippet.
----@alias lsp.InsertTextFormat
----| 1 # PlainText
----| 2 # Snippet
-
---The message type
---@alias lsp.MessageType
---| 1 # Error
---| 2 # Warning
---| 3 # Info
---| 4 # Log
+---| 5 # Debug
---Defines how the host (editor) should sync
---document changes to the language server.
@@ -3723,6 +4475,12 @@ error('Cannot require a meta file')
---@alias lsp.CompletionItemTag
---| 1 # Deprecated
+---Defines whether the insert text in a completion item should be interpreted as
+---plain text or a snippet.
+---@alias lsp.InsertTextFormat
+---| 1 # PlainText
+---| 2 # Snippet
+
---How whitespace and indentation is handled during completion
---item insertion.
---
@@ -3766,6 +4524,7 @@ error('Cannot require a meta file')
---Describes how an {@link InlineCompletionItemProvider inline completion provider} was triggered.
---
---@since 3.18.0
+---@proposed
---@alias lsp.InlineCompletionTriggerKind
---| 0 # Invoked
---| 1 # Automatic
@@ -3912,11 +4671,11 @@ error('Cannot require a meta file')
---@since 3.17.0
---@alias lsp.DocumentDiagnosticReport lsp.RelatedFullDocumentDiagnosticReport|lsp.RelatedUnchangedDocumentDiagnosticReport
----@alias lsp.PrepareRenameResult lsp.Range|anonym44|anonym45
+---@alias lsp.PrepareRenameResult lsp.Range|lsp._anonym44.PrepareRenameResult|lsp._anonym45.PrepareRenameResult
---A document selector is the combination of one or many document filters.
---
----@sample `let sel:DocumentSelector = [{ language: 'typescript' }, { language: 'json', pattern: '**∕tsconfig.json' }]`;
+---\@sample `let sel:DocumentSelector = [{ language: 'typescript' }, { language: 'json', pattern: '**∕tsconfig.json' }]`;
---
---The use of a string as a document filter is deprecated @since 3.16.0.
---@alias lsp.DocumentSelector lsp.DocumentFilter[]
@@ -3933,7 +4692,7 @@ error('Cannot require a meta file')
---An event describing a change to a text document. If only a text is provided
---it is considered to be the full content of the document.
----@alias lsp.TextDocumentContentChangeEvent anonym46|anonym47
+---@alias lsp.TextDocumentContentChangeEvent lsp._anonym46.TextDocumentContentChangeEvent|lsp._anonym47.TextDocumentContentChangeEvent
---MarkedString can be used to render human readable text. It is either a markdown string
---or a code-block that provides a language and a code snippet. The language identifier
@@ -3947,7 +4706,7 @@ error('Cannot require a meta file')
---
---Note that markdown strings will be sanitized - that means html will be escaped.
---@deprecated use MarkupContent instead.
----@alias lsp.MarkedString string|anonym48
+---@alias lsp.MarkedString string|lsp._anonym48.MarkedString
---A document filter describes a top level text document or
---a notebook cell document.
@@ -3972,120 +4731,145 @@ error('Cannot require a meta file')
---- `*` to match one or more characters in a path segment
---- `?` to match on one character in a path segment
---- `**` to match any number of path segments, including none
----- `{}` to group sub patterns into an OR expression. (e.g. `**​/*.{ts,js}` matches all TypeScript and JavaScript files)
+---- `{}` to group sub patterns into an OR expression. (e.g. `**/*.{ts,js}` matches all TypeScript and JavaScript files)
---- `[]` to declare a range of characters to match in a path segment (e.g., `example.[0-9]` to match on `example.0`, `example.1`, …)
---- `[!...]` to negate a range of characters to match in a path segment (e.g., `example.[!0-9]` to match on `example.a`, `example.b`, but not `example.0`)
---
----@sample A language filter that applies to typescript files on disk: `{ language: 'typescript', scheme: 'file' }`
----@sample A language filter that applies to all package.json paths: `{ language: 'json', pattern: '**package.json' }`
+---\@sample A language filter that applies to typescript files on disk: `{ language: 'typescript', scheme: 'file' }`
+---\@sample A language filter that applies to all package.json paths: `{ language: 'json', pattern: '**package.json' }`
---
---@since 3.17.0
----@alias lsp.TextDocumentFilter anonym49|anonym50|anonym51
+---@alias lsp.TextDocumentFilter lsp._anonym49.TextDocumentFilter|lsp._anonym50.TextDocumentFilter|lsp._anonym51.TextDocumentFilter
---A notebook document filter denotes a notebook document by
---different properties. The properties will be match
---against the notebook's URI (same as with documents)
---
---@since 3.17.0
----@alias lsp.NotebookDocumentFilter anonym52|anonym53|anonym54
+---@alias lsp.NotebookDocumentFilter lsp._anonym52.NotebookDocumentFilter|lsp._anonym53.NotebookDocumentFilter|lsp._anonym54.NotebookDocumentFilter
---The glob pattern to watch relative to the base path. Glob patterns can have the following syntax:
---- `*` to match one or more characters in a path segment
---- `?` to match on one character in a path segment
---- `**` to match any number of path segments, including none
----- `{}` to group conditions (e.g. `**​/*.{ts,js}` matches all TypeScript and JavaScript files)
+---- `{}` to group conditions (e.g. `**/*.{ts,js}` matches all TypeScript and JavaScript files)
---- `[]` to declare a range of characters to match in a path segment (e.g., `example.[0-9]` to match on `example.0`, `example.1`, …)
---- `[!...]` to negate a range of characters to match in a path segment (e.g., `example.[!0-9]` to match on `example.a`, `example.b`, but not `example.0`)
---
---@since 3.17.0
---@alias lsp.Pattern string
----@class anonym1
+---@class lsp._anonym1.serverInfo
+---
---The name of the server as defined by the server.
---@field name string
+---
---The server's version as defined by the server.
---@field version? string
----@class anonym3
+---@class lsp._anonym3.itemDefaults.editRange
+---
---@field insert lsp.Range
+---
---@field replace lsp.Range
----@class anonym2
+---@class lsp._anonym2.itemDefaults
+---
---A default commit character set.
---
---@since 3.17.0
---@field commitCharacters? string[]
+---
---A default edit range.
---
---@since 3.17.0
----@field editRange? lsp.Range|anonym3
+---@field editRange? lsp.Range|lsp._anonym3.itemDefaults.editRange
+---
---A default insert text format.
---
---@since 3.17.0
---@field insertTextFormat? lsp.InsertTextFormat
+---
---A default insert text mode.
---
---@since 3.17.0
---@field insertTextMode? lsp.InsertTextMode
+---
---A default data value.
---
---@since 3.17.0
---@field data? lsp.LSPAny
----@class anonym4
+---@class lsp._anonym4.disabled
+---
---Human readable description of why the code action is currently disabled.
---
---This is displayed in the code actions UI.
---@field reason string
----@class anonym5
+---@class lsp._anonym5.location
+---
---@field uri lsp.DocumentUri
----@class anonym6
+---@class lsp._anonym6.range
----@class anonym7
+---@class lsp._anonym7.full
+---
---The server supports deltas for full documents.
---@field delta? boolean
----@class anonym9
+---@class lsp._anonym9.cells.structure
+---
---The change to the cell array.
---@field array lsp.NotebookCellArrayChange
+---
---Additional opened cell text documents.
---@field didOpen? lsp.TextDocumentItem[]
+---
---Additional closed cell text documents.
---@field didClose? lsp.TextDocumentIdentifier[]
----@class anonym10
+---@class lsp._anonym10.cells.textContent
+---
---@field document lsp.VersionedTextDocumentIdentifier
+---
---@field changes lsp.TextDocumentContentChangeEvent[]
----@class anonym8
+---@class lsp._anonym8.cells
+---
---Changes to the cell structure to add or
---remove cells.
----@field structure? anonym9
+---@field structure? lsp._anonym9.cells.structure
+---
---Changes to notebook cells properties like its
---kind, execution summary or metadata.
---@field data? lsp.NotebookCell[]
+---
---Changes to the text content of notebook cells.
----@field textContent? anonym10[]
+---@field textContent? lsp._anonym10.cells.textContent[]
----@class anonym11
+---@class lsp._anonym11.clientInfo
+---
---The name of the client as defined by the client.
---@field name string
+---
---The client's version as defined by the client.
---@field version? string
----@class anonym12
+---@class lsp._anonym12.workspace
+---
---The server supports workspace folder.
---
---@since 3.6.0
---@field workspaceFolders? lsp.WorkspaceFoldersServerCapabilities
+---
---The server is interested in notifications/requests for operations on files.
---
---@since 3.16.0
---@field fileOperations? lsp.FileOperationOptions
----@class anonym13
+---@class lsp._anonym13.completionItem
+---
---The server has support for completion item label
---details (see also `CompletionItemLabelDetails`) when
---receiving a completion item in a resolve call.
@@ -4093,43 +4877,53 @@ error('Cannot require a meta file')
---@since 3.17.0
---@field labelDetailsSupport? boolean
----@class anonym15
+---@class lsp._anonym15.notebookSelector.cells
+---
---@field language string
----@class anonym14
+---@class lsp._anonym14.notebookSelector
+---
---The notebook to be synced If a string
---value is provided it matches against the
---notebook type. '*' matches every notebook.
---@field notebook string|lsp.NotebookDocumentFilter
+---
---The cells of the matching notebook to be synced.
----@field cells? anonym15[]
+---@field cells? lsp._anonym15.notebookSelector.cells[]
----@class anonym17
+---@class lsp._anonym17.notebookSelector.cells
+---
---@field language string
----@class anonym16
+---@class lsp._anonym16.notebookSelector
+---
---The notebook to be synced If a string
---value is provided it matches against the
---notebook type. '*' matches every notebook.
---@field notebook? string|lsp.NotebookDocumentFilter
+---
---The cells of the matching notebook to be synced.
----@field cells anonym17[]
+---@field cells lsp._anonym17.notebookSelector.cells[]
----@class anonym18
+---@class lsp._anonym18.staleRequestSupport
+---
---The client will actively cancel the request.
---@field cancel boolean
+---
---The list of requests for which the client
---will retry the request if it receives a
---response with error code `ContentModified`
---@field retryOnContentModified string[]
----@class anonym19
+---@class lsp._anonym19.changeAnnotationSupport
+---
---Whether the client groups edits with equal labels into tree nodes,
---for instance all edits labelled with "Changes in Strings" would
---be a tree node.
---@field groupsOnLabel? boolean
----@class anonym20
+---@class lsp._anonym20.symbolKind
+---
---The symbol kind values the client supports. When this
---property exists the client also guarantees that it will
---handle values outside its set gracefully and falls back
@@ -4140,27 +4934,33 @@ error('Cannot require a meta file')
---the initial version of the protocol.
---@field valueSet? lsp.SymbolKind[]
----@class anonym21
+---@class lsp._anonym21.tagSupport
+---
---The tags supported by the client.
---@field valueSet lsp.SymbolTag[]
----@class anonym22
+---@class lsp._anonym22.resolveSupport
+---
---The properties that a client can resolve lazily. Usually
---`location.range`
---@field properties string[]
----@class anonym24
+---@class lsp._anonym24.completionItem.tagSupport
+---
---The tags supported by the client.
---@field valueSet lsp.CompletionItemTag[]
----@class anonym25
+---@class lsp._anonym25.completionItem.resolveSupport
+---
---The properties that a client can resolve lazily.
---@field properties string[]
----@class anonym26
+---@class lsp._anonym26.completionItem.insertTextModeSupport
+---
---@field valueSet lsp.InsertTextMode[]
----@class anonym23
+---@class lsp._anonym23.completionItem
+---
---Client supports snippets as insert text.
---
---A snippet can define tab stops and placeholders with `$1`, `$2`
@@ -4168,46 +4968,56 @@ error('Cannot require a meta file')
---the end of the snippet. Placeholders with equal identifiers are linked,
---that is typing in one will update others too.
---@field snippetSupport? boolean
+---
---Client supports commit characters on a completion item.
---@field commitCharactersSupport? boolean
+---
---Client supports the following content formats for the documentation
---property. The order describes the preferred format of the client.
---@field documentationFormat? lsp.MarkupKind[]
+---
---Client supports the deprecated property on a completion item.
---@field deprecatedSupport? boolean
+---
---Client supports the preselect property on a completion item.
---@field preselectSupport? boolean
+---
---Client supports the tag property on a completion item. Clients supporting
---tags have to handle unknown tags gracefully. Clients especially need to
---preserve unknown tags when sending a completion item back to the server in
---a resolve call.
---
---@since 3.15.0
----@field tagSupport? anonym24
+---@field tagSupport? lsp._anonym24.completionItem.tagSupport
+---
---Client support insert replace edit to control different behavior if a
---completion item is inserted in the text or should replace text.
---
---@since 3.16.0
---@field insertReplaceSupport? boolean
+---
---Indicates which properties a client can resolve lazily on a completion
---item. Before version 3.16.0 only the predefined properties `documentation`
---and `details` could be resolved lazily.
---
---@since 3.16.0
----@field resolveSupport? anonym25
+---@field resolveSupport? lsp._anonym25.completionItem.resolveSupport
+---
---The client supports the `insertTextMode` property on
---a completion item to override the whitespace handling mode
---as defined by the client (see `insertTextMode`).
---
---@since 3.16.0
----@field insertTextModeSupport? anonym26
+---@field insertTextModeSupport? lsp._anonym26.completionItem.insertTextModeSupport
+---
---The client has support for completion item label
---details (see also `CompletionItemLabelDetails`).
---
---@since 3.17.0
---@field labelDetailsSupport? boolean
----@class anonym27
+---@class lsp._anonym27.completionItemKind
+---
---The completion item kind values the client supports. When this
---property exists the client also guarantees that it will
---handle values outside its set gracefully and falls back
@@ -4218,7 +5028,8 @@ error('Cannot require a meta file')
---the initial version of the protocol.
---@field valueSet? lsp.CompletionItemKind[]
----@class anonym28
+---@class lsp._anonym28.completionList
+---
---The client supports the following itemDefaults on
---a completion list.
---
@@ -4229,26 +5040,38 @@ error('Cannot require a meta file')
---@since 3.17.0
---@field itemDefaults? string[]
----@class anonym30
+---@class lsp._anonym30.signatureInformation.parameterInformation
+---
---The client supports processing label offsets instead of a
---simple label string.
---
---@since 3.14.0
---@field labelOffsetSupport? boolean
----@class anonym29
+---@class lsp._anonym29.signatureInformation
+---
---Client supports the following content formats for the documentation
---property. The order describes the preferred format of the client.
---@field documentationFormat? lsp.MarkupKind[]
+---
---Client capabilities specific to parameter information.
----@field parameterInformation? anonym30
+---@field parameterInformation? lsp._anonym30.signatureInformation.parameterInformation
+---
---The client supports the `activeParameter` property on `SignatureInformation`
---literal.
---
---@since 3.16.0
---@field activeParameterSupport? boolean
+---
+---The client supports the `activeParameter` property on
+---`SignatureInformation` being set to `null` to indicate that no
+---parameter should be active.
+---
+---@since 3.18.0
+---@field noActiveParameterSupport? boolean
----@class anonym31
+---@class lsp._anonym31.symbolKind
+---
---The symbol kind values the client supports. When this
---property exists the client also guarantees that it will
---handle values outside its set gracefully and falls back
@@ -4259,138 +5082,177 @@ error('Cannot require a meta file')
---the initial version of the protocol.
---@field valueSet? lsp.SymbolKind[]
----@class anonym32
+---@class lsp._anonym32.tagSupport
+---
---The tags supported by the client.
---@field valueSet lsp.SymbolTag[]
----@class anonym34
+---@class lsp._anonym34.codeActionLiteralSupport.codeActionKind
+---
---The code action kind values the client supports. When this
---property exists the client also guarantees that it will
---handle values outside its set gracefully and falls back
---to a default value when unknown.
---@field valueSet lsp.CodeActionKind[]
----@class anonym33
+---@class lsp._anonym33.codeActionLiteralSupport
+---
---The code action kind is support with the following value
---set.
----@field codeActionKind anonym34
+---@field codeActionKind lsp._anonym34.codeActionLiteralSupport.codeActionKind
----@class anonym35
+---@class lsp._anonym35.resolveSupport
+---
---The properties that a client can resolve lazily.
---@field properties string[]
----@class anonym36
+---@class lsp._anonym36.foldingRangeKind
+---
---The folding range kind values the client supports. When this
---property exists the client also guarantees that it will
---handle values outside its set gracefully and falls back
---to a default value when unknown.
---@field valueSet? lsp.FoldingRangeKind[]
----@class anonym37
+---@class lsp._anonym37.foldingRange
+---
---If set, the client signals that it supports setting collapsedText on
---folding ranges to display custom labels instead of the default text.
---
---@since 3.17.0
---@field collapsedText? boolean
----@class anonym38
+---@class lsp._anonym38.tagSupport
+---
---The tags supported by the client.
---@field valueSet lsp.DiagnosticTag[]
----@class anonym40
+---@class lsp._anonym40.requests.range
----@class anonym41
+---@class lsp._anonym41.requests.full
+---
---The client will send the `textDocument/semanticTokens/full/delta` request if
---the server provides a corresponding handler.
---@field delta? boolean
----@class anonym39
+---@class lsp._anonym39.requests
+---
---The client will send the `textDocument/semanticTokens/range` request if
---the server provides a corresponding handler.
----@field range? boolean|anonym40
+---@field range? boolean|lsp._anonym40.requests.range
+---
---The client will send the `textDocument/semanticTokens/full` request if
---the server provides a corresponding handler.
----@field full? boolean|anonym41
+---@field full? boolean|lsp._anonym41.requests.full
----@class anonym42
+---@class lsp._anonym42.resolveSupport
+---
---The properties that a client can resolve lazily.
---@field properties string[]
----@class anonym43
+---@class lsp._anonym43.messageActionItem
+---
---Whether the client supports additional attributes which
---are preserved and send back to the server in the
---request's response.
---@field additionalPropertiesSupport? boolean
----@class anonym44
+---@class lsp._anonym44.PrepareRenameResult
+---
---@field range lsp.Range
+---
---@field placeholder string
----@class anonym45
+---@class lsp._anonym45.PrepareRenameResult
+---
---@field defaultBehavior boolean
----@class anonym46
+---@class lsp._anonym46.TextDocumentContentChangeEvent
+---
---The range of the document that changed.
---@field range lsp.Range
+---
---The optional length of the range that got replaced.
---
---@deprecated use range instead.
---@field rangeLength? uinteger
+---
---The new text for the provided range.
---@field text string
----@class anonym47
+---@class lsp._anonym47.TextDocumentContentChangeEvent
+---
---The new text of the whole document.
---@field text string
----@class anonym48
+---@class lsp._anonym48.MarkedString
+---
---@field language string
+---
---@field value string
----@class anonym49
+---@class lsp._anonym49.TextDocumentFilter
+---
---A language id, like `typescript`.
---@field language string
+---
---A Uri {@link Uri.scheme scheme}, like `file` or `untitled`.
---@field scheme? string
----A glob pattern, like `*.{ts,js}`.
+---
+---A glob pattern, like **/*.{ts,js}. See TextDocumentFilter for examples.
---@field pattern? string
----@class anonym50
+---@class lsp._anonym50.TextDocumentFilter
+---
---A language id, like `typescript`.
---@field language? string
+---
---A Uri {@link Uri.scheme scheme}, like `file` or `untitled`.
---@field scheme string
----A glob pattern, like `*.{ts,js}`.
+---
+---A glob pattern, like **/*.{ts,js}. See TextDocumentFilter for examples.
---@field pattern? string
----@class anonym51
+---@class lsp._anonym51.TextDocumentFilter
+---
---A language id, like `typescript`.
---@field language? string
+---
---A Uri {@link Uri.scheme scheme}, like `file` or `untitled`.
---@field scheme? string
----A glob pattern, like `*.{ts,js}`.
+---
+---A glob pattern, like **/*.{ts,js}. See TextDocumentFilter for examples.
---@field pattern string
----@class anonym52
+---@class lsp._anonym52.NotebookDocumentFilter
+---
---The type of the enclosing notebook.
---@field notebookType string
+---
---A Uri {@link Uri.scheme scheme}, like `file` or `untitled`.
---@field scheme? string
+---
---A glob pattern.
---@field pattern? string
----@class anonym53
+---@class lsp._anonym53.NotebookDocumentFilter
+---
---The type of the enclosing notebook.
---@field notebookType? string
+---
---A Uri {@link Uri.scheme scheme}, like `file` or `untitled`.
---@field scheme string
+---
---A glob pattern.
---@field pattern? string
----@class anonym54
+---@class lsp._anonym54.NotebookDocumentFilter
+---
---The type of the enclosing notebook.
---@field notebookType? string
+---
---A Uri {@link Uri.scheme scheme}, like `file` or `untitled`.
---@field scheme? string
+---
---A glob pattern.
---@field pattern string
diff --git a/runtime/lua/vim/lsp/tagfunc.lua b/runtime/lua/vim/lsp/_tagfunc.lua
index 4ad50e4a58..4ad50e4a58 100644
--- a/runtime/lua/vim/lsp/tagfunc.lua
+++ b/runtime/lua/vim/lsp/_tagfunc.lua
diff --git a/runtime/lua/vim/lsp/_watchfiles.lua b/runtime/lua/vim/lsp/_watchfiles.lua
index 1fd112631d..49328fbe9b 100644
--- a/runtime/lua/vim/lsp/_watchfiles.lua
+++ b/runtime/lua/vim/lsp/_watchfiles.lua
@@ -1,95 +1,20 @@
local bit = require('bit')
-local watch = require('vim._watch')
+local glob = vim.glob
+local watch = vim._watch
local protocol = require('vim.lsp.protocol')
local ms = protocol.Methods
local lpeg = vim.lpeg
local M = {}
---- Parses the raw pattern into an |lpeg| pattern. LPeg patterns natively support the "this" or "that"
---- alternative constructions described in the LSP spec that cannot be expressed in a standard Lua pattern.
----
----@param pattern string The raw glob pattern
----@return vim.lpeg.Pattern? pattern An |lpeg| representation of the pattern, or nil if the pattern is invalid.
-local function parse(pattern)
- local l = lpeg
-
- local P, S, V = lpeg.P, lpeg.S, lpeg.V
- local C, Cc, Ct, Cf = lpeg.C, lpeg.Cc, lpeg.Ct, lpeg.Cf
-
- local pathsep = '/'
-
- local function class(inv, ranges)
- for i, r in ipairs(ranges) do
- ranges[i] = r[1] .. r[2]
- end
- local patt = l.R(unpack(ranges))
- if inv == '!' then
- patt = P(1) - patt
- end
- return patt
- end
-
- local function add(acc, a)
- return acc + a
- end
-
- local function mul(acc, m)
- return acc * m
- end
-
- local function star(stars, after)
- return (-after * (l.P(1) - pathsep)) ^ #stars * after
- end
-
- local function dstar(after)
- return (-after * l.P(1)) ^ 0 * after
- end
-
- local p = P({
- 'Pattern',
- Pattern = V('Elem') ^ -1 * V('End'),
- Elem = Cf(
- (V('DStar') + V('Star') + V('Ques') + V('Class') + V('CondList') + V('Literal'))
- * (V('Elem') + V('End')),
- mul
- ),
- DStar = P('**') * (P(pathsep) * (V('Elem') + V('End')) + V('End')) / dstar,
- Star = C(P('*') ^ 1) * (V('Elem') + V('End')) / star,
- Ques = P('?') * Cc(l.P(1) - pathsep),
- Class = P('[') * C(P('!') ^ -1) * Ct(Ct(C(1) * '-' * C(P(1) - ']')) ^ 1 * ']') / class,
- CondList = P('{') * Cf(V('Cond') * (P(',') * V('Cond')) ^ 0, add) * '}',
- -- TODO: '*' inside a {} condition is interpreted literally but should probably have the same
- -- wildcard semantics it usually has.
- -- Fixing this is non-trivial because '*' should match non-greedily up to "the rest of the
- -- pattern" which in all other cases is the entire succeeding part of the pattern, but at the end of a {}
- -- condition means "everything after the {}" where several other options separated by ',' may
- -- exist in between that should not be matched by '*'.
- Cond = Cf((V('Ques') + V('Class') + V('CondList') + (V('Literal') - S(',}'))) ^ 1, mul)
- + Cc(l.P(0)),
- Literal = P(1) / l.P,
- End = P(-1) * Cc(l.P(-1)),
- })
-
- return p:match(pattern) --[[@as vim.lpeg.Pattern?]]
-end
-
----@private
---- Implementation of LSP 3.17.0's pattern matching: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#pattern
----
----@param pattern string|vim.lpeg.Pattern The glob pattern (raw or parsed) to match.
----@param s string The string to match against pattern.
----@return boolean Whether or not pattern matches s.
-function M._match(pattern, s)
- if type(pattern) == 'string' then
- local p = assert(parse(pattern))
- return p:match(s) ~= nil
- end
- return pattern:match(s) ~= nil
+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
-M._watchfunc = (vim.fn.has('win32') == 1 or vim.fn.has('mac') == 1) and watch.watch or watch.poll
-
---@type table<integer, table<string, function[]>> client id -> registration id -> cancel function
local cancels = vim.defaulttable()
@@ -112,9 +37,9 @@ local to_lsp_change_type = {
--- Default excludes the same as VSCode's `files.watcherExclude` setting.
--- https://github.com/microsoft/vscode/blob/eef30e7165e19b33daa1e15e92fa34ff4a5df0d3/src/vs/workbench/contrib/files/browser/files.contribution.ts#L261
---@type vim.lpeg.Pattern parsed Lpeg pattern
-M._poll_exclude_pattern = parse('**/.git/{objects,subtree-cache}/**')
- + parse('**/node_modules/*/**')
- + parse('**/.hg/store/**')
+M._poll_exclude_pattern = glob.to_lpeg('**/.git/{objects,subtree-cache}/**')
+ + glob.to_lpeg('**/node_modules/*/**')
+ + glob.to_lpeg('**/.hg/store/**')
--- Registers the workspace/didChangeWatchedFiles capability dynamically.
---
@@ -125,12 +50,8 @@ function M.register(reg, ctx)
local client = assert(vim.lsp.get_client_by_id(client_id), 'Client must be running')
-- Ill-behaved servers may not honor the client capability and try to register
-- anyway, so ignore requests when the user has opted out of the feature.
- local has_capability = vim.tbl_get(
- client.config.capabilities or {},
- 'workspace',
- 'didChangeWatchedFiles',
- 'dynamicRegistration'
- )
+ local has_capability =
+ vim.tbl_get(client.capabilities, 'workspace', 'didChangeWatchedFiles', 'dynamicRegistration')
if not has_capability or not client.workspace_folders then
return
end
@@ -143,7 +64,7 @@ function M.register(reg, ctx)
local glob_pattern = w.globPattern
if type(glob_pattern) == 'string' then
- local pattern = parse(glob_pattern)
+ local pattern = glob.to_lpeg(glob_pattern)
if not pattern then
error('Cannot parse pattern: ' .. glob_pattern)
end
@@ -155,7 +76,7 @@ function M.register(reg, ctx)
local base_uri = glob_pattern.baseUri
local uri = type(base_uri) == 'string' and base_uri or base_uri.uri
local base_dir = vim.uri_to_fname(uri)
- local pattern = parse(glob_pattern.pattern)
+ local pattern = glob.to_lpeg(glob_pattern.pattern)
if not pattern then
error('Cannot parse pattern: ' .. glob_pattern.pattern)
end
@@ -248,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 cf9acc0808..50121f30b2 100644
--- a/runtime/lua/vim/lsp/buf.lua
+++ b/runtime/lua/vim/lsp/buf.lua
@@ -11,8 +11,8 @@ 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.
---@return function _cancel_all_requests Function which can be used to
@@ -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.0')
- 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()
@@ -46,10 +36,10 @@ function M.hover()
end
local function request_with_options(name, params, options)
- local req_handler
+ local req_handler --- @type function?
if options then
req_handler = function(err, result, ctx, config)
- local client = vim.lsp.get_client_by_id(ctx.client_id)
+ local client = assert(vim.lsp.get_client_by_id(ctx.client_id))
local handler = client.handlers[name] or vim.lsp.handlers[name]
handler(err, result, ctx, vim.tbl_extend('force', config or {}, options))
end
@@ -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
---
----@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.
+--- 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
+---
+--- 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,44 +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: <pre>lua
---- -- Never request typescript-language-server for formatting
---- vim.lsp.buf.format {
---- filter = function(client) return client.name ~= "tsserver" end
---- }
---- </pre>
----
---- - 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()
@@ -218,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 =
@@ -228,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
@@ -255,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()
@@ -299,12 +329,12 @@ function M.rename(new_name, options)
)[1]
end
- local try_use_client
- try_use_client = function(idx, client)
+ local function try_use_client(idx, client)
if not client then
return
end
+ --- @param name string
local function rename(name)
local params = util.make_position_params(win, client.offset_encoding)
params.newName = name
@@ -385,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()
@@ -397,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
@@ -424,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)
@@ -472,6 +502,7 @@ end
--- Add the folder at path to the workspace folders. If {path} is
--- not provided, the user will be prompted for a path using |input()|.
+--- @param workspace_folder? string
function M.add_workspace_folder(workspace_folder)
workspace_folder = workspace_folder
or npcall(vim.fn.input, 'Workspace Folder: ', vim.fn.expand('%:p:h'), 'dir')
@@ -511,6 +542,7 @@ end
--- Remove the folder at path from the workspace folders. If
--- {path} is not provided, the user will be prompted for
--- a path using |input()|.
+--- @param workspace_folder? string
function M.remove_workspace_folder(workspace_folder)
workspace_folder = workspace_folder
or npcall(vim.fn.input, 'Workspace Folder: ', vim.fn.expand('%:p:h'))
@@ -542,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
@@ -579,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.
@@ -599,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)
@@ -644,15 +695,16 @@ 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
- client._exec_cmd(command, ctx)
+ 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
@@ -673,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')
@@ -723,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)
@@ -753,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
@@ -811,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
new file mode 100644
index 0000000000..ff0db166d5
--- /dev/null
+++ b/runtime/lua/vim/lsp/client.lua
@@ -0,0 +1,1056 @@
+local uv = vim.uv
+local api = vim.api
+local lsp = vim.lsp
+local log = lsp.log
+local ms = lsp.protocol.Methods
+local changetracking = lsp._changetracking
+local validate = vim.validate
+
+--- @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: vim.lsp.ClientConfig)
+
+--- @class vim.lsp.Client.Flags
+--- @inlinedoc
+---
+--- Allow using incremental sync for buffer edits
+--- (defailt: `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
+---
+--- 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)
+---
+--- 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'
+---
+--- 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 vim.lsp.Client.Progress: vim.Ringbuf<{token: integer|string, value: any}>
+--- @field pending table<lsp.ProgressToken,lsp.LSPAny>
+
+--- @class vim.lsp.Client
+---
+--- The id allocated to the client.
+--- @field id integer
+---
+--- 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.
+--- @field name string
+---
+--- RPC client object, for low level interaction with the client.
+--- See |vim.lsp.rpc.start()|.
+--- @field rpc vim.lsp.rpc.PublicClient
+---
+--- 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.
+--- @field offset_encoding string
+---
+--- The handlers used by the client as described in |lsp-handler|.
+--- @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
+--- `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.
+--- @field requests table<integer,{ type: string, bufnr: integer, method: string}>
+---
+--- copy of the table that was passed by the user
+--- to |vim.lsp.start_client()|.
+--- @field config vim.lsp.ClientConfig
+---
+--- 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 vim.lsp.Client.Progress
+---
+--- @field initialized true?
+---
+--- The workspace folders configured in the client when the server starts.
+--- This property is only available if the client supports workspace folders.
+--- It can be `null` if the client supports workspace folders but none are
+--- configured.
+--- @field workspace_folders lsp.WorkspaceFolder[]?
+--- @field root_dir string
+---
+--- @field attached_buffers table<integer,true>
+--- @field private _log_prefix string
+---
+--- Track this so that we can escalate automatically if we've already tried a
+--- graceful shutdown
+--- @field private _graceful_shutdown_failed true?
+---
+--- The initial trace setting. If omitted trace is disabled ("off").
+--- trace = "off" | "messages" | "verbose";
+--- @field private _trace 'off'|'messages'|'verbose'
+---
+--- 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.
+--- @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
+---
+--- 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)
+--- @field capabilities lsp.ClientCapabilities
+--- @field dynamic_capabilities lsp.DynamicCapabilities
+---
+--- Sends a request to the server.
+--- This is a thin wrapper around {client.rpc.request} with some additional
+--- checking.
+--- 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.
+--- @field request fun(method: string, params: table?, handler: lsp.Handler?, bufnr: integer): boolean, integer?
+---
+--- 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`.
+--- @field request_sync fun(method: string, params: table?, timeout_ms: integer?, bufnr: integer): {err: lsp.ResponseError|nil, result:any}|nil, string|nil err # a dictionary, where
+---
+--- 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).
+--- @field notify fun(method: string, params: table?): boolean
+---
+--- Cancels a request with a given request id.
+--- Returns: same as `notify()`.
+--- @field cancel_request fun(id: integer): boolean
+---
+--- 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.
+--- @field stop fun(force?: boolean)
+---
+--- Runs the on_attach function from the client's config if it was defined.
+--- Useful for buffer-local setup.
+--- @field on_attach fun(bufnr: integer)
+---
+--- @field private _before_init_cb? vim.lsp.client.before_init_cb
+--- @field private _on_attach_cbs vim.lsp.client.on_attach_cb[]
+--- @field private _on_init_cbs vim.lsp.client.on_init_cb[]
+--- @field private _on_exit_cbs vim.lsp.client.on_exit_cb[]
+--- @field private _on_error_cb? fun(code: integer, err: string)
+---
+--- 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.
+--- @field supports_method fun(method: string, opts?: {bufnr: integer?}): boolean
+---
+--- Checks whether a client is stopped.
+--- Returns: true if the client is fully stopped.
+--- @field is_stopped fun(): boolean
+local Client = {}
+Client.__index = Client
+
+--- @param cls table
+--- @param meth any
+--- @return function
+local function method_wrapper(cls, meth)
+ return function(...)
+ return meth(cls, ...)
+ end
+end
+
+local client_index = 0
+
+--- Checks whether a given path is a directory.
+--- @param filename (string) path to check
+--- @return boolean # true if {filename} exists and is a directory, false otherwise
+local function is_dir(filename)
+ validate({ filename = { filename, 's' } })
+ local stat = uv.fs_stat(filename)
+ return stat and stat.type == 'directory' or false
+end
+
+local valid_encodings = {
+ ['utf-8'] = 'utf-8',
+ ['utf-16'] = 'utf-16',
+ ['utf-32'] = 'utf-32',
+ ['utf8'] = 'utf-8',
+ ['utf16'] = 'utf-16',
+ ['utf32'] = 'utf-32',
+ UTF8 = 'utf-8',
+ UTF16 = 'utf-16',
+ UTF32 = 'utf-32',
+}
+
+--- Normalizes {encoding} to valid LSP encoding names.
+--- @param encoding string? Encoding to normalize
+--- @return string # normalized encoding name
+local function validate_encoding(encoding)
+ validate({
+ encoding = { encoding, 's', true },
+ })
+ if not encoding then
+ return valid_encodings.UTF16
+ end
+ return valid_encodings[encoding:lower()]
+ or error(
+ string.format(
+ "Invalid offset encoding %q. Must be one of: 'utf-8', 'utf-16', 'utf-32'",
+ encoding
+ )
+ )
+end
+
+--- Augments a validator function with support for optional (nil) values.
+--- @param fn (fun(v): boolean) The original validator function; should return a
+--- bool.
+--- @return fun(v): boolean # The augmented function. Also returns true if {v} is
+--- `nil`.
+local function optional_validator(fn)
+ return function(v)
+ return v == nil or fn(v)
+ end
+end
+
+--- By default, get_language_id just returns the exact filetype it is passed.
+--- It is possible to pass in something that will calculate a different filetype,
+--- to be sent by the client.
+--- @param _bufnr integer
+--- @param filetype string
+local function default_get_language_id(_bufnr, filetype)
+ return filetype
+end
+
+--- Validates a client configuration as given to |vim.lsp.start_client()|.
+--- @param config vim.lsp.ClientConfig
+local function validate_config(config)
+ validate({
+ config = { config, 't' },
+ })
+ validate({
+ handlers = { config.handlers, 't', true },
+ capabilities = { config.capabilities, 't', true },
+ cmd_cwd = { config.cmd_cwd, optional_validator(is_dir), 'directory' },
+ cmd_env = { config.cmd_env, 't', true },
+ detached = { config.detached, 'b', true },
+ name = { config.name, 's', true },
+ on_error = { config.on_error, 'f', true },
+ on_exit = { config.on_exit, { 'f', 't' }, true },
+ on_init = { config.on_init, { 'f', 't' }, true },
+ on_attach = { config.on_attach, { 'f', 't' }, true },
+ settings = { config.settings, 't', true },
+ commands = { config.commands, 't', true },
+ before_init = { config.before_init, { 'f', 't' }, true },
+ offset_encoding = { config.offset_encoding, 's', true },
+ flags = { config.flags, 't', true },
+ get_language_id = { config.get_language_id, 'f', true },
+ })
+
+ assert(
+ (
+ not config.flags
+ or not config.flags.debounce_text_changes
+ or type(config.flags.debounce_text_changes) == 'number'
+ ),
+ 'flags.debounce_text_changes must be a number with the debounce time in milliseconds'
+ )
+end
+
+--- @param trace string
+--- @return 'off'|'messages'|'verbose'
+local function get_trace(trace)
+ local valid_traces = {
+ off = 'off',
+ messages = 'messages',
+ verbose = 'verbose',
+ }
+ return trace and valid_traces[trace] or 'off'
+end
+
+--- @param id integer
+--- @param config vim.lsp.ClientConfig
+--- @return string
+local function get_name(id, config)
+ local name = config.name
+ if name then
+ return name
+ end
+
+ if type(config.cmd) == 'table' and config.cmd[1] then
+ return assert(vim.fs.basename(config.cmd[1]))
+ end
+
+ return tostring(id)
+end
+
+--- @param workspace_folders lsp.WorkspaceFolder[]?
+--- @param root_dir string?
+--- @return lsp.WorkspaceFolder[]?
+local function get_workspace_folders(workspace_folders, root_dir)
+ if workspace_folders then
+ return workspace_folders
+ end
+ if root_dir then
+ return {
+ {
+ uri = vim.uri_from_fname(root_dir),
+ name = string.format('%s', root_dir),
+ },
+ }
+ end
+end
+
+--- @generic T
+--- @param x elem_or_list<T>?
+--- @return T[]
+local function ensure_list(x)
+ if type(x) == 'table' then
+ return x
+ end
+ return { x }
+end
+
+--- @package
+--- @param config vim.lsp.ClientConfig
+--- @return vim.lsp.Client?
+function Client.create(config)
+ validate_config(config)
+
+ client_index = client_index + 1
+ local id = client_index
+ local name = get_name(id, config)
+
+ --- @class vim.lsp.Client
+ local self = {
+ id = id,
+ config = config,
+ handlers = config.handlers or {},
+ offset_encoding = validate_encoding(config.offset_encoding),
+ name = name,
+ _log_prefix = string.format('LSP[%s]', name),
+ requests = {},
+ attached_buffers = {},
+ server_capabilities = {},
+ dynamic_capabilities = lsp._dynamic.new(id),
+ commands = config.commands or {},
+ settings = config.settings or {},
+ flags = config.flags or {},
+ get_language_id = config.get_language_id or default_get_language_id,
+ capabilities = config.capabilities or lsp.protocol.make_client_capabilities(),
+ workspace_folders = get_workspace_folders(config.workspace_folders, config.root_dir),
+ root_dir = config.root_dir,
+ _before_init_cb = config.before_init,
+ _on_init_cbs = ensure_list(config.on_init),
+ _on_exit_cbs = ensure_list(config.on_exit),
+ _on_attach_cbs = ensure_list(config.on_attach),
+ _on_error_cb = config.on_error,
+ _root_dir = config.root_dir,
+ _trace = get_trace(config.trace),
+
+ --- Contains $/progress report messages.
+ --- They have the format {token: integer|string, value: any}
+ --- For "work done progress", value will be one of:
+ --- - lsp.WorkDoneProgressBegin,
+ --- - lsp.WorkDoneProgressReport (extended with title from Begin)
+ --- - lsp.WorkDoneProgressEnd (extended with title from Begin)
+ progress = vim.ringbuf(50) --[[@as vim.lsp.Client.Progress]],
+
+ --- @deprecated use client.progress instead
+ messages = { name = name, messages = {}, progress = {}, status = {} },
+ }
+
+ self.request = method_wrapper(self, Client._request)
+ self.request_sync = method_wrapper(self, Client._request_sync)
+ self.notify = method_wrapper(self, Client._notify)
+ self.cancel_request = method_wrapper(self, Client._cancel_request)
+ self.stop = method_wrapper(self, Client._stop)
+ self.is_stopped = method_wrapper(self, Client._is_stopped)
+ self.on_attach = method_wrapper(self, Client._on_attach)
+ self.supports_method = method_wrapper(self, Client._supports_method)
+
+ --- @type table<string|integer, string> title of unfinished progress sequences by token
+ self.progress.pending = {}
+
+ --- @type vim.lsp.rpc.Dispatchers
+ local dispatchers = {
+ notification = method_wrapper(self, Client._notification),
+ server_request = method_wrapper(self, Client._server_request),
+ on_error = method_wrapper(self, Client._on_error),
+ on_exit = method_wrapper(self, Client._on_exit),
+ }
+
+ -- Start the RPC client.
+ local rpc --- @type vim.lsp.rpc.PublicClient?
+ local config_cmd = config.cmd
+ if type(config_cmd) == 'function' then
+ rpc = config_cmd(dispatchers)
+ else
+ rpc = lsp.rpc.start(config_cmd, dispatchers, {
+ cwd = config.cmd_cwd,
+ env = config.cmd_env,
+ detached = config.detached,
+ })
+ end
+
+ -- Return nil if the rpc client fails to start
+ if not rpc then
+ return
+ end
+
+ self.rpc = rpc
+
+ setmetatable(self, Client)
+
+ return self
+end
+
+--- @private
+--- @param cbs function[]
+--- @param error_id integer
+--- @param ... any
+function Client:_run_callbacks(cbs, error_id, ...)
+ for _, cb in pairs(cbs) do
+ --- @type boolean, string?
+ local status, err = pcall(cb, ...)
+ if not status then
+ self:write_error(error_id, err)
+ end
+ end
+end
+
+--- @package
+function Client:initialize()
+ local config = self.config
+
+ local root_uri --- @type string?
+ local root_path --- @type string?
+ if self.workspace_folders then
+ root_uri = self.workspace_folders[1].uri
+ root_path = vim.uri_to_fname(root_uri)
+ end
+
+ local initialize_params = {
+ -- The process Id of the parent process that started the server. Is null if
+ -- the process has not been started by another process. If the parent
+ -- process is not alive then the server should exit (see exit notification)
+ -- its process.
+ processId = uv.os_getpid(),
+ -- Information about the client
+ -- since 3.15.0
+ clientInfo = {
+ name = 'Neovim',
+ version = tostring(vim.version()),
+ },
+ -- The rootPath of the workspace. Is null if no folder is open.
+ --
+ -- @deprecated in favour of rootUri.
+ rootPath = root_path or vim.NIL,
+ -- The rootUri of the workspace. Is null if no folder is open. If both
+ -- `rootPath` and `rootUri` are set `rootUri` wins.
+ rootUri = root_uri or vim.NIL,
+ workspaceFolders = self.workspace_folders or vim.NIL,
+ -- User provided initialization options.
+ initializationOptions = config.init_options,
+ capabilities = self.capabilities,
+ trace = self._trace,
+ }
+
+ self:_run_callbacks(
+ { self._before_init_cb },
+ lsp.client_errors.BEFORE_INIT_CALLBACK_ERROR,
+ initialize_params,
+ config
+ )
+
+ log.trace(self._log_prefix, 'initialize_params', initialize_params)
+
+ local rpc = self.rpc
+
+ rpc.request('initialize', initialize_params, function(init_err, result)
+ assert(not init_err, tostring(init_err))
+ assert(result, 'server sent empty result')
+ rpc.notify('initialized', vim.empty_dict())
+ self.initialized = true
+
+ -- These are the cleaned up capabilities we use for dynamically deciding
+ -- when to send certain events to clients.
+ self.server_capabilities =
+ assert(result.capabilities, "initialize result doesn't contain capabilities")
+ self.server_capabilities = assert(lsp.protocol.resolve_capabilities(self.server_capabilities))
+
+ if self.server_capabilities.positionEncoding then
+ self.offset_encoding = self.server_capabilities.positionEncoding
+ end
+
+ if next(self.settings) then
+ self:_notify(ms.workspace_didChangeConfiguration, { settings = self.settings })
+ end
+
+ self:_run_callbacks(self._on_init_cbs, lsp.client_errors.ON_INIT_CALLBACK_ERROR, self, result)
+
+ log.info(
+ self._log_prefix,
+ 'server_capabilities',
+ { server_capabilities = self.server_capabilities }
+ )
+ end)
+end
+
+--- @private
+--- Returns the handler associated with an LSP method.
+--- Returns the default handler if the user hasn't set a custom one.
+---
+--- @param method (string) LSP method name
+--- @return lsp.Handler|nil handler for the given method, if defined, or the default from |vim.lsp.handlers|
+function Client:_resolve_handler(method)
+ return self.handlers[method] or lsp.handlers[method]
+end
+
+--- Returns the buffer number for the given {bufnr}.
+---
+--- @param bufnr (integer|nil) Buffer number to resolve. Defaults to current buffer
+--- @return integer bufnr
+local function resolve_bufnr(bufnr)
+ validate({ bufnr = { bufnr, 'n', true } })
+ if bufnr == nil or bufnr == 0 then
+ return api.nvim_get_current_buf()
+ end
+ return bufnr
+end
+
+--- @private
+--- Sends a request to the server.
+---
+--- This is a thin wrapper around {client.rpc.request} with some additional
+--- checks for capabilities and handler availability.
+---
+--- @param method string LSP method name.
+--- @param params table|nil LSP request params.
+--- @param handler lsp.Handler|nil Response |lsp-handler| for this method.
+--- @param bufnr integer Buffer handle (0 for current).
+--- @return boolean status, integer|nil request_id {status} is a bool indicating
+--- whether the request was successful. If it is `false`, then it will
+--- always be `false` (the client has shutdown). If it was
+--- successful, then it will return {request_id} as the
+--- second result. You can use this with `client.cancel_request(request_id)`
+--- to cancel the-request.
+--- @see |vim.lsp.buf_request_all()|
+function Client:_request(method, params, handler, bufnr)
+ if not handler then
+ handler = assert(
+ self:_resolve_handler(method),
+ string.format('not found: %q request handler for client %q.', method, self.name)
+ )
+ end
+ -- Ensure pending didChange notifications are sent so that the server doesn't operate on a stale state
+ changetracking.flush(self, bufnr)
+ local version = lsp.util.buf_versions[bufnr]
+ bufnr = resolve_bufnr(bufnr)
+ log.debug(self._log_prefix, 'client.request', self.id, method, params, handler, bufnr)
+ local success, request_id = self.rpc.request(method, params, function(err, result)
+ local context = {
+ method = method,
+ client_id = self.id,
+ bufnr = bufnr,
+ params = params,
+ version = version,
+ }
+ handler(err, result, context)
+ end, function(request_id)
+ local request = self.requests[request_id]
+ request.type = 'complete'
+ api.nvim_exec_autocmds('LspRequest', {
+ buffer = api.nvim_buf_is_valid(bufnr) and bufnr or nil,
+ modeline = false,
+ data = { client_id = self.id, request_id = request_id, request = request },
+ })
+ self.requests[request_id] = nil
+ end)
+
+ if success and request_id then
+ local request = { type = 'pending', bufnr = bufnr, method = method }
+ self.requests[request_id] = request
+ api.nvim_exec_autocmds('LspRequest', {
+ buffer = bufnr,
+ modeline = false,
+ data = { client_id = self.id, request_id = request_id, request = request },
+ })
+ end
+
+ return success, request_id
+end
+
+-- TODO(lewis6991): duplicated from lsp.lua
+local wait_result_reason = { [-1] = 'timeout', [-2] = 'interrupted', [-3] = 'error' }
+
+--- Concatenates and writes a list of strings to the Vim error buffer.
+---
+--- @param ... string List to write to the buffer
+local function err_message(...)
+ 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
+--- Sends a request to the server and synchronously waits for the response.
+---
+--- This is a wrapper around {client.request}
+---
+--- @param method (string) LSP method name.
+--- @param params (table) LSP request params.
+--- @param timeout_ms (integer|nil) Maximum time in milliseconds to wait for
+--- a result. Defaults to 1000
+--- @param bufnr (integer) Buffer handle (0 for current).
+--- @return {err: lsp.ResponseError|nil, result:any}|nil, string|nil err # 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`.
+--- @see |vim.lsp.buf_request_sync()|
+function Client:_request_sync(method, params, timeout_ms, bufnr)
+ local request_result = nil
+ local function _sync_handler(err, result)
+ request_result = { err = err, result = result }
+ end
+
+ local success, request_id = self:_request(method, params, _sync_handler, bufnr)
+ if not success then
+ return nil
+ end
+
+ local wait_result, reason = vim.wait(timeout_ms or 1000, function()
+ return request_result ~= nil
+ end, 10)
+
+ if not wait_result then
+ if request_id then
+ self:_cancel_request(request_id)
+ end
+ return nil, wait_result_reason[reason]
+ end
+ return request_result
+end
+
+--- @private
+--- Sends a notification to an LSP server.
+---
+--- @param method string LSP method name.
+--- @param params table|nil LSP request params.
+--- @return boolean status true if the notification was successful.
+--- If it is false, then it will always be false
+--- (the client has shutdown).
+function Client:_notify(method, params)
+ if method ~= ms.textDocument_didChange then
+ changetracking.flush(self)
+ end
+
+ local client_active = self.rpc.notify(method, params)
+
+ if client_active then
+ vim.schedule(function()
+ api.nvim_exec_autocmds('LspNotify', {
+ modeline = false,
+ data = {
+ client_id = self.id,
+ method = method,
+ params = params,
+ },
+ })
+ end)
+ end
+
+ return client_active
+end
+
+--- @private
+--- Cancels a request with a given request id.
+---
+--- @param id (integer) id of request to cancel
+--- @return boolean status true if notification was successful. false otherwise
+--- @see |vim.lsp.client.notify()|
+function Client:_cancel_request(id)
+ validate({ id = { id, 'n' } })
+ local request = self.requests[id]
+ if request and request.type == 'pending' then
+ request.type = 'cancel'
+ api.nvim_exec_autocmds('LspRequest', {
+ buffer = request.bufnr,
+ modeline = false,
+ data = { client_id = self.id, request_id = id, request = request },
+ })
+ end
+ return self.rpc.notify(ms.dollar_cancelRequest, { id = id })
+end
+
+--- @private
+--- 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.
+---
+--- @param force boolean|nil
+function Client:_stop(force)
+ local rpc = self.rpc
+
+ if rpc.is_closing() then
+ return
+ end
+
+ if force or not self.initialized or self._graceful_shutdown_failed then
+ rpc.terminate()
+ return
+ end
+
+ -- Sending a signal after a process has exited is acceptable.
+ rpc.request(ms.shutdown, nil, function(err, _)
+ if err == nil then
+ rpc.notify(ms.exit)
+ else
+ -- If there was an error in the shutdown request, then term to be safe.
+ rpc.terminate()
+ self._graceful_shutdown_failed = true
+ end
+ vim.lsp._watchfiles.cancel(self.id)
+ end)
+end
+
+--- @private
+--- Checks whether a client is stopped.
+---
+--- @return boolean # true if client is stopped or in the process of being
+--- stopped; false otherwise
+function Client:_is_stopped()
+ return self.rpc.is_closing()
+end
+
+--- @package
+--- Execute a lsp command, either via client command function (if available)
+--- or via workspace/executeCommand (if supported by the server)
+---
+--- @param command lsp.Command
+--- @param context? {bufnr: integer}
+--- @param handler? lsp.Handler only called if a server command
+function Client:_exec_cmd(command, context, handler)
+ context = vim.deepcopy(context or {}, true) --[[@as lsp.HandlerContext]]
+ context.bufnr = context.bufnr or api.nvim_get_current_buf()
+ context.client_id = self.id
+ local cmdname = command.command
+ local fn = self.commands[cmdname] or lsp.commands[cmdname]
+ if fn then
+ fn(command, context)
+ return
+ end
+
+ local command_provider = self.server_capabilities.executeCommandProvider
+ local commands = type(command_provider) == 'table' and command_provider.commands or {}
+ if not vim.list_contains(commands, cmdname) then
+ vim.notify_once(
+ string.format(
+ 'Language server `%s` does not support command `%s`. This command may require a client extension.',
+ self.name,
+ cmdname
+ ),
+ vim.log.levels.WARN
+ )
+ return
+ end
+ -- Not using command directly to exclude extra properties,
+ -- see https://github.com/python-lsp/python-lsp-server/issues/146
+ local params = {
+ command = command.command,
+ arguments = command.arguments,
+ }
+ self.request(ms.workspace_executeCommand, params, handler, context.bufnr)
+end
+
+--- @package
+--- Default handler for the 'textDocument/didOpen' LSP notification.
+---
+--- @param bufnr integer Number of the buffer, or 0 for current
+function Client:_text_document_did_open_handler(bufnr)
+ changetracking.init(self, bufnr)
+ if not vim.tbl_get(self.server_capabilities, 'textDocumentSync', 'openClose') then
+ return
+ end
+ if not api.nvim_buf_is_loaded(bufnr) then
+ return
+ end
+ local filetype = vim.bo[bufnr].filetype
+
+ local params = {
+ textDocument = {
+ version = 0,
+ uri = vim.uri_from_bufnr(bufnr),
+ languageId = self.get_language_id(bufnr, filetype),
+ text = lsp._buf_get_full_text(bufnr),
+ },
+ }
+ self.notify(ms.textDocument_didOpen, params)
+ lsp.util.buf_versions[bufnr] = params.textDocument.version
+
+ -- Next chance we get, we should re-do the diagnostics
+ vim.schedule(function()
+ -- Protect against a race where the buffer disappears
+ -- between `did_open_handler` and the scheduled function firing.
+ if api.nvim_buf_is_valid(bufnr) then
+ local namespace = lsp.diagnostic.get_namespace(self.id)
+ vim.diagnostic.show(namespace, bufnr)
+ end
+ end)
+end
+
+--- @package
+--- Runs the on_attach function from the client's config if it was defined.
+--- @param bufnr integer Buffer number
+function Client:_on_attach(bufnr)
+ self:_text_document_did_open_handler(bufnr)
+
+ lsp._set_defaults(self, bufnr)
+
+ api.nvim_exec_autocmds('LspAttach', {
+ buffer = bufnr,
+ modeline = false,
+ data = { client_id = self.id },
+ })
+
+ self:_run_callbacks(self._on_attach_cbs, lsp.client_errors.ON_ATTACH_ERROR, self, bufnr)
+
+ -- schedule the initialization of semantic tokens to give the above
+ -- on_attach and LspAttach callbacks the ability to schedule wrap the
+ -- opt-out (deleting the semanticTokensProvider from capabilities)
+ vim.schedule(function()
+ if vim.tbl_get(self.server_capabilities, 'semanticTokensProvider', 'full') then
+ lsp.semantic_tokens.start(bufnr, self.id)
+ end
+ end)
+
+ self.attached_buffers[bufnr] = true
+end
+
+--- @private
+--- Logs the given error to the LSP log and to the error buffer.
+--- @param code integer Error code
+--- @param err any Error arguments
+function Client:write_error(code, err)
+ local client_error = lsp.client_errors[code] --- @type string|integer
+ log.error(self._log_prefix, 'on_error', { code = client_error, err = 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)
+ local required_capability = lsp._request_name_to_capability[method]
+ -- if we don't know about the method, assume that the client supports it.
+ if not required_capability then
+ return true
+ end
+ if vim.tbl_get(self.server_capabilities, unpack(required_capability)) then
+ return true
+ end
+ if self.dynamic_capabilities:supports_registration(method) then
+ return self.dynamic_capabilities:supports(method, opts)
+ end
+ return false
+end
+
+--- @private
+--- Handles a notification sent by an LSP server by invoking the
+--- corresponding handler.
+---
+--- @param method string LSP method name
+--- @param params table The parameters for that method.
+function Client:_notification(method, params)
+ log.trace('notification', method, params)
+ local handler = self:_resolve_handler(method)
+ if handler then
+ -- Method name is provided here for convenience.
+ handler(nil, params, { method = method, client_id = self.id })
+ end
+end
+
+--- @private
+--- Handles a request from an LSP server by invoking the corresponding handler.
+---
+--- @param method (string) LSP method name
+--- @param params (table) The parameters for that method
+--- @return any result
+--- @return lsp.ResponseError error code and message set in case an exception happens during the request.
+function Client:_server_request(method, params)
+ log.trace('server_request', method, params)
+ local handler = self:_resolve_handler(method)
+ if handler then
+ log.trace('server_request: found handler for', method)
+ return handler(nil, params, { method = method, client_id = self.id })
+ end
+ log.warn('server_request: no handler found for', method)
+ return nil, lsp.rpc_response_error(lsp.protocol.ErrorCodes.MethodNotFound)
+end
+
+--- @private
+--- Invoked when the client operation throws an error.
+---
+--- @param code integer Error code
+--- @param err any 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 a human-friendly name.
+function Client:_on_error(code, err)
+ self:write_error(code, err)
+ if self._on_error_cb then
+ --- @type boolean, string
+ local status, usererr = pcall(self._on_error_cb, code, err)
+ if not status then
+ log.error(self._log_prefix, 'user on_error failed', { err = usererr })
+ err_message(self._log_prefix, ' user on_error failed: ', tostring(usererr))
+ end
+ end
+end
+
+--- @private
+--- Invoked on client exit.
+---
+--- @param code integer) exit code of the process
+--- @param signal integer the signal used to terminate (if any)
+function Client:_on_exit(code, signal)
+ self:_run_callbacks(
+ self._on_exit_cbs,
+ lsp.client_errors.ON_EXIT_CALLBACK_ERROR,
+ code,
+ signal,
+ self.id
+ )
+end
+
+return Client
diff --git a/runtime/lua/vim/lsp/codelens.lua b/runtime/lua/vim/lsp/codelens.lua
index 9cccaa1d66..48c096c0c1 100644
--- a/runtime/lua/vim/lsp/codelens.lua
+++ b/runtime/lua/vim/lsp/codelens.lua
@@ -6,7 +6,7 @@ local M = {}
--- bufnr → true|nil
--- to throttle refreshes to at most one at a time
-local active_refreshes = {}
+local active_refreshes = {} --- @type table<integer,true>
---@type table<integer, table<integer, lsp.CodeLens[]>>
--- bufnr -> client_id -> lenses
@@ -48,7 +48,7 @@ local function execute_lens(lens, bufnr, client_id)
local client = vim.lsp.get_client_by_id(client_id)
assert(client, 'Client is required to execute lens, client_id=' .. client_id)
- client._exec_cmd(lens.command, { bufnr = bufnr }, function(...)
+ client:_exec_cmd(lens.command, { bufnr = bufnr }, function(...)
vim.lsp.handlers[ms.workspace_executeCommand](...)
M.refresh()
end)
@@ -75,7 +75,7 @@ end
function M.run()
local line = api.nvim_win_get_cursor(0)[1]
local bufnr = api.nvim_get_current_buf()
- local options = {}
+ local options = {} --- @type {client: integer, lens: lsp.CodeLens}[]
local lenses_by_client = lens_cache_by_buf[bufnr] or {}
for client, lenses in pairs(lenses_by_client) do
for _, lens in pairs(lenses) do
@@ -111,7 +111,7 @@ end
--- Clear the lenses
---
---@param client_id integer|nil filter by client_id. All clients if nil
----@param bufnr integer|nil filter by buffer. All buffers if nil
+---@param bufnr integer|nil filter by buffer. All buffers if nil, 0 for current buffer
function M.clear(client_id, bufnr)
bufnr = bufnr and resolve_bufnr(bufnr)
local buffers = bufnr and { bufnr }
@@ -230,6 +230,7 @@ local function resolve_lenses(lenses, bufnr, client_id, callback)
if lens.command then
countdown()
else
+ assert(client)
client.request('codeLens/resolve', lens, function(_, result)
if api.nvim_buf_is_loaded(bufnr) and result and result.command then
lens.command = result.command
@@ -257,10 +258,13 @@ end
--- |lsp-handler| for the method `textDocument/codeLens`
---
+---@param err lsp.ResponseError?
+---@param result lsp.CodeLens[]
+---@param ctx lsp.HandlerContext
function M.on_codelens(err, result, ctx, _)
if err then
- active_refreshes[ctx.bufnr] = nil
- local _ = log.error() and log.error('codelens', err)
+ active_refreshes[assert(ctx.bufnr)] = nil
+ log.error('codelens', err)
return
end
@@ -270,30 +274,41 @@ function M.on_codelens(err, result, ctx, _)
-- once resolved.
M.display(result, ctx.bufnr, ctx.client_id)
resolve_lenses(result, ctx.bufnr, ctx.client_id, function()
- active_refreshes[ctx.bufnr] = nil
+ active_refreshes[assert(ctx.bufnr)] = nil
M.display(result, ctx.bufnr, ctx.client_id)
end)
end
---- Refresh the codelens for the current buffer
+--- @class vim.lsp.codelens.refresh.Opts
+--- @inlinedoc
+--- @field bufnr integer? filter by buffer. All buffers if nil, 0 for current buffer
+
+--- Refresh the lenses.
---
--- It is recommended to trigger this using an autocmd or via keymap.
---
--- Example:
---
--- ```vim
---- autocmd BufEnter,CursorHold,InsertLeave <buffer> lua vim.lsp.codelens.refresh()
+--- autocmd BufEnter,CursorHold,InsertLeave <buffer> lua vim.lsp.codelens.refresh({ bufnr = 0 })
--- ```
-function M.refresh()
+---
+--- @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)
+ local buffers = bufnr and { bufnr }
+ or vim.tbl_filter(api.nvim_buf_is_loaded, api.nvim_list_bufs())
local params = {
textDocument = util.make_text_document_params(),
}
- local bufnr = api.nvim_get_current_buf()
- if active_refreshes[bufnr] then
- return
+
+ for _, buf in ipairs(buffers) do
+ if not active_refreshes[buf] then
+ active_refreshes[buf] = true
+ vim.lsp.buf_request(buf, ms.textDocument_codeLens, params, M.on_codelens)
+ end
end
- active_refreshes[bufnr] = true
- vim.lsp.buf_request(0, ms.textDocument_codeLens, params, M.on_codelens)
end
return M
diff --git a/runtime/lua/vim/lsp/diagnostic.lua b/runtime/lua/vim/lsp/diagnostic.lua
index b6f0cfa0b3..08cea13548 100644
--- a/runtime/lua/vim/lsp/diagnostic.lua
+++ b/runtime/lua/vim/lsp/diagnostic.lua
@@ -1,8 +1,4 @@
----@brief lsp-diagnostic
-
-local util = require('vim.lsp.util')
local protocol = require('vim.lsp.protocol')
-local log = require('vim.lsp.log')
local ms = protocol.Methods
local api = vim.api
@@ -24,7 +20,7 @@ end
---@param severity lsp.DiagnosticSeverity
local function severity_lsp_to_vim(severity)
if type(severity) == 'string' then
- severity = protocol.DiagnosticSeverity[severity]
+ severity = protocol.DiagnosticSeverity[severity] --- @type integer
end
return severity
end
@@ -37,6 +33,10 @@ local function severity_vim_to_lsp(severity)
return severity
end
+---@param lines string[]?
+---@param lnum integer
+---@param col integer
+---@param offset_encoding string
---@return integer
local function line_byte_from_position(lines, lnum, col, offset_encoding)
if not lines or offset_encoding == 'utf-8' then
@@ -46,12 +46,14 @@ local function line_byte_from_position(lines, lnum, col, offset_encoding)
local line = lines[lnum + 1]
local ok, result = pcall(vim.str_byteindex, line, col, offset_encoding == 'utf-16')
if ok then
- return result
+ return result --- @type integer
end
return col
end
+---@param bufnr integer
+---@return string[]?
local function get_buf_lines(bufnr)
if vim.api.nvim_buf_is_loaded(bufnr) then
return vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
@@ -89,7 +91,7 @@ local function tags_lsp_to_vim(diagnostic, client_id)
tags = tags or {}
tags.deprecated = true
else
- log.info(string.format('Unknown DiagnosticTag %d from LSP client %d', tag, client_id))
+ vim.lsp.log.info(string.format('Unknown DiagnosticTag %d from LSP client %d', tag, client_id))
end
end
return tags
@@ -98,16 +100,17 @@ end
---@param diagnostics lsp.Diagnostic[]
---@param bufnr integer
---@param client_id integer
----@return Diagnostic[]
+---@return vim.Diagnostic[]
local function diagnostic_lsp_to_vim(diagnostics, bufnr, client_id)
local buf_lines = get_buf_lines(bufnr)
local client = vim.lsp.get_client_by_id(client_id)
local offset_encoding = client and client.offset_encoding or 'utf-16'
- ---@diagnostic disable-next-line:no-unknown
+ --- @param diagnostic lsp.Diagnostic
+ --- @return vim.Diagnostic
return vim.tbl_map(function(diagnostic)
- ---@cast diagnostic lsp.Diagnostic
local start = diagnostic.range.start
local _end = diagnostic.range['end']
+ --- @type vim.Diagnostic
return {
lnum = start.line,
col = line_byte_from_position(buf_lines, start.line, start.character, offset_encoding),
@@ -131,12 +134,29 @@ local function diagnostic_lsp_to_vim(diagnostics, bufnr, client_id)
end, diagnostics)
end
---- @param diagnostics Diagnostic[]
+--- @param diagnostic vim.Diagnostic
+--- @return lsp.DiagnosticTag[]?
+local function tags_vim_to_lsp(diagnostic)
+ if not diagnostic._tags then
+ return
+ end
+
+ local tags = {} --- @type lsp.DiagnosticTag[]
+ if diagnostic._tags.unnecessary then
+ tags[#tags + 1] = protocol.DiagnosticTag.Unnecessary
+ end
+ if diagnostic._tags.deprecated then
+ tags[#tags + 1] = protocol.DiagnosticTag.Deprecated
+ end
+ return tags
+end
+
+--- @param diagnostics vim.Diagnostic[]
--- @return lsp.Diagnostic[]
local function diagnostic_vim_to_lsp(diagnostics)
- ---@diagnostic disable-next-line:no-unknown
+ ---@param diagnostic vim.Diagnostic
+ ---@return lsp.Diagnostic
return vim.tbl_map(function(diagnostic)
- ---@cast diagnostic Diagnostic
return vim.tbl_extend('keep', {
-- "keep" the below fields over any duplicate fields in diagnostic.user_data.lsp
range = {
@@ -153,6 +173,7 @@ local function diagnostic_vim_to_lsp(diagnostics)
message = diagnostic.message,
source = diagnostic.source,
code = diagnostic.code,
+ tags = tags_vim_to_lsp(diagnostic),
}, diagnostic.user_data and (diagnostic.user_data.lsp or {}) or {})
end, diagnostics)
end
@@ -198,6 +219,47 @@ function M.get_namespace(client_id, is_pull)
end
end
+local function convert_severity(opt)
+ if type(opt) == 'table' and not opt.severity and opt.severity_limit then
+ vim.deprecate('severity_limit', '{min = severity} See vim.diagnostic.severity', '0.11')
+ opt.severity = { min = severity_lsp_to_vim(opt.severity_limit) }
+ end
+end
+
+--- @param uri string
+--- @param client_id? integer
+--- @param diagnostics vim.Diagnostic[]
+--- @param is_pull boolean
+--- @param config? vim.diagnostic.Opts
+local function handle_diagnostics(uri, client_id, diagnostics, is_pull, config)
+ local fname = vim.uri_to_fname(uri)
+
+ if #diagnostics == 0 and vim.fn.bufexists(fname) == 0 then
+ return
+ end
+
+ local bufnr = vim.fn.bufadd(fname)
+ if not bufnr then
+ return
+ end
+
+ client_id = get_client_id(client_id)
+ local namespace = M.get_namespace(client_id, is_pull)
+
+ if config then
+ --- @cast config table<string, table>
+ for _, opt in pairs(config) do
+ convert_severity(opt)
+ end
+ -- Persist configuration to ensure buffer reloads use the same
+ -- configuration. To make lsp.with configuration work (See :help
+ -- lsp-handler-configuration)
+ vim.diagnostic.config(config, namespace)
+ end
+
+ vim.diagnostic.set(namespace, bufnr, diagnostic_lsp_to_vim(diagnostics, bufnr, client_id))
+end
+
--- |lsp-handler| for the method "textDocument/publishDiagnostics"
---
--- See |vim.diagnostic.config()| for configuration options. Handler-specific
@@ -223,40 +285,12 @@ end
--- )
--- ```
---
----@param config table Configuration table (see |vim.diagnostic.config()|).
+---@param _ lsp.ResponseError?
+---@param result lsp.PublishDiagnosticsParams
+---@param ctx lsp.HandlerContext
+---@param config? vim.diagnostic.Opts Configuration table (see |vim.diagnostic.config()|).
function M.on_publish_diagnostics(_, result, ctx, config)
- local client_id = ctx.client_id
- local uri = result.uri
- local fname = vim.uri_to_fname(uri)
- local diagnostics = result.diagnostics
- if #diagnostics == 0 and vim.fn.bufexists(fname) == 0 then
- return
- end
- local bufnr = vim.fn.bufadd(fname)
-
- if not bufnr then
- return
- end
-
- client_id = get_client_id(client_id)
- local namespace = M.get_namespace(client_id, false)
-
- if config then
- for _, opt in pairs(config) do
- if type(opt) == 'table' then
- if not opt.severity and opt.severity_limit then
- opt.severity = { min = severity_lsp_to_vim(opt.severity_limit) }
- end
- end
- end
-
- -- Persist configuration to ensure buffer reloads use the same
- -- configuration. To make lsp.with configuration work (See :help
- -- lsp-handler-configuration)
- vim.diagnostic.config(config, namespace)
- end
-
- vim.diagnostic.set(namespace, bufnr, diagnostic_lsp_to_vim(diagnostics, bufnr, client_id))
+ handle_diagnostics(result.uri, ctx.client_id, result.diagnostics, false, config)
end
--- |lsp-handler| for the method "textDocument/diagnostic"
@@ -284,48 +318,16 @@ end
--- )
--- ```
---
----@param config table Configuration table (see |vim.diagnostic.config()|).
+---@param _ lsp.ResponseError?
+---@param result lsp.DocumentDiagnosticReport
+---@param ctx lsp.HandlerContext
+---@param config vim.diagnostic.Opts Configuration table (see |vim.diagnostic.config()|).
function M.on_diagnostic(_, result, ctx, config)
- local client_id = ctx.client_id
- local uri = ctx.params.textDocument.uri
- local fname = vim.uri_to_fname(uri)
-
- if result == nil then
- return
- end
-
- if result.kind == 'unchanged' then
- return
- end
-
- local diagnostics = result.items
- if #diagnostics == 0 and vim.fn.bufexists(fname) == 0 then
+ if result == nil or result.kind == 'unchanged' then
return
end
- local bufnr = vim.fn.bufadd(fname)
-
- if not bufnr then
- return
- end
-
- client_id = get_client_id(client_id)
-
- local namespace = M.get_namespace(client_id, true)
-
- if config then
- for _, opt in pairs(config) do
- if type(opt) == 'table' and not opt.severity and opt.severity_limit then
- opt.severity = { min = severity_lsp_to_vim(opt.severity_limit) }
- end
- end
-
- -- Persist configuration to ensure buffer reloads use the same
- -- configuration. To make lsp.with configuration work (See :help
- -- lsp-handler-configuration)
- vim.diagnostic.config(config, namespace)
- end
- vim.diagnostic.set(namespace, bufnr, diagnostic_lsp_to_vim(diagnostics, bufnr, client_id))
+ handle_diagnostics(ctx.params.textDocument.uri, ctx.client_id, result.items, true, config)
end
--- Clear push diagnostics and diagnostic cache.
@@ -335,7 +337,7 @@ end
--- implementation so it's simply marked @private rather than @deprecated.
---
---@param client_id integer
----@param buffer_client_map table map of buffers to active clients
+---@param buffer_client_map table<integer, table<integer, table>> map of buffers to active clients
---@private
function M.reset(client_id, buffer_client_map)
buffer_client_map = vim.deepcopy(buffer_client_map)
@@ -356,34 +358,28 @@ end
---
---@param bufnr integer|nil The buffer number
---@param line_nr integer|nil The line number
----@param opts table|nil Configuration keys
---- - severity: (DiagnosticSeverity, default nil)
---- - Only return diagnostics with this severity. Overrides severity_limit
---- - severity_limit: (DiagnosticSeverity, default nil)
---- - Limit severity of diagnostics found. E.g. "Warning" means { "Error", "Warning" } will be valid.
+---@param opts {severity?:lsp.DiagnosticSeverity}?
+--- - severity: (lsp.DiagnosticSeverity)
+--- - Only return diagnostics with this severity.
---@param client_id integer|nil the client id
---@return table Table with map of line number to list of diagnostics.
--- Structured: { [1] = {...}, [5] = {.... } }
---@private
function M.get_line_diagnostics(bufnr, line_nr, opts, client_id)
- opts = opts or {}
- if opts.severity then
- opts.severity = severity_lsp_to_vim(opts.severity)
- elseif opts.severity_limit then
- opts.severity = { min = severity_lsp_to_vim(opts.severity_limit) }
- end
+ convert_severity(opts)
+ local diag_opts = {} --- @type vim.diagnostic.GetOpts
- if client_id then
- opts.namespace = M.get_namespace(client_id, false)
+ if opts and opts.severity then
+ diag_opts.severity = severity_lsp_to_vim(opts.severity)
end
- if not line_nr then
- line_nr = vim.api.nvim_win_get_cursor(0)[1] - 1
+ if client_id then
+ diag_opts.namespace = M.get_namespace(client_id, false)
end
- opts.lnum = line_nr
+ diag_opts.lnum = line_nr or (api.nvim_win_get_cursor(0)[1] - 1)
- return diagnostic_vim_to_lsp(vim.diagnostic.get(bufnr, opts))
+ return diagnostic_vim_to_lsp(vim.diagnostic.get(bufnr, diag_opts))
end
--- Clear diagnostics from pull based clients
@@ -394,12 +390,13 @@ 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 = {}
--- Disable pull diagnostics for a buffer
+--- @param bufnr integer
--- @private
local function disable(bufnr)
local bufstate = bufstates[bufnr]
@@ -416,7 +413,7 @@ end
local function _refresh(bufnr, opts)
opts = opts or {}
opts['bufnr'] = bufnr
- util._refresh(ms.textDocument_diagnostic, opts)
+ vim.lsp.util._refresh(ms.textDocument_diagnostic, opts)
end
--- Enable pull diagnostics for a buffer
@@ -440,7 +437,8 @@ function M._enable(bufnr)
return
end
if bufstates[bufnr] and bufstates[bufnr].enabled then
- _refresh(bufnr, { only_visible = true, client_id = opts.data.client_id })
+ local client_id = opts.data.client_id --- @type integer?
+ _refresh(bufnr, { only_visible = true, client_id = client_id })
end
end,
group = augroup,
diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua
index 6fde55cf04..daf4fec8d2 100644
--- a/runtime/lua/vim/lsp/handlers.lua
+++ b/runtime/lua/vim/lsp/handlers.lua
@@ -4,6 +4,7 @@ local ms = protocol.Methods
local util = require('vim.lsp.util')
local api = vim.api
+--- @type table<string,lsp.Handler>
local M = {}
-- FIXME: DOC: Expose in vimdocs
@@ -15,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)
@@ -56,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)
@@ -69,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 {}
@@ -105,11 +106,11 @@ 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
- ---@type lsp.Client
- local client = vim.lsp.get_client_by_id(client_id)
+ local client = assert(vim.lsp.get_client_by_id(client_id))
client.dynamic_capabilities:register(result.registrations)
for bufnr, _ in pairs(client.attached_buffers) do
@@ -120,7 +121,7 @@ M[ms.client_registerCapability] = function(_, result, ctx)
local unsupported = {}
for _, reg in ipairs(result.registrations) do
if reg.method == ms.workspace_didChangeWatchedFiles then
- require('vim.lsp._watchfiles').register(reg, ctx)
+ vim.lsp._watchfiles.register(reg, ctx)
elseif not client.dynamic_capabilities:supports_registration(reg.method) then
unsupported[#unsupported + 1] = reg.method
end
@@ -136,21 +137,22 @@ 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 = vim.lsp.get_client_by_id(client_id)
+ local client = assert(vim.lsp.get_client_by_id(client_id))
client.dynamic_capabilities:unregister(result.unregisterations)
for _, unreg in ipairs(result.unregisterations) do
if unreg.method == ms.workspace_didChangeWatchedFiles then
- require('vim.lsp._watchfiles').unregister(unreg, ctx)
+ vim.lsp._watchfiles.unregister(unreg, ctx)
end
end
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,
@@ -158,7 +160,7 @@ M[ms.workspace_applyEdit] = function(_, workspace_edit, ctx)
)
-- TODO(ashkan) Do something more with label?
local client_id = ctx.client_id
- local client = vim.lsp.get_client_by_id(client_id)
+ local client = assert(vim.lsp.get_client_by_id(client_id))
if workspace_edit.label then
print('Workspace edit', workspace_edit.label)
end
@@ -170,7 +172,16 @@ M[ms.workspace_applyEdit] = function(_, workspace_edit, ctx)
}
end
---see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_configuration
+---@param table table e.g., { foo = { bar = "z" } }
+---@param section string indicating the field of the table, e.g., "foo.bar"
+---@return any|nil setting value read from the table, or `nil` not found
+local function lookup_section(table, section)
+ local keys = vim.split(section, '.', { plain = true }) --- @type string[]
+ return vim.tbl_get(table, unpack(keys))
+end
+
+--- @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)
@@ -189,10 +200,13 @@ M[ms.workspace_configuration] = function(_, result, ctx)
local response = {}
for _, item in ipairs(result.items) do
if item.section then
- local value = util.lookup_section(client.config.settings, item.section)
+ local value = lookup_section(client.settings, item.section)
-- For empty sections with no explicit '' key, return settings as is
- if value == vim.NIL and item.section == '' then
- value = client.config.settings or vim.NIL
+ if value == nil and item.section == '' then
+ value = client.settings
+ end
+ if value == nil then
+ value = vim.NIL
end
table.insert(response, value)
end
@@ -200,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)
@@ -212,41 +226,42 @@ M[ms.workspace_workspaceFolders] = function(_, _, ctx)
end
M[ms.textDocument_publishDiagnostics] = function(...)
- return require('vim.lsp.diagnostic').on_publish_diagnostics(...)
+ return vim.lsp.diagnostic.on_publish_diagnostics(...)
end
M[ms.textDocument_diagnostic] = function(...)
- return require('vim.lsp.diagnostic').on_diagnostic(...)
+ return vim.lsp.diagnostic.on_diagnostic(...)
end
M[ms.textDocument_codeLens] = function(...)
- return require('vim.lsp.codelens').on_codelens(...)
+ return vim.lsp.codelens.on_codelens(...)
end
M[ms.textDocument_inlayHint] = function(...)
- return require('vim.lsp.inlay_hint').on_inlayhint(...)
+ 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')
- else
- local client = vim.lsp.get_client_by_id(ctx.client_id)
- config = config or {}
- local title = 'References'
- local items = util.locations_to_items(result, client.offset_encoding)
+ return
+ end
- if config.loclist then
- vim.fn.setloclist(0, {}, ' ', { title = title, items = items, context = ctx })
- api.nvim_command('lopen')
- elseif config.on_list then
- assert(type(config.on_list) == 'function', 'on_list is not a function')
- config.on_list({ title = title, items = items, context = ctx })
- else
- vim.fn.setqflist({}, ' ', { title = title, items = items, context = ctx })
- api.nvim_command('botright copen')
- end
+ local client = assert(vim.lsp.get_client_by_id(ctx.client_id))
+ config = config or {}
+ local title = 'References'
+ local items = util.locations_to_items(result, client.offset_encoding)
+
+ if config.loclist then
+ vim.fn.setloclist(0, {}, ' ', { title = title, items = items, context = ctx })
+ api.nvim_command('lopen')
+ elseif config.on_list then
+ assert(type(config.on_list) == 'function', 'on_list is not a function')
+ config.on_list({ title = title, items = items, context = ctx })
+ else
+ vim.fn.setqflist({}, ' ', { title = title, items = items, context = ctx })
+ api.nvim_command('botright copen')
end
end
@@ -259,31 +274,32 @@ end
---
---@param map_result function `((resp, bufnr) -> list)` to convert the response
---@param entity string name of the resource used in a `not found` error message
----@param title_fn function Function to call to generate list title
+---@param title_fn fun(ctx: lsp.HandlerContext): string Function to call to generate list title
+---@return lsp.Handler
local function response_to_list(map_result, entity, title_fn)
return function(_, result, ctx, config)
if not result or vim.tbl_isempty(result) then
vim.notify('No ' .. entity .. ' found')
+ return
+ end
+ config = config or {}
+ local title = title_fn(ctx)
+ local items = map_result(result, ctx.bufnr)
+
+ if config.loclist then
+ vim.fn.setloclist(0, {}, ' ', { title = title, items = items, context = ctx })
+ api.nvim_command('lopen')
+ elseif config.on_list then
+ assert(type(config.on_list) == 'function', 'on_list is not a function')
+ config.on_list({ title = title, items = items, context = ctx })
else
- config = config or {}
- local title = title_fn(ctx)
- local items = map_result(result, ctx.bufnr)
-
- if config.loclist then
- vim.fn.setloclist(0, {}, ' ', { title = title, items = items, context = ctx })
- api.nvim_command('lopen')
- elseif config.on_list then
- assert(type(config.on_list) == 'function', 'on_list is not a function')
- config.on_list({ title = title, items = items, context = ctx })
- else
- vim.fn.setqflist({}, ' ', { title = title, items = items, context = ctx })
- api.nvim_command('botright copen')
- end
+ vim.fn.setqflist({}, ' ', { title = title, items = items, context = ctx })
+ api.nvim_command('botright copen')
end
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',
@@ -293,45 +309,46 @@ 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)
return
end
- local client = vim.lsp.get_client_by_id(ctx.client_id)
+ local client = assert(vim.lsp.get_client_by_id(ctx.client_id))
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
end
- local client = vim.lsp.get_client_by_id(ctx.client_id)
+ local client = assert(vim.lsp.get_client_by_id(ctx.client_id))
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
end
- local client = vim.lsp.get_client_by_id(ctx.client_id)
+ local client = assert(vim.lsp.get_client_by_id(ctx.client_id))
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
end
- local row, col = unpack(api.nvim_win_get_cursor(0))
+ local cursor = api.nvim_win_get_cursor(0)
+ local row, col = cursor[1], cursor[2]
local line = assert(api.nvim_buf_get_lines(0, row - 1, row, false)[1])
local line_to_cursor = line:sub(col + 1)
local textMatch = vim.fn.match(line_to_cursor, '\\k*$')
@@ -354,6 +371,9 @@ end
--- )
--- ```
---
+---@param _ lsp.ResponseError?
+---@param result lsp.Hover
+---@param ctx lsp.HandlerContext
---@param config table Configuration table.
--- - border: (default=nil)
--- - Add borders to the floating window
@@ -388,20 +408,21 @@ 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 (table) table containing the context of the request, including the method
+---@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
- local _ = log.info() and log.info(ctx.method, 'No location found')
+ log.info(ctx.method, 'No location found')
return nil
end
- local client = vim.lsp.get_client_by_id(ctx.client_id)
+ local client = assert(vim.lsp.get_client_by_id(ctx.client_id))
config = config or {}
@@ -427,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".
@@ -449,8 +470,9 @@ M[ms.textDocument_implementation] = location_handler
--- )
--- ```
---
----@param result table Response from the language server
----@param ctx table Client context
+---@param _ lsp.ResponseError?
+---@param result lsp.SignatureHelp Response from the language server
+---@param ctx lsp.HandlerContext Client context
---@param config table Configuration table.
--- - border: (default=nil)
--- - Add borders to the floating window
@@ -470,7 +492,7 @@ function M.signature_help(_, result, ctx, config)
end
return
end
- local client = vim.lsp.get_client_by_id(ctx.client_id)
+ local client = assert(vim.lsp.get_client_by_id(ctx.client_id))
local triggers =
vim.tbl_get(client.server_capabilities, 'signatureHelpProvider', 'triggerCharacters')
local ft = vim.bo[ctx.bufnr].filetype
@@ -490,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
@@ -506,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"` 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, {
@@ -536,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
@@ -564,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
@@ -583,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
@@ -626,19 +652,25 @@ end
---@see https://microsoft.github.io/language-server-protocol/specification/#workspace_inlayHint_refresh
M[ms.workspace_inlayHint_refresh] = function(err, result, ctx, config)
- return require('vim.lsp.inlay_hint').on_refresh(err, result, ctx, config)
+ return vim.lsp.inlay_hint.on_refresh(err, result, ctx, config)
+end
+
+---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#semanticTokens_refreshRequest
+M[ms.workspace_semanticTokens_refresh] = function(err, result, ctx, _config)
+ return vim.lsp.semantic_tokens._refresh(err, result, ctx)
end
-- Add boilerplate error validation and logging for all of these.
for k, fn in pairs(M) do
M[k] = function(err, result, ctx, config)
- local _ = log.trace()
- and log.trace('default_handler', ctx.method, {
+ if log.trace() then
+ log.trace('default_handler', ctx.method, {
err = err,
result = result,
ctx = vim.inspect(ctx),
config = config,
})
+ end
if err then
-- LSP spec:
diff --git a/runtime/lua/vim/lsp/health.lua b/runtime/lua/vim/lsp/health.lua
index fe06006108..797a1097f9 100644
--- a/runtime/lua/vim/lsp/health.lua
+++ b/runtime/lua/vim/lsp/health.lua
@@ -1,13 +1,12 @@
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 log = require('vim.lsp.log')
+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]
+ local log_level_string = log.levels[current_log_level] ---@type string
report_info(string.format('LSP log level : %s', log_level_string))
if current_log_level < log.levels.WARN then
@@ -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 {}), ',')
@@ -38,7 +39,7 @@ function M.check()
'%s (id=%s, root_dir=%s, attached_to=[%s])',
client.name,
client.id,
- vim.fn.fnamemodify(client.config.root_dir, ':~'),
+ vim.fn.fnamemodify(client.root_dir, ':~'),
attached_to
)
)
@@ -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 4f7a3b0076..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_hint? table<integer, table<integer, lsp.InlayHint[]>> client_id -> (lnum -> hints)
+---@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')
@@ -17,13 +17,15 @@ local augroup = api.nvim_create_augroup('vim_lsp_inlayhint', {})
--- |lsp-handler| for the method `textDocument/inlayHint`
--- Store hints for a specific buffer and client
+---@param result lsp.InlayHint[]?
+---@param ctx lsp.HandlerContext
---@private
function M.on_inlayhint(err, result, ctx, _)
if err then
- local _ = log.error() and log.error('inlayhint', err)
+ log.error('inlayhint', err)
return
end
- local bufnr = ctx.bufnr
+ local bufnr = assert(ctx.bufnr)
if util.buf_versions[bufnr] ~= ctx.version then
return
end
@@ -35,12 +37,12 @@ function M.on_inlayhint(err, result, ctx, _)
if not bufstate or not bufstate.enabled then
return
end
- if not (bufstate.client_hint and bufstate.version) then
- bufstate.client_hint = vim.defaulttable()
+ if not (bufstate.client_hints and bufstate.version) then
+ bufstate.client_hints = vim.defaulttable()
bufstate.version = ctx.version
end
- local hints_by_client = bufstate.client_hint
- local client = vim.lsp.get_client_by_id(client_id)
+ local hints_by_client = bufstate.client_hints
+ local client = assert(vim.lsp.get_client_by_id(client_id))
local new_hints_by_lnum = vim.defaulttable()
local num_unprocessed = #result
@@ -52,6 +54,8 @@ function M.on_inlayhint(err, result, ctx, _)
end
local lines = api.nvim_buf_get_lines(bufnr, 0, -1, false)
+ ---@param position lsp.Position
+ ---@return integer
local function pos_to_byte(position)
local col = position.character
if col > 0 then
@@ -78,6 +82,7 @@ function M.on_inlayhint(err, result, ctx, _)
end
--- |lsp-handler| for the method `textDocument/inlayHint/refresh`
+---@param ctx lsp.HandlerContext
---@private
function M.on_refresh(err, _, ctx, _)
if err then
@@ -98,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
@@ -125,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 } })
@@ -155,7 +154,7 @@ function M.get(filter)
end
local bufstate = bufstates[bufnr]
- if not (bufstate and bufstate.client_hint) then
+ if not (bufstate and bufstate.client_hints) then
return {}
end
@@ -178,7 +177,7 @@ function M.get(filter)
--- @type vim.lsp.inlay_hint.get.ret[]
local hints = {}
for _, client in pairs(clients) do
- local hints_by_lnum = bufstate.client_hint[client.id]
+ local hints_by_lnum = bufstate.client_hints[client.id]
if hints_by_lnum then
for lnum = range.start.line, range['end'].line do
local line_hints = hints_by_lnum[lnum] or {}
@@ -211,11 +210,11 @@ local function clear(bufnr)
return
end
local bufstate = bufstates[bufnr]
- local client_lens = (bufstate or {}).client_hint or {}
- local client_ids = vim.tbl_keys(client_lens)
+ local client_lens = (bufstate or {}).client_hints or {}
+ local client_ids = vim.tbl_keys(client_lens) --- @type integer[]
for _, iter_client_id in ipairs(client_ids) do
if bufstate then
- bufstate.client_hint[iter_client_id] = {}
+ bufstate.client_hints[iter_client_id] = {}
end
end
api.nvim_buf_clear_namespace(bufnr, namespace, 0, -1)
@@ -236,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? table 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 {}
@@ -312,7 +311,7 @@ api.nvim_set_decoration_provider(namespace, {
if bufstate.version ~= util.buf_versions[bufnr] then
return
end
- local hints_by_client = bufstate.client_hint
+ local hints_by_client = assert(bufstate.client_hints)
for lnum = topline, botline do
if bufstate.applied[lnum] ~= bufstate.version then
@@ -321,14 +320,15 @@ api.nvim_set_decoration_provider(namespace, {
local line_hints = hints_by_lnum[lnum] or {}
for _, hint in pairs(line_hints) do
local text = ''
- if type(hint.label) == 'string' then
- text = hint.label
+ local label = hint.label
+ if type(label) == 'string' then
+ text = label
else
- for _, part in ipairs(hint.label) do
+ for _, part in ipairs(label) do
text = text .. part.value
end
end
- local vt = {}
+ local vt = {} --- @type {[1]: string, [2]: string?}[]
if hint.paddingLeft then
vt[#vt + 1] = { ' ' }
end
@@ -360,7 +360,13 @@ function M.is_enabled(bufnr)
return bufstates[bufnr] and bufstates[bufnr].enabled or false
end
---- Enable/disable/toggle inlay hints for a buffer
+--- Enables or disables inlay hints for a buffer.
+---
+--- To "toggle", pass the inverse of `is_enabled()`:
+---
+--- ```lua
+--- vim.lsp.inlay_hint.enable(0, not vim.lsp.inlay_hint.is_enabled())
+--- ```
---
--- @param bufnr (integer|nil) Buffer handle, or 0 or nil for current
--- @param enable (boolean|nil) true/nil to enable, false to disable
diff --git a/runtime/lua/vim/lsp/log.lua b/runtime/lua/vim/lsp/log.lua
index 6d2e0bc292..9f2bd71158 100644
--- a/runtime/lua/vim/lsp/log.lua
+++ b/runtime/lua/vim/lsp/log.lua
@@ -2,138 +2,144 @@
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'
-local format_func = function(arg)
+
+local function format_func(arg)
return vim.inspect(arg, { newline = '' })
end
-do
- local function notify(msg, level)
- if vim.in_fast_event() then
- vim.schedule(function()
- vim.notify(msg, level)
- end)
- else
+local function notify(msg, level)
+ if vim.in_fast_event() then
+ vim.schedule(function()
vim.notify(msg, level)
- end
+ end)
+ else
+ vim.notify(msg, level)
end
+end
+
+local logfilename = vim.fs.joinpath(vim.fn.stdpath('log'), 'lsp.log')
+
+-- TODO: Ideally the directory should be created in open_logfile(), right
+-- before opening the log file, but open_logfile() can be called from libuv
+-- callbacks, where using fn.mkdir() is not allowed.
+vim.fn.mkdir(vim.fn.stdpath('log'), 'p')
+
+--- Returns the log filename.
+---@return string log filename
+function log.get_filename()
+ return logfilename
+end
- local path_sep = vim.uv.os_uname().version:match('Windows') and '\\' or '/'
- local function path_join(...)
- return table.concat(vim.tbl_flatten({ ... }), path_sep)
+--- @type file*?, string?
+local logfile, openerr
+
+--- Opens log file. Returns true if file is open, false on error
+local function open_logfile()
+ -- Try to open file only once
+ if logfile then
+ return true
+ end
+ if openerr then
+ return false
end
- local logfilename = path_join(vim.fn.stdpath('log'), 'lsp.log')
- -- TODO: Ideally the directory should be created in open_logfile(), right
- -- before opening the log file, but open_logfile() can be called from libuv
- -- callbacks, where using fn.mkdir() is not allowed.
- vim.fn.mkdir(vim.fn.stdpath('log'), 'p')
+ 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, log_levels.ERROR)
+ return false
+ end
- --- Returns the log filename.
- ---@return string log filename
- function log.get_filename()
- return logfilename
+ local log_info = vim.uv.fs_stat(logfilename)
+ if log_info and log_info.size > 1e9 then
+ local warn_msg = string.format(
+ 'LSP client log is large (%d MB): %s',
+ log_info.size / (1000 * 1000),
+ logfilename
+ )
+ notify(warn_msg)
end
- local logfile, openerr
- --- Opens log file. Returns true if file is open, false on error
- local function open_logfile()
- -- Try to open file only once
- if logfile then
+ -- Start message for logging
+ logfile:write(string.format('[START][%s] LSP logging initiated\n', os.date(log_date_format)))
+ return true
+end
+
+for level, levelnr in pairs(log_levels) do
+ -- Also export the log level on the root object.
+ log[level] = levelnr
+
+ -- Add a reverse lookup.
+ log.levels[levelnr] = level
+end
+
+--- @param level string
+--- @param levelnr integer
+--- @return fun(...:any): boolean?
+local function create_logger(level, levelnr)
+ return function(...)
+ if levelnr < current_log_level then
+ return false
+ end
+ local argc = select('#', ...)
+ if argc == 0 then
return true
end
- if openerr then
+ if not open_logfile() then
return false
end
-
- 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)
- return false
+ local info = debug.getinfo(2, 'Sl')
+ local header = string.format(
+ '[%s][%s] ...%s:%s',
+ level,
+ os.date(log_date_format),
+ info.short_src:sub(-16),
+ info.currentline
+ )
+ local parts = { header }
+ for i = 1, argc do
+ local arg = select(i, ...)
+ table.insert(parts, arg == nil and 'nil' or format_func(arg))
end
+ assert(logfile)
+ logfile:write(table.concat(parts, '\t'), '\n')
+ logfile:flush()
+ end
+end
- local log_info = vim.uv.fs_stat(logfilename)
- if log_info and log_info.size > 1e9 then
- local warn_msg = string.format(
- 'LSP client log is large (%d MB): %s',
- log_info.size / (1000 * 1000),
- logfilename
- )
- notify(warn_msg)
- end
+-- If called without arguments, it will check whether the log level is
+-- greater than or equal to this one. When called with arguments, it will
+-- log at that level (if applicable, it is checked either way).
- -- Start message for logging
- logfile:write(string.format('[START][%s] LSP logging initiated\n', os.date(log_date_format)))
- return true
- end
+--- @nodoc
+log.debug = create_logger('DEBUG', log_levels.DEBUG)
- for level, levelnr in pairs(log.levels) do
- -- Also export the log level on the root object.
- log[level] = levelnr
- -- FIXME: DOC
- -- Should be exposed in the vim docs.
- --
- -- Set the lowercase name as the main use function.
- -- If called without arguments, it will check whether the log level is
- -- greater than or equal to this one. When called with arguments, it will
- -- log at that level (if applicable, it is checked either way).
- --
- -- Recommended usage:
- -- ```
- -- local _ = log.warn() and log.warn("123")
- -- ```
- --
- -- This way you can avoid string allocations if the log level isn't high enough.
- if level ~= 'OFF' then
- log[level:lower()] = function(...)
- local argc = select('#', ...)
- if levelnr < current_log_level then
- return false
- end
- if argc == 0 then
- return true
- end
- if not open_logfile() then
- return false
- end
- local info = debug.getinfo(2, 'Sl')
- local header = string.format(
- '[%s][%s] ...%s:%s',
- level,
- os.date(log_date_format),
- string.sub(info.short_src, #info.short_src - 15),
- info.currentline
- )
- local parts = { header }
- for i = 1, argc do
- local arg = select(i, ...)
- if arg == nil then
- table.insert(parts, 'nil')
- else
- table.insert(parts, format_func(arg))
- end
- end
- logfile:write(table.concat(parts, '\t'), '\n')
- logfile:flush()
- end
- end
- end
-end
+--- @nodoc
+log.error = create_logger('ERROR', log_levels.ERROR)
--- This is put here on purpose after the loop above so that it doesn't
--- interfere with iterating the levels
-vim.tbl_add_reverse_lookup(log.levels)
+--- @nodoc
+log.info = create_logger('INFO', log_levels.INFO)
+
+--- @nodoc
+log.trace = create_logger('TRACE', log_levels.TRACE)
+
+--- @nodoc
+log.warn = create_logger('WARN', log_levels.WARN)
--- Sets the current log level.
---@param level (string|integer) One of `vim.lsp.log.levels`
@@ -163,7 +169,7 @@ end
--- Checks whether the level is sufficient for logging.
---@param level integer log level
----@returns (bool) true if would log, false if not
+---@return bool : true if would log, false if not
function log.should_log(level)
return level >= current_log_level
end
diff --git a/runtime/lua/vim/lsp/protocol.lua b/runtime/lua/vim/lsp/protocol.lua
index a7c3914834..599f02425e 100644
--- a/runtime/lua/vim/lsp/protocol.lua
+++ b/runtime/lua/vim/lsp/protocol.lua
@@ -1,24 +1,17 @@
--- Protocol for the Microsoft Language Server Protocol (mslsp)
-
-local protocol = {}
+--- @diagnostic disable: duplicate-doc-alias
---[=[
----@private
---- Useful for interfacing with:
---- https://github.com/microsoft/language-server-protocol/raw/gh-pages/_specifications/specification-3-14.md
-function transform_schema_comments()
- nvim.command [[silent! '<,'>g/\/\*\*\|\*\/\|^$/d]]
- nvim.command [[silent! '<,'>s/^\(\s*\) \* \=\(.*\)/\1--\2/]]
-end
----@private
-function transform_schema_to_table()
- transform_schema_comments()
- nvim.command [[silent! '<,'>s/: \S\+//]]
- nvim.command [[silent! '<,'>s/export const //]]
- nvim.command [[silent! '<,'>s/export namespace \(\S*\)\s*{/protocol.\1 = {/]]
- nvim.command [[silent! '<,'>s/namespace \(\S*\)\s*{/protocol.\1 = {/]]
+---@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 constants = {
--- @enum lsp.DiagnosticSeverity
@@ -313,10 +306,13 @@ local constants = {
},
}
-for k, v in pairs(constants) do
- local tbl = vim.deepcopy(v)
- 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
--[=[
@@ -722,11 +718,7 @@ function protocol.make_client_capabilities()
codeActionLiteralSupport = {
codeActionKind = {
- valueSet = (function()
- local res = vim.tbl_values(constants.CodeActionKind)
- table.sort(res)
- return res
- end)(),
+ valueSet = get_value_set(constants.CodeActionKind),
},
},
isPreferredSupport = true,
@@ -751,18 +743,18 @@ 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 = (function()
- local res = {}
- for k in ipairs(protocol.CompletionItemKind) do
- if type(k) == 'number' then
- table.insert(res, k)
- end
- end
- return res
- end)(),
+ valueSet = get_value_set(constants.CompletionItemKind),
+ },
+ completionList = {
+ itemDefaults = {
+ 'editRange',
+ 'insertTextFormat',
+ 'insertTextMode',
+ 'data',
+ },
},
-- TODO(tjdevries): Implement this
@@ -783,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,
},
@@ -804,15 +796,7 @@ function protocol.make_client_capabilities()
documentSymbol = {
dynamicRegistration = false,
symbolKind = {
- valueSet = (function()
- local res = {}
- for k in ipairs(protocol.SymbolKind) do
- if type(k) == 'number' then
- table.insert(res, k)
- end
- end
- return res
- end)(),
+ valueSet = get_value_set(constants.SymbolKind),
},
hierarchicalDocumentSymbolSupport = true,
},
@@ -823,15 +807,7 @@ function protocol.make_client_capabilities()
publishDiagnostics = {
relatedInformation = true,
tagSupport = {
- valueSet = (function()
- local res = {}
- for k in ipairs(protocol.DiagnosticTag) do
- if type(k) == 'number' then
- table.insert(res, k)
- end
- end
- return res
- end)(),
+ valueSet = get_value_set(constants.DiagnosticTag),
},
dataSupport = true,
},
@@ -843,15 +819,7 @@ function protocol.make_client_capabilities()
symbol = {
dynamicRegistration = false,
symbolKind = {
- valueSet = (function()
- local res = {}
- for k in ipairs(protocol.SymbolKind) do
- if type(k) == 'number' then
- table.insert(res, k)
- end
- end
- return res
- end)(),
+ valueSet = get_value_set(constants.SymbolKind),
},
},
configuration = true,
@@ -891,9 +859,9 @@ end
--- Creates a normalized object describing LSP server capabilities.
---@param server_capabilities table Table of capabilities supported by the server
----@return table|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.
@@ -934,7 +902,7 @@ end
-- Generated by gen_lsp.lua, keep at end of file.
--- LSP method names.
---
----@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#metaModel
+---@see https://microsoft.github.io/language-server-protocol/specification/#metaModel
protocol.Methods = {
--- A request to resolve the incoming calls for a given `CallHierarchyItem`.
--- @since 3.16.0
@@ -1021,16 +989,14 @@ protocol.Methods = {
--- `filterText`, `insertText`, and `textEdit`, must not be changed during resolve.
textDocument_completion = 'textDocument/completion',
--- A request to resolve the type definition locations of a symbol at a given text
- --- document position. The request's parameter is of type [TextDocumentPositionParams]
- --- (#TextDocumentPositionParams) the response is of type {@link Declaration}
- --- or a typed array of {@link DeclarationLink} or a Thenable that resolves
- --- to such.
+ --- document position. The request's parameter is of type {@link TextDocumentPositionParams}
+ --- the response is of type {@link Declaration} or a typed array of {@link DeclarationLink}
+ --- or a Thenable that resolves to such.
textDocument_declaration = 'textDocument/declaration',
--- A request to resolve the definition location of a symbol at a given text
- --- document position. The request's parameter is of type [TextDocumentPosition]
- --- (#TextDocumentPosition) the response is of either type {@link Definition}
- --- or a typed array of {@link DefinitionLink} or a Thenable that resolves
- --- to such.
+ --- document position. The request's parameter is of type {@link TextDocumentPosition}
+ --- the response is of either type {@link Definition} or a typed array of
+ --- {@link DefinitionLink} or a Thenable that resolves to such.
textDocument_definition = 'textDocument/definition',
--- The document diagnostic request definition.
--- @since 3.17.0
@@ -1064,9 +1030,9 @@ protocol.Methods = {
--- that resolves to such.
textDocument_documentColor = 'textDocument/documentColor',
--- Request to resolve a {@link DocumentHighlight} for a given
- --- text document position. The request's parameter is of type [TextDocumentPosition]
- --- (#TextDocumentPosition) the request response is of type [DocumentHighlight[]]
- --- (#DocumentHighlight) or a Thenable that resolves to such.
+ --- text document position. The request's parameter is of type {@link TextDocumentPosition}
+ --- the request response is an array of type {@link DocumentHighlight}
+ --- or a Thenable that resolves to such.
textDocument_documentHighlight = 'textDocument/documentHighlight',
--- A request to provide document links
textDocument_documentLink = 'textDocument/documentLink',
@@ -1080,16 +1046,15 @@ protocol.Methods = {
--- response is of type {@link FoldingRangeList} or a Thenable
--- that resolves to such.
textDocument_foldingRange = 'textDocument/foldingRange',
- --- A request to to format a whole document.
+ --- A request to format a whole document.
textDocument_formatting = 'textDocument/formatting',
--- Request to request hover information at a given text document position. The request's
--- parameter is of type {@link TextDocumentPosition} the response is of
--- type {@link Hover} or a Thenable that resolves to such.
textDocument_hover = 'textDocument/hover',
--- A request to resolve the implementation locations of a symbol at a given text
- --- document position. The request's parameter is of type [TextDocumentPositionParams]
- --- (#TextDocumentPositionParams) the response is of type {@link Definition} or a
- --- Thenable that resolves to such.
+ --- document position. The request's parameter is of type {@link TextDocumentPositionParams}
+ --- the response is of type {@link Definition} or a Thenable that resolves to such.
textDocument_implementation = 'textDocument/implementation',
--- A request to provide inlay hints in a document. The request's parameter is of
--- type {@link InlayHintsParams}, the response is of type
@@ -1100,6 +1065,7 @@ protocol.Methods = {
--- type {@link InlineCompletionParams}, the response is of type
--- {@link InlineCompletion InlineCompletion[]} or a Thenable that resolves to such.
--- @since 3.18.0
+ --- @proposed
textDocument_inlineCompletion = 'textDocument/inlineCompletion',
--- A request to provide inline values in a document. The request's parameter is of
--- type {@link InlineValueParams}, the response is of type
@@ -1155,9 +1121,8 @@ protocol.Methods = {
textDocument_semanticTokens_range = 'textDocument/semanticTokens/range',
textDocument_signatureHelp = 'textDocument/signatureHelp',
--- A request to resolve the type definition locations of a symbol at a given text
- --- document position. The request's parameter is of type [TextDocumentPositionParams]
- --- (#TextDocumentPositionParams) the response is of type {@link Definition} or a
- --- Thenable that resolves to such.
+ --- document position. The request's parameter is of type {@link TextDocumentPositionParams}
+ --- the response is of type {@link Definition} or a Thenable that resolves to such.
textDocument_typeDefinition = 'textDocument/typeDefinition',
--- A document will save notification is sent from the client to the server before
--- the document is actually saved.
@@ -1200,14 +1165,14 @@ protocol.Methods = {
--- symbol's location.
--- @since 3.17.0
workspaceSymbol_resolve = 'workspaceSymbol/resolve',
- --- A request sent from the server to the client to modified certain resources.
+ --- A request sent from the server to the client to modify certain resources.
workspace_applyEdit = 'workspace/applyEdit',
--- A request to refresh all code actions
--- @since 3.16.0
workspace_codeLens_refresh = 'workspace/codeLens/refresh',
--- The 'workspace/configuration' request is sent from the server to the client to fetch a certain
--- configuration setting.
- --- This pull model replaces the old push model were the client signaled configuration change via an
+ --- This pull model replaces the old push model where the client signaled configuration change via an
--- event. If the server still needs to react to configuration changes (since the server caches the
--- result of `workspace/configuration` requests) the server should register for an empty configuration
--- change event and empty the cache if such an event is received.
@@ -1240,9 +1205,12 @@ protocol.Methods = {
--- files were renamed from within the client.
--- @since 3.16.0
workspace_didRenameFiles = 'workspace/didRenameFiles',
- --- A request send from the client to the server to execute a command. The request might return
+ --- A request sent from the client to the server to execute a command. The request might return
--- a workspace edit which the client will apply to the workspace.
workspace_executeCommand = 'workspace/executeCommand',
+ --- @since 3.18.0
+ --- @proposed
+ workspace_foldingRange_refresh = 'workspace/foldingRange/refresh',
--- @since 3.17.0
workspace_inlayHint_refresh = 'workspace/inlayHint/refresh',
--- @since 3.17.0
diff --git a/runtime/lua/vim/lsp/rpc.lua b/runtime/lua/vim/lsp/rpc.lua
index 6ab5708721..984e4f040a 100644
--- a/runtime/lua/vim/lsp/rpc.lua
+++ b/runtime/lua/vim/lsp/rpc.lua
@@ -6,7 +6,7 @@ local validate, schedule, schedule_wrap = vim.validate, vim.schedule, vim.schedu
local is_win = uv.os_uname().version:find('Windows')
--- Checks whether a given path exists and is a directory.
----@param filename (string) path to check
+---@param filename string path to check
---@return boolean
local function is_dir(filename)
local stat = uv.fs_stat(filename)
@@ -15,34 +15,39 @@ end
--- Embeds the given string into a table and correctly computes `Content-Length`.
---
----@param encoded_message (string)
----@return string containing encoded message and `Content-Length` attribute
-local function format_message_with_content_length(encoded_message)
+---@param message string
+---@return string message with `Content-Length` attribute
+local function format_message_with_content_length(message)
return table.concat({
'Content-Length: ',
- tostring(#encoded_message),
+ tostring(#message),
'\r\n\r\n',
- encoded_message,
+ message,
})
end
+---@class (private) vim.lsp.rpc.Headers: {string: any}
+---@field content_length integer
+
--- Parses an LSP Message's header
---
----@param header string: The header to parse.
----@return table # parsed headers
+---@param header string The header to parse.
+---@return vim.lsp.rpc.Headers#parsed headers
local function parse_headers(header)
assert(type(header) == 'string', 'header must be a string')
+ --- @type vim.lsp.rpc.Headers
local headers = {}
for line in vim.gsplit(header, '\r\n', { plain = true }) do
if line == '' then
break
end
+ --- @type string?, string?
local key, value = line:match('^%s*(%S+)%s*:%s*(.+)%s*$')
if key then
- key = key:lower():gsub('%-', '_')
+ key = key:lower():gsub('%-', '_') --- @type string
headers[key] = value
else
- local _ = log.error() and log.error('invalid header line %q', line)
+ log.error('invalid header line %q', line)
error(string.format('invalid header line %q', line))
end
end
@@ -73,15 +78,25 @@ local function request_parser_loop()
-- be searching for.
-- TODO(ashkan) I'd like to remove this, but it seems permanent :(
local buffer_start = buffer:find(header_start_pattern)
+ if not buffer_start then
+ error(
+ string.format(
+ "Headers were expected, a different response was received. The server response was '%s'.",
+ buffer
+ )
+ )
+ end
local headers = parse_headers(buffer:sub(buffer_start, start - 1))
local content_length = headers.content_length
-- Use table instead of just string to buffer the message. It prevents
-- a ton of strings allocating.
-- ref. http://www.lua.org/pil/11.6.html
+ ---@type string[]
local body_chunks = { buffer:sub(finish + 1) }
local body_length = #body_chunks[1]
-- Keep waiting for data until we have enough.
while body_length < content_length do
+ ---@type string
local chunk = coroutine.yield()
or error('Expected more data for the body. The server may have died.') -- TODO hmm.
table.insert(body_chunks, chunk)
@@ -96,17 +111,17 @@ local function request_parser_loop()
end
local body = table.concat(body_chunks)
-- Yield our data.
- buffer = rest
- .. (
- coroutine.yield(headers, body)
- or error('Expected more data for the body. The server may have died.')
- ) -- TODO hmm.
+
+ --- @type string
+ local data = coroutine.yield(headers, body)
+ or error('Expected more data for the body. The server may have died.')
+ buffer = rest .. data
else
-- Get more data since we don't have enough.
- buffer = buffer
- .. (
- coroutine.yield() or error('Expected more data for the header. The server may have died.')
- ) -- TODO hmm.
+ --- @type string
+ local data = coroutine.yield()
+ or error('Expected more data for the header. The server may have died.')
+ buffer = buffer .. data
end
end
end
@@ -115,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,
@@ -125,12 +140,17 @@ 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.
---
----@param err (table) The error object
----@returns (string) The formatted error message
+---@param err table The error object
+---@return string error_message The formatted error message
function M.format_rpc_error(err)
validate({
err = { err, 't' },
@@ -138,7 +158,7 @@ function M.format_rpc_error(err)
-- There is ErrorCodes in the LSP specification,
-- but in ResponseError.code it is not used and the actual type is number.
- local code
+ local code --- @type string
if protocol.ErrorCodes[err.code] then
code = string.format('code_name = %s,', protocol.ErrorCodes[err.code])
else
@@ -157,13 +177,17 @@ function M.format_rpc_error(err)
return table.concat(message_parts, ' ')
end
---- Creates an RPC response object/table.
+--- Creates an RPC response table `error` to be sent to the LSP response.
+---
+---@param code integer RPC error code defined, see `vim.lsp.protocol.ErrorCodes`
+---@param message? string arbitrary message to send to server
+---@param data? any arbitrary data to send to server
---
----@param code integer RPC error code defined in `vim.lsp.protocol.ErrorCodes`
----@param message string|nil arbitrary message to send to server
----@param data any|nil arbitrary data to send to server
+---@see lsp.ErrorCodes See `vim.lsp.protocol.ErrorCodes`
+---@return lsp.ResponseError
function M.rpc_response_error(code, message, data)
-- TODO should this error or just pick a sane error (like InternalError)?
+ ---@type string
local code_name = assert(protocol.ErrorCodes[code], 'Invalid RPC error code')
return setmetatable({
code = code,
@@ -174,52 +198,55 @@ function M.rpc_response_error(code, message, data)
})
end
-local default_dispatchers = {}
-
----@private
---- Default dispatcher for notifications sent to an LSP server.
----
----@param method (string) The invoked LSP method
----@param params (table): Parameters for the invoked LSP method
-function default_dispatchers.notification(method, params)
- local _ = log.debug() and log.debug('notification', method, params)
-end
-
----@private
---- Default dispatcher for requests sent to an LSP server.
----
----@param method (string) The invoked LSP method
----@param params (table): Parameters for the invoked LSP method
----@return nil
----@return table `vim.lsp.protocol.ErrorCodes.MethodNotFound`
-function default_dispatchers.server_request(method, params)
- local _ = log.debug() and log.debug('server_request', method, params)
- return nil, M.rpc_response_error(protocol.ErrorCodes.MethodNotFound)
-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)
+--- @field on_error fun(code: integer, err: any)
+
+--- @type vim.lsp.rpc.Dispatchers
+local default_dispatchers = {
+ --- Default dispatcher for notifications sent to an LSP server.
+ ---
+ ---@param method string The invoked LSP method
+ ---@param params table Parameters for the invoked LSP method
+ notification = function(method, params)
+ log.debug('notification', method, params)
+ end,
----@private
---- Default dispatcher for when a client exits.
----
----@param code (integer): Exit code
----@param signal (integer): Number describing the signal used to terminate (if
----any)
-function default_dispatchers.on_exit(code, signal)
- local _ = log.info() and log.info('client_exit', { code = code, signal = signal })
-end
+ --- Default dispatcher for requests sent to an LSP server.
+ ---
+ ---@param method string The invoked LSP method
+ ---@param params table Parameters for the invoked LSP method
+ ---@return any result (always nil for the default dispatchers)
+ ---@return lsp.ResponseError error `vim.lsp.protocol.ErrorCodes.MethodNotFound`
+ server_request = function(method, params)
+ log.debug('server_request', method, params)
+ return nil, M.rpc_response_error(protocol.ErrorCodes.MethodNotFound)
+ end,
+
+ --- Default dispatcher for when a client exits.
+ ---
+ ---@param code integer Exit code
+ ---@param signal integer Number describing the signal used to terminate (if any)
+ on_exit = function(code, signal)
+ log.info('client_exit', { code = code, signal = signal })
+ end,
----@private
---- Default dispatcher for client errors.
----
----@param code (integer): Error code
----@param err (any): Details about the error
----any)
-function default_dispatchers.on_error(code, err)
- local _ = log.error() and log.error('client_error:', M.client_errors[code], err)
-end
+ --- Default dispatcher for client errors.
+ ---
+ ---@param code integer Error code
+ ---@param err any Details about the error
+ on_error = function(code, err)
+ log.error('client_error:', M.client_errors[code], err)
+ end,
+}
---@private
function M.create_read_loop(handle_body, on_no_chunk, on_error)
- local parse_chunk = coroutine.wrap(request_parser_loop)
+ local parse_chunk = coroutine.wrap(request_parser_loop) --[[@as fun(chunk: string?): vim.lsp.rpc.Headers?, string?]]
parse_chunk()
return function(err, chunk)
if err then
@@ -246,31 +273,32 @@ function M.create_read_loop(handle_body, on_no_chunk, on_error)
end
end
----@class RpcClient
+---@class (private) vim.lsp.rpc.Client
---@field message_index integer
----@field message_callbacks table
----@field notify_reply_callbacks table
----@field transport table
----@field dispatchers table
-
----@class RpcClient
+---@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
+---@field transport vim.lsp.rpc.Transport
+---@field dispatchers vim.lsp.rpc.Dispatchers
local Client = {}
---@private
function Client:encode_and_send(payload)
- local _ = log.debug() and log.debug('rpc.send', payload)
+ log.debug('rpc.send', payload)
if self.transport.is_closing() then
return false
end
- local encoded = vim.json.encode(payload)
- self.transport.write(format_message_with_content_length(encoded))
+ local jsonstr = assert(
+ vim.json.encode(payload),
+ string.format("Couldn't encode payload '%s'", vim.inspect(payload))
+ )
+ self.transport.write(format_message_with_content_length(jsonstr))
return true
end
----@private
+---@package
--- Sends a notification to the LSP server.
----@param method (string) The invoked LSP method
----@param params (any): Parameters for the invoked LSP method
+---@param method string The invoked LSP method
+---@param params any Parameters for the invoked LSP method
---@return boolean `true` if notification could be sent, `false` if not
function Client:notify(method, params)
return self:encode_and_send({
@@ -291,14 +319,15 @@ function Client:send_response(request_id, err, result)
})
end
----@private
---- Sends a request to the LSP server and runs {callback} upon response.
+---@package
+--- Sends a request to the LSP server and runs {callback} upon response. |vim.lsp.rpc.request()|
---
----@param method (string) The invoked LSP method
----@param params (table|nil) Parameters for the invoked LSP method
----@param callback fun(err: lsp.ResponseError|nil, result: any) Callback to invoke
----@param notify_reply_callback (function|nil) Callback to invoke as soon as a request is no longer pending
----@return boolean success, integer|nil request_id true, request_id if request could be sent, `false` if not
+---@param method string The invoked LSP method
+---@param params table? Parameters for the invoked LSP method
+---@param callback fun(err?: lsp.ResponseError, result: any) Callback to invoke
+---@param notify_reply_callback fun(message_id: integer)|nil Callback to invoke as soon as a request is no longer pending
+---@return boolean success `true` if request could be sent, `false` if not
+---@return integer? message_id if request could be sent, `nil` if not
function Client:request(method, params, callback, notify_reply_callback)
validate({
callback = { callback, 'f' },
@@ -318,18 +347,20 @@ function Client:request(method, params, callback, notify_reply_callback)
if message_callbacks then
message_callbacks[message_id] = schedule_wrap(callback)
else
- return false
+ return false, nil
end
if notify_reply_callback and notify_reply_callbacks then
notify_reply_callbacks[message_id] = schedule_wrap(notify_reply_callback)
end
return result, message_id
else
- return false
+ return false, nil
end
end
----@private
+---@package
+---@param errkind integer
+---@param ... any
function Client:on_error(errkind, ...)
assert(M.client_errors[errkind])
-- TODO what to do if this fails?
@@ -337,6 +368,13 @@ function Client:on_error(errkind, ...)
end
---@private
+---@param errkind integer
+---@param status boolean
+---@param head any
+---@param ... any
+---@return boolean status
+---@return any head
+---@return any|nil ...
function Client:pcall_handler(errkind, status, head, ...)
if not status then
self:on_error(errkind, head, ...)
@@ -346,6 +384,12 @@ function Client:pcall_handler(errkind, status, head, ...)
end
---@private
+---@param errkind integer
+---@param fn function
+---@param ... any
+---@return boolean status
+---@return any head
+---@return any|nil ...
function Client:try_call(errkind, fn, ...)
return self:pcall_handler(errkind, pcall(fn, ...))
end
@@ -354,17 +398,17 @@ end
-- time and log them. This would require storing the timestamp. I could call
-- them with an error then, perhaps.
----@private
+---@package
function Client:handle_body(body)
local ok, decoded = pcall(vim.json.decode, body, { luanil = { object = true } })
if not ok then
self:on_error(M.client_errors.INVALID_SERVER_JSON, decoded)
return
end
- local _ = log.debug() and log.debug('rpc.receive', decoded)
+ log.debug('rpc.receive', decoded)
if type(decoded.method) == 'string' and decoded.id then
- local err
+ local err --- @type lsp.ResponseError|nil
-- Schedule here so that the users functions don't trigger an error and
-- we can still use the result.
schedule(function()
@@ -376,11 +420,10 @@ function Client:handle_body(body)
decoded.method,
decoded.params
)
- local _ = log.debug()
- and log.debug(
- 'server_request: callback result',
- { status = status, result = result, err = err }
- )
+ log.debug(
+ 'server_request: callback result',
+ { status = status, result = result, err = err }
+ )
if status then
if result == nil and err == nil then
error(
@@ -391,10 +434,12 @@ function Client:handle_body(body)
)
end
if err then
+ ---@cast err lsp.ResponseError
assert(
type(err) == 'table',
'err must be a table. Use rpc_response_error to help format errors.'
)
+ ---@type string
local code_name = assert(
protocol.ErrorCodes[err.code],
'Errors must use protocol.ErrorCodes. Use rpc_response_error to help format errors.'
@@ -431,7 +476,7 @@ function Client:handle_body(body)
if decoded.error then
local mute_error = false
if decoded.error.code == protocol.ErrorCodes.RequestCancelled then
- local _ = log.debug() and log.debug('Received cancellation ack', decoded)
+ log.debug('Received cancellation ack', decoded)
mute_error = true
end
@@ -457,7 +502,7 @@ function Client:handle_body(body)
if decoded.error then
decoded.error = setmetatable(decoded.error, {
__tostring = M.format_rpc_error,
- })
+ }) --- @type table
end
self:try_call(
M.client_errors.SERVER_RESULT_CALLBACK_ERROR,
@@ -467,7 +512,7 @@ function Client:handle_body(body)
)
else
self:on_error(M.client_errors.NO_RESULT_CALLBACK_FOUND, decoded)
- local _ = log.error() and log.error('No callback found for server response id ' .. result_id)
+ log.error('No callback found for server response id ' .. result_id)
end
elseif type(decoded.method) == 'string' then
-- Notification
@@ -483,7 +528,14 @@ function Client:handle_body(body)
end
end
----@return RpcClient
+---@class (private) vim.lsp.rpc.Transport
+---@field write fun(msg: string)
+---@field is_closing fun(): boolean
+---@field terminate fun()
+
+---@param dispatchers vim.lsp.rpc.Dispatchers
+---@param transport vim.lsp.rpc.Transport
+---@return vim.lsp.rpc.Client
local function new_client(dispatchers, transport)
local state = {
message_index = 0,
@@ -495,8 +547,17 @@ local function new_client(dispatchers, transport)
return setmetatable(state, { __index = Client })
end
----@param client RpcClient
+---@class vim.lsp.rpc.PublicClient
+---@field request fun(method: string, params: table?, callback: fun(err: lsp.ResponseError|nil, result: any), notify_reply_callback: fun(integer)|nil):boolean,integer? see |vim.lsp.rpc.request()|
+---@field notify fun(method: string, params: any):boolean see |vim.lsp.rpc.notify()|
+---@field is_closing fun(): boolean
+---@field terminate fun()
+
+---@param client vim.lsp.rpc.Client
+---@return vim.lsp.rpc.PublicClient
local function public_client(client)
+ ---@type vim.lsp.rpc.PublicClient
+ ---@diagnostic disable-next-line: missing-fields
local result = {}
---@private
@@ -512,17 +573,18 @@ local function public_client(client)
--- Sends a request to the LSP server and runs {callback} upon response.
---
---@param method (string) The invoked LSP method
- ---@param params (table|nil) Parameters for the invoked LSP method
- ---@param callback fun(err: lsp.ResponseError | nil, result: any) Callback to invoke
- ---@param notify_reply_callback (function|nil) Callback to invoke as soon as a request is no longer pending
- ---@return boolean success, integer|nil request_id true, message_id if request could be sent, `false` if not
+ ---@param params (table?) Parameters for the invoked LSP method
+ ---@param callback fun(err: lsp.ResponseError|nil, result: any) Callback to invoke
+ ---@param notify_reply_callback fun(message_id: integer)|nil Callback to invoke as soon as a request is no longer pending
+ ---@return boolean success `true` if request could be sent, `false` if not
+ ---@return integer? message_id if request could be sent, `nil` if not
function result.request(method, params, callback, notify_reply_callback)
return client:request(method, params, callback, notify_reply_callback)
end
--- Sends a notification to the LSP server.
---@param method (string) The invoked LSP method
- ---@param params (table|nil): Parameters for the invoked LSP method
+ ---@param params (table?) Parameters for the invoked LSP method
---@return boolean `true` if notification could be sent, `false` if not
function result.notify(method, params)
return client:notify(method, params)
@@ -531,43 +593,46 @@ local function public_client(client)
return result
end
+---@param dispatchers vim.lsp.rpc.Dispatchers?
+---@return vim.lsp.rpc.Dispatchers
local function merge_dispatchers(dispatchers)
- if dispatchers then
- local user_dispatchers = dispatchers
- dispatchers = {}
- for dispatch_name, default_dispatch in pairs(default_dispatchers) do
- local user_dispatcher = user_dispatchers[dispatch_name]
- if user_dispatcher then
- if type(user_dispatcher) ~= 'function' then
- error(string.format('dispatcher.%s must be a function', dispatch_name))
- end
- -- server_request is wrapped elsewhere.
- if
- not (dispatch_name == 'server_request' or dispatch_name == 'on_exit') -- TODO this blocks the loop exiting for some reason.
- then
- user_dispatcher = schedule_wrap(user_dispatcher)
- end
- dispatchers[dispatch_name] = user_dispatcher
- else
- dispatchers[dispatch_name] = default_dispatch
- end
+ if not dispatchers then
+ return default_dispatchers
+ end
+ ---@diagnostic disable-next-line: no-unknown
+ for name, fn in pairs(dispatchers) do
+ if type(fn) ~= 'function' then
+ error(string.format('dispatcher.%s must be a function', name))
end
- else
- dispatchers = default_dispatchers
end
- return dispatchers
+ ---@type vim.lsp.rpc.Dispatchers
+ local merged = {
+ notification = (
+ dispatchers.notification and vim.schedule_wrap(dispatchers.notification)
+ or default_dispatchers.notification
+ ),
+ on_error = (
+ dispatchers.on_error and vim.schedule_wrap(dispatchers.on_error)
+ or default_dispatchers.on_error
+ ),
+ on_exit = dispatchers.on_exit or default_dispatchers.on_exit,
+ server_request = dispatchers.server_request or default_dispatchers.server_request,
+ }
+ return merged
end
---- Create a LSP RPC client factory that connects via TCP to the given host
---- and port
+--- Create a LSP RPC client factory that connects via TCP to the given host and port.
+---
+--- Return a function that can be passed to the `cmd` field for
+--- |vim.lsp.start_client()| or |vim.lsp.start()|.
---
----@param host string
----@param port integer
----@return function
+---@param host string host to connect to
+---@param port integer port to connect to
+---@return fun(dispatchers: vim.lsp.rpc.Dispatchers): vim.lsp.rpc.PublicClient
function M.connect(host, port)
return function(dispatchers)
dispatchers = merge_dispatchers(dispatchers)
- local tcp = uv.new_tcp()
+ local tcp = assert(uv.new_tcp())
local closing = false
local transport = {
write = function(msg)
@@ -608,35 +673,84 @@ function M.connect(host, port)
end
end
+--- Create a LSP RPC client factory that connects via named pipes (Windows)
+--- or unix domain sockets (Unix) to the given pipe_path (file path on
+--- Unix and name on Windows).
+---
+--- Return a function that can be passed to the `cmd` field for
+--- |vim.lsp.start_client()| or |vim.lsp.start()|.
+---
+---@param pipe_path string file path of the domain socket (Unix) or name of the named pipe (Windows) to connect to
+---@return fun(dispatchers: vim.lsp.rpc.Dispatchers): vim.lsp.rpc.PublicClient
+function M.domain_socket_connect(pipe_path)
+ return function(dispatchers)
+ dispatchers = merge_dispatchers(dispatchers)
+ local pipe =
+ assert(uv.new_pipe(false), string.format('pipe with name %s could not be opened.', pipe_path))
+ local closing = false
+ local transport = {
+ write = vim.schedule_wrap(function(msg)
+ pipe:write(msg)
+ end),
+ is_closing = function()
+ return closing
+ end,
+ terminate = function()
+ if not closing then
+ closing = true
+ pipe:shutdown()
+ pipe:close()
+ dispatchers.on_exit(0, 0)
+ end
+ end,
+ }
+ local client = new_client(dispatchers, transport)
+ pipe:connect(pipe_path, function(err)
+ if err then
+ vim.schedule(function()
+ vim.notify(
+ string.format('Could not connect to :%s, reason: %s', pipe_path, vim.inspect(err)),
+ vim.log.levels.WARN
+ )
+ end)
+ return
+ end
+ local handle_body = function(body)
+ client:handle_body(body)
+ end
+ pipe:read_start(M.create_read_loop(handle_body, transport.terminate, function(read_err)
+ client:on_error(M.client_errors.READ_ERROR, read_err)
+ end))
+ end)
+
+ return public_client(client)
+ end
+end
+
+--- 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 cmd_args (table) List of additional string arguments to pass to {cmd}.
----@param dispatchers table|nil Dispatchers for LSP message types. Valid
----dispatcher names are:
---- - `"notification"`
---- - `"server_request"`
---- - `"on_error"`
---- - `"on_exit"`
----@param extra_spawn_params table|nil Additional context for the LSP
---- server process. May contain:
---- - {cwd} (string) Working directory for the LSP server process
---- - {env} (table) Additional environment variables for LSP server process
----@return table|nil 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.
---- - `terminate()` terminates the RPC client.
-function M.start(cmd, cmd_args, dispatchers, extra_spawn_params)
- if log.info() then
- log.info('Starting RPC client', { cmd = cmd, args = cmd_args, extra = extra_spawn_params })
- end
+--- @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.
+--- - `terminate()` terminates the RPC client.
+function M.start(cmd, dispatchers, extra_spawn_params)
+ log.info('Starting RPC client', { cmd = cmd, extra = extra_spawn_params })
validate({
- cmd = { cmd, 's' },
- cmd_args = { cmd_args, 't' },
+ cmd = { cmd, 't' },
dispatchers = { dispatchers, 't', true },
})
@@ -671,8 +785,8 @@ function M.start(cmd, cmd_args, dispatchers, extra_spawn_params)
end)
local stderr_handler = function(_, chunk)
- if chunk and log.error() then
- log.error('rpc', cmd, 'stderr', chunk)
+ if chunk then
+ log.error('rpc', cmd[1], 'stderr', chunk)
end
end
@@ -681,10 +795,7 @@ function M.start(cmd, cmd_args, dispatchers, extra_spawn_params)
detached = extra_spawn_params.detached
end
- local cmd1 = { cmd }
- vim.list_extend(cmd1, cmd_args)
-
- local ok, sysobj_or_err = pcall(vim.system, cmd1, {
+ local ok, sysobj_or_err = pcall(vim.system, cmd, {
stdin = true,
stdout = stdout_handler,
stderr = stderr_handler,
@@ -697,15 +808,16 @@ function M.start(cmd, cmd_args, dispatchers, extra_spawn_params)
if not ok then
local err = sysobj_or_err --[[@as string]]
- local msg = string.format('Spawning language server with cmd: `%s` failed', cmd)
+ local sfx --- @type string
if string.match(err, 'ENOENT') then
- msg = msg
- .. '. The language server is either not installed, missing from PATH, or not executable.'
+ sfx = '. The language server is either not installed, missing from PATH, or not executable.'
else
- msg = msg .. string.format(' with error message: %s', err)
+ sfx = string.format(' with error message: %s', err)
end
+ local msg =
+ string.format('Spawning language server with cmd: `%s` failed%s', vim.inspect(cmd), sfx)
vim.notify(msg, vim.log.levels.WARN)
- return
+ return nil
end
sysobj = sysobj_or_err --[[@as vim.SystemObj]]
diff --git a/runtime/lua/vim/lsp/semantic_tokens.lua b/runtime/lua/vim/lsp/semantic_tokens.lua
index a5831c0beb..20ac0a125f 100644
--- a/runtime/lua/vim/lsp/semantic_tokens.lua
+++ b/runtime/lua/vim/lsp/semantic_tokens.lua
@@ -1,35 +1,34 @@
local api = vim.api
local bit = require('bit')
-local handlers = require('vim.lsp.handlers')
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
--- @field type string token type as string
---- @field modifiers table token modifiers as a set. E.g., { static = true, readonly = true }
+--- @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
---- @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 (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
@@ -72,9 +71,11 @@ end
--- Extracts modifier strings from the encoded number in the token array
---
+---@param x integer
+---@param modifiers_table table<integer,string>
---@return table<string, boolean>
local function modifiers_from_number(x, modifiers_table)
- local modifiers = {}
+ local modifiers = {} ---@type table<string,boolean>
local idx = 1
while x > 0 do
if bit.band(x, 1) == 1 then
@@ -89,20 +90,24 @@ end
--- Converts a raw token list to a list of highlight ranges used by the on_win callback
---
+---@param data integer[]
+---@param bufnr integer
+---@param client vim.lsp.Client
+---@param request STActiveRequest
---@return STTokenRange[]
local function tokens_to_ranges(data, bufnr, client, request)
local legend = client.server_capabilities.semanticTokensProvider.legend
local token_types = legend.tokenTypes
local token_modifiers = legend.tokenModifiers
local lines = api.nvim_buf_get_lines(bufnr, 0, -1, false)
- local ranges = {}
+ local ranges = {} ---@type STTokenRange[]
local start = uv.hrtime()
local ms_to_ns = 1000 * 1000
local yield_interval_ns = 5 * ms_to_ns
local co, is_main = coroutine.running()
- local line
+ local line ---@type integer?
local start_char = 0
for i = 1, #data, 5 do
-- if this function is called from the main coroutine, let it run to completion with no yield
@@ -167,6 +172,7 @@ end
---
---@private
---@param bufnr integer
+---@return STHighlighter
function STHighlighter.new(bufnr)
local self = setmetatable({}, { __index = STHighlighter })
@@ -221,7 +227,7 @@ function STHighlighter.new(bufnr)
return self
end
----@private
+---@package
function STHighlighter:destroy()
for client_id, _ in pairs(self.client_state) do
self:detach(client_id)
@@ -231,7 +237,7 @@ function STHighlighter:destroy()
STHighlighter.active[self.bufnr] = nil
end
----@private
+---@package
function STHighlighter:attach(client_id)
local state = self.client_state[client_id]
if not state then
@@ -244,7 +250,7 @@ function STHighlighter:attach(client_id)
end
end
----@private
+---@package
function STHighlighter:detach(client_id)
local state = self.client_state[client_id]
if state then
@@ -267,7 +273,7 @@ end
--- Finally, if the request was successful, the requestId and document version
--- are saved to facilitate document synchronization in the response.
---
----@private
+---@package
function STHighlighter:send_request()
local version = util.buf_versions[self.bufnr]
@@ -303,7 +309,8 @@ function STHighlighter:send_request()
-- look client up again using ctx.client_id instead of using a captured
-- client object
local c = vim.lsp.get_client_by_id(ctx.client_id)
- local highlighter = STHighlighter.active[ctx.bufnr]
+ local bufnr = assert(ctx.bufnr)
+ local highlighter = STHighlighter.active[bufnr]
if not err and c and highlighter then
coroutine.wrap(STHighlighter.process_response)(highlighter, response, c, version)
end
@@ -328,6 +335,7 @@ end
--- Finally, a redraw command is issued to force nvim to redraw the screen to
--- pick up changed highlight tokens.
---
+---@param response lsp.SemanticTokens|lsp.SemanticTokensDelta
---@private
function STHighlighter:process_response(response, client, version)
local state = self.client_state[client.id]
@@ -348,15 +356,15 @@ function STHighlighter:process_response(response, client, version)
-- if we have a response to a delta request, update the state of our tokens
-- appropriately. if it's a full response, just use that
- local tokens
+ local tokens ---@type integer[]
local token_edits = response.edits
if token_edits then
table.sort(token_edits, function(a, b)
return a.start < b.start
end)
- tokens = {}
- local old_tokens = state.current_result.tokens
+ tokens = {} --- @type integer[]
+ local old_tokens = assert(state.current_result.tokens)
local idx = 1
for _, token_edit in ipairs(token_edits) do
vim.list_extend(tokens, old_tokens, idx, token_edit.start)
@@ -404,7 +412,9 @@ end
--- handler to avoid the "blink" that occurs due to the timing between the
--- response handler and the actual redraw.
---
----@private
+---@package
+---@param topline integer
+---@param botline integer
function STHighlighter:on_win(topline, botline)
for client_id, state in pairs(self.client_state) do
local current_result = state.current_result
@@ -450,7 +460,7 @@ function STHighlighter:on_win(topline, botline)
end
local ft = vim.bo[self.bufnr].filetype
- local highlights = current_result.highlights
+ local highlights = assert(current_result.highlights)
local first = lower_bound(highlights, topline, 1, #highlights + 1)
local last = upper_bound(highlights, botline, first, #highlights + 1) - 1
@@ -480,7 +490,7 @@ end
--- Reset the buffer's highlighting state and clears the extmark highlights.
---
----@private
+---@package
function STHighlighter:reset()
for client_id, state in pairs(self.client_state) do
api.nvim_buf_clear_namespace(self.bufnr, state.namespace, 0, -1)
@@ -499,7 +509,7 @@ end
--- in the on_win callback. The rest of the current results are saved
--- in case the server supports delta requests.
---
----@private
+---@package
---@param client_id integer
function STHighlighter:mark_dirty(client_id)
local state = self.client_state[client_id]
@@ -521,7 +531,7 @@ function STHighlighter:mark_dirty(client_id)
end
end
----@private
+---@package
function STHighlighter:on_change()
self:reset_timer()
if self.debounce > 0 then
@@ -562,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)
@@ -636,6 +646,10 @@ function M.stop(bufnr, client_id)
end
end
+--- @nodoc
+--- @class STTokenRangeInspect : STTokenRange
+--- @field client_id integer
+
--- Return the semantic token(s) at the given position.
--- If called without arguments, returns the token under the cursor.
---
@@ -643,13 +657,14 @@ end
---@param row integer|nil Position row (default cursor position)
---@param col integer|nil Position column (default cursor position)
---
----@return table|nil (table|nil) List of tokens at position. Each token has
+---@return STTokenRangeInspect[]|nil (table|nil) List of tokens at position. Each token has
--- the following fields:
--- - line (integer) line number, 0-based
--- - start_col (integer) start column, 0-based
--- - end_col (integer) end column, 0-based
--- - type (string) token type as string, e.g. "variable"
--- - modifiers (table) token modifiers as a set. E.g., { static = true, readonly = true }
+--- - client_id (integer)
function M.get_at_pos(bufnr, row, col)
if bufnr == nil or bufnr == 0 then
bufnr = api.nvim_get_current_buf()
@@ -665,13 +680,14 @@ function M.get_at_pos(bufnr, row, col)
row, col = cursor[1] - 1, cursor[2]
end
- local tokens = {}
+ local tokens = {} --- @type STTokenRangeInspect[]
for client_id, client in pairs(highlighter.client_state) do
local highlights = client.current_result.highlights
if highlights then
local idx = lower_bound(highlights, row, 1, #highlights + 1)
for i = idx, #highlights do
local token = highlights[i]
+ --- @cast token STTokenRangeInspect
if token.line > row then
break
@@ -712,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
@@ -720,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
@@ -747,6 +768,7 @@ function M.highlight_token(token, bufnr, client_id, hl_group, opts)
})
end
+--- @package
--- |lsp-handler| for the method `workspace/semanticTokens/refresh`
---
--- Refresh requests are sent by the server to indicate a project-wide change
@@ -754,9 +776,7 @@ end
--- invalidate the current results of all buffers and automatically kick off a
--- new request for buffers that are displayed in a window. For those that aren't, a
--- the BufWinEnter event should take care of it next time it's displayed.
----
----@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#semanticTokens_refreshRequest
-handlers[ms.workspace_semanticTokens_refresh] = function(err, _, ctx)
+function M._refresh(err, _, ctx)
if err then
return vim.NIL
end
diff --git a/runtime/lua/vim/lsp/sync.lua b/runtime/lua/vim/lsp/sync.lua
index ca01cdc08b..936579e003 100644
--- a/runtime/lua/vim/lsp/sync.lua
+++ b/runtime/lua/vim/lsp/sync.lua
@@ -53,13 +53,12 @@ local str_utf_end = vim.str_utf_end
---@param line string the line to index into
---@param byte integer the byte idx
---@param offset_encoding string utf-8|utf-16|utf-32|nil (default: utf-8)
---@returns integer the utf idx for the given encoding
+---@return integer utf_idx for the given encoding
local function byte_to_utf(line, byte, offset_encoding)
-- convert to 0 based indexing for str_utfindex
byte = byte - 1
- local utf_idx
- local _
+ local utf_idx, _ --- @type integer, integer
-- Convert the byte range to utf-{8,16,32} and convert 1-based (lua) indexing to 0-based
if offset_encoding == 'utf-16' then
_, utf_idx = str_utfindex(line, byte)
@@ -73,9 +72,11 @@ local function byte_to_utf(line, byte, offset_encoding)
return utf_idx + 1
end
+---@param line string
+---@param offset_encoding string
+---@return integer
local function compute_line_length(line, offset_encoding)
- local length
- local _
+ local length, _ --- @type integer, integer
if offset_encoding == 'utf-16' then
_, length = str_utfindex(line)
elseif offset_encoding == 'utf-32' then
@@ -94,7 +95,7 @@ end
---@return integer byte_idx of first change position
---@return integer char_idx of first change position
local function align_end_position(line, byte, offset_encoding)
- local char
+ local char --- @type integer
-- If on the first byte, or an empty string: the trivial case
if byte == 1 or #line == 0 then
char = byte
@@ -119,14 +120,19 @@ local function align_end_position(line, byte, offset_encoding)
return byte, char
end
+---@class vim.lsp.sync.Range
+---@field line_idx integer
+---@field byte_idx integer
+---@field char_idx integer
+
--- Finds the first line, byte, and char index of the difference between the previous and current lines buffer normalized to the previous codepoint.
----@param prev_lines table list of lines from previous buffer
----@param curr_lines table list of lines from current buffer
+---@param prev_lines string[] list of lines from previous buffer
+---@param curr_lines string[] list of lines from current buffer
---@param firstline integer firstline from on_lines, adjusted to 1-index
---@param lastline integer lastline from on_lines, adjusted to 1-index
---@param new_lastline integer new_lastline from on_lines, adjusted to 1-index
---@param offset_encoding string utf-8|utf-16|utf-32|nil (fallback to utf-8)
----@return table result table include line_idx, byte_idx, and char_idx of first change position
+---@return vim.lsp.sync.Range result table include line_idx, byte_idx, and char_idx of first change position
local function compute_start_range(
prev_lines,
curr_lines,
@@ -135,14 +141,14 @@ local function compute_start_range(
new_lastline,
offset_encoding
)
- local char_idx
- local byte_idx
+ local char_idx --- @type integer?
+ local byte_idx --- @type integer?
-- If firstline == lastline, no existing text is changed. All edit operations
-- occur on a new line pointed to by lastline. This occurs during insertion of
-- new lines(O), the new newline is inserted at the line indicated by
-- new_lastline.
if firstline == lastline then
- local line_idx
+ local line_idx --- @type integer
local line = prev_lines[firstline - 1]
if line then
line_idx = firstline - 1
@@ -198,15 +204,15 @@ end
--- Normalized to the next codepoint.
--- prev_end_range is the text range sent to the server representing the changed region.
--- curr_end_range is the text that should be collected and sent to the server.
---
----@param prev_lines table list of lines
----@param curr_lines table list of lines
----@param start_range table
+---
+---@param prev_lines string[] list of lines
+---@param curr_lines string[] list of lines
+---@param start_range vim.lsp.sync.Range
+---@param firstline integer
---@param lastline integer
---@param new_lastline integer
---@param offset_encoding string
----@return integer|table end_line_idx and end_col_idx of range
----@return table|nil end_col_idx of range
+---@return vim.lsp.sync.Range, vim.lsp.sync.Range
local function compute_end_range(
prev_lines,
curr_lines,
@@ -250,7 +256,7 @@ local function compute_end_range(
-- Editing the same line
-- If the byte offset is zero, that means there is a difference on the last byte (not newline)
if prev_line_idx == curr_line_idx then
- local max_length
+ local max_length --- @type integer
if start_line_idx == prev_line_idx then
-- Search until beginning of difference
max_length = min(
@@ -283,7 +289,7 @@ local function compute_end_range(
local prev_end_range =
{ line_idx = prev_line_idx, byte_idx = prev_byte_idx, char_idx = prev_char_idx }
- local curr_end_range
+ local curr_end_range ---@type vim.lsp.sync.Range
-- Deletion event, new_range cannot be before start
if curr_line_idx < start_line_idx then
curr_end_range = { line_idx = start_line_idx, byte_idx = 1, char_idx = 1 }
@@ -343,6 +349,12 @@ end
-- codeunits for utf-32
-- Line endings count here as 2 chars for \r\n (dos), 1 char for \n (unix), and 1 char for \r (mac)
-- These correspond to Windows, Linux/macOS (OSX and newer), and macOS (version 9 and prior)
+---@param lines string[]
+---@param start_range vim.lsp.sync.Range
+---@param end_range vim.lsp.sync.Range
+---@param offset_encoding string
+---@param line_ending string
+---@return integer
local function compute_range_length(lines, start_range, end_range, offset_encoding, line_ending)
local line_ending_length = #line_ending
-- Single line case
@@ -351,7 +363,7 @@ local function compute_range_length(lines, start_range, end_range, offset_encodi
end
local start_line = lines[start_range.line_idx]
- local range_length
+ local range_length --- @type integer
if start_line and #start_line > 0 then
range_length = compute_line_length(start_line, offset_encoding)
- start_range.char_idx
@@ -387,7 +399,8 @@ end
---@param lastline integer line to begin search in old_lines for last difference
---@param new_lastline integer line to begin search in new_lines for last difference
---@param offset_encoding string encoding requested by language server
----@return table TextDocumentContentChangeEvent see https://microsoft.github.io/language-server-protocol/specification/#textDocumentContentChangeEvent
+---@param line_ending string
+---@return lsp.TextDocumentContentChangeEvent : see https://microsoft.github.io/language-server-protocol/specification/#textDocumentContentChangeEvent
function M.compute_diff(
prev_lines,
curr_lines,
diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua
index 32b220746f..f8e5b6a90d 100644
--- a/runtime/lua/vim/lsp/util.lua
+++ b/runtime/lua/vim/lsp/util.lua
@@ -3,7 +3,7 @@ local snippet = require('vim.lsp._snippet_grammar')
local validate = vim.validate
local api = vim.api
local list_extend = vim.list_extend
-local highlight = require('vim.highlight')
+local highlight = vim.highlight
local uv = vim.uv
local npcall = vim.F.npcall
@@ -180,6 +180,7 @@ local _str_byteindex_enc = M._str_byteindex_enc
---@param new_lines (table) list of strings to replace the original
---@return table The modified {lines} object
function M.set_lines(lines, A, B, new_lines)
+ vim.deprecate('vim.lsp.util.set_lines()', 'nil', '0.12')
-- 0-indexing to 1-indexing
local i_0 = A[1] + 1
-- If it extends past the end, truncate it to the end. This is because the
@@ -346,7 +347,7 @@ end
---@private
---@deprecated Use vim.lsp.status() or access client.progress directly
function M.get_progress_messages()
- vim.deprecate('vim.lsp.util.get_progress_messages', 'vim.lsp.status', '0.11.0')
+ vim.deprecate('vim.lsp.util.get_progress_messages()', 'vim.lsp.status()', '0.11')
local new_messages = {}
local progress_remove = {}
@@ -418,6 +419,9 @@ function M.apply_text_edits(text_edits, bufnr, offset_encoding)
if not next(text_edits) then
return
end
+
+ assert(bufnr ~= 0, 'Explicit buffer number is required')
+
if not api.nvim_buf_is_loaded(bufnr) then
vim.fn.bufload(bufnr)
end
@@ -456,7 +460,7 @@ function M.apply_text_edits(text_edits, bufnr, offset_encoding)
-- save and restore local marks since they get deleted by nvim_buf_set_lines
local marks = {}
- for _, m in pairs(vim.fn.getmarklist(bufnr or vim.api.nvim_get_current_buf())) do
+ for _, m in pairs(vim.fn.getmarklist(bufnr)) do
if m.mark:match("^'[a-z]$") then
marks[m.mark:sub(2, 2)] = { m.pos[2], m.pos[3] - 1 } -- api-indexed
end
@@ -515,7 +519,7 @@ function M.apply_text_edits(text_edits, bufnr, offset_encoding)
local max = api.nvim_buf_line_count(bufnr)
-- no need to restore marks that still exist
- for _, m in pairs(vim.fn.getmarklist(bufnr or vim.api.nvim_get_current_buf())) do
+ for _, m in pairs(vim.fn.getmarklist(bufnr)) do
marks[m.mark:sub(2, 2)] = nil
end
-- restore marks
@@ -547,12 +551,16 @@ end
--- Can be used to extract the completion items from a
--- `textDocument/completion` request, which may return one of
--- `CompletionItem[]`, `CompletionList` or null.
+---
+--- Note that this method doesn't apply `itemDefaults` to `CompletionList`s, and hence the returned
+--- results might be incorrect.
+---
---@deprecated
---@param result table The result of a `textDocument/completion` request
---@return lsp.CompletionItem[] List of completion items
---@see https://microsoft.github.io/language-server-protocol/specification#textDocument_completion
function M.extract_completion_items(result)
- vim.deprecate('vim.lsp.util.extract_completion_items', nil, '0.11')
+ vim.deprecate('vim.lsp.util.extract_completion_items()', nil, '0.11')
if type(result) == 'table' and result.items then
-- result is a `CompletionList`
return result.items
@@ -570,6 +578,7 @@ end
---
---@param text_document_edit table: a `TextDocumentEdit` object
---@param index integer: Optional index of the edit, if from a list of edits (or nil, if not from a list)
+---@param offset_encoding? string
---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentEdit
function M.apply_text_document_edit(text_document_edit, index, offset_encoding)
local text_document = text_document_edit.textDocument
@@ -612,7 +621,7 @@ end
---@param input string unparsed snippet
---@return string parsed snippet
function M.parse_snippet(input)
- vim.deprecate('vim.lsp.util.parse_snippet', nil, '0.11')
+ vim.deprecate('vim.lsp.util.parse_snippet()', nil, '0.11')
local ok, parsed = pcall(function()
return snippet.parse(input)
end)
@@ -634,76 +643,129 @@ end
---@return table[] items
---@see complete-items
function M.text_document_completion_list_to_complete_items(result, prefix)
- vim.deprecate('vim.lsp.util.text_document_completion_list_to_complete_items', nil, '0.11')
- return require('vim.lsp._completion')._lsp_to_complete_items(result, prefix)
+ vim.deprecate('vim.lsp.util.text_document_completion_list_to_complete_items()', nil, '0.11')
+ return vim.lsp._completion._lsp_to_complete_items(result, prefix)
end
---- Like vim.fn.bufwinid except it works across tabpages.
-local function bufwinid(bufnr)
- for _, win in ipairs(api.nvim_list_wins()) do
- if api.nvim_win_get_buf(win) == bufnr then
- return win
+local function path_components(path)
+ return vim.split(path, '/', { plain = true })
+end
+
+local function path_under_prefix(path, prefix)
+ for i, c in ipairs(prefix) do
+ if c ~= path[i] then
+ return false
end
end
+ return true
end
---- Get list of buffers for a directory
-local function get_dir_bufs(path)
- path = path:gsub('([^%w])', '%%%1')
+--- Get list of buffers whose filename matches the given path prefix (normalized full path)
+---@return integer[]
+local function get_bufs_with_prefix(prefix)
+ prefix = path_components(prefix)
local buffers = {}
for _, v in ipairs(vim.api.nvim_list_bufs()) do
- local bufname = vim.api.nvim_buf_get_name(v):gsub('buffer://', '')
- if bufname:find(path) then
+ local bname = vim.api.nvim_buf_get_name(v)
+ local path = path_components(vim.fs.normalize(bname, { expand_env = false }))
+ if path_under_prefix(path, prefix) then
table.insert(buffers, v)
end
end
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 opts (table)
--- overwrite? bool
--- ignoreIfExists? bool
+--- 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 thme 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 target_exists = uv.fs_stat(new_fname) ~= nil
- if target_exists and not opts.overwrite or opts.ignoreIfExists then
- vim.notify('Rename target already exists. Skipping rename.')
+ local skip = not opts.overwrite or opts.ignoreIfExists
+
+ local old_fname_full = vim.uv.fs_realpath(vim.fs.normalize(old_fname, { expand_env = false }))
+ if not old_fname_full then
+ vim.notify('Invalid path: ' .. old_fname, vim.log.levels.ERROR)
return
end
- local oldbufs = {}
- local win = nil
-
- if vim.fn.isdirectory(old_fname) == 1 then
- oldbufs = get_dir_bufs(old_fname)
- else
- local oldbuf = vim.fn.bufadd(old_fname)
- table.insert(oldbufs, oldbuf)
- win = bufwinid(oldbuf)
+ local target_exists = uv.fs_stat(new_fname) ~= nil
+ if target_exists and skip then
+ vim.notify(new_fname .. ' already exists. Skipping rename.', vim.log.levels.ERROR)
+ return
end
- for _, b in ipairs(oldbufs) do
- vim.fn.bufload(b)
- -- The there may be pending changes in the buffer
- api.nvim_buf_call(b, function()
- vim.cmd('w!')
+ 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 ok, err = os.rename(old_fname, new_fname)
+ local newdir = assert(vim.fs.dirname(new_fname))
+ vim.fn.mkdir(newdir, 'p')
+
+ 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
- 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
@@ -745,7 +807,7 @@ end
---
---@param workspace_edit table `WorkspaceEdit`
---@param offset_encoding string utf-8|utf-16|utf-32 (required)
---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
function M.apply_workspace_edit(workspace_edit, offset_encoding)
if offset_encoding == nil then
vim.notify_once(
@@ -789,7 +851,7 @@ end
--- Note that if the input is of type `MarkupContent` and its kind is `plaintext`,
--- then the corresponding value is returned without further modifications.
---
----@param input (`MarkedString` | `MarkedString[]` | `MarkupContent`)
+---@param input (lsp.MarkedString | lsp.MarkedString[] | lsp.MarkupContent)
---@param contents (table|nil) List of strings to extend with converted lines. Defaults to {}.
---@return string[] extended with lines of converted markdown.
---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_hover
@@ -1055,7 +1117,7 @@ function M.show_document(location, offset_encoding, opts)
vim.fn.settagstack(vim.fn.win_getid(), { items = items }, 't')
end
- local win = opts.reuse_win and bufwinid(bufnr)
+ local win = opts.reuse_win and vim.fn.win_findbuf(bufnr)[1]
or focus and api.nvim_get_current_win()
or create_window_without_focus()
@@ -1068,7 +1130,7 @@ function M.show_document(location, offset_encoding, opts)
-- location may be Location or LocationLink
local range = location.range or location.targetSelectionRange
if range then
- --- Jump to new location (adjusting for encoding of characters)
+ -- Jump to new location (adjusting for encoding of characters)
local row = range.start.line
local col = get_line_byte_from_position(bufnr, range.start, offset_encoding)
api.nvim_win_set_cursor(win, { row + 1, col })
@@ -1105,6 +1167,7 @@ end
--- - for LocationLink, targetRange is shown (e.g., body of function definition)
---
---@param location table a single `Location` or `LocationLink`
+---@param opts table
---@return integer|nil buffer id of float window
---@return integer|nil window id of float window
function M.preview_location(location, opts)
@@ -1218,6 +1281,7 @@ end
---
--- If you want to open a popup with fancy markdown, use `open_floating_preview` instead
---
+---@param bufnr integer
---@param contents table of lines to show in window
---@param opts table with optional fields
--- - height of floating window
@@ -1410,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.
@@ -1426,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)
@@ -1497,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
@@ -1573,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)
@@ -1754,6 +1844,14 @@ local position_sort = sort_by_key(function(v)
return { v.start.line, v.start.character }
end)
+---@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
+---@field text string
+---@field user_data lsp.Location|lsp.LocationLink
+
--- Returns the items with the byte position calculated correctly and in sorted
--- order, for display in quickfix and location lists.
---
@@ -1763,10 +1861,10 @@ end)
--- The result can be passed to the {list} argument of |setqflist()| or
--- |setloclist()|.
---
----@param locations table list of `Location`s or `LocationLink`s
+---@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 table 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(
@@ -1777,6 +1875,7 @@ function M.locations_to_items(locations, offset_encoding)
end
local items = {}
+ ---@type table<string, {start: lsp.Position, location: lsp.Location|lsp.LocationLink}[]>
local grouped = setmetatable({}, {
__index = function(t, k)
local v = {}
@@ -1791,6 +1890,7 @@ function M.locations_to_items(locations, offset_encoding)
table.insert(grouped[uri], { start = range.start, location = d })
end
+ ---@type string[]
local keys = vim.tbl_keys(grouped)
table.sort(keys)
-- TODO(ashkan) I wish we could do this lazily.
@@ -1799,16 +1899,13 @@ function M.locations_to_items(locations, offset_encoding)
table.sort(rows, position_sort)
local filename = vim.uri_to_fname(uri)
- -- list of row numbers
- local uri_rows = {}
+ local line_numbers = {}
for _, temp in ipairs(rows) do
- local pos = temp.start
- local row = pos.line
- table.insert(uri_rows, row)
+ table.insert(line_numbers, temp.start.line)
end
-- get all the lines for this uri
- local lines = get_lines(vim.uri_to_bufnr(uri), uri_rows)
+ local lines = get_lines(vim.uri_to_bufnr(uri), line_numbers)
for _, temp in ipairs(rows) do
local pos = temp.start
@@ -1837,6 +1934,7 @@ end
--- Converts symbols to quickfix list items.
---
---@param symbols table DocumentSymbol[] or SymbolInformation[]
+---@param bufnr integer
function M.symbols_to_items(symbols, bufnr)
local function _symbols_to_items(_symbols, _items, _bufnr)
for _, symbol in ipairs(_symbols) do
@@ -1879,6 +1977,7 @@ end
---@param lines table list of lines to trim
---@return table trimmed list of lines
function M.trim_empty_lines(lines)
+ vim.deprecate('vim.lsp.util.trim_empty_lines()', 'vim.split() with `trimempty`', '0.12')
local start = 1
for i = 1, #lines do
if lines[i] ~= nil and #lines[i] > 0 then
@@ -1905,6 +2004,7 @@ end
---@param lines table list of lines
---@return string filetype or "markdown" if it was unchanged.
function M.try_trim_markdown_code_blocks(lines)
+ vim.deprecate('vim.lsp.util.try_trim_markdown_code_blocks()', 'nil', '0.12')
local language_id = lines[1]:match('^```(.*)')
if language_id then
local has_inner_code_fence = false
@@ -2089,7 +2189,7 @@ end
--- Creates a `DocumentFormattingParams` object for the current buffer and cursor position.
---
---@param options table|nil with valid `FormattingOptions` entries
----@return `DocumentFormattingParams` object
+---@return lsp.DocumentFormattingParams object
---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_formatting
function M.make_formatting_params(options)
validate({ options = { options, 't', true } })
@@ -2106,8 +2206,8 @@ end
--- Returns the UTF-32 and UTF-16 offsets for a position in a certain buffer.
---
---@param buf integer buffer number (0 for current)
----@param row 0-indexed line
----@param col 0-indexed byte offset in line
+---@param row integer 0-indexed line
+---@param col integer 0-indexed byte offset in line
---@param offset_encoding string utf-8|utf-16|utf-32 defaults to `offset_encoding` of first client of `buf`
---@return integer `offset_encoding` index of the character in line {row} column {col} in buffer {buf}
function M.character_offset(buf, row, col, offset_encoding)
@@ -2130,8 +2230,10 @@ end
---
---@param settings table language server settings
---@param section string indicating the field of the settings table
----@return table|string The value of settings accessed via section
+---@return table|string|vim.NIL The value of settings accessed via section. `vim.NIL` if not found.
+---@deprecated
function M.lookup_section(settings, section)
+ vim.deprecate('vim.lsp.util.lookup_section()', 'vim.tbl_get() with `vim.split`', '0.12')
for part in vim.gsplit(section, '.', { plain = true }) do
settings = settings[part]
if settings == nil then
@@ -2170,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
@@ -2228,6 +2330,6 @@ end
M._get_line_byte_from_position = get_line_byte_from_position
---@nodoc
-M.buf_versions = {}
+M.buf_versions = {} ---@type table<integer,integer>
return M
diff --git a/runtime/lua/vim/provider.lua b/runtime/lua/vim/provider.lua
new file mode 100644
index 0000000000..08b3fd6cbd
--- /dev/null
+++ b/runtime/lua/vim/provider.lua
@@ -0,0 +1,7 @@
+local M = vim._defer_require('vim.provider', {
+ perl = ..., --- @module 'vim.provider.perl'
+ python = ..., --- @module 'vim.provider.python'
+ ruby = ..., --- @module 'vim.provider.ruby'
+})
+
+return M
diff --git a/runtime/lua/vim/provider/perl.lua b/runtime/lua/vim/provider/perl.lua
new file mode 100644
index 0000000000..da4af0a2a7
--- /dev/null
+++ b/runtime/lua/vim/provider/perl.lua
@@ -0,0 +1,66 @@
+local M = {}
+local s_err ---@type string?
+local s_host ---@type string?
+
+function M.require(host, prog)
+ local args = { prog, '-e', 'use Neovim::Ext; start_host();' }
+
+ -- Collect registered perl plugins into args
+ local perl_plugins = vim.fn['remote#host#PluginsForHost'](host.name) ---@type any
+ ---@param plugin any
+ for _, plugin in ipairs(perl_plugins) do
+ table.insert(args, plugin.path)
+ end
+
+ return vim.fn['provider#Poll'](args, host.orig_name, '$NVIM_PERL_LOG_FILE')
+end
+
+--- @return string? path to detected perl, if any; nil if not found
+--- @return string? error message if perl can't be detected; nil if success
+function M.detect()
+ -- use g:perl_host_prog if set or check if perl is on the path
+ local prog = vim.fn.exepath(vim.g.perl_host_prog or 'perl')
+ if prog == '' then
+ return nil, 'No perl executable found'
+ end
+
+ -- if perl is available, make sure we have 5.22+
+ vim.fn.system({ prog, '-e', 'use v5.22' })
+ if vim.v.shell_error ~= 0 then
+ return nil, 'Perl version is too old, 5.22+ required'
+ end
+
+ -- if perl is available, make sure the required module is available
+ vim.fn.system({ prog, '-W', '-MNeovim::Ext', '-e', '' })
+ if vim.v.shell_error ~= 0 then
+ return nil, '"Neovim::Ext" cpan module is not installed'
+ end
+ return prog, nil
+end
+
+function M.call(method, args)
+ if s_err then
+ return
+ end
+
+ if not s_host then
+ -- Ensure that we can load the Perl host before bootstrapping
+ local ok, result = pcall(vim.fn['remote#host#Require'], 'legacy-perl-provider') ---@type any, any
+ if not ok then
+ s_err = result
+ vim.api.nvim_echo({ { result, 'WarningMsg' } }, true, {})
+ return
+ end
+ s_host = result
+ end
+
+ return vim.fn.rpcrequest(s_host, 'perl_' .. method, unpack(args))
+end
+
+function M.start()
+ -- The perl provider plugin will run in a separate instance of the perl host.
+ vim.fn['remote#host#RegisterClone']('legacy-perl-provider', 'perl')
+ vim.fn['remote#host#RegisterPlugin']('legacy-perl-provider', 'ScriptHost.pm', {})
+end
+
+return M
diff --git a/runtime/lua/vim/provider/python.lua b/runtime/lua/vim/provider/python.lua
new file mode 100644
index 0000000000..8322131238
--- /dev/null
+++ b/runtime/lua/vim/provider/python.lua
@@ -0,0 +1,150 @@
+local M = {}
+local min_version = '3.7'
+local s_err ---@type string?
+local s_host ---@type string?
+
+local python_candidates = {
+ 'python3',
+ 'python3.12',
+ 'python3.11',
+ 'python3.10',
+ 'python3.9',
+ 'python3.8',
+ 'python3.7',
+ 'python',
+}
+
+--- @param prog string
+--- @param module string
+--- @return integer, string
+local function import_module(prog, module)
+ local program = [[
+import sys, importlib.util;
+sys.path = [p for p in sys.path if p != ""];
+sys.stdout.write(str(sys.version_info[0]) + "." + str(sys.version_info[1]));]]
+
+ program = program
+ .. string.format('sys.exit(2 * int(importlib.util.find_spec("%s") is None))', module)
+
+ local out = vim.system({ prog, '-W', 'ignore', '-c', program }):wait()
+ return out.code, assert(out.stdout)
+end
+
+--- @param prog string
+--- @param module string
+--- @return string?
+local function check_for_module(prog, module)
+ local prog_path = vim.fn.exepath(prog)
+ if prog_path == '' then
+ return prog .. ' not found in search path or not executable.'
+ end
+
+ -- Try to load module, and output Python version.
+ -- Exit codes:
+ -- 0 module can be loaded.
+ -- 2 module cannot be loaded.
+ -- Otherwise something else went wrong (e.g. 1 or 127).
+ local prog_exitcode, prog_version = import_module(prog, module)
+ if prog_exitcode == 2 or prog_exitcode == 0 then
+ -- Check version only for expected return codes.
+ if vim.version.lt(prog_version, min_version) then
+ return string.format(
+ '%s is Python %s and cannot provide Python >= %s.',
+ prog_path,
+ prog_version,
+ min_version
+ )
+ end
+ end
+
+ if prog_exitcode == 2 then
+ return string.format('%s does not have the "%s" module.', prog_path, module)
+ elseif prog_exitcode == 127 then
+ -- This can happen with pyenv's shims.
+ return string.format('%s does not exist: %s', prog_path, prog_version)
+ elseif prog_exitcode ~= 0 then
+ return string.format(
+ 'Checking %s caused an unknown error. (%s, output: %s) Report this at https://github.com/neovim/neovim',
+ prog_path,
+ prog_exitcode,
+ prog_version
+ )
+ end
+
+ return nil
+end
+
+--- @param module string
+--- @return string? path to detected python, if any; nil if not found
+--- @return string? error message if python can't be detected by {module}; nil if success
+function M.detect_by_module(module)
+ local python_exe = vim.fn.expand(vim.g.python3_host_prog or '', true)
+
+ if python_exe ~= '' then
+ return vim.fn.exepath(vim.fn.expand(python_exe, true)), nil
+ end
+
+ local errors = {}
+ for _, exe in ipairs(python_candidates) do
+ local error = check_for_module(exe, module)
+ if not error then
+ return exe, error
+ end
+ -- Accumulate errors in case we don't find any suitable Python executable.
+ table.insert(errors, error)
+ end
+
+ -- No suitable Python executable found.
+ return nil, 'Could not load Python :\n' .. table.concat(errors, '\n')
+end
+
+function M.require(host)
+ -- Python host arguments
+ local prog = M.detect_by_module('neovim')
+ local args = {
+ prog,
+ '-c',
+ 'import sys; sys.path = [p for p in sys.path if p != ""]; import neovim; neovim.start_host()',
+ }
+
+ -- Collect registered Python plugins into args
+ local python_plugins = vim.fn['remote#host#PluginsForHost'](host.name) ---@type any
+ ---@param plugin any
+ for _, plugin in ipairs(python_plugins) do
+ table.insert(args, plugin.path)
+ end
+
+ return vim.fn['provider#Poll'](
+ args,
+ host.orig_name,
+ '$NVIM_PYTHON_LOG_FILE',
+ { ['overlapped'] = true }
+ )
+end
+
+function M.call(method, args)
+ if s_err then
+ return
+ end
+
+ if not s_host then
+ -- Ensure that we can load the Python3 host before bootstrapping
+ local ok, result = pcall(vim.fn['remote#host#Require'], 'legacy-python3-provider') ---@type any, any
+ if not ok then
+ s_err = result
+ vim.api.nvim_echo({ { result, 'WarningMsg' } }, true, {})
+ return
+ end
+ s_host = result
+ end
+
+ return vim.fn.rpcrequest(s_host, 'python_' .. method, unpack(args))
+end
+
+function M.start()
+ -- The Python3 provider plugin will run in a separate instance of the Python3 host.
+ vim.fn['remote#host#RegisterClone']('legacy-python3-provider', 'python3')
+ vim.fn['remote#host#RegisterPlugin']('legacy-python3-provider', 'script_host.py', {})
+end
+
+return M
diff --git a/runtime/lua/vim/provider/ruby.lua b/runtime/lua/vim/provider/ruby.lua
new file mode 100644
index 0000000000..3ad86001f3
--- /dev/null
+++ b/runtime/lua/vim/provider/ruby.lua
@@ -0,0 +1,61 @@
+local M = {}
+local s_err ---@type string?
+local s_host ---@type string?
+
+function M.require(host)
+ local prog = M.detect()
+ local args = { prog }
+ local ruby_plugins = vim.fn['remote#host#PluginsForHost'](host.name) ---@type any
+
+ ---@param plugin any
+ for _, plugin in ipairs(ruby_plugins) do
+ table.insert(args, plugin.path)
+ end
+
+ return vim.fn['provider#Poll'](args, host.orig_name, '$NVIM_RUBY_LOG_FILE')
+end
+
+function M.call(method, args)
+ if s_err then
+ return
+ end
+
+ if not s_host then
+ local ok, result = pcall(vim.fn['remote#host#Require'], 'legacy-ruby-provider') ---@type any, any
+ if not ok then
+ s_err = result
+ vim.api.nvim_echo({ { result, 'WarningMsg' } }, true, {})
+ return
+ end
+ s_host = result
+ end
+
+ return vim.fn.rpcrequest(s_host, 'ruby_' .. method, unpack(args))
+end
+
+function M.detect()
+ local prog ---@type string
+ if vim.g.ruby_host_prog then
+ prog = vim.fn.expand(vim.g.ruby_host_prog, true)
+ elseif vim.fn.has('win32') == 1 then
+ prog = vim.fn.exepath('neovim-ruby-host.bat')
+ else
+ local p = vim.fn.exepath('neovim-ruby-host')
+ if p == '' then
+ prog = ''
+ else
+ -- neovim-ruby-host could be an rbenv shim for another Ruby version.
+ vim.fn.system(p)
+ prog = vim.v.shell_error ~= 0 and '' or p
+ end
+ end
+ local err = prog == '' and 'missing ruby or ruby-host' or ''
+ return prog, err
+end
+
+function M.start(plugin_path)
+ vim.fn['remote#host#RegisterClone']('legacy-ruby-provider', 'ruby')
+ vim.fn['remote#host#RegisterPlugin']('legacy-ruby-provider', plugin_path, {})
+end
+
+return M
diff --git a/runtime/lua/vim/re.lua b/runtime/lua/vim/re.lua
index 007eb27ed8..114f74eb80 100644
--- a/runtime/lua/vim/re.lua
+++ b/runtime/lua/vim/re.lua
@@ -3,6 +3,7 @@
-- written by Roberto Ierusalimschy
--
--- vendored from lpeg-1.1.0
+--- documentation available at runtime/lua/vim/_meta/re.lua
-- imported functions and modules
local tonumber, type, print, error = tonumber, type, print, error
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 9542d93789..bd553598c7 100644
--- a/runtime/lua/vim/shared.lua
+++ b/runtime/lua/vim/shared.lua
@@ -6,46 +6,40 @@
-- or the test suite. (Eventually the test suite will be run in a worker process,
-- so this wouldn't be a separate case to consider)
+---@nodoc
---@diagnostic disable-next-line: lowercase-global
vim = vim or {}
-local function _id(v)
- return v
-end
+---@generic T
+---@param orig T
+---@param cache? table<any,any>
+---@return T
+local function deepcopy(orig, cache)
+ if orig == vim.NIL then
+ return vim.NIL
+ elseif type(orig) == 'userdata' or type(orig) == 'thread' then
+ error('Cannot deepcopy object of type ' .. type(orig))
+ elseif type(orig) ~= 'table' then
+ return orig
+ end
-local deepcopy
+ --- @cast orig table<any,any>
-local deepcopy_funcs = {
- table = function(orig, cache)
- if cache[orig] then
- return cache[orig]
- end
- local copy = {}
+ if cache and cache[orig] then
+ return cache[orig]
+ end
+ local copy = {} --- @type table<any,any>
+
+ if cache then
cache[orig] = copy
- local mt = getmetatable(orig)
- for k, v in pairs(orig) do
- copy[deepcopy(k, cache)] = deepcopy(v, cache)
- end
- return setmetatable(copy, mt)
- end,
- number = _id,
- string = _id,
- ['nil'] = _id,
- boolean = _id,
- ['function'] = _id,
-}
-
-deepcopy = function(orig, _cache)
- local f = deepcopy_funcs[type(orig)]
- if f then
- return f(orig, _cache or {})
- else
- if type(orig) == 'userdata' and orig == vim.NIL then
- return vim.NIL
- end
- error('Cannot deepcopy object of type ' .. type(orig))
end
+
+ for k, v in pairs(orig) do
+ copy[deepcopy(k, cache)] = deepcopy(v, cache)
+ end
+
+ return setmetatable(copy, getmetatable(orig))
end
--- Returns a deep copy of the given object. Non-table objects are copied as
@@ -54,13 +48,31 @@ end
--- same functions as those in the input table. Userdata and threads are not
--- copied and will throw an error.
---
+--- Note: `noref=true` is much more performant on tables with unique table
+--- fields, while `noref=false` is more performant on tables that reuse table
+--- fields.
+---
---@generic T: table
---@param orig T Table to copy
+---@param noref? boolean
+--- When `false` (default) a contained table is only copied once and all
+--- references point to this single copy. When `true` every occurrence of a
+--- table results in a new copy. This also means that a cyclic reference can
+--- cause `deepcopy()` to fail.
---@return T Table of copied keys and (nested) values.
-function vim.deepcopy(orig)
- return deepcopy(orig)
+function vim.deepcopy(orig, noref)
+ return deepcopy(orig, not noref and {} or nil)
end
+--- @class vim.gsplit.Opts
+--- @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").
---
@@ -89,12 +101,10 @@ end
---
--- @param s string String to split
--- @param sep string Separator or pattern
---- @param opts (table|nil) 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
+ local plain --- @type boolean?
local trimempty = false
if type(opts) == 'boolean' then
plain = opts -- For backwards compatibility.
@@ -111,6 +121,11 @@ function vim.gsplit(s, sep, opts)
local segs = {}
local empty_start = true -- Only empty segments seen so far.
+ --- @param i integer?
+ --- @param j integer
+ --- @param ... unknown
+ --- @return string
+ --- @return ...
local function _pass(i, j, ...)
if i then
assert(j + 1 > start, 'Infinite loop detected')
@@ -180,8 +195,8 @@ end
---
---@param s string String to split
---@param sep string Separator or pattern
----@param opts (table|nil) Keyword arguments |kwargs| accepted by |vim.gsplit()|
----@return string[] List of split components
+---@param opts? vim.gsplit.Opts Keyword arguments |kwargs|:
+---@return string[] : List of split components
function vim.split(s, sep, opts)
local t = {}
for c in vim.gsplit(s, sep, opts) do
@@ -195,14 +210,15 @@ end
---
---@see From https://github.com/premake/premake-core/blob/master/src/base/table.lua
---
----@generic T: table
+---@generic T
---@param t table<T, any> (table) Table
----@return T[] (list) List of keys
+---@return T[] : List of keys
function vim.tbl_keys(t)
- assert(type(t) == 'table', string.format('Expected table, got %s', type(t)))
+ vim.validate({ t = { t, 't' } })
+ --- @cast t table<any,any>
local keys = {}
- for k, _ in pairs(t) do
+ for k in pairs(t) do
table.insert(keys, k)
end
return keys
@@ -213,12 +229,14 @@ end
---
---@generic T
---@param t table<any, T> (table) Table
----@return T[] (list) List of values
+---@return T[] : List of values
function vim.tbl_values(t)
- assert(type(t) == 'table', string.format('Expected table, got %s', type(t)))
+ vim.validate({ t = { t, 't' } })
local values = {}
- for _, v in pairs(t) do
+ for _, v in
+ pairs(t --[[@as table<any,any>]])
+ do
table.insert(values, v)
end
return values
@@ -227,13 +245,14 @@ 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
----@return table Table of transformed values
+---@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' } })
+ --- @cast t table<any,any>
- local rettab = {}
+ local rettab = {} --- @type table<any,any>
for k, v in pairs(t) do
rettab[k] = func(v)
end
@@ -245,19 +264,26 @@ end
---@generic T
---@param func fun(value: T): boolean (function) Function
---@param t table<any, T> (table) Table
----@return T[] (table) Table of filtered values
+---@return T[] : Table of filtered values
function vim.tbl_filter(func, t)
vim.validate({ func = { func, 'c' }, t = { t, 't' } })
+ --- @cast t table<any,any>
- local rettab = {}
+ local rettab = {} --- @type table<any,any>
for _, entry in pairs(t) do
if func(entry) then
- table.insert(rettab, entry)
+ rettab[#rettab + 1] = entry
end
end
return rettab
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
--- a predicate that is checked for each value.
---
@@ -274,13 +300,13 @@ end
---
---@param t table Table to check
---@param value any Value to compare or predicate function reference
----@param opts (table|nil) 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 } })
+ --- @cast t table<any,any>
- local pred
+ local pred --- @type fun(v: any): boolean?
if opts and opts.predicate then
vim.validate({ value = { value, 'c' } })
pred = value
@@ -307,6 +333,7 @@ end
---@return boolean `true` if `t` contains `value`
function vim.list_contains(t, value)
vim.validate({ t = { t, 't' } })
+ --- @cast t table<any,any>
for _, v in ipairs(t) do
if v == value then
@@ -323,7 +350,7 @@ end
---@param t table Table to check
---@return boolean `true` if `t` is empty
function vim.tbl_isempty(t)
- assert(type(t) == 'table', string.format('Expected table, got %s', type(t)))
+ vim.validate({ t = { t, 't' } })
return next(t) == nil
end
@@ -345,7 +372,7 @@ local function tbl_extend(behavior, deep_extend, ...)
)
end
- local ret = {}
+ local ret = {} --- @type table<any,any>
if vim._empty_dict_mt ~= nil and getmetatable(select(1, ...)) == vim._empty_dict_mt then
ret = vim.empty_dict()
end
@@ -353,6 +380,7 @@ local function tbl_extend(behavior, deep_extend, ...)
for i = 1, select('#', ...) do
local tbl = select(i, ...)
vim.validate({ ['after the second argument'] = { tbl, 't' } })
+ --- @cast tbl table<any,any>
if tbl then
for k, v in pairs(tbl) do
if deep_extend and can_merge(v) and can_merge(ret[k]) then
@@ -379,7 +407,7 @@ end
--- - "keep": use value from the leftmost map
--- - "force": use value from the rightmost map
---@param ... table Two or more tables
----@return table Merged table
+---@return table : Merged table
function vim.tbl_extend(behavior, ...)
return tbl_extend(behavior, false, ...)
end
@@ -415,12 +443,14 @@ function vim.deep_equal(a, b)
return false
end
if type(a) == 'table' then
+ --- @cast a table<any,any>
+ --- @cast b table<any,any>
for k, v in pairs(a) do
if not vim.deep_equal(v, b[k]) then
return false
end
end
- for k, _ in pairs(b) do
+ for k in pairs(b) do
if a[k] == nil then
return false
end
@@ -432,12 +462,17 @@ end
--- Add the reverse lookup values to an existing table.
--- For example:
---- ``tbl_add_reverse_lookup { A = 1 } == { [1] = 'A', A = 1 }``
+--- `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)
for _, k in ipairs(keys) do
local v = o[k]
@@ -467,15 +502,14 @@ end
---
---@param o table Table to index
---@param ... any Optional keys (0 or more, variadic) via which to index the table
----
----@return any Nested value indexed by key (if it exists), else nil
+---@return any : Nested value indexed by key (if it exists), else nil
function vim.tbl_get(o, ...)
local keys = { ... }
if #keys == 0 then
return nil
end
for i, k in ipairs(keys) do
- o = o[k]
+ o = o[k] --- @type any
if o == nil then
return nil
elseif type(o) ~= 'table' and next(keys, i) then
@@ -494,8 +528,8 @@ end
---@generic T: table
---@param dst T List which will be modified and appended to
---@param src table List from which values will be inserted
----@param start (integer|nil) Start index on src. Defaults to 1
----@param finish (integer|nil) Final index on src. Defaults to `#src`
+---@param start integer? Start index on src. Defaults to 1
+---@param finish integer? Final index on src. Defaults to `#src`
---@return T dst
function vim.list_extend(dst, src, start, finish)
vim.validate({
@@ -519,6 +553,7 @@ end
---@return table Flattened copy of the given list-like table
function vim.tbl_flatten(t)
local result = {}
+ --- @param _t table<any,any>
local function _tbl_flatten(_t)
local n = #_t
for i = 1, n do
@@ -538,10 +573,13 @@ 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)
- assert(type(t) == 'table', string.format('Expected table, got %s', type(t)))
+ vim.validate({ t = { t, 't' } })
+ --- @cast t table<any,any>
-- collect the keys
local keys = {}
@@ -557,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).
@@ -576,10 +615,12 @@ function vim.tbl_isarray(t)
return false
end
+ --- @cast t table<any,any>
+
local count = 0
for k, _ in pairs(t) do
- --- Check if the number k is an integer
+ -- Check if the number k is an integer
if type(k) == 'number' and k == math.floor(k) then
count = count + 1
else
@@ -614,23 +655,21 @@ function vim.tbl_islist(t)
return false
end
- local num_elem = vim.tbl_count(t)
-
- if num_elem == 0 then
- -- TODO(bfredl): in the future, we will always be inside nvim
- -- then this check can be deleted.
- if vim._empty_dict_mt == nil then
- return nil
- end
+ 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`.
@@ -642,9 +681,10 @@ end
---
---@see https://github.com/Tieske/Penlight/blob/master/lua/pl/tablex.lua
---@param t table Table
----@return integer Number of non-nil values in table
+---@return integer : Number of non-nil values in table
function vim.tbl_count(t)
vim.validate({ t = { t, 't' } })
+ --- @cast t table<any,any>
local count = 0
for _ in pairs(t) do
@@ -656,12 +696,12 @@ end
--- Creates a copy of a table containing only elements from start to end (inclusive)
---
---@generic T
----@param list T[] (list) Table
+---@param list T[] Table
---@param start integer|nil Start range of slice
---@param finish integer|nil End range of slice
----@return T[] (list) Copy of table sliced from start to finish (inclusive)
+---@return T[] Copy of table sliced from start to finish (inclusive)
function vim.list_slice(list, start, finish)
- local new_list = {}
+ local new_list = {} --- @type `T`[]
for i = start or 1, finish or #list do
new_list[#new_list + 1] = list[i]
end
@@ -710,6 +750,16 @@ function vim.endswith(s, suffix)
end
do
+ --- @alias vim.validate.Type
+ --- | 't' | 'table'
+ --- | 's' | 'string'
+ --- | 'n' | 'number'
+ --- | 'f' | 'function'
+ --- | 'c' | 'callable'
+ --- | 'nil'
+ --- | 'thread'
+ --- | 'userdata
+
local type_names = {
['table'] = 'table',
t = 'table',
@@ -728,10 +778,18 @@ 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
+ --- @field [3]? boolean
+
local function _is_type(val, t)
return type(val) == t or (t == 'callable' and vim.is_callable(val))
end
+ --- @param opt table<vim.validate.Type,vim.validate.Spec>
+ --- @return boolean, string?
local function is_valid(opt)
if type(opt) ~= 'table' then
return false, string.format('opt: expected table, got %s', type(opt))
@@ -790,7 +848,7 @@ do
end
end
- return true, nil
+ return true
end
--- Validates a parameter specification (types and values).
@@ -798,41 +856,40 @@ do
--- Usage example:
---
--- ```lua
- --- function user.new(name, age, hobbies)
- --- vim.validate{
- --- name={name, 'string'},
- --- age={age, 'number'},
- --- hobbies={hobbies, 'table'},
- --- }
- --- ...
- --- end
+ --- function user.new(name, age, hobbies)
+ --- vim.validate{
+ --- name={name, 'string'},
+ --- age={age, 'number'},
+ --- hobbies={hobbies, 'table'},
+ --- }
+ --- ...
+ --- end
--- ```
---
--- Examples with explicit argument values (can be run directly):
---
--- ```lua
- --- vim.validate{arg1={{'foo'}, 'table'}, arg2={'foo', 'string'}}
- --- --> NOP (success)
+ --- vim.validate{arg1={{'foo'}, 'table'}, arg2={'foo', 'string'}}
+ --- --> NOP (success)
---
- --- vim.validate{arg1={1, 'table'}}
- --- --> error('arg1: expected table, got number')
+ --- vim.validate{arg1={1, 'table'}}
+ --- --> error('arg1: expected table, got number')
---
- --- vim.validate{arg1={3, function(a) return (a % 2) == 0 end, 'even number'}}
- --- --> error('arg1: expected even number, got 3')
+ --- vim.validate{arg1={3, function(a) return (a % 2) == 0 end, 'even number'}}
+ --- --> error('arg1: expected even number, got 3')
--- ```
---
--- If multiple types are valid they can be given as a list.
---
--- ```lua
- --- vim.validate{arg1={{'foo'}, {'table', 'string'}}, arg2={'foo', {'table', 'string'}}}
- --- -- NOP (success)
- ---
- --- vim.validate{arg1={1, {'string', 'table'}}}
- --- -- error('arg1: expected string|table, got number')
+ --- vim.validate{arg1={{'foo'}, {'table', 'string'}}, arg2={'foo', {'table', 'string'}}}
+ --- -- NOP (success)
---
+ --- vim.validate{arg1={1, {'string', 'table'}}}
+ --- -- error('arg1: expected string|table, got number')
--- ```
---
- ---@param opt table Names of parameters to validate. Each key is a parameter
+ ---@param opt table<vim.validate.Type,vim.validate.Spec> (table) Names of parameters to validate. Each key is a parameter
--- name; each value is a tuple in one of these forms:
--- 1. (arg_value, type_name, optional)
--- - arg_value: argument value
@@ -878,7 +935,7 @@ end
--- a.b.c = 1
--- ```
---
----@param createfn function?(key:any):any Provides the value for a missing `key`.
+---@param createfn? fun(key:any):any Provides the value for a missing `key`.
---@return table # Empty table with `__index` metamethod.
function vim.defaulttable(createfn)
createfn = createfn or function(_)
@@ -898,6 +955,7 @@ do
---@field private _idx_read integer
---@field private _idx_write integer
---@field private _size integer
+ ---@overload fun(self): table?
local Ringbuf = {}
--- Clear all items
@@ -946,19 +1004,19 @@ do
--- Once the buffer is full, adding a new entry overrides the oldest entry.
---
--- ```lua
- --- local ringbuf = vim.ringbuf(4)
- --- ringbuf:push("a")
- --- ringbuf:push("b")
- --- ringbuf:push("c")
- --- ringbuf:push("d")
- --- ringbuf:push("e") -- overrides "a"
- --- print(ringbuf:pop()) -- returns "b"
- --- print(ringbuf:pop()) -- returns "c"
+ --- local ringbuf = vim.ringbuf(4)
+ --- ringbuf:push("a")
+ --- ringbuf:push("b")
+ --- ringbuf:push("c")
+ --- ringbuf:push("d")
+ --- ringbuf:push("e") -- overrides "a"
+ --- print(ringbuf:pop()) -- returns "b"
+ --- print(ringbuf:pop()) -- returns "c"
---
- --- -- Can be used as iterator. Pops remaining items:
- --- for val in ringbuf do
- --- print(val)
- --- end
+ --- -- Can be used as iterator. Pops remaining items:
+ --- for val in ringbuf do
+ --- print(val)
+ --- end
--- ```
---
--- Returns a Ringbuf instance with the following methods:
@@ -969,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 = {},
@@ -986,4 +1044,24 @@ do
end
end
+--- @private
+--- @generic T
+--- @param root string
+--- @param mod T
+--- @return T
+function vim._defer_require(root, mod)
+ return setmetatable({}, {
+ ---@param t table<string, any>
+ ---@param k string
+ __index = function(t, k)
+ if not mod[k] then
+ return
+ end
+ local name = string.format('%s.%s', root, k)
+ t[k] = require(name)
+ return t[k]
+ end,
+ })
+end
+
return vim
diff --git a/runtime/lua/vim/snippet.lua b/runtime/lua/vim/snippet.lua
index 32a8ea0b0d..5e60efa778 100644
--- a/runtime/lua/vim/snippet.lua
+++ b/runtime/lua/vim/snippet.lua
@@ -1,6 +1,7 @@
-local G = require('vim.lsp._snippet_grammar')
+local G = vim.lsp._snippet_grammar
local snippet_group = vim.api.nvim_create_augroup('vim/snippet', {})
local snippet_ns = vim.api.nvim_create_namespace('vim/snippet')
+local hl_group = 'SnippetTabstop'
--- Returns the 0-based cursor position.
---
@@ -42,9 +43,7 @@ local function resolve_variable(var, default)
elseif var == 'TM_FILENAME' then
return expand_or_default('%:t')
elseif var == 'TM_FILENAME_BASE' then
- -- Not using '%:t:r' since we want to remove all extensions.
- local filename_base = expand_or_default('%:t'):gsub('%.[^%.]*$', '')
- return filename_base
+ return expand_or_default('%:t:r')
elseif var == 'TM_DIRECTORY' then
return expand_or_default('%:p:h:t')
elseif var == 'TM_FILEPATH' then
@@ -102,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
@@ -119,11 +118,11 @@ local Tabstop = {}
--- @return vim.snippet.Tabstop
function Tabstop.new(index, bufnr, range, choices)
local extmark_id = vim.api.nvim_buf_set_extmark(bufnr, snippet_ns, range[1], range[2], {
- right_gravity = false,
+ right_gravity = true,
end_right_gravity = true,
end_line = range[3],
end_col = range[4],
- hl_group = 'SnippetTabstop',
+ hl_group = hl_group,
})
local self = setmetatable(
@@ -163,7 +162,22 @@ function Tabstop:set_text(text)
vim.api.nvim_buf_set_text(self.bufnr, range[1], range[2], range[3], range[4], text_to_lines(text))
end
---- @class vim.snippet.Session
+--- Sets the right gravity of the tabstop's extmark.
+---
+--- @package
+--- @param right_gravity boolean
+function Tabstop:set_right_gravity(right_gravity)
+ local range = self:get_range()
+ self.extmark_id = vim.api.nvim_buf_set_extmark(self.bufnr, snippet_ns, range[1], range[2], {
+ right_gravity = right_gravity,
+ end_right_gravity = true,
+ end_line = range[3],
+ end_col = range[4],
+ hl_group = hl_group,
+ })
+end
+
+--- @class (private) vim.snippet.Session
--- @field bufnr integer
--- @field extmark_id integer
--- @field tabstops table<integer, vim.snippet.Tabstop[]>
@@ -220,8 +234,17 @@ function Session:get_dest_index(direction)
end
end
---- @class vim.snippet.Snippet
---- @field private _session? vim.snippet.Session
+--- Sets the right gravity of the tabstop group with the given index.
+---
+--- @package
+--- @param index integer
+--- @param right_gravity boolean
+function Session:set_group_gravity(index, right_gravity)
+ for _, tabstop in ipairs(self.tabstops[index]) do
+ tabstop:set_right_gravity(right_gravity)
+ end
+end
+
local M = { session = nil }
--- Displays the choices for the given tabstop as completion items.
@@ -562,9 +585,15 @@ function M.jump(direction)
-- Clear the autocommands so that we can move the cursor freely while selecting the tabstop.
vim.api.nvim_clear_autocmds({ group = snippet_group, buffer = M._session.bufnr })
+ -- Deactivate expansion of the current tabstop.
+ M._session:set_group_gravity(M._session.current_tabstop.index, true)
+
M._session.current_tabstop = dest
select_tabstop(dest)
+ -- Activate expansion of the destination tabstop.
+ M._session:set_group_gravity(dest.index, false)
+
-- Restore the autocommands.
setup_autocmds(M._session.bufnr)
end
diff --git a/runtime/lua/vim/termcap.lua b/runtime/lua/vim/termcap.lua
index 862cc52149..ec29acca48 100644
--- a/runtime/lua/vim/termcap.lua
+++ b/runtime/lua/vim/termcap.lua
@@ -12,7 +12,10 @@ local M = {}
--- emulator supports the XTGETTCAP sequence.
---
--- @param caps string|table A terminal capability or list of capabilities to query
---- @param cb function(cap:string, seq:string) Function to call when a response is received
+--- @param cb function(cap:string, found:bool, seq:string?) Callback function which is called for
+--- each capability in {caps}. {found} is set to true if the capability was found or false
+--- otherwise. {seq} is the control sequence for the capability if found, or nil for
+--- boolean capabilities.
function M.query(caps, cb)
vim.validate({
caps = { caps, { 'string', 'table' } },
@@ -23,21 +26,40 @@ function M.query(caps, cb)
caps = { caps }
end
- local count = #caps
+ local pending = {} ---@type table<string, boolean>
+ for _, v in ipairs(caps) do
+ pending[v] = true
+ end
+
+ local timer = assert(vim.uv.new_timer())
- vim.api.nvim_create_autocmd('TermResponse', {
+ local id = vim.api.nvim_create_autocmd('TermResponse', {
+ nested = true,
callback = function(args)
local resp = args.data ---@type string
- local k, v = resp:match('^\027P1%+r(%x+)=(%x+)$')
- if k and v then
+ local k, rest = resp:match('^\027P1%+r(%x+)(.*)$')
+ if k and rest then
local cap = vim.text.hexdecode(k)
- local seq =
- vim.text.hexdecode(v):gsub('\\E', '\027'):gsub('%%p%d', ''):gsub('\\(%d+)', string.char)
+ if not pending[cap] then
+ -- Received a response for a capability we didn't request. This can happen if there are
+ -- multiple concurrent XTGETTCAP requests
+ return
+ end
+
+ local seq ---@type string?
+ if rest:match('^=%x+$') then
+ seq = vim.text
+ .hexdecode(rest:sub(2))
+ :gsub('\\E', '\027')
+ :gsub('%%p%d', '')
+ :gsub('\\(%d+)', string.char)
+ end
- cb(cap, seq)
+ cb(cap, true, seq)
- count = count - 1
- if count == 0 then
+ pending[cap] = nil
+
+ if next(pending) == nil then
return true
end
end
@@ -57,6 +79,23 @@ function M.query(caps, cb)
end
io.stdout:write(query)
+
+ timer:start(1000, 0, function()
+ -- Delete the autocommand if no response was received
+ vim.schedule(function()
+ -- Suppress error if autocommand has already been deleted
+ pcall(vim.api.nvim_del_autocmd, id)
+
+ -- Call the callback for all capabilities that were not found
+ for k in pairs(pending) do
+ cb(k, false, nil)
+ end
+ end)
+
+ if not timer:is_closing() then
+ timer:close()
+ end
+ end)
end
return M
diff --git a/runtime/lua/vim/text.lua b/runtime/lua/vim/text.lua
index cfb0f9b821..576b962838 100644
--- a/runtime/lua/vim/text.lua
+++ b/runtime/lua/vim/text.lua
@@ -1,4 +1,4 @@
---- Text processing functions.
+-- Text processing functions.
local M = {}
diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua
index e7a66c00b2..a09619f369 100644
--- a/runtime/lua/vim/treesitter.lua
+++ b/runtime/lua/vim/treesitter.lua
@@ -1,44 +1,21 @@
local api = vim.api
-local LanguageTree = require('vim.treesitter.languagetree')
-local Range = require('vim.treesitter._range')
----@type table<integer,LanguageTree>
+---@type table<integer,vim.treesitter.LanguageTree>
local parsers = setmetatable({}, { __mode = 'v' })
----@class TreesitterModule
----@field highlighter TSHighlighter
----@field query TSQueryModule
----@field language TSLanguageModule
-local M = setmetatable({}, {
- __index = function(t, k)
- ---@diagnostic disable:no-unknown
- if k == 'highlighter' then
- t[k] = require('vim.treesitter.highlighter')
- return t[k]
- elseif k == 'language' then
- t[k] = require('vim.treesitter.language')
- return t[k]
- elseif k == 'query' then
- t[k] = require('vim.treesitter.query')
- return t[k]
- end
-
- local query = require('vim.treesitter.query')
- if query[k] then
- vim.deprecate('vim.treesitter.' .. k .. '()', 'vim.treesitter.query.' .. k .. '()', '0.10')
- t[k] = query[k]
- return t[k]
- end
-
- local language = require('vim.treesitter.language')
- if language[k] then
- vim.deprecate('vim.treesitter.' .. k .. '()', 'vim.treesitter.language.' .. k .. '()', '0.10')
- t[k] = language[k]
- return t[k]
- end
- end,
+local M = vim._defer_require('vim.treesitter', {
+ _fold = ..., --- @module 'vim.treesitter._fold'
+ _query_linter = ..., --- @module 'vim.treesitter._query_linter'
+ _range = ..., --- @module 'vim.treesitter._range'
+ dev = ..., --- @module 'vim.treesitter.dev'
+ highlighter = ..., --- @module 'vim.treesitter.highlighter'
+ language = ..., --- @module 'vim.treesitter.language'
+ languagetree = ..., --- @module 'vim.treesitter.languagetree'
+ query = ..., --- @module 'vim.treesitter.query'
})
+local LanguageTree = M.languagetree
+
--- @nodoc
M.language_version = vim._ts_get_language_version()
@@ -53,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()
@@ -100,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 {}
@@ -142,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' },
@@ -195,12 +172,12 @@ 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
assert(source)
- return Range.add_bytes(source, metadata.range)
+ return M._range.add_bytes(source, metadata.range)
end
return { node:range(true) }
end
@@ -209,7 +186,7 @@ end
---@param range Range
---@returns string
local function buf_range_get_text(buf, range)
- local start_row, start_col, end_row, end_col = Range.unpack4(range)
+ local start_row, start_col, end_row, end_col = M._range.unpack4(range)
if end_col == 0 then
if start_row == end_row then
start_col = -1
@@ -237,7 +214,7 @@ function M.get_node_text(node, source, opts)
if metadata.text then
return metadata.text
elseif type(source) == 'number' then
- local range = vim.treesitter.get_range(node, source, metadata)
+ local range = M.get_range(node, source, metadata)
return buf_range_get_text(source, range)
end
@@ -266,9 +243,9 @@ function M.node_contains(node, range)
vim.validate({
-- allow a table so nodes can be mocked
node = { node, { 'userdata', 'table' } },
- range = { range, Range.validate, 'integer list with 4 or 6 elements' },
+ range = { range, M._range.validate, 'integer list with 4 or 6 elements' },
})
- return Range.contains({ node:range() }, range)
+ return M._range.contains({ node:range() }, range)
end
--- Returns a list of highlight captures at the given position
@@ -317,6 +294,7 @@ function M.get_captures_at_pos(bufnr, row, col)
for capture, node, metadata in iter do
if M.is_in_node_range(node, row, col) then
+ ---@diagnostic disable-next-line: invisible
local c = q._query.captures[capture] -- name of the capture in the query
if c ~= nil then
table.insert(matches, { capture = c, metadata = metadata, lang = tree:lang() })
@@ -348,6 +326,23 @@ function M.get_captures_at_cursor(winnr)
return captures
end
+--- 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
---
--- NOTE: Calling this on an unparsed tree can yield an invalid node.
@@ -358,11 +353,7 @@ end
--- vim.treesitter.get_parser(bufnr):parse(range)
--- ```
---
----@param opts table|nil 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
---- - 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)
@@ -374,7 +365,7 @@ function M.get_node(opts)
bufnr = api.nvim_get_current_buf()
end
- local row, col
+ local row, col --- @type integer, integer
if opts.pos then
assert(#opts.pos == 2, 'Position must be a (row, col) tuple')
row, col = opts.pos[1], opts.pos[2]
@@ -392,34 +383,6 @@ function M.get_node(opts)
local ts_range = { row, col, row, col }
- local root_lang_tree = M.get_parser(bufnr)
- 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 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
@@ -428,26 +391,12 @@ function M.get_node_at_pos(bufnr, row, col, opts)
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.
---
--- Note: By default, disables regex syntax highlighting, which may be required for some plugins.
---- In this case, add ``vim.bo.syntax = 'on'`` after the call to `start`.
+--- In this case, add `vim.bo.syntax = 'on'` after the call to `start`.
---
--- Example:
---
@@ -461,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)
@@ -483,13 +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.
+--- [<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,
@@ -501,7 +451,7 @@ end
--- argument and should return a string.
function M.inspect_tree(opts)
---@diagnostic disable-next-line: invisible
- require('vim.treesitter.dev').inspect_tree(opts)
+ M.dev.inspect_tree(opts)
end
--- Returns the fold level for {lnum} in the current buffer. Can be set directly to 'foldexpr':
@@ -513,19 +463,7 @@ end
---@param lnum integer|nil Line number to calculate fold level for
---@return string
function M.foldexpr(lnum)
- return require('vim.treesitter._fold').foldexpr(lnum)
-end
-
---- Returns the highlighted content of the first line of the fold or falls back to |foldtext()|
---- if no treesitter parser is found. Can be set directly to 'foldtext':
----
---- ```lua
---- vim.wo.foldtext = 'v:lua.vim.treesitter.foldtext()'
---- ```
----
----@return { [1]: string, [2]: string[] }[] | string
-function M.foldtext()
- return require('vim.treesitter._fold').foldtext()
+ return M._fold.foldexpr(lnum)
end
return M
diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua
index 5c1cc06908..d96cc966de 100644
--- a/runtime/lua/vim/treesitter/_fold.lua
+++ b/runtime/lua/vim/treesitter/_fold.lua
@@ -5,35 +5,20 @@ local Range = require('vim.treesitter._range')
local api = vim.api
---@class TS.FoldInfo
----@field levels table<integer,string>
----@field levels0 table<integer,integer>
----@field private start_counts table<integer,integer>
----@field private stop_counts table<integer,integer>
+---@field levels string[] the foldexpr result for each line
+---@field levels0 integer[] the raw fold levels
+---@field edits? {[1]: integer, [2]: integer} line range edited since the last invocation of the callback scheduled in on_bytes. 0-indexed, end-exclusive.
local FoldInfo = {}
FoldInfo.__index = FoldInfo
---@private
function FoldInfo.new()
return setmetatable({
- start_counts = {},
- stop_counts = {},
levels0 = {},
levels = {},
}, FoldInfo)
end
----@package
----@param srow integer
----@param erow integer
-function FoldInfo:invalidate_range(srow, erow)
- for i = srow, erow do
- self.start_counts[i + 1] = nil
- self.stop_counts[i + 1] = nil
- self.levels0[i + 1] = nil
- self.levels[i + 1] = nil
- end
-end
-
--- Efficiently remove items from middle of a list a list.
---
--- Calling table.remove() in a loop will re-index the tail of the table on
@@ -55,12 +40,10 @@ end
---@package
---@param srow integer
----@param erow integer
+---@param erow integer 0-indexed, exclusive
function FoldInfo:remove_range(srow, erow)
list_remove(self.levels, srow + 1, erow)
list_remove(self.levels0, srow + 1, erow)
- list_remove(self.start_counts, srow + 1, erow)
- list_remove(self.stop_counts, srow + 1, erow)
end
--- Efficiently insert items into the middle of a list.
@@ -91,46 +74,37 @@ end
---@package
---@param srow integer
----@param erow integer
+---@param erow integer 0-indexed, exclusive
function FoldInfo:add_range(srow, erow)
- list_insert(self.levels, srow + 1, erow, '-1')
+ list_insert(self.levels, srow + 1, erow, '=')
list_insert(self.levels0, srow + 1, erow, -1)
- list_insert(self.start_counts, srow + 1, erow, nil)
- list_insert(self.stop_counts, srow + 1, erow, nil)
end
---@package
----@param lnum integer
-function FoldInfo:add_start(lnum)
- self.start_counts[lnum] = (self.start_counts[lnum] or 0) + 1
-end
-
----@package
----@param lnum integer
-function FoldInfo:add_stop(lnum)
- self.stop_counts[lnum] = (self.stop_counts[lnum] or 0) + 1
-end
-
----@package
----@param lnum integer
----@return integer
-function FoldInfo:get_start(lnum)
- return self.start_counts[lnum] or 0
+---@param srow integer
+---@param erow_old integer
+---@param erow_new integer 0-indexed, exclusive
+function FoldInfo:edit_range(srow, erow_old, erow_new)
+ if self.edits then
+ self.edits[1] = math.min(srow, self.edits[1])
+ if erow_old <= self.edits[2] then
+ self.edits[2] = self.edits[2] + (erow_new - erow_old)
+ end
+ self.edits[2] = math.max(self.edits[2], erow_new)
+ else
+ self.edits = { srow, erow_new }
+ end
end
---@package
----@param lnum integer
----@return integer
-function FoldInfo:get_stop(lnum)
- return self.stop_counts[lnum] or 0
-end
-
-local function trim_level(level)
- local max_fold_level = vim.wo.foldnestmax
- if level > max_fold_level then
- return max_fold_level
+---@return integer? srow
+---@return integer? erow 0-indexed, exclusive
+function FoldInfo:flush_edit()
+ if self.edits then
+ local srow, erow = self.edits[1], self.edits[2]
+ self.edits = nil
+ return srow, erow
end
- return level
end
--- If a parser doesn't have any ranges explicitly set, treesitter will
@@ -140,10 +114,10 @@ end
--- TODO(lewis6991): Handle this generally
---
--- @param bufnr integer
---- @param erow integer?
+--- @param erow integer? 0-indexed, exclusive
--- @return integer
local function normalise_erow(bufnr, erow)
- local max_erow = api.nvim_buf_line_count(bufnr) - 1
+ local max_erow = api.nvim_buf_line_count(bufnr)
return math.min(erow or max_erow, max_erow)
end
@@ -152,31 +126,30 @@ end
---@param bufnr integer
---@param info TS.FoldInfo
---@param srow integer?
----@param erow integer?
+---@param erow integer? 0-indexed, exclusive
---@param parse_injections? boolean
local function get_folds_levels(bufnr, info, srow, erow, parse_injections)
srow = srow or 0
erow = normalise_erow(bufnr, erow)
- info:invalidate_range(srow, erow)
-
- local prev_start = -1
- local prev_stop = -1
-
local parser = ts.get_parser(bufnr)
parser:parse(parse_injections and { srow, erow } or nil)
+ local enter_counts = {} ---@type table<integer, integer>
+ local leave_counts = {} ---@type table<integer, integer>
+ local prev_start = -1
+ local prev_stop = -1
+
parser:for_each_tree(function(tree, ltree)
local query = ts.query.get(ltree:lang(), 'folds')
if not query then
return
end
- -- erow in query is end-exclusive
- local q_erow = erow and erow + 1 or -1
-
- for id, node, metadata in query:iter_captures(tree:root(), bufnr, srow, q_erow) do
+ -- Collect folds starting from srow - 1, because we should first subtract the folds that end at
+ -- srow - 1 from the level of srow - 1 to get accurate level of srow.
+ for id, node, metadata in query:iter_captures(tree:root(), bufnr, math.max(srow - 1, 0), erow) do
if query.captures[id] == 'fold' then
local range = ts.get_range(node, bufnr, metadata[id])
local start, _, stop, stop_col = Range.unpack4(range)
@@ -193,8 +166,8 @@ local function get_folds_levels(bufnr, info, srow, erow, parse_injections)
if
fold_length > vim.wo.foldminlines and not (start == prev_start and stop == prev_stop)
then
- info:add_start(start + 1)
- info:add_stop(stop + 1)
+ enter_counts[start + 1] = (enter_counts[start + 1] or 0) + 1
+ leave_counts[stop + 1] = (leave_counts[stop + 1] or 0) + 1
prev_start = start
prev_stop = stop
end
@@ -202,16 +175,15 @@ local function get_folds_levels(bufnr, info, srow, erow, parse_injections)
end
end)
- local current_level = info.levels0[srow] or 0
+ local nestmax = vim.wo.foldnestmax
+ local level0_prev = info.levels0[srow] or 0
+ local leave_prev = leave_counts[srow] or 0
-- We now have the list of fold opening and closing, fill the gaps and mark where fold start
- for lnum = srow + 1, erow + 1 do
- local last_trimmed_level = trim_level(current_level)
- current_level = current_level + info:get_start(lnum)
- info.levels0[lnum] = current_level
-
- local trimmed_level = trim_level(current_level)
- current_level = current_level - info:get_stop(lnum)
+ for lnum = srow + 1, erow do
+ local enter_line = enter_counts[lnum] or 0
+ local leave_line = leave_counts[lnum] or 0
+ local level0 = level0_prev - leave_prev + enter_line
-- Determine if it's the start/end of a fold
-- NB: vim's fold-expr interface does not have a mechanism to indicate that
@@ -219,14 +191,36 @@ local function get_folds_levels(bufnr, info, srow, erow, parse_injections)
-- ( \n ( \n )) \n (( \n ) \n )
-- versus
-- ( \n ( \n ) \n ( \n ) \n )
- -- If it did have such a mechanism, (trimmed_level - last_trimmed_level)
+ -- Both are represented by ['>1', '>2', '2', '>2', '2', '1'], and
+ -- vim interprets as the second case.
+ -- If it did have such a mechanism, (clamped - clamped_prev)
-- would be the correct number of starts to pass on.
+ local adjusted = level0 ---@type integer
local prefix = ''
- if trimmed_level - last_trimmed_level > 0 then
+ if enter_line > 0 then
prefix = '>'
+ if leave_line > 0 then
+ -- If this line ends a fold f1 and starts a fold f2, then move f1's end to the previous line
+ -- so that f2 gets the correct level on this line. This may reduce the size of f1 below
+ -- foldminlines, but we don't handle it for simplicity.
+ adjusted = level0 - leave_line
+ leave_line = 0
+ end
+ end
+
+ -- Clamp at foldnestmax.
+ local clamped = adjusted
+ if adjusted > nestmax then
+ prefix = ''
+ clamped = nestmax
end
- info.levels[lnum] = prefix .. tostring(trimmed_level)
+ -- Record the "real" level, so that it can be used as "base" of later get_folds_levels().
+ info.levels0[lnum] = adjusted
+ info.levels[lnum] = prefix .. tostring(clamped)
+
+ leave_prev = leave_line
+ level0_prev = adjusted
end
end
@@ -296,8 +290,12 @@ end
local function on_changedtree(bufnr, foldinfo, tree_changes)
schedule_if_loaded(bufnr, function()
for _, change in ipairs(tree_changes) do
- local srow, _, erow = Range.unpack4(change)
- get_folds_levels(bufnr, foldinfo, srow, erow)
+ local srow, _, erow, ecol = Range.unpack4(change)
+ if ecol > 0 then
+ erow = erow + 1
+ end
+ -- Start from `srow - foldminlines`, because this edit may have shrunken the fold below limit.
+ get_folds_levels(bufnr, foldinfo, math.max(srow - vim.wo.foldminlines, 0), erow)
end
if #tree_changes > 0 then
foldupdate(bufnr)
@@ -309,19 +307,46 @@ end
---@param foldinfo TS.FoldInfo
---@param start_row integer
---@param old_row integer
+---@param old_col integer
---@param new_row integer
-local function on_bytes(bufnr, foldinfo, start_row, old_row, new_row)
- local end_row_old = start_row + old_row
- local end_row_new = start_row + new_row
+---@param new_col integer
+local function on_bytes(bufnr, foldinfo, start_row, start_col, old_row, old_col, new_row, new_col)
+ -- extend the end to fully include the range
+ local end_row_old = start_row + old_row + 1
+ local end_row_new = start_row + new_row + 1
if new_row ~= old_row then
+ -- foldexpr can be evaluated before the scheduled callback is invoked. So it may observe the
+ -- outdated levels, which may spuriously open the folds that didn't change. So we should shift
+ -- folds as accurately as possible. For this to be perfectly accurate, we should track the
+ -- actual TSNodes that account for each fold, and compare the node's range with the edited
+ -- range. But for simplicity, we just check whether the start row is completely removed (e.g.,
+ -- `dd`) or shifted (e.g., `o`).
if new_row < old_row then
- foldinfo:remove_range(end_row_new, end_row_old)
+ if start_col == 0 and new_row == 0 and new_col == 0 then
+ foldinfo:remove_range(start_row, start_row + (end_row_old - end_row_new))
+ else
+ foldinfo:remove_range(end_row_new, end_row_old)
+ end
else
- foldinfo:add_range(start_row, end_row_new)
+ if start_col == 0 and old_row == 0 and old_col == 0 then
+ foldinfo:add_range(start_row, start_row + (end_row_new - end_row_old))
+ else
+ foldinfo:add_range(end_row_old, end_row_new)
+ end
end
+ foldinfo:edit_range(start_row, end_row_old, end_row_new)
+
+ -- This callback must not use on_bytes arguments, because they can be outdated when the callback
+ -- is invoked. For example, `J` with non-zero count triggers multiple on_bytes before executing
+ -- the scheduled callback. So we should collect the edits.
schedule_if_loaded(bufnr, function()
- get_folds_levels(bufnr, foldinfo, start_row, end_row_new)
+ local srow, erow = foldinfo:flush_edit()
+ if not srow then
+ return
+ end
+ -- Start from `srow - foldminlines`, because this edit may have shrunken the fold below limit.
+ get_folds_levels(bufnr, foldinfo, math.max(srow - vim.wo.foldminlines, 0), erow)
foldupdate(bufnr)
end)
end
@@ -348,8 +373,8 @@ function M.foldexpr(lnum)
on_changedtree(bufnr, foldinfos[bufnr], tree_changes)
end,
- on_bytes = function(_, _, start_row, _, _, old_row, _, _, new_row, _, _)
- on_bytes(bufnr, foldinfos[bufnr], start_row, old_row, new_row)
+ on_bytes = function(_, _, start_row, start_col, _, old_row, old_col, _, new_row, new_col, _)
+ on_bytes(bufnr, foldinfos[bufnr], start_row, start_col, old_row, old_col, new_row, new_col)
end,
on_detach = function()
@@ -361,96 +386,15 @@ function M.foldexpr(lnum)
return foldinfos[bufnr].levels[lnum] or '0'
end
----@package
----@return { [1]: string, [2]: string[] }[]|string
-function M.foldtext()
- local foldstart = vim.v.foldstart
- local bufnr = api.nvim_get_current_buf()
-
- ---@type boolean, LanguageTree
- local ok, parser = pcall(ts.get_parser, bufnr)
- if not ok then
- return vim.fn.foldtext()
- end
-
- local query = ts.query.get(parser:lang(), 'highlights')
- if not query then
- return vim.fn.foldtext()
- end
-
- local tree = parser:parse({ foldstart - 1, foldstart })[1]
-
- local line = api.nvim_buf_get_lines(bufnr, foldstart - 1, foldstart, false)[1]
- if not line then
- return vim.fn.foldtext()
- end
-
- ---@type { [1]: string, [2]: string[], range: { [1]: integer, [2]: integer } }[] | { [1]: string, [2]: string[] }[]
- local result = {}
-
- local line_pos = 0
-
- for id, node, metadata in query:iter_captures(tree:root(), 0, foldstart - 1, foldstart) do
- local name = query.captures[id]
- local start_row, start_col, end_row, end_col = node:range()
-
- local priority = tonumber(metadata.priority or vim.highlight.priorities.treesitter)
-
- if start_row == foldstart - 1 and end_row == foldstart - 1 then
- -- check for characters ignored by treesitter
- if start_col > line_pos then
- table.insert(result, {
- line:sub(line_pos + 1, start_col),
- {},
- range = { line_pos, start_col },
- })
- end
- line_pos = end_col
-
- local text = line:sub(start_col + 1, end_col)
- table.insert(result, { text, { { '@' .. name, priority } }, range = { start_col, end_col } })
- end
- end
-
- local i = 1
- while i <= #result do
- -- find first capture that is not in current range and apply highlights on the way
- local j = i + 1
- while
- j <= #result
- and result[j].range[1] >= result[i].range[1]
- and result[j].range[2] <= result[i].range[2]
- do
- for k, v in ipairs(result[i][2]) do
- if not vim.tbl_contains(result[j][2], v) then
- table.insert(result[j][2], k, v)
- end
- end
- j = j + 1
- end
-
- -- remove the parent capture if it is split into children
- if j > i + 1 then
- table.remove(result, i)
- else
- -- highlights need to be sorted by priority, on equal prio, the deeper nested capture (earlier
- -- in list) should be considered higher prio
- if #result[i][2] > 1 then
- table.sort(result[i][2], function(a, b)
- return a[2] < b[2]
- end)
- end
-
- result[i][2] = vim.tbl_map(function(tbl)
- return tbl[1]
- end, result[i][2])
- result[i] = { result[i][1], result[i][2] }
-
- i = i + 1
+api.nvim_create_autocmd('OptionSet', {
+ pattern = { 'foldminlines', 'foldnestmax' },
+ desc = 'Refresh treesitter folds',
+ callback = function()
+ for _, bufnr in ipairs(vim.tbl_keys(foldinfos)) do
+ foldinfos[bufnr] = FoldInfo.new()
+ get_folds_levels(bufnr, foldinfos[bufnr])
+ foldupdate(bufnr)
end
- end
-
- return result
-end
-
+ end,
+})
return M
diff --git a/runtime/lua/vim/treesitter/_meta.lua b/runtime/lua/vim/treesitter/_meta.lua
index 80c998b555..19d97d2820 100644
--- a/runtime/lua/vim/treesitter/_meta.lua
+++ b/runtime/lua/vim/treesitter/_meta.lua
@@ -1,4 +1,5 @@
---@meta
+error('Cannot require a meta file')
---@class TSNode: userdata
---@field id fun(self: TSNode): string
@@ -33,27 +34,26 @@
---@field byte_length fun(self: TSNode): integer
local TSNode = {}
----@param query userdata
+---@param query TSQuery
---@param captures true
---@param start? integer
---@param end_? integer
---@param opts? table
----@return fun(): integer, TSNode, any
+---@return fun(): integer, TSNode, vim.treesitter.query.TSMatch
function TSNode:_rawquery(query, captures, start, end_, opts) end
----@param query userdata
+---@param query TSQuery
---@param captures false
---@param start? integer
---@param end_? integer
---@param opts? table
----@return fun(): string, any
+---@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
----@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[]
+---@class TSParser: userdata
+---@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)[])
@@ -62,19 +62,31 @@ function TSNode:_rawquery(query, captures, start, end_, opts) end
---@field _set_logger fun(self: TSParser, lex: boolean, parse: boolean, cb: TSLoggerCallback)
---@field _logger fun(self: TSParser): TSLoggerCallback
----@class TSTree
+---@class TSTree: userdata
---@field root fun(self: TSTree): TSNode
---@field edit fun(self: TSTree, _: integer, _: integer, _: integer, _: integer, _: integer, _: integer, _: integer, _: integer, _:integer)
---@field copy fun(self: TSTree): TSTree
---@field included_ranges fun(self: TSTree, include_bytes: true): Range6[]
---@field included_ranges fun(self: TSTree, include_bytes: false): Range4[]
+---@class TSQuery: userdata
+---@field inspect fun(self: TSQuery): TSQueryInfo
+
+---@class (exact) TSQueryInfo
+---@field captures string[]
+---@field patterns table<integer, (integer|string)[][]>
+
---@return integer
vim._ts_get_language_version = function() end
---@return integer
vim._ts_get_minimum_language_version = function() end
+---@param lang string Language to use for the query
+---@param query string Query string in s-expr syntax
+---@return TSQuery
+vim._ts_parse_query = function(lang, query) end
+
---@param lang string
---@return TSParser
vim._create_ts_parser = function(lang) end
diff --git a/runtime/lua/vim/treesitter/_query_linter.lua b/runtime/lua/vim/treesitter/_query_linter.lua
index 87d74789a3..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 {}
@@ -92,7 +92,7 @@ local function get_error_entry(err, node)
end_col = end_col + #underlined
elseif msg:match('^Invalid') then
-- Use the length of the problematic type/capture/field
- end_col = end_col + #msg:match('"([^"]+)"')
+ end_col = end_col + #(msg:match('"([^"]+)"') or '')
end
return {
@@ -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,28 +122,30 @@ local parse = vim.func._memoize(hash_parse, function(node, buf, lang)
end)
--- @param buf integer
---- @param match table<integer,TSNode>
---- @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
- for id, node in pairs(match) do
- local cap_id = query.captures[id]
+ for id, nodes in pairs(match) do
+ for _, node in ipairs(nodes) do
+ local cap_id = query.captures[id]
- -- perform language-independent checks only for first lang
- if lang_context.is_first_lang and cap_id == 'error' then
- local node_text = vim.treesitter.get_node_text(node, buf):gsub('\n', ' ')
- add_lint_for_node(diagnostics, { node:range() }, 'Syntax error: ' .. node_text)
- end
+ -- perform language-independent checks only for first lang
+ if lang_context.is_first_lang and cap_id == 'error' then
+ local node_text = vim.treesitter.get_node_text(node, buf):gsub('\n', ' ')
+ add_lint_for_node(diagnostics, { node:range() }, 'Syntax error: ' .. node_text)
+ end
- -- other checks rely on Neovim parser introspection
- if lang and parser_info and cap_id == 'toplevel' then
- local err = parse(node, buf, lang)
- if err then
- add_lint_for_node(diagnostics, err.range, err.msg, lang)
+ -- other checks rely on Neovim parser introspection
+ if lang and parser_info and cap_id == 'toplevel' then
+ local err = parse(node, buf, lang)
+ if err then
+ add_lint_for_node(diagnostics, err.range, err.msg, lang)
+ end
end
end
end
@@ -151,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()
@@ -173,7 +175,7 @@ function M.lint(buf, opts)
parser:parse()
parser:for_each_tree(function(tree, ltree)
if ltree:lang() == 'query' then
- for _, match, _ in query:iter_matches(tree:root(), buf, 0, -1) do
+ for _, match, _ in query:iter_matches(tree:root(), buf, 0, -1, { all = true }) do
local lang_context = {
lang = lang,
parser_info = parser_info,
@@ -195,7 +197,7 @@ function M.clear(buf)
end
--- @private
---- @param findstart integer
+--- @param findstart 0|1
--- @param base string
function M.omnifunc(findstart, base)
if findstart == 1 then
diff --git a/runtime/lua/vim/treesitter/dev.lua b/runtime/lua/vim/treesitter/dev.lua
index 69ddc9b558..dc2a14d238 100644
--- a/runtime/lua/vim/treesitter/dev.lua
+++ b/runtime/lua/vim/treesitter/dev.lua
@@ -1,31 +1,29 @@
local api = vim.api
----@class TSDevModule
local M = {}
----@class TSTreeView
+---@class (private) vim.treesitter.dev.TSTreeView
---@field ns integer API namespace
----@field opts table Options table with the following keys:
---- - anon (boolean): If true, display anonymous nodes
---- - lang (boolean): If true, display the language alongside each node
---- - indent (number): Number of spaces to indent nested lines. Default is 2.
----@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 = {}
----@class TSP.Node
----@field id integer Node id
----@field text string Node text
----@field named boolean True if this is a named (non-anonymous) node
----@field depth integer Depth of the node within the tree
----@field lnum integer Beginning line number of this node in the source buffer
----@field col integer Beginning column number of this node in the source buffer
----@field end_lnum integer Final line number of this node in the source buffer
----@field end_col integer Final column number of this node in the source buffer
+---@private
+---@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 (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
+---@field text string? Text displayed in the inspector for this node. Not computed until the
+--- inspector is drawn.
---@field lang string Source language of this node
----@field root TSNode
----@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
@@ -43,48 +41,26 @@ local TSTreeView = {}
---
---@param node TSNode Starting node to begin traversal |tsnode|
---@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
-local function traverse(node, depth, lang, injections, 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,
+ depth = depth,
+ lang = lang,
+ field = field,
+ })
+
local injection = injections[node:id()]
if injection then
- traverse(injection.root, depth, injection.lang, injections, tree)
+ traverse(injection.root, depth + 1, nil, injection.lang, injections, tree)
end
- for child, field in node:iter_children() do
- local type = child:type()
- local lnum, col, end_lnum, end_col = child:range()
- local named = child:named()
- local text ---@type string
- if named then
- if field then
- text = string.format('%s: (%s', field, type)
- else
- text = string.format('(%s', type)
- end
- else
- text = string.format('"%s"', type:gsub('\n', '\\n'):gsub('"', '\\"'))
- end
-
- table.insert(tree, {
- id = child:id(),
- text = text,
- named = named,
- depth = depth,
- lnum = lnum,
- col = col,
- end_lnum = end_lnum,
- end_col = end_col,
- lang = lang,
- })
-
- traverse(child, depth + 1, lang, injections, tree)
-
- if named then
- tree[#tree].text = string.format('%s)', tree[#tree].text)
- end
+ for child, child_field in node:iter_children() do
+ traverse(child, depth + 1, child_field, lang, injections, tree)
end
return tree
@@ -95,44 +71,45 @@ 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
function TSTreeView:new(bufnr, lang)
local ok, parser = pcall(vim.treesitter.get_parser, bufnr or 0, lang)
if not ok then
- return nil, 'No parser available for the given buffer'
+ local err = parser --[[ @as string ]]
+ return nil, 'No parser available for the given buffer:\n' .. err
end
-- For each child tree (injected language), find the root of the tree and locate the node within
-- 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()
for _, child in pairs(parent_ltree:children()) do
- child:for_each_tree(function(tree, ltree)
+ for _, tree in pairs(child:trees()) do
local r = tree:root()
local node = assert(parent:named_descendant_for_range(r:range()))
local id = node:id()
if not injections[id] or r:byte_length() > injections[id].root:byte_length() then
injections[id] = {
- lang = ltree:lang(),
+ lang = child:lang(),
root = r,
}
end
- end)
+ end
end
end)
- local nodes = traverse(root, 0, parser:lang(), injections, {})
+ 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.named then
+ if v.node:named() then
named[#named + 1] = v
end
end
@@ -141,6 +118,7 @@ function TSTreeView:new(bufnr, lang)
ns = api.nvim_create_namespace('treesitter/dev-inspect'),
nodes = nodes,
named = named,
+ ---@type vim.treesitter.dev.TSTreeViewOpts
opts = {
anon = false,
lang = false,
@@ -155,16 +133,12 @@ end
local decor_ns = api.nvim_create_namespace('ts.dev')
----@param lnum integer
----@param col integer
----@param end_lnum integer
----@param end_col integer
+---@param range Range4
---@return string
-local function get_range_str(lnum, col, end_lnum, end_col)
- if lnum == end_lnum then
- return string.format('[%d:%d - %d]', lnum + 1, col + 1, end_col)
- end
- return string.format('[%d:%d - %d:%d]', lnum + 1, col + 1, end_lnum + 1, end_col)
+local function range_to_string(range)
+ ---@type integer, integer, integer, integer
+ local row, col, end_row, end_col = unpack(range)
+ return string.format('[%d, %d] - [%d, %d]', row, col, end_row, end_col)
end
---@param w integer
@@ -183,7 +157,10 @@ end
local function set_dev_properties(w, b)
vim.wo[w].scrolloff = 5
vim.wo[w].wrap = false
- vim.wo[w].foldmethod = 'manual' -- disable folding
+ vim.wo[w].foldmethod = 'expr'
+ vim.wo[w].foldexpr = 'v:lua.vim.treesitter.foldexpr()' -- explicitly set foldexpr
+ vim.wo[w].foldenable = false -- Don't fold on first open InspectTree
+ vim.wo[w].foldlevel = 99
vim.bo[b].buflisted = false
vim.bo[b].buftype = 'nofile'
vim.bo[b].bufhidden = 'wipe'
@@ -192,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
@@ -213,7 +190,7 @@ local function set_inspector_cursor(treeview, lang, source_buf, inspect_buf, ins
local cursor_node_id = cursor_node:id()
for i, v in treeview:iter() do
- if v.id == cursor_node_id then
+ if v.node:id() == cursor_node_id then
local start = v.depth * treeview.opts.indent ---@type integer
local end_col = start + #v.text
api.nvim_buf_set_extmark(inspect_buf, treeview.ns, i - 1, start, {
@@ -228,6 +205,8 @@ end
--- Write the contents of this View into {bufnr}.
---
+--- Calling this function computes the text that is displayed for each node.
+---
---@param bufnr integer Buffer number to write into.
---@package
function TSTreeView:draw(bufnr)
@@ -235,13 +214,35 @@ function TSTreeView:draw(bufnr)
local lines = {} ---@type string[]
local lang_hl_marks = {} ---@type table[]
- for _, item in self:iter() do
- local range_str = get_range_str(item.lnum, item.col, item.end_lnum, item.end_col)
+ for i, item in self:iter() do
+ local range_str = range_to_string({ item.node:range() })
local lang_str = self.opts.lang and string.format(' %s', item.lang) or ''
+
+ local text ---@type string
+ if item.node:named() then
+ if item.field then
+ text = string.format('%s: (%s', item.field, item.node:type())
+ else
+ text = string.format('(%s', item.node:type())
+ end
+ else
+ text = string.format('"%s"', item.node:type():gsub('\n', '\\n'):gsub('"', '\\"'))
+ end
+
+ local next = self:get(i + 1)
+ if not next or next.depth <= item.depth then
+ local parens = item.depth - (next and next.depth or 0) + (item.node:named() and 1 or 0)
+ if parens > 0 then
+ text = string.format('%s%s', text, string.rep(')', parens))
+ end
+ end
+
+ item.text = text
+
local line = string.format(
'%s%s ; %s%s',
string.rep(' ', item.depth * self.opts.indent),
- item.text,
+ text,
range_str,
lang_str
)
@@ -253,7 +254,7 @@ function TSTreeView:draw(bufnr)
}
end
- lines[#lines + 1] = line
+ lines[i] = line
end
api.nvim_buf_set_lines(bufnr, 0, -1, false, lines)
@@ -275,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
@@ -284,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
@@ -292,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 },
@@ -364,9 +374,9 @@ function M.inspect_tree(opts)
desc = 'Jump to the node under the cursor in the source buffer',
callback = function()
local row = api.nvim_win_get_cursor(w)[1]
- local pos = treeview:get(row)
+ local lnum, col = treeview:get(row).node:start()
api.nvim_set_current_win(win)
- api.nvim_win_set_cursor(win, { pos.lnum + 1, pos.col })
+ api.nvim_win_set_cursor(win, { lnum + 1, col })
end,
})
api.nvim_buf_set_keymap(b, 'n', 'a', '', {
@@ -374,7 +384,7 @@ function M.inspect_tree(opts)
callback = function()
local row, col = unpack(api.nvim_win_get_cursor(w)) ---@type integer, integer
local curnode = treeview:get(row)
- while curnode and not curnode.named do
+ while curnode and not curnode.node:named() do
row = row - 1
curnode = treeview:get(row)
end
@@ -386,9 +396,9 @@ function M.inspect_tree(opts)
return
end
- local id = curnode.id
+ local id = curnode.node:id()
for i, node in treeview:iter() do
- if node.id == id then
+ if node.node:id() == id then
api.nvim_win_set_cursor(w, { i, col })
break
end
@@ -424,20 +434,20 @@ function M.inspect_tree(opts)
api.nvim_buf_clear_namespace(buf, treeview.ns, 0, -1)
local row = api.nvim_win_get_cursor(w)[1]
- local pos = treeview:get(row)
- api.nvim_buf_set_extmark(buf, treeview.ns, pos.lnum, pos.col, {
- end_row = pos.end_lnum,
- end_col = math.max(0, pos.end_col),
+ local lnum, col, end_lnum, end_col = treeview:get(row).node:range()
+ api.nvim_buf_set_extmark(buf, treeview.ns, lnum, col, {
+ end_row = end_lnum,
+ end_col = math.max(0, end_col),
hl_group = 'Visual',
})
local topline, botline = vim.fn.line('w0', win), vim.fn.line('w$', win)
-- Move the cursor if highlighted range is completely out of view
- if pos.lnum < topline and pos.end_lnum < topline then
- api.nvim_win_set_cursor(win, { pos.end_lnum + 1, 0 })
- elseif pos.lnum > botline and pos.end_lnum > botline then
- api.nvim_win_set_cursor(win, { pos.lnum + 1, 0 })
+ if lnum < topline and end_lnum < topline then
+ api.nvim_win_set_cursor(win, { end_lnum + 1, 0 })
+ elseif lnum > botline and end_lnum > botline then
+ api.nvim_win_set_cursor(win, { lnum + 1, 0 })
end
end,
})
@@ -462,7 +472,9 @@ function M.inspect_tree(opts)
return true
end
+ local treeview_opts = treeview.opts
treeview = assert(TSTreeView:new(buf, opts.lang))
+ treeview.opts = treeview_opts
treeview:draw(b)
end,
})
diff --git a/runtime/lua/vim/treesitter/health.lua b/runtime/lua/vim/treesitter/health.lua
index ed1161e97f..a9b066d158 100644
--- a/runtime/lua/vim/treesitter/health.lua
+++ b/runtime/lua/vim/treesitter/health.lua
@@ -1,6 +1,6 @@
local M = {}
local ts = vim.treesitter
-local health = require('vim.health')
+local health = vim.health
--- Performs a healthcheck for treesitter integration
function M.check()
diff --git a/runtime/lua/vim/treesitter/highlighter.lua b/runtime/lua/vim/treesitter/highlighter.lua
index 496193c6ed..388680259a 100644
--- a/runtime/lua/vim/treesitter/highlighter.lua
+++ b/runtime/lua/vim/treesitter/highlighter.lua
@@ -2,50 +2,25 @@ local api = vim.api
local query = vim.treesitter.query
local Range = require('vim.treesitter._range')
----@alias TSHlIter fun(end_line: integer|nil): integer, TSNode, TSMetadata
-
----@class TSHighlightState
----@field next_row integer
----@field iter TSHlIter|nil
-
----@class TSHighlighter
----@field active table<integer,TSHighlighter>
----@field bufnr integer
----@field orig_spelloptions string
----@field _highlight_states table<TSTree,TSHighlightState>
----@field _queries table<string,TSHighlighterQuery>
----@field tree LanguageTree
----@field redraw_count integer
-local TSHighlighter = rawget(vim.treesitter, 'TSHighlighter') or {}
-TSHighlighter.__index = TSHighlighter
+local ns = api.nvim_create_namespace('treesitter/highlighter')
---- @nodoc
-TSHighlighter.active = TSHighlighter.active or {}
+---@alias vim.treesitter.highlighter.Iter fun(end_line: integer|nil): integer, TSNode, vim.treesitter.query.TSMetadata
----@class TSHighlighterQuery
----@field _query Query|nil
----@field hl_cache table<integer,integer>
+---@class (private) vim.treesitter.highlighter.Query
+---@field private _query vim.treesitter.Query?
+---@field private lang string
+---@field private hl_cache table<integer,integer>
local TSHighlighterQuery = {}
TSHighlighterQuery.__index = TSHighlighterQuery
-local ns = api.nvim_create_namespace('treesitter/highlighter')
-
---@private
+---@param lang string
+---@param query_string string?
+---@return vim.treesitter.highlighter.Query
function TSHighlighterQuery.new(lang, query_string)
- local self = setmetatable({}, { __index = TSHighlighterQuery })
-
- self.hl_cache = setmetatable({}, {
- __index = function(table, capture)
- local name = self._query.captures[capture]
- local id = 0
- if not vim.startswith(name, '_') then
- id = api.nvim_get_hl_id_by_name('@' .. name .. '.' .. lang)
- end
-
- rawset(table, capture, id)
- return id
- end,
- })
+ local self = setmetatable({}, TSHighlighterQuery)
+ self.lang = lang
+ self.hl_cache = {}
if query_string then
self._query = query.parse(lang, query_string)
@@ -57,18 +32,57 @@ function TSHighlighterQuery.new(lang, query_string)
end
---@package
+---@param capture integer
+---@return integer?
+function TSHighlighterQuery:get_hl_from_capture(capture)
+ if not self.hl_cache[capture] then
+ local name = self._query.captures[capture]
+ local id = 0
+ if not vim.startswith(name, '_') then
+ id = api.nvim_get_hl_id_by_name('@' .. name .. '.' .. self.lang)
+ end
+ self.hl_cache[capture] = id
+ end
+
+ return self.hl_cache[capture]
+end
+
+---@package
function TSHighlighterQuery:query()
return self._query
end
+---@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
+
+---@nodoc
+---@class vim.treesitter.highlighter
+---@field active table<integer,vim.treesitter.highlighter>
+---@field bufnr integer
+---@field private orig_spelloptions string
+--- A map of highlight states.
+--- This state is kept during rendering across each line update.
+---@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 = {},
+}
+
+TSHighlighter.__index = TSHighlighter
+
---@package
---
--- 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 TSHighlighter Created highlighter object
+---@return vim.treesitter.highlighter Created highlighter object
function TSHighlighter.new(tree, opts)
local self = setmetatable({}, TSHighlighter)
@@ -98,15 +112,12 @@ function TSHighlighter.new(tree, opts)
end,
}, true)
- self.bufnr = tree:source() --[[@as integer]]
- self.edit_count = 0
+ local source = tree:source()
+ assert(type(source) == 'number')
+
+ self.bufnr = source
self.redraw_count = 0
- self.line_count = {}
- -- A map of highlight states.
- -- This state is kept during rendering across each line update.
self._highlight_states = {}
-
- ---@type table<string,TSHighlighterQuery>
self._queries = {}
-- Queries for a specific language can be overridden by a custom
@@ -144,11 +155,9 @@ end
--- @nodoc
--- Removes all internal references to the highlighter
function TSHighlighter:destroy()
- if TSHighlighter.active[self.bufnr] then
- TSHighlighter.active[self.bufnr] = nil
- end
+ TSHighlighter.active[self.bufnr] = nil
- if vim.api.nvim_buf_is_loaded(self.bufnr) then
+ if api.nvim_buf_is_loaded(self.bufnr) then
vim.bo[self.bufnr].spelloptions = self.orig_spelloptions
vim.b[self.bufnr].ts_highlight = nil
if vim.g.syntax_on == 1 then
@@ -157,23 +166,49 @@ function TSHighlighter:destroy()
end
end
----@package
----@param tstree TSTree
----@return TSHighlightState
-function TSHighlighter:get_highlight_state(tstree)
- if not self._highlight_states[tstree] then
- self._highlight_states[tstree] = {
+---@param srow integer
+---@param erow integer exclusive
+---@private
+function TSHighlighter:prepare_highlight_states(srow, erow)
+ self._highlight_states = {}
+
+ self.tree:for_each_tree(function(tstree, tree)
+ if not tstree then
+ return
+ end
+
+ local root_node = tstree:root()
+ local root_start_row, _, root_end_row, _ = root_node:range()
+
+ -- Only consider trees within the visible range
+ if root_start_row > erow or root_end_row < srow then
+ return
+ end
+
+ local highlighter_query = self:get_query(tree:lang())
+
+ -- Some injected languages may not have highlight queries.
+ if not highlighter_query:query() then
+ return
+ 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,
- }
- end
-
- return self._highlight_states[tstree]
+ highlighter_query = highlighter_query,
+ })
+ end)
end
----@private
-function TSHighlighter:reset_highlight_state()
- self._highlight_states = {}
+---@param fn fun(state: vim.treesitter.highlighter.State)
+---@package
+function TSHighlighter:for_each_highlight_state(fn)
+ for _, state in ipairs(self._highlight_states) do
+ fn(state)
+ end
end
---@package
@@ -197,10 +232,9 @@ function TSHighlighter:on_changedtree(changes)
end
--- Gets the query used for @param lang
---
---@package
---@param lang string Language used by the highlighter.
----@return TSHighlighterQuery
+---@return vim.treesitter.highlighter.Query
function TSHighlighter:get_query(lang)
if not self._queries[lang] then
self._queries[lang] = TSHighlighterQuery.new(lang)
@@ -209,35 +243,23 @@ function TSHighlighter:get_query(lang)
return self._queries[lang]
end
----@param self TSHighlighter
+---@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.tree:for_each_tree(function(tstree, tree)
- if not tstree then
- return
- end
-
- local root_node = tstree:root()
+ self:for_each_highlight_state(function(state)
+ local root_node = state.tstree:root()
local root_start_row, _, root_end_row, _ = root_node:range()
- -- Only worry about trees within the line range
+ -- Only consider trees that contain this line
if root_start_row > line or root_end_row < line then
return
end
- local state = self:get_highlight_state(tstree)
- local highlighter_query = self:get_query(tree:lang())
-
- -- Some injected languages may not have highlight queries.
- if not highlighter_query:query() then
- return
- end
-
if state.iter == nil or state.next_row < line then
state.iter =
- highlighter_query:query():iter_captures(root_node, self.bufnr, line, root_end_row + 1)
+ state.highlighter_query:query():iter_captures(root_node, self.bufnr, line, root_end_row + 1)
end
while line >= state.next_row do
@@ -250,9 +272,9 @@ local function on_line_impl(self, buf, line, is_spell_nav)
local start_row, start_col, end_row, end_col = Range.unpack4(range)
if capture then
- local hl = highlighter_query.hl_cache[capture]
+ local hl = state.highlighter_query:get_hl_from_capture(capture)
- local capture_name = highlighter_query:query().captures[capture]
+ local capture_name = state.highlighter_query:query().captures[capture]
local spell = nil ---@type boolean?
if capture_name == 'spell' then
spell = true
@@ -308,7 +330,7 @@ function TSHighlighter._on_spell_nav(_, _, buf, srow, _, erow, _)
return
end
- self:reset_highlight_state()
+ self:prepare_highlight_states(srow, erow)
for row = srow, erow do
on_line_impl(self, buf, row, true)
@@ -326,7 +348,7 @@ function TSHighlighter._on_win(_, _win, buf, topline, botline)
return false
end
self.tree:parse({ topline, botline + 1 })
- self:reset_highlight_state()
+ self:prepare_highlight_states(topline, botline + 1)
self.redraw_count = self.redraw_count + 1
return true
end
diff --git a/runtime/lua/vim/treesitter/language.lua b/runtime/lua/vim/treesitter/language.lua
index 15bf666a1e..47abf65332 100644
--- a/runtime/lua/vim/treesitter/language.lua
+++ b/runtime/lua/vim/treesitter/language.lua
@@ -1,6 +1,5 @@
local api = vim.api
----@class TSLanguageModule
local M = {}
---@type table<string,string>
@@ -37,6 +36,11 @@ end
---@deprecated
function M.require_language(lang, path, silent, symbol_name)
+ vim.deprecate(
+ 'vim.treesitter.language.require_language()',
+ 'vim.treesitter.language.add()',
+ '0.12'
+ )
local opts = {
silent = silent,
path = path,
@@ -52,10 +56,17 @@ function M.require_language(lang, path, silent, symbol_name)
return true
end
----@class treesitter.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}
@@ -63,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 treesitter.RequireLangOpts
opts = opts or {}
local path = opts.path
local filetype = opts.filetype or lang
@@ -114,6 +120,10 @@ local function ensure_list(x)
end
--- Register a parser named {lang} to be used for {filetype}(s).
+---
+--- Note: this adds or overrides the mapping for {filetype}, any existing mappings from other
+--- filetypes to {lang} will be preserved.
+---
--- @param lang string Name of parser
--- @param filetype string|string[] Filetype(s) to associate with lang
function M.register(lang, filetype)
@@ -140,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 0171b416cd..62714d3f1b 100644
--- a/runtime/lua/vim/treesitter/languagetree.lua
+++ b/runtime/lua/vim/treesitter/languagetree.lua
@@ -1,6 +1,4 @@
---- @defgroup lua-treesitter-languagetree
----
---- @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.
@@ -69,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
@@ -91,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
@@ -104,14 +105,11 @@ 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 opts vim.treesitter.LanguageTree.new.Opts?
---@param parent_lang? string Parent language name of this tree
----@return LanguageTree parser object
+---@return vim.treesitter.LanguageTree parser object
function LanguageTree.new(source, lang, opts, parent_lang)
language.add(lang)
- ---@type LanguageTreeOpts
opts = opts or {}
if source == 0 then
@@ -120,7 +118,7 @@ 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,
@@ -196,7 +194,7 @@ local function tcall(f, ...)
end
---@private
----@vararg any
+---@param ... any
function LanguageTree:_log(...)
if not self._logger then
return
@@ -348,7 +346,13 @@ function LanguageTree:_parse_regions(range)
-- If there are no ranges, set to an empty list
-- so the included ranges in the parser are cleared.
for i, ranges in pairs(self:included_regions()) do
- if not self._valid[i] and intercepts_region(ranges, range) then
+ if
+ not self._valid[i]
+ and (
+ intercepts_region(ranges, range)
+ or (self._trees[i] and intercepts_region(self._trees[i]:included_ranges(false), range))
+ )
+ then
self._parser:set_included_ranges(ranges)
local parse_time, tree, tree_changes =
tcall(self._parser.parse, self._parser, self._trees[i], self._source, true)
@@ -427,7 +431,7 @@ function LanguageTree:parse(range)
local query_time = 0
local total_parse_time = 0
- --- At least 1 region is invalid
+ -- At least 1 region is invalid
if not self:is_valid(true) then
changes, no_regions_parsed, total_parse_time = self:_parse_regions(range)
-- Need to run injections when we parsed something
@@ -460,7 +464,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')
@@ -469,6 +473,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
@@ -477,7 +482,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)
@@ -494,7 +499,7 @@ 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)
@@ -664,7 +669,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)
@@ -698,13 +703,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
@@ -751,6 +757,11 @@ end)
---@param alias string language or filetype name
---@return string? # resolved parser name
local function resolve_lang(alias)
+ -- validate that `alias` is a legal language
+ if not (alias and alias:match('[%w_]+') == alias) then
+ return
+ end
+
if has_parser(alias) then
return alias
end
@@ -773,8 +784,8 @@ end
---@private
--- Extract injections according to:
--- https://tree-sitter.github.io/tree-sitter/syntax-highlighting#language-injection
----@param match table<integer,TSNode>
----@param metadata TSMetadata
+---@param match table<integer,TSNode[]>
+---@param metadata vim.treesitter.query.TSMetadata
---@return string?, boolean, Range6[]
function LanguageTree:_get_injection(match, metadata)
local ranges = {} ---@type Range6[]
@@ -785,14 +796,16 @@ function LanguageTree:_get_injection(match, metadata)
or (injection_lang and resolve_lang(injection_lang))
local include_children = metadata['injection.include-children'] ~= nil
- for id, node in pairs(match) do
- local name = self._injection_query.captures[id]
- -- Lang should override any other language tag
- if name == 'injection.language' then
- local text = vim.treesitter.get_node_text(node, self._source, { metadata = metadata[id] })
- lang = resolve_lang(text)
- elseif name == 'injection.content' then
- ranges = get_node_ranges(node, self._source, metadata[id], include_children)
+ for id, nodes in pairs(match) do
+ for _, node in ipairs(nodes) do
+ local name = self._injection_query.captures[id]
+ -- Lang should override any other language tag
+ if name == 'injection.language' then
+ local text = vim.treesitter.get_node_text(node, self._source, { metadata = metadata[id] })
+ lang = resolve_lang(text)
+ elseif name == 'injection.content' then
+ ranges = get_node_ranges(node, self._source, metadata[id], include_children)
+ end
end
end
@@ -825,7 +838,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
@@ -833,7 +846,13 @@ function LanguageTree:_get_injections()
local start_line, _, end_line, _ = root_node:range()
for pattern, match, metadata in
- self._injection_query:iter_matches(root_node, self._source, start_line, end_line + 1)
+ self._injection_query:iter_matches(
+ root_node,
+ self._source,
+ start_line,
+ end_line + 1,
+ { all = true }
+ )
do
local lang, combined, ranges = self:_get_injection(match, metadata)
if lang then
@@ -1133,7 +1152,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 8cbbffcd60..a086f5e876 100644
--- a/runtime/lua/vim/treesitter/query.lua
+++ b/runtime/lua/vim/treesitter/query.lua
@@ -1,19 +1,50 @@
local api = vim.api
local language = require('vim.treesitter.language')
----@class Query
----@field captures string[] List of captures used in query
----@field info TSQueryInfo Contains used queries, predicates, directives
----@field query userdata Parsed query
+local M = {}
+
+---@nodoc
+---Parsed query, see |vim.treesitter.query.parse()|
+---
+---@class vim.treesitter.Query
+---@field lang string name of the language for this parser
+---@field captures string[] list of (unique) capture names defined in query
+---@field info vim.treesitter.QueryInfo contains information used in the query (e.g. captures, predicates, directives)
+---@field query TSQuery userdata query object
local Query = {}
Query.__index = Query
----@class TSQueryInfo
----@field captures table
----@field patterns table<string,any[][]>
+---@package
+---@see vim.treesitter.query.parse
+---@param lang string
+---@param ts_query TSQuery
+---@return vim.treesitter.Query
+function Query.new(lang, ts_query)
+ local self = setmetatable({}, Query)
+ local query_info = ts_query:inspect() ---@type TSQueryInfo
+ self.query = ts_query
+ self.lang = lang
+ self.info = {
+ captures = query_info.captures,
+ patterns = query_info.patterns,
+ }
+ self.captures = self.info.captures
+ return self
+end
----@class TSQueryModule
-local M = {}
+---@nodoc
+---Information for Query, see |vim.treesitter.query.parse()|
+---@class vim.treesitter.QueryInfo
+---
+---List of (unique) capture names defined in query.
+---@field captures string[]
+---
+---Contains information about predicates and directives.
+---Key is pattern id, and value is list of predicates or directives defined in the pattern.
+---A predicate or directive is a list of (integer|string); integer represents `capture_id`, and
+---string represents (literal) arguments to predicate/directive. See |treesitter-predicates|
+---and |treesitter-directives| for more details.
+---@field patterns table<integer, (integer|string)[][]>
---@param files string[]
---@return string[]
@@ -53,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
@@ -163,7 +184,7 @@ local function read_query_files(filenames)
end
-- The explicitly set queries from |vim.treesitter.query.set()|
----@type table<string,table<string,Query>>
+---@type table<string,table<string,vim.treesitter.Query>>
local explicit_queries = setmetatable({}, {
__index = function(t, k)
local lang_queries = {}
@@ -173,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
@@ -191,18 +206,12 @@ 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
---@param query_name string Name of the query (e.g. "highlights")
---
----@return Query|nil Parsed query
+---@return vim.treesitter.Query|nil : Parsed query. `nil` if no query files are found.
M.get = vim.func._memoize('concat-2', function(lang, query_name)
if explicit_queries[lang][query_name] then
return explicit_queries[lang][query_name]
@@ -218,92 +227,96 @@ 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).
---
--- Returns a `Query` (see |lua-treesitter-query|) object which can be used to
--- search nodes in the syntax tree for the patterns defined in {query}
---- using `iter_*` methods below.
+--- using the `iter_captures` and `iter_matches` methods.
---
--- Exposes `info` and `captures` with additional context about {query}.
---- - `captures` contains the list of unique capture names defined in
---- {query}.
---- -` info.captures` also points to `captures`.
+--- - `captures` contains the list of unique capture names defined in {query}.
+--- - `info.captures` also points to `captures`.
--- - `info.patterns` contains information about predicates.
---
---@param lang string Language to use for the query
---@param query string Query in s-expr syntax
---
----@return Query Parsed query
+---@return vim.treesitter.Query Parsed query
+---
+---@see |vim.treesitter.query.get()|
M.parse = vim.func._memoize('concat-2', function(lang, query)
language.add(lang)
- local self = setmetatable({}, Query)
- self.query = vim._ts_parse_query(lang, query)
- self.info = self.query:inspect()
- self.captures = self.info.captures
- return self
+ local ts_query = vim._ts_parse_query(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
-
----@alias TSMatch table<integer,TSNode>
-
----@alias TSPredicate fun(match: TSMatch, _, _, predicate: any[]): boolean
-
--- Predicate handler receive the following arguments
--- (match, pattern, bufnr, predicate)
----@type table<string,TSPredicate>
-local predicate_handlers = {
- ['eq?'] = function(match, _, source, predicate)
- local node = match[predicate[2]]
- if not node then
+--- 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 vim.treesitter.query.TSMatch
+ --- @param source integer|string
+ --- @param predicate any[]
+ --- @param any boolean
+ ['eq'] = function(match, source, predicate, any)
+ local nodes = match[predicate[2]]
+ if not nodes or #nodes == 0 then
return true
end
- local node_text = vim.treesitter.get_node_text(node, source)
- local str ---@type string
- if type(predicate[3]) == 'string' then
- -- (#eq? @aa "foo")
- str = predicate[3]
- else
- -- (#eq? @aa @bb)
- str = vim.treesitter.get_node_text(match[predicate[3]], source)
- end
+ for _, node in ipairs(nodes) do
+ local node_text = vim.treesitter.get_node_text(node, source)
+
+ local str ---@type string
+ if type(predicate[3]) == 'string' then
+ -- (#eq? @aa "foo")
+ str = predicate[3]
+ else
+ -- (#eq? @aa @bb)
+ local other = assert(match[predicate[3]])
+ assert(#other == 1, '#eq? does not support comparison with captures on multiple nodes')
+ str = vim.treesitter.get_node_text(other[1], source)
+ end
- if node_text ~= str or str == nil then
- return false
+ local res = str ~= nil and node_text == str
+ if any and res then
+ return true
+ elseif not any and not res then
+ return false
+ end
end
- return true
+ return not any
end,
- ['lua-match?'] = function(match, _, source, predicate)
- local node = match[predicate[2]]
- if not node then
+ --- @param match vim.treesitter.query.TSMatch
+ --- @param source integer|string
+ --- @param predicate any[]
+ --- @param any boolean
+ ['lua-match'] = function(match, source, predicate, any)
+ local nodes = match[predicate[2]]
+ if not nodes or #nodes == 0 then
return true
end
- local regex = predicate[3]
- return string.find(vim.treesitter.get_node_text(node, source), regex) ~= nil
+
+ for _, node in ipairs(nodes) do
+ local regex = predicate[3]
+ local res = string.find(vim.treesitter.get_node_text(node, source), regex) ~= nil
+ if any and res then
+ return true
+ elseif not any and not res then
+ return false
+ end
+ end
+
+ return not any
end,
- ['match?'] = (function()
+ ['match'] = (function()
local magic_prefixes = { ['\\v'] = true, ['\\m'] = true, ['\\M'] = true, ['\\V'] = true }
local function check_magic(str)
if string.len(str) < 2 or magic_prefixes[string.sub(str, 1, 2)] then
@@ -320,85 +333,161 @@ local predicate_handlers = {
end,
})
- return function(match, _, source, pred)
- ---@cast match TSMatch
- local node = match[pred[2]]
- if not node then
+ --- @param match vim.treesitter.query.TSMatch
+ --- @param source integer|string
+ --- @param predicate any[]
+ --- @param any boolean
+ return function(match, source, predicate, any)
+ local nodes = match[predicate[2]]
+ if not nodes or #nodes == 0 then
return true
end
- ---@diagnostic disable-next-line no-unknown
- local regex = compiled_vim_regexes[pred[3]]
- return regex:match_str(vim.treesitter.get_node_text(node, source))
+
+ for _, node in ipairs(nodes) do
+ local regex = compiled_vim_regexes[predicate[3]] ---@type vim.regex
+ local res = regex:match_str(vim.treesitter.get_node_text(node, source))
+ if any and res then
+ return true
+ elseif not any and not res then
+ return false
+ end
+ end
+ return not any
end
end)(),
- ['contains?'] = function(match, _, source, predicate)
- local node = match[predicate[2]]
- if not node then
+ --- @param match vim.treesitter.query.TSMatch
+ --- @param source integer|string
+ --- @param predicate any[]
+ --- @param any boolean
+ ['contains'] = function(match, source, predicate, any)
+ local nodes = match[predicate[2]]
+ if not nodes or #nodes == 0 then
return true
end
- local node_text = vim.treesitter.get_node_text(node, source)
- for i = 3, #predicate do
- if string.find(node_text, predicate[i], 1, true) then
- return true
+ for _, node in ipairs(nodes) do
+ local node_text = vim.treesitter.get_node_text(node, source)
+
+ for i = 3, #predicate do
+ local res = string.find(node_text, predicate[i], 1, true)
+ if any and res then
+ return true
+ elseif not any and not res then
+ return false
+ end
end
end
- return false
+ return not any
+ end,
+}
+
+---@nodoc
+---@class vim.treesitter.query.TSMatch
+---@field pattern? integer
+---@field active? boolean
+---@field [integer] TSNode[]
+
+---@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)
+---@type table<string,TSPredicate>
+local predicate_handlers = {
+ ['eq?'] = function(match, _, source, predicate)
+ return impl['eq'](match, source, predicate, false)
+ end,
+
+ ['any-eq?'] = function(match, _, source, predicate)
+ return impl['eq'](match, source, predicate, true)
+ end,
+
+ ['lua-match?'] = function(match, _, source, predicate)
+ return impl['lua-match'](match, source, predicate, false)
+ end,
+
+ ['any-lua-match?'] = function(match, _, source, predicate)
+ return impl['lua-match'](match, source, predicate, true)
+ end,
+
+ ['match?'] = function(match, _, source, predicate)
+ return impl['match'](match, source, predicate, false)
+ end,
+
+ ['any-match?'] = function(match, _, source, predicate)
+ return impl['match'](match, source, predicate, true)
+ end,
+
+ ['contains?'] = function(match, _, source, predicate)
+ return impl['contains'](match, source, predicate, false)
+ end,
+
+ ['any-contains?'] = function(match, _, source, predicate)
+ return impl['contains'](match, source, predicate, true)
end,
['any-of?'] = function(match, _, source, predicate)
- local node = match[predicate[2]]
- if not node then
+ local nodes = match[predicate[2]]
+ if not nodes or #nodes == 0 then
return true
end
- local node_text = vim.treesitter.get_node_text(node, source)
- -- Since 'predicate' will not be used by callers of this function, use it
- -- to store a string set built from the list of words to check against.
- local string_set = predicate['string_set']
- if not string_set then
- string_set = {}
- for i = 3, #predicate do
- ---@diagnostic disable-next-line:no-unknown
- string_set[predicate[i]] = true
+ for _, node in ipairs(nodes) do
+ local node_text = vim.treesitter.get_node_text(node, source)
+
+ -- Since 'predicate' will not be used by callers of this function, use it
+ -- to store a string set built from the list of words to check against.
+ local string_set = predicate['string_set'] --- @type table<string, boolean>
+ if not string_set then
+ string_set = {}
+ for i = 3, #predicate do
+ string_set[predicate[i]] = true
+ end
+ predicate['string_set'] = string_set
+ end
+
+ if string_set[node_text] then
+ return true
end
- predicate['string_set'] = string_set
end
- return string_set[node_text]
+ return false
end,
['has-ancestor?'] = function(match, _, _, predicate)
- local node = match[predicate[2]]
- if not node then
+ local nodes = match[predicate[2]]
+ if not nodes or #nodes == 0 then
return true
end
- local ancestor_types = {}
- for _, type in ipairs({ unpack(predicate, 3) }) do
- ancestor_types[type] = true
- end
+ for _, node in ipairs(nodes) do
+ local ancestor_types = {} --- @type table<string, boolean>
+ for _, type in ipairs({ unpack(predicate, 3) }) do
+ ancestor_types[type] = true
+ end
- node = node:parent()
- while node do
- if ancestor_types[node:type()] then
- return true
+ local cur = node:parent()
+ while cur do
+ if ancestor_types[cur:type()] then
+ return true
+ end
+ cur = cur:parent()
end
- node = node:parent()
end
return false
end,
['has-parent?'] = function(match, _, _, predicate)
- local node = match[predicate[2]]
- if not node then
+ local nodes = match[predicate[2]]
+ if not nodes or #nodes == 0 then
return true
end
- if vim.list_contains({ unpack(predicate, 3) }, node:parent():type()) then
- return true
+ for _, node in ipairs(nodes) do
+ if vim.list_contains({ unpack(predicate, 3) }, node:parent():type()) then
+ return true
+ end
end
return false
end,
@@ -406,14 +495,16 @@ local predicate_handlers = {
-- As we provide lua-match? also expose vim-match?
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)
@@ -441,13 +532,17 @@ local directive_handlers = {
-- Shifts the range of a node.
-- Example: (#offset! @_node 0 1 0 -1)
['offset!'] = function(match, _, _, pred, metadata)
- ---@cast pred integer[]
- local capture_id = pred[2]
+ local capture_id = pred[2] --[[@as integer]]
+ local nodes = match[capture_id]
+ assert(#nodes == 1, '#offset! does not support captures on multiple nodes')
+
+ local node = nodes[1]
+
if not metadata[capture_id] then
metadata[capture_id] = {}
end
- local range = metadata[capture_id].range or { match[capture_id]:range() }
+ local range = metadata[capture_id].range or { node:range() }
local start_row_offset = pred[3] or 0
local start_col_offset = pred[4] or 0
local end_row_offset = pred[5] or 0
@@ -471,7 +566,9 @@ local directive_handlers = {
local id = pred[2]
assert(type(id) == 'number')
- local node = match[id]
+ local nodes = match[id]
+ assert(#nodes == 1, '#gsub! does not support captures on multiple nodes')
+ local node = nodes[1]
local text = vim.treesitter.get_node_text(node, bufnr, { metadata = metadata[id] }) or ''
if not metadata[id] then
@@ -491,10 +588,9 @@ local directive_handlers = {
local capture_id = pred[2]
assert(type(capture_id) == 'number')
- local node = match[capture_id]
- if not node then
- return
- end
+ local nodes = match[capture_id]
+ assert(#nodes == 1, '#trim! does not support captures on multiple nodes')
+ local node = nodes[1]
local start_row, start_col, end_row, end_col = node:range()
@@ -525,38 +621,93 @@ local directive_handlers = {
--- Adds a new predicate to be used in queries
---
---@param name string Name of the predicate, without leading #
----@param handler function(match:table<string,TSNode>, pattern:string, bufnr:integer, predicate:string[])
+---@param handler function(match: table<integer,TSNode[]>, pattern: integer, source: integer|string, predicate: any[], metadata: table)
--- - see |vim.treesitter.query.add_directive()| for argument meanings
----@param force boolean|nil
-function M.add_predicate(name, handler, force)
- if predicate_handlers[name] and not force then
- error(string.format('Overriding %s', name))
+---@param opts table<string, any> Optional options:
+--- - force (boolean): Override an existing
+--- predicate of the same name
+--- - all (boolean): Use the correct
+--- implementation of the match table where
+--- capture IDs map to a list of nodes instead
+--- of a single node. Defaults to false (for
+--- backward compatibility). This option will
+--- eventually become the default and removed.
+function M.add_predicate(name, handler, opts)
+ -- Backward compatibility: old signature had "force" as boolean argument
+ if type(opts) == 'boolean' then
+ opts = { force = opts }
end
- predicate_handlers[name] = handler
+ opts = opts or {}
+
+ if predicate_handlers[name] and not opts.force then
+ error(string.format('Overriding existing predicate %s', name))
+ end
+
+ if opts.all then
+ predicate_handlers[name] = handler
+ else
+ --- @param match table<integer, TSNode[]>
+ local function wrapper(match, ...)
+ local m = {} ---@type table<integer, TSNode>
+ for k, v in pairs(match) do
+ if type(k) == 'number' then
+ m[k] = v[#v]
+ end
+ end
+ return handler(m, ...)
+ end
+ predicate_handlers[name] = wrapper
+ end
end
--- Adds a new directive to be used in queries
---
--- Handlers can set match level data by setting directly on the
---- metadata object `metadata.key = value`, additionally, handlers
+--- metadata object `metadata.key = value`. Additionally, handlers
--- can set node level data by using the capture id on the
--- metadata table `metadata[capture_id].key = value`
---
---@param name string Name of the directive, without leading #
----@param handler function(match:table<string,TSNode>, pattern:string, bufnr:integer, predicate:string[], metadata:table)
---- - match: see |treesitter-query|
---- - node-level data are accessible via `match[capture_id]`
---- - pattern: see |treesitter-query|
+---@param handler function(match: table<integer,TSNode[]>, pattern: integer, source: integer|string, predicate: any[], metadata: table)
+--- - match: A table mapping capture IDs to a list of captured nodes
+--- - pattern: the index of the matching pattern in the query file
--- - predicate: list of strings containing the full directive being called, e.g.
--- `(node (#set! conceal "-"))` would get the predicate `{ "#set!", "conceal", "-" }`
----@param force boolean|nil
-function M.add_directive(name, handler, force)
- if directive_handlers[name] and not force then
- error(string.format('Overriding %s', name))
+---@param opts table<string, any> Optional options:
+--- - force (boolean): Override an existing
+--- predicate of the same name
+--- - all (boolean): Use the correct
+--- implementation of the match table where
+--- capture IDs map to a list of nodes instead
+--- of a single node. Defaults to false (for
+--- backward compatibility). This option will
+--- eventually become the default and removed.
+function M.add_directive(name, handler, opts)
+ -- Backward compatibility: old signature had "force" as boolean argument
+ if type(opts) == 'boolean' then
+ opts = { force = opts }
end
- directive_handlers[name] = handler
+ opts = opts or {}
+
+ if directive_handlers[name] and not opts.force then
+ error(string.format('Overriding existing directive %s', name))
+ end
+
+ if opts.all then
+ directive_handlers[name] = handler
+ else
+ --- @param match table<integer, TSNode[]>
+ local function wrapper(match, ...)
+ local m = {} ---@type table<integer, TSNode>
+ for k, v in pairs(match) do
+ m[k] = v[#v]
+ end
+ handler(m, ...)
+ end
+ directive_handlers[name] = wrapper
+ end
end
--- Lists the currently available directives to use in queries.
@@ -580,8 +731,8 @@ local function is_directive(name)
end
---@private
----@param match TSMatch
----@param pattern string
+---@param match vim.treesitter.query.TSMatch
+---@param pattern integer
---@param source integer|string
function Query:match_preds(match, pattern, source)
local preds = self.info.patterns[pattern]
@@ -591,18 +742,14 @@ function Query:match_preds(match, pattern, source)
-- continue on the other case. This way unknown predicates will not be considered,
-- which allows some testing and easier user extensibility (#12173).
-- Also, tree-sitter strips the leading # from predicates for us.
- local pred_name ---@type string
-
- local is_not ---@type boolean
+ local is_not = false
-- Skip over directives... they will get processed after all the predicates.
if not is_directive(pred[1]) then
- if string.sub(pred[1], 1, 4) == 'not-' then
- pred_name = string.sub(pred[1], 5)
+ local pred_name = pred[1]
+ if pred_name:match('^not%-') then
+ pred_name = pred_name:sub(5)
is_not = true
- else
- pred_name = pred[1]
- is_not = false
end
local handler = predicate_handlers[pred_name]
@@ -623,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]
@@ -645,14 +792,16 @@ end
--- Returns the start and stop value if set else the node's range.
-- When the node's range is used, the stop is incremented by 1
-- to make the search inclusive.
----@param start integer
----@param stop integer
+---@param start integer|nil
+---@param stop integer|nil
---@param node TSNode
---@return integer, integer
local function value_or_node_range(start, stop, node)
- if start == nil and stop == nil then
- local node_start, _, node_stop, _ = node:range()
- return node_start, node_stop + 1 -- Make stop inclusive
+ if start == nil then
+ start = node:start()
+ end
+ if stop == nil then
+ stop = node:end_() + 1 -- Make stop inclusive
end
return start, stop
@@ -683,10 +832,10 @@ end
---
---@param node TSNode under which the search will occur
---@param source (integer|string) Source buffer or string to extract text from
----@param start integer Starting line for the search
----@param stop integer Stopping line for the search (end-exclusive)
+---@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
@@ -695,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)
+ 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 = {}
@@ -719,46 +868,55 @@ end
--- Iterates the matches of self on a given range.
---
---- Iterate over all matches within a {node}. The arguments are the same as
---- for |Query:iter_captures()| but the iterated values are different:
---- an (1-based) index of the pattern in the query, a table mapping
---- capture indices to nodes, and metadata from any directives processing the match.
---- If the query has more than one pattern, the capture table might be sparse
---- and e.g. `pairs()` method should be used over `ipairs`.
---- Here is an example iterating over all captures in every match:
+--- Iterate over all matches within a {node}. The arguments are the same as for
+--- |Query:iter_captures()| but the iterated values are different: an (1-based)
+--- index of the pattern in the query, a table mapping capture indices to a list
+--- of nodes, and metadata from any directives processing the match.
+---
+--- WARNING: Set `all=true` to ensure all matching nodes in a match are
+--- returned, otherwise only the last node in a match is returned, breaking captures
+--- involving quantifiers such as `(comment)+ @comment`. The default option
+--- `all=false` is only provided for backward compatibility and will be removed
+--- after Nvim 0.10.
+---
+--- Example:
---
--- ```lua
---- for pattern, match, metadata in cquery:iter_matches(tree:root(), bufnr, first, last) do
---- for id, node in pairs(match) do
+--- for pattern, match, metadata in cquery:iter_matches(tree:root(), bufnr, 0, -1, { all = true }) do
+--- for id, nodes in pairs(match) do
--- local name = query.captures[id]
---- -- `node` was captured by the `name` capture in the match
+--- for _, node in ipairs(nodes) do
+--- -- `node` was captured by the `name` capture in the match
---
---- local node_data = metadata[id] -- Node level metadata
----
---- -- ... use the info here ...
+--- local node_data = metadata[id] -- Node level metadata
+--- ... use the info here ...
+--- end
--- end
--- end
--- ```
---
+---
---@param node TSNode under which the search will occur
---@param source (integer|string) Source buffer or string to search
----@param start integer Starting line for the search
----@param stop integer Stopping line for the search (end-exclusive)
----@param opts table|nil Options:
+---@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_()`.
+---@param opts? table Optional keyword arguments:
--- - max_start_depth (integer) if non-zero, sets the maximum start depth
--- for each match. This is used to prevent traversing too deep into a tree.
---- Requires treesitter >= 0.20.9.
+--- - all (boolean) When set, the returned match table maps capture IDs to a list of nodes.
+--- Older versions of iter_matches incorrectly mapped capture IDs to a single node, which is
+--- incorrect behavior. This option will eventually become the default and removed.
---
----@return (fun(): integer, table<integer,TSNode>, table): pattern id, match, metadata
+---@return (fun(): integer, table<integer, TSNode[]>, table): pattern id, match, metadata
function Query:iter_matches(node, source, start, stop, opts)
+ local all = opts and opts.all
if type(source) == 'number' and source == 0 then
source = api.nvim_get_current_buf()
end
start, stop = value_or_node_range(start, stop, node)
- local raw_iter = node:_rawquery(self.query, false, start, stop, opts)
- ---@cast raw_iter fun(): string, any
+ 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 = {}
@@ -771,14 +929,33 @@ function Query:iter_matches(node, source, start, stop, opts)
self:apply_directives(match, pattern, source, metadata)
end
+
+ if not all then
+ -- Convert the match table into the old buggy version for backward
+ -- compatibility. This is slow. Plugin authors, if you're reading this, set the "all"
+ -- option!
+ local old_match = {} ---@type table<integer, TSNode>
+ for k, v in pairs(match or {}) do
+ old_match[k] = v[#v]
+ end
+ return pattern, old_match, metadata
+ end
+
return pattern, match, metadata
end
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.
---
@@ -793,15 +970,12 @@ 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|nil) 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
- require('vim.treesitter._query_linter').clear(buf)
+ vim.treesitter._query_linter.clear(buf)
else
- require('vim.treesitter._query_linter').lint(buf, opts)
+ vim.treesitter._query_linter.lint(buf, opts)
end
end
@@ -813,13 +987,15 @@ end
--- vim.bo.omnifunc = 'v:lua.vim.treesitter.query.omnifunc'
--- ```
---
+--- @param findstart 0|1
+--- @param base string
function M.omnifunc(findstart, base)
- return require('vim.treesitter._query_linter').omnifunc(findstart, base)
+ return vim.treesitter._query_linter.omnifunc(findstart, base)
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
@@ -827,7 +1003,7 @@ end
---
--- @param lang? string language to open the query editor for. If omitted, inferred from the current buffer's filetype.
function M.edit(lang)
- require('vim.treesitter.dev').edit_query(lang)
+ vim.treesitter.dev.edit_query(lang)
end
return M
diff --git a/runtime/lua/vim/ui.lua b/runtime/lua/vim/ui.lua
index b6ddf337ce..b0e7ca1a35 100644
--- a/runtime/lua/vim/ui.lua
+++ b/runtime/lua/vim/ui.lua
@@ -20,7 +20,7 @@ local M = {}
--- end)
--- ```
---
----@param items table Arbitrary items
+---@param items any[] Arbitrary items
---@param opts table Additional options
--- - prompt (string|nil)
--- Text of the prompt. Defaults to `Select one of:`
@@ -32,7 +32,7 @@ local M = {}
--- Plugins reimplementing `vim.ui.select` may wish to
--- use this to infer the structure or semantics of
--- `items`, or the context in which select() was called.
----@param on_choice function ((item|nil, idx|nil) -> ())
+---@param on_choice fun(item: any|nil, idx: integer|nil)
--- Called once the user made a choice.
--- `idx` is the 1-based index of `item` within `items`.
--- `nil` if the user aborted the dialog.
@@ -44,7 +44,7 @@ function M.select(items, opts, on_choice)
opts = opts or {}
local choices = { opts.prompt or 'Select one of:' }
local format_item = opts.format_item or tostring
- for i, item in pairs(items) do
+ for i, item in ipairs(items) do
table.insert(choices, string.format('%d: %s', i, format_item(item)))
end
local choice = vim.fn.inputlist(choices)
@@ -66,7 +66,7 @@ end
--- end)
--- ```
---
----@param opts table Additional options. See |input()|
+---@param opts table? Additional options. See |input()|
--- - prompt (string|nil)
--- Text of the prompt
--- - default (string|nil)
@@ -87,6 +87,7 @@ end
--- `nil` if the user aborted the dialog.
function M.input(opts, on_confirm)
vim.validate({
+ opts = { opts, 'table', true },
on_confirm = { on_confirm, 'function', false },
})
@@ -133,7 +134,7 @@ function M.open(path)
path = vim.fn.expand(path)
end
- local cmd
+ local cmd --- @type string[]
if vim.fn.has('mac') == 1 then
cmd = { 'open', path }
@@ -143,12 +144,12 @@ function M.open(path)
else
return nil, 'vim.ui.open: rundll32 not found'
end
- elseif vim.fn.executable('wslview') == 1 then
- cmd = { 'wslview', path }
+ elseif vim.fn.executable('explorer.exe') == 1 then
+ cmd = { 'explorer.exe', path }
elseif vim.fn.executable('xdg-open') == 1 then
cmd = { 'xdg-open', path }
else
- return nil, 'vim.ui.open: no handler found (tried: wslview, xdg-open)'
+ return nil, 'vim.ui.open: no handler found (tried: explorer.exe, xdg-open)'
end
local rv = vim.system(cmd, { text = true, detach = true }):wait()
diff --git a/runtime/lua/vim/ui/clipboard/osc52.lua b/runtime/lua/vim/ui/clipboard/osc52.lua
index 6483f0387d..50afbe63a5 100644
--- a/runtime/lua/vim/ui/clipboard/osc52.lua
+++ b/runtime/lua/vim/ui/clipboard/osc52.lua
@@ -13,7 +13,9 @@ function M.copy(reg)
local clipboard = reg == '+' and 'c' or 'p'
return function(lines)
local s = table.concat(lines, '\n')
- io.stdout:write(osc52(clipboard, vim.base64.encode(s)))
+ -- The data to be written here can be quite long.
+ -- Use nvim_chan_send() as io.stdout:write() doesn't handle EAGAIN. #26688
+ vim.api.nvim_chan_send(2, osc52(clipboard, vim.base64.encode(s)))
end
end
diff --git a/runtime/lua/vim/uri.lua b/runtime/lua/vim/uri.lua
index 2dc817c5c1..b4e4098b91 100644
--- a/runtime/lua/vim/uri.lua
+++ b/runtime/lua/vim/uri.lua
@@ -1,4 +1,4 @@
----TODO: This is implemented only for files currently.
+-- TODO: This is implemented only for files currently.
-- https://tools.ietf.org/html/rfc3986
-- https://tools.ietf.org/html/rfc2732
-- https://tools.ietf.org/html/rfc2396
@@ -10,14 +10,14 @@ local tohex = require('bit').tohex
local URI_SCHEME_PATTERN = '^([a-zA-Z]+[a-zA-Z0-9.+-]*):.*'
local WINDOWS_URI_SCHEME_PATTERN = '^([a-zA-Z]+[a-zA-Z0-9.+-]*):[a-zA-Z]:.*'
local PATTERNS = {
- ---RFC 2396
- ---https://tools.ietf.org/html/rfc2396#section-2.2
+ -- RFC 2396
+ -- https://tools.ietf.org/html/rfc2396#section-2.2
rfc2396 = "^A-Za-z0-9%-_.!~*'()",
- ---RFC 2732
- ---https://tools.ietf.org/html/rfc2732
+ -- RFC 2732
+ -- https://tools.ietf.org/html/rfc2732
rfc2732 = "^A-Za-z0-9%-_.!~*'()[]",
- ---RFC 3986
- ---https://tools.ietf.org/html/rfc3986#section-2.2
+ -- RFC 3986
+ -- https://tools.ietf.org/html/rfc3986#section-2.2
rfc3986 = "^A-Za-z0-9%-._~!$&'()*+,;=:@/",
}
@@ -104,6 +104,10 @@ function M.uri_to_fname(uri)
if scheme ~= 'file' then
return uri
end
+ local fragment_index = uri:find('#')
+ if fragment_index ~= nil then
+ uri = uri:sub(1, fragment_index - 1)
+ end
uri = M.uri_decode(uri)
--TODO improve this.
if is_windows_file_uri(uri) then
@@ -116,7 +120,6 @@ end
---Gets the buffer for a uri.
---Creates a new unloaded buffer if no buffer for the uri already exists.
---
---@param uri string
---@return integer bufnr
function M.uri_to_bufnr(uri)
diff --git a/runtime/lua/vim/userreg.lua b/runtime/lua/vim/userreg.lua
new file mode 100644
index 0000000000..5abcff0407
--- /dev/null
+++ b/runtime/lua/vim/userreg.lua
@@ -0,0 +1,51 @@
+-- Defualt implementation of the userregfunc. This default implementation is
+-- extensible and allows other plugins to register handlers for different
+-- registers.
+--
+-- The default handler behaves just as a normal register would.
+
+local userreg = {}
+
+-- Returns a "default handler" which behaves exactly like the builtin registers
+-- in Vim. Simply stores whatever was yanked and returns the last thing that was
+-- yanked.
+function userreg._default_handler()
+ local d = {}
+
+ function d.do_yank(self, content)
+ self.content = content
+ end
+
+ function d.do_put(self)
+ return self.content or {}
+ end
+
+ return d
+end
+
+-- The store for registers default handler
+userreg._regtable = {}
+
+-- Function for the userreg. This function will defer to the handler registered
+-- to the given register. If no handler is registered to the given register, the
+-- default handler is used.
+function userreg.fn(action, register, content)
+ if not userreg._regtable[register] then
+ userreg._regtable[register] = userreg._default_handler()
+ end
+
+ if action == "yank" then
+ userreg._regtable[register]:do_yank(content)
+ return nil
+ else
+ return userreg._regtable[register]:do_put()
+ end
+end
+
+-- Registers a handler with a register. Future yanks and puts will defer to the
+-- handler when determining the content to put/yank.
+function userreg.register_handler(register, handler)
+ userreg._regtable[register] = handler
+end
+
+return userreg
diff --git a/runtime/lua/vim/version.lua b/runtime/lua/vim/version.lua
index 306eef90d3..0b149700b5 100644
--- a/runtime/lua/vim/version.lua
+++ b/runtime/lua/vim/version.lua
@@ -1,6 +1,5 @@
---- @defgroup vim.version
----
---- @brief The \`vim.version\` module provides functions for comparing versions and ranges
+--- @brief
+--- The `vim.version` module provides functions for comparing versions and ranges
--- conforming to the https://semver.org spec. Plugins, and plugin managers, can use this to check
--- available tools and dependencies on the current system.
---
@@ -13,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()|.
@@ -55,7 +54,8 @@
local M = {}
----@class Version
+---@nodoc
+---@class vim.Version
---@field [1] number
---@field [2] number
---@field [3] number
@@ -69,6 +69,8 @@ Version.__index = Version
--- Compares prerelease strings: per semver, number parts must be must be treated as numbers:
--- "pre1.10" is greater than "pre1.2". https://semver.org/#spec-item-11
+---@param prerel1 string?
+---@param prerel2 string?
local function cmp_prerel(prerel1, prerel2)
if not prerel1 or not prerel2 then
return prerel1 and -1 or (prerel2 and 1 or 0)
@@ -78,8 +80,8 @@ local function cmp_prerel(prerel1, prerel2)
local iter1 = prerel1:gmatch('([^0-9]*)(%d*)')
local iter2 = prerel2:gmatch('([^0-9]*)(%d*)')
while true do
- local word1, n1 = iter1()
- local word2, n2 = iter2()
+ local word1, n1 = iter1() --- @type string?, string|number|nil
+ local word2, n2 = iter2() --- @type string?, string|number|nil
if word1 == nil and word2 == nil then -- Done iterating.
return 0
end
@@ -110,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
@@ -131,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
@@ -143,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
@@ -152,13 +154,13 @@ 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
- return setmetatable(vim.deepcopy(version), Version)
+ return setmetatable(vim.deepcopy(version, true), Version)
end
return setmetatable({
major = version[1] or 0,
@@ -168,6 +170,7 @@ function M._version(version, strict) -- Adapted from https://github.com/folke/la
end
if not strict then -- TODO: add more "scrubbing".
+ --- @cast version string
version = version:match('%d[^ ]*')
end
@@ -201,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)
@@ -214,21 +217,22 @@ 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
version = M.parse(version)
elseif getmetatable(version) ~= Version then
-- Need metatable to compare versions.
- version = setmetatable(vim.deepcopy(version), Version)
+ version = setmetatable(vim.deepcopy(version, true), Version)
end
if version then
if version.prerelease ~= self.from.prerelease then
@@ -259,16 +263,18 @@ end
--- print(r:has(vim.version())) -- check against current Nvim version
--- ```
---
---- Or use cmp(), eq(), lt(), and gt() to compare `.to` and `.from` directly:
+--- Or use cmp(), le(), lt(), ge(), gt(), and/or eq() to compare a version
+--- against `.to` and `.from` directly:
---
--- ```lua
---- local r = vim.version.range('1.0.0 - 2.0.0')
---- print(vim.version.gt({1,0,3}, r.from) and vim.version.lt({1,0,3}, r.to))
+--- local r = vim.version.range('1.0.0 - 2.0.0') -- >=1.0, <2.0
+--- print(vim.version.ge({1,0,3}, r.from) and vim.version.lt({1,0,3}, r.to))
--- ```
---
--- @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 })
@@ -297,8 +303,9 @@ function M.range(spec) -- Adapted from https://github.com/folke/lazy.nvim
local semver = M.parse(version)
if semver then
- local from = semver
- local to = vim.deepcopy(semver)
+ 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
elseif mods == '<' then
@@ -308,9 +315,9 @@ function M.range(spec) -- Adapted from https://github.com/folke/lazy.nvim
to.patch = to.patch + 1
elseif mods == '>' then
from.patch = from.patch + 1
- to = nil ---@diagnostic disable-line: cast-local-type
+ to = nil
elseif mods == '>=' then
- to = nil ---@diagnostic disable-line: cast-local-type
+ to = nil
elseif mods == '~' then
if #parts >= 2 then
to[2] = to[2] + 1
@@ -331,11 +338,12 @@ function M.range(spec) -- Adapted from https://github.com/folke/lazy.nvim
end
end
end
+ ---@diagnostic enable: need-check-nil
return setmetatable({ from = from, to = to }, { __index = VersionRange })
end
end
----@param v string|Version
+---@param v string|vim.Version
---@return string
local function create_err_msg(v)
if type(v) == 'string' then
@@ -364,8 +372,8 @@ end
---
--- @note Per semver, build metadata is ignored when comparing two otherwise-equivalent versions.
---
----@param v1 Version|number[] Version object.
----@param v2 Version|number[] 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))
@@ -380,24 +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[]
----@param v2 Version|number[]
+---@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 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[]
----@param v2 Version|number[]
+---@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 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[]
----@param v2 Version|number[]
+---@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
@@ -417,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 table|nil 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 }
@@ -426,8 +450,9 @@ end
setmetatable(M, {
--- Returns the current Nvim version.
+ ---@return vim.Version
__call = function()
- local version = vim.fn.api_info().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)
diff --git a/runtime/lua/vim/vimhelp.lua b/runtime/lua/vim/vimhelp.lua
index a4d6a50b12..4af6866d48 100644
--- a/runtime/lua/vim/vimhelp.lua
+++ b/runtime/lua/vim/vimhelp.lua
@@ -2,26 +2,28 @@
local M = {}
--- Called when editing the doc/syntax.txt file
-function M.highlight_groups()
- local save_cursor = vim.fn.getcurpos()
-
- local start_lnum = vim.fn.search([[\*highlight-groups\*]], 'c')
- if start_lnum == 0 then
- return
- end
- local end_lnum = vim.fn.search('^======')
- if end_lnum == 0 then
- return
- end
-
+--- Apply current colorscheme to lists of default highlight groups
+---
+--- Note: {patterns} is assumed to be sorted by occurrence in the file.
+--- @param patterns {start:string,stop:string,match:string}[]
+function M.highlight_groups(patterns)
local ns = vim.api.nvim_create_namespace('vimhelp')
vim.api.nvim_buf_clear_namespace(0, ns, 0, -1)
- for lnum = start_lnum, end_lnum do
- local word = vim.api.nvim_buf_get_lines(0, lnum - 1, lnum, true)[1]:match('^(%w+)\t')
- if vim.fn.hlexists(word) ~= 0 then
- vim.api.nvim_buf_set_extmark(0, ns, lnum - 1, 0, { end_col = #word, hl_group = word })
+ local save_cursor = vim.fn.getcurpos()
+
+ for _, pat in pairs(patterns) do
+ local start_lnum = vim.fn.search(pat.start, 'c')
+ local end_lnum = vim.fn.search(pat.stop)
+ if start_lnum == 0 or end_lnum == 0 then
+ break
+ end
+
+ for lnum = start_lnum, end_lnum do
+ local word = vim.api.nvim_buf_get_lines(0, lnum - 1, lnum, true)[1]:match(pat.match)
+ if vim.fn.hlexists(word) ~= 0 then
+ vim.api.nvim_buf_set_extmark(0, ns, lnum - 1, 0, { end_col = #word, hl_group = word })
+ end
end
end