diff options
Diffstat (limited to 'runtime/lua/vim')
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 |