diff options
Diffstat (limited to 'runtime/lua/vim')
71 files changed, 7198 insertions, 4512 deletions
diff --git a/runtime/lua/vim/_comment.lua b/runtime/lua/vim/_comment.lua index 044cd69716..de7f62632c 100644 --- a/runtime/lua/vim/_comment.lua +++ b/runtime/lua/vim/_comment.lua @@ -9,8 +9,8 @@ local function get_commentstring(ref_position) local buf_cs = vim.bo.commentstring - local has_ts_parser, ts_parser = pcall(vim.treesitter.get_parser) - if not has_ts_parser then + local ts_parser = vim.treesitter.get_parser(0, '', { error = false }) + if not ts_parser then return buf_cs end @@ -194,14 +194,9 @@ local function toggle_lines(line_start, line_end, ref_position) -- - Debatable for highlighting in text area (like LSP semantic tokens). -- Mostly because it causes flicker as highlighting is preserved during -- comment toggling. - package.loaded['vim._comment']._lines = vim.tbl_map(f, lines) - local lua_cmd = string.format( - 'vim.api.nvim_buf_set_lines(0, %d, %d, false, package.loaded["vim._comment"]._lines)', - line_start - 1, - line_end - ) - vim.cmd.lua({ lua_cmd, mods = { lockmarks = true } }) - package.loaded['vim._comment']._lines = nil + vim._with({ lockmarks = true }, function() + vim.api.nvim_buf_set_lines(0, line_start - 1, line_end, false, vim.tbl_map(f, lines)) + end) end --- Operator which toggles user-supplied range of lines diff --git a/runtime/lua/vim/_defaults.lua b/runtime/lua/vim/_defaults.lua index 5b964b84a0..6cad1dbca9 100644 --- a/runtime/lua/vim/_defaults.lua +++ b/runtime/lua/vim/_defaults.lua @@ -85,13 +85,13 @@ do vim.keymap.set( 'x', 'Q', - "mode() == 'V' ? ':normal! @<C-R>=reg_recorded()<CR><CR>' : 'Q'", + "mode() ==# 'V' ? ':normal! @<C-R>=reg_recorded()<CR><CR>' : 'Q'", { silent = true, expr = true, desc = ':help v_Q-default' } ) vim.keymap.set( 'x', '@', - "mode() == 'V' ? ':normal! @'.getcharstr().'<CR>' : '@'", + "mode() ==# 'V' ? ':normal! @'.getcharstr().'<CR>' : '@'", { silent = true, expr = true, desc = ':help v_@-default' } ) @@ -113,9 +113,11 @@ do local gx_desc = 'Opens filepath or URI under cursor with the system handler (file explorer, web browser, …)' vim.keymap.set({ 'n' }, 'gx', function() - local err = do_open(require('vim.ui')._get_url()) - if err then - vim.notify(err, vim.log.levels.ERROR) + for _, url in ipairs(require('vim.ui')._get_urls()) do + local err = do_open(url) + if err then + vim.notify(err, vim.log.levels.ERROR) + end end end, { desc = gx_desc }) vim.keymap.set({ 'x' }, 'gx', function() @@ -180,12 +182,20 @@ do --- See |[d-default|, |]d-default|, and |CTRL-W_d-default|. do vim.keymap.set('n', ']d', function() - vim.diagnostic.goto_next({ float = false }) - end, { desc = 'Jump to the next diagnostic' }) + vim.diagnostic.jump({ count = vim.v.count1 }) + end, { desc = 'Jump to the next diagnostic in the current buffer' }) vim.keymap.set('n', '[d', function() - vim.diagnostic.goto_prev({ float = false }) - end, { desc = 'Jump to the previous diagnostic' }) + vim.diagnostic.jump({ count = -vim.v.count1 }) + end, { desc = 'Jump to the previous diagnostic in the current buffer' }) + + vim.keymap.set('n', ']D', function() + vim.diagnostic.jump({ count = math.huge, wrap = false }) + end, { desc = 'Jump to the last diagnostic in the current buffer' }) + + vim.keymap.set('n', '[D', function() + vim.diagnostic.jump({ count = -math.huge, wrap = false }) + end, { desc = 'Jump to the first diagnostic in the current buffer' }) vim.keymap.set('n', '<C-W>d', function() vim.diagnostic.open_float() @@ -198,13 +208,187 @@ do { remap = true, desc = 'Show diagnostics under the cursor' } ) end + + --- vim-unimpaired style mappings. See: https://github.com/tpope/vim-unimpaired + do + -- Quickfix mappings + vim.keymap.set('n', '[q', function() + vim.cmd.cprevious({ count = vim.v.count1 }) + end, { + desc = ':cprevious', + }) + + vim.keymap.set('n', ']q', function() + vim.cmd.cnext({ count = vim.v.count1 }) + end, { + desc = ':cnext', + }) + + vim.keymap.set('n', '[Q', function() + vim.cmd.crewind({ count = vim.v.count ~= 0 and vim.v.count or nil }) + end, { + desc = ':crewind', + }) + + vim.keymap.set('n', ']Q', function() + vim.cmd.clast({ count = vim.v.count ~= 0 and vim.v.count or nil }) + end, { + desc = ':clast', + }) + + vim.keymap.set('n', '[<C-Q>', function() + vim.cmd.cpfile({ count = vim.v.count1 }) + end, { + desc = ':cpfile', + }) + + vim.keymap.set('n', ']<C-Q>', function() + vim.cmd.cnfile({ count = vim.v.count1 }) + end, { + desc = ':cnfile', + }) + + -- Location list mappings + vim.keymap.set('n', '[l', function() + vim.cmd.lprevious({ count = vim.v.count1 }) + end, { + desc = ':lprevious', + }) + + vim.keymap.set('n', ']l', function() + vim.cmd.lnext({ count = vim.v.count1 }) + end, { + desc = ':lnext', + }) + + vim.keymap.set('n', '[L', function() + vim.cmd.lrewind({ count = vim.v.count ~= 0 and vim.v.count or nil }) + end, { + desc = ':lrewind', + }) + + vim.keymap.set('n', ']L', function() + vim.cmd.llast({ count = vim.v.count ~= 0 and vim.v.count or nil }) + end, { + desc = ':llast', + }) + + vim.keymap.set('n', '[<C-L>', function() + vim.cmd.lpfile({ count = vim.v.count1 }) + end, { + desc = ':lpfile', + }) + + vim.keymap.set('n', ']<C-L>', function() + vim.cmd.lnfile({ count = vim.v.count1 }) + end, { + desc = ':lnfile', + }) + + -- Argument list + vim.keymap.set('n', '[a', function() + vim.cmd.previous({ count = vim.v.count1 }) + end, { + desc = ':previous', + }) + + vim.keymap.set('n', ']a', function() + -- count doesn't work with :next, must use range. See #30641. + vim.cmd.next({ range = { vim.v.count1 } }) + end, { + desc = ':next', + }) + + vim.keymap.set('n', '[A', function() + if vim.v.count ~= 0 then + vim.cmd.argument({ count = vim.v.count }) + else + vim.cmd.rewind() + end + end, { + desc = ':rewind', + }) + + vim.keymap.set('n', ']A', function() + if vim.v.count ~= 0 then + vim.cmd.argument({ count = vim.v.count }) + else + vim.cmd.last() + end + end, { + desc = ':last', + }) + + -- Tags + vim.keymap.set('n', '[t', function() + -- count doesn't work with :tprevious, must use range. See #30641. + vim.cmd.tprevious({ range = { vim.v.count1 } }) + end, { desc = ':tprevious' }) + + vim.keymap.set('n', ']t', function() + -- count doesn't work with :tnext, must use range. See #30641. + vim.cmd.tnext({ range = { vim.v.count1 } }) + end, { desc = ':tnext' }) + + vim.keymap.set('n', '[T', function() + -- count doesn't work with :trewind, must use range. See #30641. + vim.cmd.trewind({ range = vim.v.count ~= 0 and { vim.v.count } or nil }) + end, { desc = ':trewind' }) + + vim.keymap.set('n', ']T', function() + -- :tlast does not accept a count, so use :trewind if count given + if vim.v.count ~= 0 then + vim.cmd.trewind({ range = { vim.v.count } }) + else + vim.cmd.tlast() + end + end, { desc = ':tlast' }) + + vim.keymap.set('n', '[<C-T>', function() + -- count doesn't work with :ptprevious, must use range. See #30641. + vim.cmd.ptprevious({ range = { vim.v.count1 } }) + end, { desc = ' :ptprevious' }) + + vim.keymap.set('n', ']<C-T>', function() + -- count doesn't work with :ptnext, must use range. See #30641. + vim.cmd.ptnext({ range = { vim.v.count1 } }) + end, { desc = ':ptnext' }) + + -- Buffers + vim.keymap.set('n', '[b', function() + vim.cmd.bprevious({ count = vim.v.count1 }) + end, { desc = ':bprevious' }) + + vim.keymap.set('n', ']b', function() + vim.cmd.bnext({ count = vim.v.count1 }) + end, { desc = ':bnext' }) + + vim.keymap.set('n', '[B', function() + if vim.v.count ~= 0 then + vim.cmd.buffer({ count = vim.v.count }) + else + vim.cmd.brewind() + end + end, { desc = ':brewind' }) + + vim.keymap.set('n', ']B', function() + if vim.v.count ~= 0 then + vim.cmd.buffer({ count = vim.v.count }) + else + vim.cmd.blast() + end + end, { desc = ':blast' }) + end end --- Default menus do --- Right click popup menu - -- TODO VimScript, no l10n vim.cmd([[ + anoremenu PopUp.Go\ to\ definition <Cmd>lua vim.lsp.buf.definition()<CR> + amenu PopUp.Open\ in\ web\ browser gx + anoremenu PopUp.Inspect <Cmd>Inspect<CR> + anoremenu PopUp.-1- <Nop> vnoremenu PopUp.Cut "+x vnoremenu PopUp.Copy "+y anoremenu PopUp.Paste "+gP @@ -213,10 +397,36 @@ do nnoremenu PopUp.Select\ All ggVG vnoremenu PopUp.Select\ All gg0oG$ inoremenu PopUp.Select\ All <C-Home><C-O>VG - anoremenu PopUp.Inspect <Cmd>Inspect<CR> - anoremenu PopUp.-1- <Nop> + anoremenu PopUp.-2- <Nop> anoremenu PopUp.How-to\ disable\ mouse <Cmd>help disable-mouse<CR> ]]) + + local function enable_ctx_menu(ctx) + vim.cmd([[ + amenu disable PopUp.Go\ to\ definition + amenu disable PopUp.Open\ in\ web\ browser + ]]) + + if ctx == 'url' then + vim.cmd([[amenu enable PopUp.Open\ in\ web\ browser]]) + elseif ctx == 'lsp' then + vim.cmd([[anoremenu enable PopUp.Go\ to\ definition]]) + end + end + + local nvim_popupmenu_augroup = vim.api.nvim_create_augroup('nvim_popupmenu', {}) + vim.api.nvim_create_autocmd('MenuPopup', { + pattern = '*', + group = nvim_popupmenu_augroup, + desc = 'Mouse popup menu', + -- nested = true, + callback = function() + local urls = require('vim.ui')._get_urls() + local url = vim.startswith(urls[1], 'http') + local ctx = url and 'url' or (vim.lsp.get_clients({ bufnr = 0 })[1] and 'lsp' or nil) + enable_ctx_menu(ctx) + end, + }) end --- Default autocommands. See |default-autocmds| @@ -274,6 +484,26 @@ do end, }) + vim.api.nvim_create_autocmd('TermOpen', { + group = nvim_terminal_augroup, + desc = 'Default settings for :terminal buffers', + callback = function() + vim.bo.modifiable = false + vim.bo.undolevels = -1 + vim.bo.scrollback = vim.o.scrollback < 0 and 10000 or math.max(1, vim.o.scrollback) + vim.bo.textwidth = 0 + vim.wo[0][0].wrap = false + vim.wo[0][0].list = false + + -- This is gross. Proper list options support when? + local winhl = vim.o.winhighlight + if winhl ~= '' then + winhl = winhl .. ',' + end + vim.wo[0][0].winhighlight = winhl .. 'StatusLine:StatusLineTerm,StatusLineNC:StatusLineTermNC' + end, + }) + vim.api.nvim_create_autocmd('CmdwinEnter', { pattern = '[:>]', desc = 'Limit syntax sync to maxlines=1 in the command window', @@ -462,10 +692,14 @@ do --- 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 + local colorterm = os.getenv('COLORTERM') + if tty.rgb or colorterm == 'truecolor' or colorterm == '24bit' then + -- The TUI was able to determine truecolor support or $COLORTERM explicitly indicates + -- truecolor support setoption('termguicolors', true) - else + elseif colorterm == nil or colorterm == '' then + -- Neither the TUI nor $COLORTERM indicate that truecolor is supported, so query the + -- terminal local caps = {} ---@type table<string, boolean> require('vim.termcap').query({ 'Tc', 'RGB', 'setrgbf', 'setrgbb' }, function(cap, found) if not found then diff --git a/runtime/lua/vim/_editor.lua b/runtime/lua/vim/_editor.lua index 5e9be509c8..2e829578a7 100644 --- a/runtime/lua/vim/_editor.lua +++ b/runtime/lua/vim/_editor.lua @@ -1,17 +1,19 @@ -- Nvim-Lua stdlib: the `vim` module (:help lua-stdlib) -- --- Lua code lives in one of three places: --- 1. runtime/lua/vim/ (the runtime): For "nice to have" features, e.g. the --- `inspect` and `lpeg` modules. --- 2. runtime/lua/vim/shared.lua: pure Lua functions which always --- are available. Used in the test runner, as well as worker threads --- and processes launched from Nvim. --- 3. runtime/lua/vim/_editor.lua: Code which directly interacts with --- the Nvim editor state. Only available in the main thread. +-- Lua code lives in one of four places: +-- 1. Plugins! Not everything needs to live on "vim.*". Plugins are the correct model for +-- non-essential features which the user may want to disable or replace with a third-party +-- plugin. Examples: "editorconfig", "comment". +-- - "opt-out": runtime/plugin/*.lua +-- - "opt-in": runtime/pack/dist/opt/ +-- 2. runtime/lua/vim/ (the runtime): Lazy-loaded modules. Examples: `inspect`, `lpeg`. +-- 3. runtime/lua/vim/shared.lua: pure Lua functions which always are available. Used in the test +-- runner, as well as worker threads and processes launched from Nvim. +-- 4. runtime/lua/vim/_editor.lua: Eager-loaded code which directly interacts with the Nvim +-- editor state. Only available in the main thread. -- --- Guideline: "If in doubt, put it in the runtime". --- --- Most functions should live directly in `vim.`, not in submodules. +-- The top level "vim.*" namespace is for fundamental Lua and editor features. Use submodules for +-- everything else (but avoid excessive "nesting"), or plugins (see above). -- -- Compatibility with Vim's `if_lua` is explicitly a non-goal. -- @@ -19,9 +21,7 @@ -- - https://github.com/luafun/luafun -- - https://github.com/rxi/lume -- - http://leafo.net/lapis/reference/utilities.html --- - https://github.com/torch/paths -- - https://github.com/bakpakin/Fennel (pretty print, repl) --- - https://github.com/howl-editor/howl/tree/master/lib/howl/util -- These are for loading runtime modules lazily since they aren't available in -- the nvim binary as specified in executor.c @@ -208,8 +208,10 @@ vim.inspect = vim.inspect do local tdots, tick, got_line1, undo_started, trailing_nl = 0, 0, false, false, false - --- Paste handler, invoked by |nvim_paste()| when a conforming UI - --- (such as the |TUI|) pastes text into the editor. + --- Paste handler, invoked by |nvim_paste()|. + --- + --- Note: This is provided only as a "hook", don't call it directly; call |nvim_paste()| instead, + --- which arranges redo (dot-repeat) and invokes `vim.paste`. --- --- Example: To remove ANSI color codes when pasting: --- @@ -220,7 +222,7 @@ do --- -- Scrub ANSI color codes from paste input. --- lines[i] = line:gsub('\27%[[0-9;mK]+', '') --- end - --- overridden(lines, phase) + --- return overridden(lines, phase) --- end --- end)(vim.paste) --- ``` @@ -494,6 +496,7 @@ do vim.t = make_dict_accessor('t') end +--- @deprecated --- Gets a dict of line segment ("chunk") positions for the region from `pos1` to `pos2`. --- --- Input and output positions are byte positions, (0,0)-indexed. "End of line" column @@ -507,6 +510,8 @@ end ---@return table region Dict of the form `{linenr = {startcol,endcol}}`. `endcol` is exclusive, and ---whole lines are returned as `{startcol,endcol} = {0,-1}`. function vim.region(bufnr, pos1, pos2, regtype, inclusive) + vim.deprecate('vim.region', 'vim.fn.getregionpos()', '0.13') + if not vim.api.nvim_buf_is_loaded(bufnr) then vim.fn.bufload(bufnr) end @@ -605,10 +610,9 @@ end --- Displays a notification to the user. --- ---- This function can be overridden by plugins to display notifications using a ---- custom provider (such as the system notification provider). By default, +--- This function can be overridden by plugins to display notifications using +--- a custom provider (such as the system notification provider). By default, --- writes to |:messages|. ---- ---@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. @@ -783,7 +787,7 @@ function vim._expand_pat(pat, env) if mt and type(mt.__index) == 'table' then field = rawget(mt.__index, key) elseif final_env == vim and (vim._submodules[key] or vim._extra[key]) then - field = vim[key] + field = vim[key] --- @type any end end final_env = field @@ -794,14 +798,24 @@ function vim._expand_pat(pat, env) end 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 + if + type(k) == 'string' + and string.sub(k, 1, string.len(match_part)) == match_part + and k:match('^[_%w]+$') ~= nil -- filter out invalid identifiers for field, e.g. 'foo#bar' + then keys[k] = true end end end + ---@param acc table<string,any> + local function _fold_to_map(acc, k, v) + acc[k] = (v or true) + return acc + end if type(final_env) == 'table' then insert_keys(final_env) @@ -810,11 +824,61 @@ function vim._expand_pat(pat, env) if mt and type(mt.__index) == 'table' then insert_keys(mt.__index) end + if final_env == vim then insert_keys(vim._submodules) insert_keys(vim._extra) end + -- Completion for dict accessors (special vim variables and vim.fn) + if mt and vim.tbl_contains({ vim.g, vim.t, vim.w, vim.b, vim.v, vim.fn }, final_env) then + local prefix, type = unpack( + vim.fn == final_env and { '', 'function' } + or vim.g == final_env and { 'g:', 'var' } + or vim.t == final_env and { 't:', 'var' } + or vim.w == final_env and { 'w:', 'var' } + or vim.b == final_env and { 'b:', 'var' } + or vim.v == final_env and { 'v:', 'var' } + or { nil, nil } + ) + assert(prefix, "Can't resolve final_env") + local vars = vim.fn.getcompletion(prefix .. match_part, type) --- @type string[] + insert_keys(vim + .iter(vars) + :map(function(s) ---@param s string + s = s:gsub('[()]+$', '') -- strip '(' and ')' for function completions + return s:sub(#prefix + 1) -- strip the prefix, e.g., 'g:foo' => 'foo' + end) + :fold({}, _fold_to_map)) + end + + -- Completion for option accessors (full names only) + if + mt + and vim.tbl_contains( + { vim.o, vim.go, vim.bo, vim.wo, vim.opt, vim.opt_local, vim.opt_global }, + final_env + ) + then + --- @type fun(option_name: string, option: vim.api.keyset.get_option_info): boolean + local filter = function(_, _) + return true + end + if vim.bo == final_env then + filter = function(_, option) + return option.scope == 'buf' + end + elseif vim.wo == final_env then + filter = function(_, option) + return option.scope == 'win' + end + end + + --- @type table<string, vim.api.keyset.get_option_info> + local options = vim.api.nvim_get_all_options_info() + insert_keys(vim.iter(options):filter(filter):fold({}, _fold_to_map)) + end + keys = vim.tbl_keys(keys) table.sort(keys) diff --git a/runtime/lua/vim/_inspector.lua b/runtime/lua/vim/_inspector.lua index f5d1640c82..fccf4b8dbe 100644 --- a/runtime/lua/vim/_inspector.lua +++ b/runtime/lua/vim/_inspector.lua @@ -27,6 +27,7 @@ local defaults = { --- ---Can also be pretty-printed with `:Inspect!`. [:Inspect!]() --- +---@since 11 ---@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 @@ -84,7 +85,7 @@ function vim.inspect_pos(bufnr, row, col, filter) -- syntax if filter.syntax and vim.api.nvim_buf_is_valid(bufnr) then - vim.api.nvim_buf_call(bufnr, function() + vim._with({ buf = bufnr }, function() for _, i1 in ipairs(vim.fn.synstack(row + 1, col + 1)) do results.syntax[#results.syntax + 1] = resolve_hl({ hl_group = vim.fn.synIDattr(i1, 'name') }) @@ -145,6 +146,7 @@ end --- ---Can also be shown with `:Inspect`. [:Inspect]() --- +---@since 11 ---@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 diff --git a/runtime/lua/vim/_meta.lua b/runtime/lua/vim/_meta.lua index 731dd5b923..c9f207cb20 100644 --- a/runtime/lua/vim/_meta.lua +++ b/runtime/lua/vim/_meta.lua @@ -34,3 +34,5 @@ vim.uri_from_fname = uri.uri_from_fname vim.uri_from_bufnr = uri.uri_from_bufnr vim.uri_to_fname = uri.uri_to_fname vim.uri_to_bufnr = uri.uri_to_bufnr + +vim.provider = require('vim.provider') diff --git a/runtime/lua/vim/_meta/api.lua b/runtime/lua/vim/_meta/api.lua index 6edf2a5a96..c66b295d3a 100644 --- a/runtime/lua/vim/_meta/api.lua +++ b/runtime/lua/vim/_meta/api.lua @@ -20,14 +20,15 @@ function vim.api.nvim__buf_stats(buffer) end --- @private --- EXPERIMENTAL: this API may change in the future. --- ---- Sets info for the completion item at the given index. If the info text was ---- shown in a window, returns the window and buffer ids, or empty dict if not ---- shown. +--- Sets info for the completion item at the given index. If the info text was shown in a window, +--- returns the window and buffer ids, or empty dict if not shown. --- --- @param index integer Completion candidate index --- @param opts vim.api.keyset.complete_set Optional parameters. ---- • info: (string) info text. ---- @return table<string,any> +--- - info: (string) info text. +--- @return table<string,any> # Dict containing these keys: +--- - winid: (number) floating window id +--- - bufnr: (number) buffer id in floating window function vim.api.nvim__complete_set(index, opts) end --- @private @@ -40,7 +41,7 @@ function vim.api.nvim__get_lib_dir() end --- @param pat any[] pattern of files to search for --- @param all boolean whether to return all matches or only the first --- @param opts vim.api.keyset.runtime is_lua: only search Lua subdirs ---- @return string[] +--- @return string[] # list of absolute paths to the found files function vim.api.nvim__get_runtime(pat, all, opts) end --- @private @@ -50,7 +51,7 @@ function vim.api.nvim__get_runtime(pat, all, opts) end --- in plugins. --- --- @param obj any Object to return. ---- @return any +--- @return any # its argument. function vim.api.nvim__id(obj) end --- @private @@ -60,18 +61,18 @@ function vim.api.nvim__id(obj) end --- in plugins. --- --- @param arr any[] Array to return. ---- @return any[] +--- @return any[] # its argument. function vim.api.nvim__id_array(arr) end --- @private ---- Returns dictionary given as argument. +--- Returns dict given as argument. --- --- This API function is used for testing. One should not rely on its presence --- in plugins. --- ---- @param dct table<string,any> Dictionary to return. ---- @return table<string,any> -function vim.api.nvim__id_dictionary(dct) end +--- @param dct table<string,any> Dict to return. +--- @return table<string,any> # its argument. +function vim.api.nvim__id_dict(dct) end --- @private --- Returns floating-point value given as argument. @@ -80,13 +81,11 @@ function vim.api.nvim__id_dictionary(dct) end --- in plugins. --- --- @param flt number Value to return. ---- @return number +--- @return number # its argument. function vim.api.nvim__id_float(flt) end --- @private ---- NB: if your UI doesn't use hlstate, this will not return hlstate first ---- time. ---- +--- NB: if your UI doesn't use hlstate, this will not return hlstate first time. --- @param grid integer --- @param row integer --- @param col integer @@ -94,35 +93,55 @@ function vim.api.nvim__id_float(flt) end function vim.api.nvim__inspect_cell(grid, row, col) end --- @private ---- For testing. The condition in schar_cache_clear_if_full is hard to reach, ---- so this function can be used to force a cache clear in a test. ---- +--- For testing. The condition in schar_cache_clear_if_full is hard to +--- reach, so this function can be used to force a cache clear in a test. function vim.api.nvim__invalidate_glyph_cache() end --- @private +--- EXPERIMENTAL: this API will change in the future. +--- +--- Get the properties for namespace +--- +--- @param ns_id integer Namespace +--- @return vim.api.keyset.ns_opts # Map defining the namespace properties, see |nvim__ns_set()| +function vim.api.nvim__ns_get(ns_id) end + +--- @private +--- EXPERIMENTAL: this API will change in the future. +--- +--- Set some properties for namespace +--- +--- @param ns_id integer Namespace +--- @param opts vim.api.keyset.ns_opts Optional parameters to set: +--- - wins: a list of windows to be scoped in +function vim.api.nvim__ns_set(ns_id, opts) end + +--- @private --- EXPERIMENTAL: this API may change in the future. --- --- Instruct Nvim to redraw various components. --- +--- +--- @see `:help :redraw` --- @param opts vim.api.keyset.redraw Optional parameters. ---- • win: Target a specific `window-ID` as described below. ---- • buf: Target a specific buffer number as described below. ---- • flush: Update the screen with pending updates. ---- • valid: When present mark `win`, `buf`, or all windows for ---- redraw. When `true`, only redraw changed lines (useful for ---- decoration providers). When `false`, forcefully redraw. ---- • range: Redraw a range in `buf`, the buffer in `win` or the ---- current buffer (useful for decoration providers). Expects a ---- tuple `[first, last]` with the first and last line number of ---- the range, 0-based end-exclusive `api-indexing`. ---- • cursor: Immediately update cursor position on the screen in ---- `win` or the current window. ---- • statuscolumn: Redraw the 'statuscolumn' in `buf`, `win` or ---- all windows. ---- • statusline: Redraw the 'statusline' in `buf`, `win` or all ---- windows. ---- • winbar: Redraw the 'winbar' in `buf`, `win` or all windows. ---- • tabline: Redraw the 'tabline'. +--- - win: Target a specific `window-ID` as described below. +--- - buf: Target a specific buffer number as described below. +--- - flush: Update the screen with pending updates. +--- - valid: When present mark `win`, `buf`, or all windows for +--- redraw. When `true`, only redraw changed lines (useful for +--- decoration providers). When `false`, forcefully redraw. +--- - range: Redraw a range in `buf`, the buffer in `win` or the +--- current buffer (useful for decoration providers). Expects a +--- tuple `[first, last]` with the first and last line number +--- of the range, 0-based end-exclusive `api-indexing`. +--- - cursor: Immediately update cursor position on the screen in +--- `win` or the current window. +--- - statuscolumn: Redraw the 'statuscolumn' in `buf`, `win` or +--- all windows. +--- - statusline: Redraw the 'statusline' in `buf`, `win` or all +--- windows. +--- - winbar: Redraw the 'winbar' in `buf`, `win` or all windows. +--- - tabline: Redraw the 'tabline'. function vim.api.nvim__redraw(opts) end --- @private @@ -136,7 +155,7 @@ function vim.api.nvim__screenshot(path) end --- @private --- Gets internal stats. --- ---- @return table<string,any> +--- @return table<string,any> # Map of various internal stats. function vim.api.nvim__stats() end --- @private @@ -144,50 +163,20 @@ function vim.api.nvim__stats() end --- @return any function vim.api.nvim__unpack(str) end ---- @private ---- EXPERIMENTAL: this API will change in the future. ---- ---- Scopes a namespace to the a window, so extmarks in the namespace will be ---- active only in the given window. ---- ---- @param window integer Window handle, or 0 for current window ---- @param ns_id integer Namespace ---- @return boolean -function vim.api.nvim__win_add_ns(window, ns_id) end - ---- @private ---- EXPERIMENTAL: this API will change in the future. ---- ---- Unscopes a namespace (un-binds it from the given scope). ---- ---- @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_del_ns(window, ns_id) end - ---- @private ---- EXPERIMENTAL: this API will change in the future. ---- ---- Gets the namespace scopes for a given window. ---- ---- @param window integer Window handle, or 0 for current window ---- @return integer[] -function vim.api.nvim__win_get_ns(window) 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. +--- 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`. +--- 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 @@ -200,98 +189,100 @@ function vim.api.nvim__win_get_ns(window) end --- @param hl_group string Name of the highlight group to use --- @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 ---- @return integer +--- @param col_end integer End of (byte-indexed) column range to highlight, +--- or -1 to highlight to end of line +--- @return integer # The ns_id that was used 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): +--- Example (Lua): capture buffer updates in a global `events` variable +--- (use "vim.print(events)" to see its contents): --- --- ```lua ---- events = {} ---- vim.api.nvim_buf_attach(0, false, { ---- on_lines = function(...) ---- table.insert(events, {...}) ---- end, ---- }) +--- events = {} +--- vim.api.nvim_buf_attach(0, false, { +--- on_lines = function(...) +--- table.insert(events, {...}) +--- end, +--- }) --- ``` --- --- +--- @see vim.api.nvim_buf_detach +--- @see `:help api-buffer-updates-lua` --- @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. +--- 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 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 +--- - 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 # False if attach failed (invalid parameter, or buffer isn't loaded); +--- otherwise True. TODO: LUA_API_NO_EVAL function vim.api.nvim_buf_attach(buffer, send_buffer, opts) end ---- call a function with buffer as temporary current buffer +--- 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 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, then 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) ---- @return any +--- only) +--- @return any # Return value of function. function vim.api.nvim_buf_call(buffer, fun) end --- @deprecated @@ -301,21 +292,22 @@ function vim.api.nvim_buf_call(buffer, fun) end --- @param line_end integer function vim.api.nvim_buf_clear_highlight(buffer, ns_id, line_start, line_end) end ---- Clears `namespace`d objects (highlights, `extmarks`, virtual text) from a ---- region. +--- Clears `namespace`d objects (highlights, `extmarks`, virtual text) from +--- a region. --- ---- Lines are 0-indexed. `api-indexing` To clear the namespace in the entire +--- 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. +--- 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`. --- +--- @see vim.api.nvim_create_user_command --- @param buffer integer Buffer handle, or 0 for current buffer. --- @param name string --- @param command any @@ -327,11 +319,13 @@ function vim.api.nvim_buf_create_user_command(buffer, name, command, 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 ---- @return boolean +--- @return boolean # true if the extmark was found, else false function vim.api.nvim_buf_del_extmark(buffer, ns_id, id) end --- Unmaps a buffer-local `mapping` for the given mode. --- +--- +--- @see vim.api.nvim_del_keymap --- @param buffer integer Buffer handle, or 0 for current buffer --- @param mode string --- @param lhs string @@ -339,9 +333,15 @@ function vim.api.nvim_buf_del_keymap(buffer, mode, lhs) end --- Deletes a named mark in the buffer. See `mark-motions`. --- +--- Note: +--- only deletes marks set in the buffer, if the mark is not set +--- in the buffer it will return false. +--- +--- @see vim.api.nvim_buf_set_mark +--- @see vim.api.nvim_del_mark --- @param buffer integer Buffer to set the mark on --- @param name string Mark name ---- @return boolean +--- @return boolean # true if the mark was deleted, else false. function vim.api.nvim_buf_del_mark(buffer, name) end --- Delete a buffer-local user-defined command. @@ -363,21 +363,21 @@ function vim.api.nvim_buf_del_var(buffer, name) end --- --- @param buffer integer Buffer handle, or 0 for current buffer --- @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` +--- - 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 --- --- @param buffer integer Buffer handle, or 0 for current buffer ---- @return integer +--- @return integer # `b:changedtick` value. function vim.api.nvim_buf_get_changedtick(buffer) end --- Gets a map of buffer-local `user-commands`. --- --- @param buffer integer Buffer handle, or 0 for current buffer --- @param opts vim.api.keyset.get_commands Optional parameters. Currently not used. ---- @return table<string,any> +--- @return table<string,any> # Map of maps describing commands. function vim.api.nvim_buf_get_commands(buffer, opts) end --- Gets the position (0-indexed) of an `extmark`. @@ -386,10 +386,10 @@ function vim.api.nvim_buf_get_commands(buffer, opts) end --- @param ns_id integer Namespace id from `nvim_create_namespace()` --- @param id integer Extmark id --- @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 +--- - 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_by_id # 0-indexed (row, col) tuple or empty list () if extmark id was +--- absent function vim.api.nvim_buf_get_extmark_by_id(buffer, ns_id, id, opts) end --- Gets `extmarks` in "traversal order" from a `charwise` region defined by @@ -400,70 +400,67 @@ function vim.api.nvim_buf_get_extmark_by_id(buffer, ns_id, id, opts) end --- respectively, thus the following are equivalent: --- --- ```lua ---- vim.api.nvim_buf_get_extmarks(0, my_ns, 0, -1, {}) ---- vim.api.nvim_buf_get_extmarks(0, my_ns, {0,0}, {-1,-1}, {}) +--- vim.api.nvim_buf_get_extmarks(0, my_ns, 0, -1, {}) +--- vim.api.nvim_buf_get_extmarks(0, my_ns, {0,0}, {-1,-1}, {}) --- ``` --- ---- If `end` is less than `start`, traversal works backwards. (Useful with ---- `limit`, to get the first marks prior to a given position.) +--- 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. +--- 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 +--- 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 ---- local api = vim.api ---- local pos = api.nvim_win_get_cursor(0) ---- local ns = api.nvim_create_namespace('my-plugin') ---- -- Create new extmark at line 1, column 1. ---- local m1 = api.nvim_buf_set_extmark(0, ns, 0, 0, {}) ---- -- Create new extmark at line 3, column 1. ---- local m2 = api.nvim_buf_set_extmark(0, ns, 2, 0, {}) ---- -- Get extmarks only from line 3. ---- local ms = api.nvim_buf_get_extmarks(0, ns, {2,0}, {2,0}, {}) ---- -- Get all marks in this buffer + namespace. ---- local all = api.nvim_buf_get_extmarks(0, ns, 0, -1, {}) ---- vim.print(ms) +--- local api = vim.api +--- local pos = api.nvim_win_get_cursor(0) +--- local ns = api.nvim_create_namespace('my-plugin') +--- -- Create new extmark at line 1, column 1. +--- local m1 = api.nvim_buf_set_extmark(0, ns, 0, 0, {}) +--- -- Create new extmark at line 3, column 1. +--- local m2 = api.nvim_buf_set_extmark(0, ns, 2, 0, {}) +--- -- Get extmarks only from line 3. +--- local ms = api.nvim_buf_get_extmarks(0, ns, {2,0}, {2,0}, {}) +--- -- Get all marks in this buffer + namespace. +--- local all = api.nvim_buf_get_extmarks(0, ns, 0, -1, {}) +--- 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 +--- @param ns_id integer Namespace id from `nvim_create_namespace()` or -1 for all 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 vim.api.keyset.get_extmark_item[] +--- - 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[] # List of `[extmark_id, row, col]` tuples in "traversal order". 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 vim.api.keyset.keymap[] +--- @return vim.api.keyset.keymap[] # Array of |maparg()|-like dictionaries describing mappings. +--- The "buffer" key holds the associated buffer handle. 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. +--- 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. @@ -472,24 +469,27 @@ function vim.api.nvim_buf_get_keymap(buffer, mode) end --- @param start integer First line index --- @param end_ integer Last line index, exclusive --- @param strict_indexing boolean Whether out-of-bounds should be an error. ---- @return string[] +--- @return string[] # Array of lines, or empty array for unloaded buffer. 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`. +--- "End of line" column position is returned as `v:maxcol` (big number). +--- See `mark-motions`. --- --- Marks are (1,0)-indexed. `api-indexing` --- +--- @see vim.api.nvim_buf_set_mark +--- @see vim.api.nvim_buf_del_mark --- @param buffer integer Buffer handle, or 0 for current buffer --- @param name string Mark name ---- @return integer[] +--- @return integer[] # (row, col) tuple, (0, 0) if the mark is not set, or is an +--- uppercase/file mark set in another buffer. function vim.api.nvim_buf_get_mark(buffer, name) end --- Gets the full file name for the buffer --- --- @param buffer integer Buffer handle, or 0 for current buffer ---- @return string +--- @return string # Buffer name function vim.api.nvim_buf_get_name(buffer) end --- @deprecated @@ -504,12 +504,12 @@ function vim.api.nvim_buf_get_number(buffer) end --- 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. +--- Unlike `line2byte()`, throws error for out-of-bounds indexing. +--- Returns -1 for unloaded buffer. --- --- @param buffer integer Buffer handle, or 0 for current buffer --- @param index integer Line index ---- @return integer +--- @return integer # Integer byte offset, or -1 for unloaded buffer. function vim.api.nvim_buf_get_offset(buffer, index) end --- @deprecated @@ -534,49 +534,54 @@ function vim.api.nvim_buf_get_option(buffer, name) end --- @param end_row integer Last line index, inclusive --- @param end_col integer Ending column (byte offset) on last line, exclusive --- @param opts vim.api.keyset.empty Optional parameters. Currently unused. ---- @return string[] +--- @return string[] # Array of lines, or empty array for unloaded buffer. function vim.api.nvim_buf_get_text(buffer, start_row, start_col, end_row, end_col, opts) end --- Gets a buffer-scoped (b:) variable. --- --- @param buffer integer Buffer handle, or 0 for current buffer --- @param name string Variable name ---- @return any +--- @return any # Variable value function vim.api.nvim_buf_get_var(buffer, name) end --- Checks if a buffer is valid and loaded. See `api-buffer` for more info --- about unloaded buffers. --- --- @param buffer integer Buffer handle, or 0 for current buffer ---- @return boolean +--- @return boolean # true if the buffer is valid and loaded, false otherwise. function vim.api.nvim_buf_is_loaded(buffer) end --- Checks if a buffer is valid. --- +--- Note: +--- Even if a buffer is valid it may have been unloaded. See |api-buffer| +--- for more info about unloaded buffers. +--- +--- --- @param buffer integer Buffer handle, or 0 for current buffer ---- @return boolean +--- @return boolean # true if the buffer is valid, false otherwise. function vim.api.nvim_buf_is_valid(buffer) end --- Returns the number of lines in the given buffer. --- --- @param buffer integer Buffer handle, or 0 for current buffer ---- @return integer +--- @return integer # Line count, or 0 for unloaded buffer. |api-buffer| 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.) +--- 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. +--- 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 +--- 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 --- range (no highlighting). --- --- @param buffer integer Buffer handle, or 0 for current buffer @@ -584,115 +589,122 @@ 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. ---- • 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 (EXPERIMENTAL) enables "scoping" for the ---- extmark. See `nvim__win_add_ns()` ---- @return integer +--- - 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. +--- @return integer # Id of the created/updated extmark function vim.api.nvim_buf_set_extmark(buffer, ns_id, line, col, opts) end --- Sets a buffer-local `mapping` for the given mode. --- +--- +--- @see vim.api.nvim_set_keymap --- @param buffer integer Buffer handle, or 0 for current buffer --- @param mode string --- @param lhs string @@ -702,9 +714,9 @@ 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. +--- 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. @@ -712,6 +724,8 @@ function vim.api.nvim_buf_set_keymap(buffer, mode, lhs, rhs, opts) end --- Out-of-bounds indices are clamped to the nearest valid value, unless --- `strict_indexing` is set. --- +--- +--- @see vim.api.nvim_buf_set_text --- @param buffer integer Buffer handle, or 0 for current buffer --- @param start integer First line index --- @param end_ integer Last line index, exclusive @@ -724,12 +738,18 @@ function vim.api.nvim_buf_set_lines(buffer, start, end_, strict_indexing, replac --- --- Marks are (1,0)-indexed. `api-indexing` --- +--- Note: +--- Passing 0 as line deletes the mark +--- +--- +--- @see vim.api.nvim_buf_del_mark +--- @see vim.api.nvim_buf_get_mark --- @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 vim.api.keyset.empty Optional parameters. Reserved for future use. ---- @return boolean +--- @return boolean # true if the mark was set, else false. function vim.api.nvim_buf_set_mark(buffer, name, line, col, opts) end --- Sets the full file name for a buffer, like `:file_f` @@ -746,21 +766,21 @@ 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. +--- 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. +--- Note: +--- Prefer |nvim_buf_set_lines()| (for performance) to add or delete entire lines. +--- Prefer |nvim_paste()| or |nvim_put()| to insert (instead of replace) text at cursor. --- ---- Prefer `nvim_put()` if you want to insert text at the cursor position. --- --- @param buffer integer Buffer handle, or 0 for current buffer --- @param start_row integer First line index @@ -790,10 +810,10 @@ function vim.api.nvim_buf_set_virtual_text(buffer, src_id, line, chunks, opts) e --- --- On execution error: fails with Vimscript error, updates v:errmsg. --- ---- @param dict any Dictionary, or String evaluating to a Vimscript `self` dict +--- @param dict any Dict, or String evaluating to a Vimscript `self` dict --- @param fn string Name of the function defined on the Vimscript dict --- @param args any[] Function arguments packed in an Array ---- @return any +--- @return any # Result of the function call function vim.api.nvim_call_dict_function(dict, fn, args) end --- Calls a Vimscript function with the given arguments. @@ -802,85 +822,83 @@ function vim.api.nvim_call_dict_function(dict, fn, args) end --- --- @param fn string Function to call --- @param args any[] Function arguments packed in an Array ---- @return any +--- @return any # Result of the function call function vim.api.nvim_call_function(fn, args) end ---- Send data to channel `id`. For a job, it writes it to the stdin of the ---- 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. +--- Send data to channel `id`. For a job, it writes it to the +--- stdin of the 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. +--- 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. --- --- @param chan integer id of the channel --- @param data string data to write. 8-bit clean: can contain NUL bytes. function vim.api.nvim_chan_send(chan, data) end ---- Clears all autocommands selected by {opts}. To delete autocmds see ---- `nvim_del_autocmd()`. +--- Clears all autocommands selected by {opts}. To delete autocmds see `nvim_del_autocmd()`. --- --- @param opts vim.api.keyset.clear_autocmds Parameters ---- • event: (string|table) Examples: ---- • 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. +--- - event: (string|table) +--- Examples: +--- - 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. 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. +--- Unlike `nvim_command()` this command takes a structured Dict 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)`. +--- 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. +--- +--- @see vim.api.nvim_exec2 +--- @see vim.api.nvim_command +--- @param cmd vim.api.keyset.cmd Command to execute. Must be a Dict 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. --- @param opts vim.api.keyset.cmd_opts Optional parameters. ---- • output: (boolean, default false) Whether to return command ---- output. ---- @return string +--- - output: (boolean, default false) Whether to return command output. +--- @return string # Command output (non-error, non-shell |:!|) if `output` is true, else empty string. 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 ---- then execute it, use `nvim_cmd()`. To modify an Ex command before ---- evaluating it, use `nvim_parse_cmd()` in conjunction with `nvim_cmd()`. +--- 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 then execute it, use `nvim_cmd()`. To modify an Ex command before evaluating it, use +--- `nvim_parse_cmd()` in conjunction with `nvim_cmd()`. --- --- @param command string Ex command string function vim.api.nvim_command(command) end --- @deprecated +--- @see vim.api.nvim_exec2 --- @param command string --- @return string function vim.api.nvim_command_output(command) end @@ -890,106 +908,99 @@ function vim.api.nvim_command_output(command) end --- To get an existing group id, do: --- --- ```lua ---- local id = vim.api.nvim_create_augroup("MyGroup", { ---- clear = false ---- }) +--- local id = vim.api.nvim_create_augroup("MyGroup", { +--- clear = false +--- }) --- ``` --- ---- +--- @see `:help autocmd-groups` --- @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 ---- commands if the group already exists `autocmd-groups`. ---- @return integer +--- @param opts vim.api.keyset.create_augroup Dict Parameters +--- - clear (bool) optional: defaults to true. Clear existing +--- commands if the group already exists `autocmd-groups`. +--- @return integer # Integer id of the created group. 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 ---- vim.api.nvim_create_autocmd({"BufEnter", "BufWinEnter"}, { ---- pattern = {"*.c", "*.h"}, ---- callback = function(ev) ---- print(string.format('event fired: %s', vim.inspect(ev))) ---- end ---- }) +--- vim.api.nvim_create_autocmd({"BufEnter", "BufWinEnter"}, { +--- pattern = {"*.c", "*.h"}, +--- callback = function(ev) +--- print(string.format('event fired: %s', vim.inspect(ev))) +--- end +--- }) --- ``` --- --- Example using an Ex command as the handler: --- --- ```lua ---- vim.api.nvim_create_autocmd({"BufEnter", "BufWinEnter"}, { ---- pattern = {"*.c", "*.h"}, ---- command = "echo 'Entering a C or C++ file'", ---- }) +--- vim.api.nvim_create_autocmd({"BufEnter", "BufWinEnter"}, { +--- pattern = {"*.c", "*.h"}, +--- command = "echo 'Entering a C or C++ file'", +--- }) --- ``` --- ---- Note: `pattern` is NOT automatically expanded (unlike with `:autocmd`), ---- thus names like "$HOME" and "~" must be expanded explicitly: +--- Note: `pattern` is NOT automatically expanded (unlike with `:autocmd`), thus names like "$HOME" +--- and "~" must be expanded explicitly: --- --- ```lua ---- pattern = vim.fn.expand("~") .. "/some/path/*.py" +--- pattern = vim.fn.expand("~") .. "/some/path/*.py" --- ``` --- ---- ---- @param event any (string|array) Event(s) that will trigger the handler ---- (`callback` or `command`). +--- @see `:help autocommand` +--- @see vim.api.nvim_del_autocmd +--- @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 a truthy value (not ---- `false` or `nil`) to delete the autocommand. Receives one ---- argument, a table with these keys: *event-args* ---- • 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()` *event-data* ---- • 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 +--- - 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 one argument, +--- a table with these keys: [event-args]() +--- - 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()] [event-data]() +--- - 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 # Autocommand id (number) function vim.api.nvim_create_autocmd(event, opts) end --- Creates a new, empty, unnamed buffer. --- +--- @see buf_open_scratch --- @param listed boolean Sets 'buflisted' --- @param scratch boolean Creates a "throwaway" `scratch-buffer` for temporary work ---- (always 'nomodified'). Also sets 'nomodeline' on the ---- buffer. ---- @return integer +--- (always 'nomodified'). Also sets 'nomodeline' on the buffer. +--- @return integer # Buffer handle, or 0 on error +--- 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. +--- namespace, the associated id is returned. If `name` is an empty string +--- a new, anonymous namespace is created. --- --- @param name string Namespace name or empty string ---- @return integer +--- @return integer # Namespace id function vim.api.nvim_create_namespace(name) end --- Creates a global `user-commands` command. @@ -999,70 +1010,57 @@ function vim.api.nvim_create_namespace(name) end --- Example: --- --- ```vim ---- :call nvim_create_user_command('SayHello', 'echo "Hello world!"', {'bang': v:true}) ---- :SayHello ---- Hello world! +--- :call nvim_create_user_command('SayHello', 'echo "Hello world!"', {'bang': v:true}) +--- :SayHello +--- Hello world! --- ``` --- ---- ---- @param name string Name of the new user command. Must begin with an uppercase ---- 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> ---- • fargs: (table) The args split by unescaped whitespace ---- (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> ---- • line1: (number) The starting line of the command range ---- <line1> ---- • line2: (number) The final line of the command range ---- <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> ---- • smods: (table) Command modifiers in a structured format. ---- Has the same structure as the "mods" key of ---- `nvim_parse_cmd()`. +--- @param name string Name of the new user command. Must begin with an uppercase 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>] +--- - fargs: (table) The args split by unescaped whitespace (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>] +--- - line1: (number) The starting line of the command range [<line1>] +--- - line2: (number) The final line of the command range [<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>] +--- - 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. ---- +--- 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. +--- @see vim.api.nvim_del_augroup_by_name +--- @see vim.api.nvim_create_augroup --- @param id integer Integer The id of the group. 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. ---- +--- 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. +--- @see `:help autocmd-groups` --- @param name string String The name of the group. function vim.api.nvim_del_augroup_by_name(name) end @@ -1079,14 +1077,20 @@ function vim.api.nvim_del_current_line() end --- --- To unmap a buffer-local mapping, use `nvim_buf_del_keymap()`. --- +--- @see vim.api.nvim_set_keymap --- @param mode string --- @param lhs string function vim.api.nvim_del_keymap(mode, lhs) end --- Deletes an uppercase/file named mark. See `mark-motions`. --- +--- Note: +--- Lowercase name (or other buffer-local mark) is an error. +--- +--- @see vim.api.nvim_buf_del_mark +--- @see vim.api.nvim_get_mark --- @param name string Mark name ---- @return boolean +--- @return boolean # true if the mark was deleted, else false. function vim.api.nvim_del_mark(name) end --- Delete a user-defined command. @@ -1102,14 +1106,13 @@ 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. +--- 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 @@ -1121,39 +1124,43 @@ function vim.api.nvim_err_write(str) end --- Writes a message to the Vim error buffer. Appends "\n", so the buffer is --- flushed (and displayed). --- +--- @see vim.api.nvim_err_write --- @param str string Message function vim.api.nvim_err_writeln(str) end ---- Evaluates a Vimscript `expression`. Dictionaries and Lists are recursively ---- expanded. +--- Evaluates a Vimscript `expression`. Dicts and Lists are recursively expanded. --- --- On execution error: fails with Vimscript error, updates v:errmsg. --- --- @param expr string Vimscript expression string ---- @return any +--- @return any # Evaluation result or expanded object function vim.api.nvim_eval(expr) end --- Evaluates statusline string. --- --- @param str string Statusline string (see 'statusline'). --- @param opts vim.api.keyset.eval_statusline Optional parameters. ---- • winid: (number) `window-ID` of the window to use as context ---- for statusline. ---- • maxwidth: (number) Maximum width of statusline. ---- • fillchar: (string) Character to fill blank spaces in the ---- statusline (see 'fillchars'). Treated as single-width even ---- if it isn't. ---- • highlights: (boolean) Return highlight information. ---- • use_winbar: (boolean) Evaluate winbar instead of statusline. ---- • use_tabline: (boolean) Evaluate tabline instead of ---- statusline. When true, {winid} is ignored. Mutually ---- exclusive with {use_winbar}. ---- • use_statuscol_lnum: (number) Evaluate statuscolumn for this ---- line number instead of statusline. ---- @return table<string,any> +--- - winid: (number) `window-ID` of the window to use as context for statusline. +--- - maxwidth: (number) Maximum width of statusline. +--- - fillchar: (string) Character to fill blank spaces in the statusline (see +--- 'fillchars'). Treated as single-width even if it isn't. +--- - highlights: (boolean) Return highlight information. +--- - use_winbar: (boolean) Evaluate winbar instead of statusline. +--- - use_tabline: (boolean) Evaluate tabline instead of statusline. When true, {winid} +--- is ignored. Mutually exclusive with {use_winbar}. +--- - use_statuscol_lnum: (number) Evaluate statuscolumn for this line number instead of statusline. +--- @return table<string,any> # Dict containing statusline information, with these keys: +--- - str: (string) Characters that will be displayed on the statusline. +--- - width: (number) Display width of the statusline. +--- - highlights: Array containing highlight information of the statusline. Only included when +--- the "highlights" key in {opts} is true. Each element of the array is a +--- |Dict| with these keys: +--- - start: (number) Byte index (0-based) of first character that uses the highlight. +--- - group: (string) Name of highlight group. function vim.api.nvim_eval_statusline(str, opts) end --- @deprecated +--- @see vim.api.nvim_exec2 --- @param src string --- @param output boolean --- @return string @@ -1162,33 +1169,38 @@ 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. +--- Unlike `nvim_command()` this function supports heredocs, script-scope (s:), +--- etc. --- --- On execution error: fails with Vimscript error, updates v:errmsg. --- +--- +--- @see `:help execute()` +--- @see vim.api.nvim_command +--- @see vim.api.nvim_cmd --- @param src string Vimscript code --- @param opts vim.api.keyset.exec_opts Optional parameters. ---- • output: (boolean, default false) Whether to capture and ---- return all (non-error, non-shell `:!`) output. ---- @return table<string,any> +--- - output: (boolean, default false) Whether to capture and return +--- all (non-error, non-shell `:!`) output. +--- @return table<string,any> # Dict containing information about execution, with these keys: +--- - output: (string|nil) Output if `opts.output` is true. function vim.api.nvim_exec2(src, opts) end ---- Execute all autocommands for {event} that match the corresponding {opts} ---- `autocmd-execute`. ---- +--- Execute all autocommands for {event} that match the corresponding +--- {opts} `autocmd-execute`. +--- @see `:help :doautocmd` --- @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. +--- @param opts vim.api.keyset.exec_autocmds Dict 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. function vim.api.nvim_exec_autocmds(event, opts) end --- Sends input-keys to Nvim, subject to various quirks controlled by `mode` @@ -1196,31 +1208,34 @@ function vim.api.nvim_exec_autocmds(event, opts) end --- --- On execution error: does not fail, but updates v:errmsg. --- ---- To input sequences like <C-o> use `nvim_replace_termcodes()` (typically +--- 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 ---- :let key = nvim_replace_termcodes("<C-o>", v:true, v:false, v:true) ---- :call nvim_feedkeys(key, 'n', v:false) +--- :let key = nvim_replace_termcodes("<C-o>", v:true, v:false, v:true) +--- :call nvim_feedkeys(key, 'n', v:false) --- ``` --- ---- +--- @see feedkeys() +--- @see vim_strsave_escape_ks --- @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 ---- false if you already used `nvim_replace_termcodes()`, and ---- true otherwise. +--- @param escape_ks boolean If true, escape K_SPECIAL bytes in `keys`. +--- This should be false if you already used +--- `nvim_replace_termcodes()`, and true otherwise. 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()`. +--- The dict has the full option names as keys and option metadata dicts as detailed at +--- `nvim_get_option_info2()`. --- ---- @return table<string,any> +--- +--- @see vim.api.nvim_get_commands +--- @return table<string,any> # dict of all options function vim.api.nvim_get_all_options_info() end --- Get all autocommands that match the corresponding {opts}. @@ -1228,39 +1243,68 @@ function vim.api.nvim_get_all_options_info() end --- These examples will get autocommands matching ALL the given criteria: --- --- ```lua ---- -- Matches all criteria ---- autocommands = vim.api.nvim_get_autocmds({ ---- group = "MyGroup", ---- event = {"BufEnter", "BufWinEnter"}, ---- pattern = {"*.c", "*.h"} ---- }) ---- ---- -- All commands from one group ---- autocommands = vim.api.nvim_get_autocmds({ ---- group = "MyGroup", ---- }) +--- -- Matches all criteria +--- autocommands = vim.api.nvim_get_autocmds({ +--- group = "MyGroup", +--- event = {"BufEnter", "BufWinEnter"}, +--- pattern = {"*.c", "*.h"} +--- }) +--- +--- -- All commands from one group +--- autocommands = vim.api.nvim_get_autocmds({ +--- group = "MyGroup", +--- }) --- ``` --- ---- NOTE: When multiple patterns or events are provided, it will find all the ---- autocommands that match any combination of them. ---- ---- @param opts vim.api.keyset.get_autocmds Dictionary with at least one of the following: ---- • group (string|integer): the autocommand group name or id to ---- match against. ---- • event (string|array): event or events to match against ---- `autocmd-events`. ---- • pattern (string|array): pattern or patterns to match against ---- `autocmd-pattern`. Cannot be used with {buffer} ---- • buffer: Buffer number or list of buffer numbers for buffer ---- local autocommands `autocmd-buflocal`. Cannot be used with ---- {pattern} ---- @return vim.api.keyset.get_autocmds.ret[] +--- NOTE: When multiple patterns or events are provided, it will find all the autocommands that +--- match any combination of them. +--- +--- @param opts vim.api.keyset.get_autocmds Dict with at least one of the following: +--- - group (string|integer): the autocommand group name or id to match against. +--- - event (string|array): event or events to match against `autocmd-events`. +--- - pattern (string|array): pattern or patterns to match against `autocmd-pattern`. +--- Cannot be used with {buffer} +--- - buffer: Buffer number or list of buffer numbers for buffer local autocommands +--- `autocmd-buflocal`. Cannot be used with {pattern} +--- @return vim.api.keyset.get_autocmds.ret[] # Array of autocommands matching the criteria, with each item +--- containing the following fields: +--- - id (number): the autocommand id (only when defined with the API). +--- - group (integer): the autocommand group id. +--- - group_name (string): the autocommand group name. +--- - desc (string): the autocommand description. +--- - event (string): the autocommand event. +--- - command (string): the autocommand command. Note: this will be empty if a callback is set. +--- - callback (function|string|nil): Lua function or name of a Vim script function +--- which is executed when this autocommand is triggered. +--- - once (boolean): whether the autocommand is only run once. +--- - pattern (string): the autocommand pattern. +--- If the autocommand is buffer local |autocmd-buffer-local|: +--- - buflocal (boolean): true if the autocommand is buffer local. +--- - buffer (number): the buffer number. function vim.api.nvim_get_autocmds(opts) end --- Gets information about a channel. --- --- @param chan integer channel_id, or 0 for current channel ---- @return table<string,any> +--- @return table<string,any> # Channel info dict with these keys: +--- - "id" Channel id. +--- - "argv" (optional) Job arguments list. +--- - "stream" Stream underlying the channel. +--- - "stdio" stdin and stdout of this Nvim instance +--- - "stderr" stderr of this Nvim instance +--- - "socket" TCP/IP socket or named pipe +--- - "job" Job with communication over its stdio. +--- - "mode" How data received on the channel is interpreted. +--- - "bytes" Send and receive raw bytes. +--- - "terminal" |terminal| instance interprets ASCII sequences. +--- - "rpc" |RPC| communication on the channel is active. +--- - "pty" (optional) Name of pseudoterminal. On a POSIX system this is a device path like +--- "/dev/pts/1". If unknown, the key will still be present if a pty is used (e.g. +--- for conpty on Windows). +--- - "buffer" (optional) Buffer connected to |terminal| instance. +--- - "client" (optional) Info about the peer (client on the other end of the RPC channel), +--- which it provided via |nvim_set_client_info()|. +--- function vim.api.nvim_get_chan_info(chan) end --- Returns the 24-bit RGB value of a `nvim_get_color_map()` color name or @@ -1269,13 +1313,12 @@ function vim.api.nvim_get_chan_info(chan) end --- Example: --- --- ```vim ---- :echo nvim_get_color_by_name("Pink") ---- :echo nvim_get_color_by_name("#cbcbcb") +--- :echo nvim_get_color_by_name("Pink") +--- :echo nvim_get_color_by_name("#cbcbcb") --- ``` --- ---- --- @param name string Color name or "#rrggbb" string ---- @return integer +--- @return integer # 24-bit RGB value, or -1 for invalid argument. function vim.api.nvim_get_color_by_name(name) end --- Returns a map of color names and RGB values. @@ -1283,67 +1326,75 @@ function vim.api.nvim_get_color_by_name(name) end --- Keys are color names (e.g. "Aqua") and values are 24-bit RGB color values --- (e.g. 65535). --- ---- @return table<string,integer> +--- @return table<string,integer> # Map of color names and RGB values. 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} ---- @return table<string,any> +--- +--- @see vim.api.nvim_get_all_options_info +--- @param opts vim.api.keyset.get_commands Optional parameters. Currently only supports +--- {"builtin":false} +--- @return table<string,any> # Map of maps describing commands. function vim.api.nvim_get_commands(opts) end --- Gets a map of the current editor state. --- --- @param opts vim.api.keyset.context Optional parameters. ---- • types: List of `context-types` ("regs", "jumps", "bufs", ---- "gvars", …) to gather, or empty for "all". ---- @return table<string,any> +--- - types: List of `context-types` ("regs", "jumps", "bufs", +--- "gvars", …) to gather, or empty for "all". +--- @return table<string,any> # map of global |context|. function vim.api.nvim_get_context(opts) end --- Gets the current buffer. --- ---- @return integer +--- @return integer # Buffer handle function vim.api.nvim_get_current_buf() end --- Gets the current line. --- ---- @return string +--- @return string # Current line string function vim.api.nvim_get_current_line() end --- Gets the current tabpage. --- ---- @return integer +--- @return integer # Tabpage handle function vim.api.nvim_get_current_tabpage() end --- Gets the current window. --- ---- @return integer +--- @return integer # Window handle function vim.api.nvim_get_current_win() end --- Gets all or specific highlight groups in a namespace. --- ---- @param ns_id integer Get highlight groups for namespace ns_id ---- `nvim_get_namespaces()`. Use 0 to get global highlight groups ---- `:highlight`. +--- Note: +--- When the `link` attribute is defined in the highlight definition +--- map, other attributes will not be taking effect (see |:hi-link|). +--- +--- +--- @param ns_id integer Get highlight groups for namespace ns_id `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 vim.api.keyset.hl_info +--- - 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.get_hl_info # Highlight groups as a map from group name to a highlight definition map as in |nvim_set_hl()|, +--- or only a single highlight definition map if requested by name or id. function vim.api.nvim_get_hl(ns_id, opts) end --- @deprecated +--- @see vim.api.nvim_get_hl_by_name --- @param hl_id integer --- @param rgb boolean --- @return table<string,any> function vim.api.nvim_get_hl_by_id(hl_id, rgb) end --- @deprecated +--- @see vim.api.nvim_get_hl_by_id --- @param name string --- @param rgb boolean --- @return table<string,any> @@ -1352,7 +1403,6 @@ 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 --- @return integer function vim.api.nvim_get_hl_id_by_name(name) end @@ -1360,39 +1410,46 @@ function vim.api.nvim_get_hl_id_by_name(name) end --- Gets the active highlight namespace. --- --- @param opts vim.api.keyset.get_ns Optional parameters ---- • winid: (number) `window-ID` for retrieving a window's ---- highlight namespace. A value of -1 is returned when ---- `nvim_win_set_hl_ns()` has not been called for the window ---- (or was called with a namespace of -1). ---- @return integer +--- - winid: (number) `window-ID` for retrieving a window's highlight +--- namespace. A value of -1 is returned when `nvim_win_set_hl_ns()` +--- has not been called for the window (or was called with a namespace +--- of -1). +--- @return integer # Namespace id, or -1 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 vim.api.keyset.keymap[] +--- @return vim.api.keyset.keymap[] # Array of |maparg()|-like dictionaries describing mappings. +--- The "buffer" key is always zero. 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`. +--- 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` --- +--- Note: +--- Lowercase name (or other buffer-local mark) is an error. +--- +--- @see vim.api.nvim_buf_set_mark +--- @see vim.api.nvim_del_mark --- @param name string Mark name --- @param opts vim.api.keyset.empty Optional parameters. Reserved for future use. ---- @return vim.api.keyset.get_mark +--- @return vim.api.keyset.get_mark # 4-tuple (row, col, buffer, buffername), (0, 0, 0, '') if the mark is +--- not set. function vim.api.nvim_get_mark(name, opts) end ---- Gets the current mode. `mode()` "blocking" is true if Nvim is waiting for ---- input. +--- Gets the current mode. `mode()` +--- "blocking" is true if Nvim is waiting for input. --- ---- @return vim.api.keyset.get_mode +--- @return vim.api.keyset.get_mode # Dict { "mode": String, "blocking": Boolean } function vim.api.nvim_get_mode() end --- Gets existing, non-anonymous `namespace`s. --- ---- @return table<string,integer> +--- @return table<string,integer> # dict that maps from names to namespace ids. function vim.api.nvim_get_namespaces() end --- @deprecated @@ -1407,100 +1464,113 @@ 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') ---- • type: type of option ("string", "number" or "boolean") ---- • default: The default value for the option ---- • was_set: Whether the option was set. ---- • last_set_sid: Last set script id (if any) ---- • last_set_linenr: line number where option was set ---- • last_set_chan: Channel where option was set (0 for local) ---- • scope: one of "global", "win", or "buf" ---- • global_local: whether win or buf option has a global value ---- • commalist: List of comma separated values ---- • flaglist: List of single char flags ---- ---- When {scope} is not provided, the last set information applies to the ---- local value in the current buffer or window if it is available, otherwise ---- the global value information is returned. This behavior can be disabled by +--- Resulting dict has keys: +--- - name: Name of the option (like 'filetype') +--- - shortname: Shortened name of the option (like 'ft') +--- - type: type of option ("string", "number" or "boolean") +--- - default: The default value for the option +--- - was_set: Whether the option was set. +--- +--- - last_set_sid: Last set script id (if any) +--- - last_set_linenr: line number where option was set +--- - last_set_chan: Channel where option was set (0 for local) +--- +--- - scope: one of "global", "win", or "buf" +--- - global_local: whether win or buf option has a global value +--- +--- - commalist: List of comma separated values +--- - flaglist: List of single char flags +--- +--- When {scope} is not provided, the last set information applies to the local +--- value in the current buffer or window if it is available, otherwise the +--- global value information is returned. This behavior can be disabled by --- explicitly specifying {scope} in the {opts} table. --- --- @param name string Option name --- @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 getting window local options. ---- • buf: Buffer number. Used for getting buffer local options. ---- Implies {scope} is "local". ---- @return vim.api.keyset.get_option_info +--- - scope: One of "global" or "local". Analogous to +--- `:setglobal` and `:setlocal`, respectively. +--- - win: `window-ID`. Used for getting window local options. +--- - buf: Buffer number. Used for getting buffer local options. +--- Implies {scope} is "local". +--- @return vim.api.keyset.get_option_info # Option Information function vim.api.nvim_get_option_info2(name, opts) end --- Gets the value of an option. The behavior of this function matches that of --- `:set`: the local value of an option is returned if it exists; otherwise, ---- the global value is returned. Local values always correspond to the ---- current buffer or window, unless "buf" or "win" is set in {opts}. +--- the global value is returned. Local values always correspond to the current +--- buffer or window, unless "buf" or "win" is set in {opts}. --- --- @param name string Option name --- @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 getting window local options. ---- • buf: Buffer number. Used for getting buffer local options. ---- Implies {scope} is "local". ---- • filetype: `filetype`. Used to get the default option for a ---- specific filetype. Cannot be used with any other option. ---- Note: this will trigger `ftplugin` and all `FileType` ---- autocommands for the corresponding filetype. ---- @return any +--- - scope: One of "global" or "local". Analogous to +--- `:setglobal` and `:setlocal`, respectively. +--- - win: `window-ID`. Used for getting window local options. +--- - buf: Buffer number. Used for getting buffer local options. +--- Implies {scope} is "local". +--- - filetype: `filetype`. Used to get the default option for a +--- specific filetype. Cannot be used with any other option. +--- Note: this will trigger `ftplugin` and all `FileType` +--- autocommands for the corresponding filetype. +--- @return any # Option value function vim.api.nvim_get_option_value(name, opts) end --- Gets info describing process `pid`. --- --- @param pid integer ---- @return any +--- @return any # Map of process properties, or NIL if process not found. function vim.api.nvim_get_proc(pid) end --- Gets the immediate children of process `pid`. --- --- @param pid integer ---- @return any[] +--- @return any[] # Array of child process ids, empty if process not found. function vim.api.nvim_get_proc_children(pid) end ---- Find files in runtime directories +--- Finds files in runtime directories, in 'runtimepath' order. --- --- "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 +--- `nvim_get_runtime_file("colors/*.{vim,lua}", 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 --- @param all boolean whether to return all matches or only the first ---- @return string[] +--- @return string[] # list of absolute paths to the found files function vim.api.nvim_get_runtime_file(name, all) end --- Gets a global (g:) variable. --- --- @param name string Variable name ---- @return any +--- @return any # Variable value function vim.api.nvim_get_var(name) end --- Gets a v: variable. --- --- @param name string Variable name ---- @return any +--- @return any # Variable value 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). +--- 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). +--- +--- To input blocks of text, `nvim_paste()` is much faster and should be preferred. --- --- On execution error: does not fail, but updates v:errmsg. --- +--- Note: +--- |keycodes| like [<CR>] are translated, so "<" is special. +--- To input a literal "<", send [<LT>]. +--- +--- For mouse events use |nvim_input_mouse()|. The pseudokey form +--- `<LeftMouse><col,row>` is deprecated since |api-level| 6. +--- +--- --- @param keys string to be typed ---- @return integer +--- @return integer # Number of bytes actually written (can be fewer than +--- requested if the buffer becomes full). function vim.api.nvim_input(keys) end --- Send mouse event from GUI. @@ -1508,15 +1578,22 @@ function vim.api.nvim_input(keys) end --- 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", "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" ---- can all be used to specify Ctrl+Alt+click. +--- Note: +--- Currently this doesn't support "scripting" multiple mouse events +--- by calling it multiple times in a loop: the intermediate mouse +--- positions will be ignored. It should be used to implement real-time +--- mouse input in a GUI. The deprecated pseudokey form +--- (`<LeftMouse><col,row>`) of |nvim_input()| has the same limitation. +--- +--- +--- @param button string Mouse button: one of "left", "right", "middle", "wheel", "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" can all be used to specify Ctrl+Alt+click. --- @param grid integer Grid number if the client uses `ui-multigrid`, else 0. --- @param row integer Mouse row-position (zero-based, like redraw events) --- @param col integer Mouse column-position (zero-based, like redraw events) @@ -1524,35 +1601,41 @@ 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. +--- Includes unlisted (unloaded/deleted) buffers, like `:ls!`. +--- Use `nvim_buf_is_loaded()` to check if a buffer is loaded. --- ---- @return integer[] +--- @return integer[] # List of buffer handles function vim.api.nvim_list_bufs() end --- Get information about all open channels. --- ---- @return any[] +--- @return any[] # Array of Dictionaries, each describing a channel with +--- the format specified at |nvim_get_chan_info()|. function vim.api.nvim_list_chans() end --- Gets the paths contained in `runtime-search-path`. --- ---- @return string[] +--- @return string[] # List of paths function vim.api.nvim_list_runtime_paths() end --- Gets the current list of tabpage handles. --- ---- @return integer[] +--- @return integer[] # List of tabpage handles function vim.api.nvim_list_tabpages() end --- Gets a list of dictionaries representing attached UIs. --- ---- @return any[] +--- @return any[] # Array of UI dictionaries, each with these keys: +--- - "height" Requested height of the UI +--- - "width" Requested width of the UI +--- - "rgb" true if the UI uses RGB colors (false implies |cterm-colors|) +--- - "ext_..." Requested UI extensions, see |ui-option| +--- - "chan" |channel-id| of remote UI function vim.api.nvim_list_uis() end --- Gets the current list of window handles. --- ---- @return integer[] +--- @return integer[] # List of window handles function vim.api.nvim_list_wins() end --- Sets the current editor state from the given `context` map. @@ -1575,210 +1658,194 @@ 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. +--- 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()`, ---- then display it using `nvim_open_win()`, and then call this function. Then ---- `nvim_chan_send()` can be called immediately to process sequences in a ---- virtual terminal having the intended size. +--- then display it using `nvim_open_win()`, and then call this function. +--- Then `nvim_chan_send()` can be called immediately to process sequences +--- in a virtual terminal having the intended size. --- --- @param buffer integer the buffer to use (expected to be empty) --- @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 +--- - 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 # Channel id, or 0 on error function vim.api.nvim_open_term(buffer, opts) end --- 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. +--- 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`. --- --- 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. +--- 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. +--- 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 ---- vim.api.nvim_open_win(0, false, ---- {relative='win', row=3, col=3, width=12, height=3}) +--- vim.api.nvim_open_win(0, false, +--- {relative='win', row=3, col=3, width=12, height=3}) --- ``` --- --- Example (Lua): buffer-relative float (travels as buffer is scrolled) --- --- ```lua ---- vim.api.nvim_open_win(0, false, ---- {relative='win', width=12, height=3, bufpos={100,10}}) +--- 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 ---- }) +--- 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.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 ---- • "win" Window given by the `win` field, or current ---- window. ---- • "cursor" Cursor position in current window. ---- • "mouse" Mouse position ---- • 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: ---- • `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 ---- be fractional. ---- • focusable: Enable focus by user actions (wincmds, mouse ---- events). Defaults to true. Non-focusable windows can be ---- entered by `nvim_set_current_win()`. ---- • 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 ---- z-indices: ---- • 100: insert completion popupmenu ---- • 200: message scrollback ---- • 250: cmdline completion popupmenu (when ---- 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 ---- options disabled. This is useful when displaying a ---- temporary float where the text should not be edited. ---- Disables 'number', 'relativenumber', 'cursorline', ---- 'cursorcolumn', 'foldcolumn', 'spell' and 'list' ---- options. 'signcolumn' is changed to `auto` and ---- 'colorcolumn' is cleared. 'statuscolumn' is changed to ---- 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.). ---- • "solid": Adds padding by a single whitespace cell. ---- • "shadow": A drop shadow effect by blending with the ---- background. ---- • If it is an array, it should have a length of eight or ---- 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"] ]. ---- ``` ---- ---- • title: Title (optional) in window border, string or list. ---- List should consist of `[text, highlight]` tuples. If ---- string, the default highlight group is `FloatTitle`. ---- • title_pos: Title position. Must be set with `title` ---- option. Value can be one of "left", "center", or "right". ---- Default is `"left"`. ---- • footer: Footer (optional) in window border, string or ---- list. List should consist of `[text, highlight]` tuples. ---- If string, the default highlight group is `FloatFooter`. ---- • footer_pos: Footer position. Must be set with `footer` ---- option. Value can be one of "left", "center", or "right". ---- Default is `"left"`. ---- • noautocmd: If true then all autocommands are blocked for ---- the duration of the call. ---- • 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 +--- - relative: Sets the window layout to "floating", placed at (row,col) +--- coordinates relative to: +--- - "editor" The global editor grid +--- - "win" Window given by the `win` field, or current window. +--- - "cursor" Cursor position in current window. +--- - "mouse" Mouse position +--- - 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: +--- - `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 be +--- fractional. +--- - focusable: Enable focus by user actions (wincmds, mouse events). +--- Defaults to true. Non-focusable windows can be entered by +--- `nvim_set_current_win()`. +--- - 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 z-indices: +--- - 100: insert completion popupmenu +--- - 200: message scrollback +--- - 250: cmdline completion popupmenu (when 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 options +--- disabled. This is useful when displaying a temporary +--- float where the text should not be edited. Disables +--- 'number', 'relativenumber', 'cursorline', 'cursorcolumn', +--- 'foldcolumn', 'spell' and 'list' options. 'signcolumn' +--- is changed to `auto` and 'colorcolumn' is cleared. +--- 'statuscolumn' is changed to 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.). +--- - "solid": Adds padding by a single whitespace cell. +--- - "shadow": A drop shadow effect by blending with the background. +--- - If it is an array, it should have a length of eight or 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"] ]. +--- ``` +--- - title: Title (optional) in window border, string or list. +--- List should consist of `[text, highlight]` tuples. +--- If string, or a tuple lacks a highlight, the default highlight group is `FloatTitle`. +--- - title_pos: Title position. Must be set with `title` option. +--- Value can be one of "left", "center", or "right". +--- Default is `"left"`. +--- - footer: Footer (optional) in window border, string or list. +--- List should consist of `[text, highlight]` tuples. +--- If string, or a tuple lacks a highlight, the default highlight group is `FloatFooter`. +--- - footer_pos: Footer position. Must be set with `footer` option. +--- Value can be one of "left", "center", or "right". +--- Default is `"left"`. +--- - noautocmd: If true then all autocommands are blocked for the duration of +--- the call. +--- - 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 # Window handle, or 0 on error function vim.api.nvim_open_win(buffer, enter, config) end --- Writes a message to the Vim output buffer. Does not append "\n", the @@ -1793,92 +1860,207 @@ function vim.api.nvim_out_write(str) end --- --- @param str string Command line string to parse. Cannot contain "\n". --- @param opts vim.api.keyset.empty Optional parameters. Reserved for future use. ---- @return vim.api.keyset.parse_cmd +--- @return vim.api.keyset.parse_cmd # Dict containing command information, with these keys: +--- - cmd: (string) Command name. +--- - range: (array) (optional) Command range ([<line1>] [<line2>]). +--- Omitted if command doesn't accept a range. +--- Otherwise, has no elements if no range was specified, one element if +--- only a single range item was specified, or two elements if both range +--- items were specified. +--- - count: (number) (optional) Command [<count>]. +--- Omitted if command cannot take a count. +--- - reg: (string) (optional) Command [<register>]. +--- Omitted if command cannot take a register. +--- - bang: (boolean) Whether command contains a [<bang>] (!) modifier. +--- - args: (array) Command arguments. +--- - addr: (string) Value of |:command-addr|. Uses short name or "line" for -addr=lines. +--- - nargs: (string) Value of |:command-nargs|. +--- - nextcmd: (string) Next command if there are multiple commands separated by a |:bar|. +--- Empty if there isn't a next command. +--- - magic: (dict) Which characters have special meaning in the command arguments. +--- - file: (boolean) The command expands filenames. Which means characters such as "%", +--- "#" and wildcards are expanded. +--- - bar: (boolean) The "|" character is treated as a command separator and the double +--- quote character (") is treated as the start of a comment. +--- - mods: (dict) |:command-modifiers|. +--- - filter: (dict) |:filter|. +--- - pattern: (string) Filter pattern. Empty string if there is no filter. +--- - force: (boolean) Whether filter is inverted or not. +--- - silent: (boolean) |:silent|. +--- - emsg_silent: (boolean) |:silent!|. +--- - unsilent: (boolean) |:unsilent|. +--- - sandbox: (boolean) |:sandbox|. +--- - noautocmd: (boolean) |:noautocmd|. +--- - browse: (boolean) |:browse|. +--- - confirm: (boolean) |:confirm|. +--- - hide: (boolean) |:hide|. +--- - horizontal: (boolean) |:horizontal|. +--- - keepalt: (boolean) |:keepalt|. +--- - keepjumps: (boolean) |:keepjumps|. +--- - keepmarks: (boolean) |:keepmarks|. +--- - keeppatterns: (boolean) |:keeppatterns|. +--- - lockmarks: (boolean) |:lockmarks|. +--- - noswapfile: (boolean) |:noswapfile|. +--- - tab: (integer) |:tab|. -1 when omitted. +--- - verbose: (integer) |:verbose|. -1 when omitted. +--- - vertical: (boolean) |:vertical|. +--- - split: (string) Split modifier string, is an empty string when there's no split +--- modifier. If there is a split modifier it can be one of: +--- - "aboveleft": |:aboveleft|. +--- - "belowright": |:belowright|. +--- - "topleft": |:topleft|. +--- - "botright": |:botright|. 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". ---- @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 ---- highlighted region and represent line, starting column ---- and ending column (latter exclusive: one should highlight ---- region [start_col, end_col)). ---- @return table<string,any> +--- - "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 highlighted region and represent line, +--- starting column and ending column (latter exclusive: +--- one should highlight region [start_col, end_col)). +--- @return table<string,any> # +--- - AST: top-level dict with these keys: +--- - "error": Dict with error, present only if parser saw some +--- error. Contains the following keys: +--- - "message": String, error message in printf format, translated. +--- Must contain exactly one "%.*s". +--- - "arg": String, error message argument. +--- - "len": Amount of bytes successfully parsed. With flags equal to "" +--- that should be equal to the length of expr string. +--- ("Successfully parsed" here means "participated in AST +--- creation", not "till the first error".) +--- - "ast": AST, either nil or a dict with these keys: +--- - "type": node type, one of the value names from ExprASTNodeType +--- stringified without "kExprNode" prefix. +--- - "start": a pair `[line, column]` describing where node is "started" +--- where "line" is always 0 (will not be 0 if you will be +--- using this API on e.g. ":let", but that is not +--- present yet). Both elements are Integers. +--- - "len": “length” of the node. This and "start" are there for +--- debugging purposes primary (debugging parser and providing +--- debug information). +--- - "children": a list of nodes described in top/"ast". There always +--- is zero, one or two children, key will not be present +--- if node has no children. Maximum number of children +--- may be found in node_maxchildren array. +--- - Local values (present only for certain nodes): +--- - "scope": a single Integer, specifies scope for "Option" and +--- "PlainIdentifier" nodes. For "Option" it is one of +--- ExprOptScope values, for "PlainIdentifier" it is one of +--- ExprVarScope values. +--- - "ident": identifier (without scope, if any), present for "Option", +--- "PlainIdentifier", "PlainKey" and "Environment" nodes. +--- - "name": Integer, register name (one character) or -1. Only present +--- for "Register" nodes. +--- - "cmp_type": String, comparison type, one of the value names from +--- ExprComparisonType, stringified without "kExprCmp" +--- prefix. Only present for "Comparison" nodes. +--- - "ccs_strategy": String, case comparison strategy, one of the +--- value names from ExprCaseCompareStrategy, +--- stringified without "kCCStrategy" prefix. Only +--- present for "Comparison" nodes. +--- - "augmentation": String, augmentation type for "Assignment" nodes. +--- Is either an empty string, "Add", "Subtract" or +--- "Concat" for "=", "+=", "-=" or ".=" respectively. +--- - "invert": Boolean, true if result of comparison needs to be +--- inverted. Only present for "Comparison" nodes. +--- - "ivalue": Integer, integer value for "Integer" nodes. +--- - "fvalue": Float, floating-point value for "Float" nodes. +--- - "svalue": String, value for "SingleQuotedString" and +--- "DoubleQuotedString" nodes. function vim.api.nvim_parse_expression(expr, flags, highlight) end ---- Pastes at cursor, in any mode. +--- Pastes at cursor (in any mode), and sets "redo" so dot (`.`) will repeat the input. UIs call +--- this to implement "paste", but it's also intended for use by scripts to input large, +--- dot-repeatable blocks of text (as opposed to `nvim_input()` which is subject to mappings/events +--- and is thus much slower). +--- +--- Invokes the `vim.paste()` handler, which handles each mode appropriately. --- ---- 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 or cancel, subsequent calls +--- are ignored ("drained") until the next paste is initiated (phase 1 or -1). --- ---- 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 ---- the next paste is initiated (phase 1 or -1). +--- Useful in mappings and scripts to insert multiline text. Example: --- ---- @param data string Multiline input. May be binary (containing NUL bytes). +--- ```lua +--- vim.keymap.set('n', 'x', function() +--- vim.api.nvim_paste([[ +--- line1 +--- line2 +--- line3 +--- ]], false, -1) +--- end, { buffer = true }) +--- ``` +--- +--- @param data string Multiline input. Lines break at LF ("\n"). 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: ---- • 1: starts the paste (exactly once) ---- • 2: continues the paste (zero or more times) ---- • 3: ends the paste (exactly once) ---- @return boolean +--- @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: +--- - 1: starts the paste (exactly once) +--- - 2: continues the paste (zero or more times) +--- - 3: ends the paste (exactly once) +--- @return boolean # +--- - true: Client may continue pasting. +--- - false: Client should cancel the paste. function vim.api.nvim_paste(data, crlf, phase) end ---- Puts text at cursor, in any mode. +--- Puts text at cursor, in any mode. For dot-repeatable input, use `nvim_paste()`. --- --- 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()` +--- - "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 ---- Replaces terminal codes and `keycodes` (<CR>, <Esc>, ...) in a string with +--- Replaces terminal codes and `keycodes` ([<CR>], [<Esc>], ...) in a string with --- the internal representation. --- +--- @see replace_termcodes +--- @see cpoptions --- @param str string String to be converted. --- @param from_part boolean Legacy Vim parameter. Usually true. ---- @param do_lt boolean Also translate <lt>. Ignored if `special` is false. ---- @param special boolean Replace `keycodes`, e.g. <CR> becomes a "\r" char. +--- @param do_lt boolean Also translate [<lt>]. Ignored if `special` is false. +--- @param special boolean Replace `keycodes`, e.g. [<CR>] becomes a "\r" char. --- @return string 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 +--- 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 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 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 vim.api.keyset.empty Optional parameters. Reserved for future use. function vim.api.nvim_select_popupmenu_item(item, insert, finish, opts) end @@ -1909,111 +2091,118 @@ 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. +--- 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). +--- 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. ---- Similarly, return `false` in `on_win` will skip the `on_lines` calls for ---- that window (but any extmarks set in `on_win` will still be used). A ---- plugin managing multiple sources of decoration should ideally only set one ---- provider, and merge the sources internally. You can use multiple `ns_id` +--- Similarly, return `false` in `on_win` will skip the `on_line` calls +--- for that window (but any extmarks set in `on_win` will still be used). +--- A 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: 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. +--- 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_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, toprow, botrow] +--- ``` +--- - 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. --- ---- • on_buf: called for each buffer being redrawn (before window ---- callbacks) ---- ``` ---- ["buf", bufnr, tick] ---- ``` +--- Note: +--- Unlike the `:highlight` command which can update a highlight group, +--- this function completely replaces the definition. For example: +--- `nvim_set_hl(0, 'Visual', {})` will clear the highlight group +--- 'Visual'. --- ---- • on_win: called when starting to redraw a specific window. ---- ``` ---- ["win", winid, bufnr, topline, botline] ---- ``` +--- The fg and bg keys also accept the string values `"fg"` or `"bg"` +--- which act as aliases to the corresponding foreground and background +--- values of the Normal group. If the Normal group has not been defined, +--- using these values results in an error. --- ---- • 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. +--- If `link` is used in combination with other attributes; only the +--- `link` will take effect (see |:hi-link|). +--- --- --- @param ns_id integer Namespace id for this highlight `nvim_create_namespace()`. ---- Use 0 to set a highlight group globally `:highlight`. ---- Highlights from non-global namespaces are not active by ---- default, use `nvim_set_hl_ns()` or `nvim_win_set_hl_ns()` to ---- activate them. +--- Use 0 to set a highlight group globally `:highlight`. +--- Highlights from non-global namespaces are not active by default, use +--- `nvim_set_hl_ns()` or `nvim_win_set_hl_ns()` to 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: 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. +--- - 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 ---- be set for a single window, see `nvim_win_set_hl_ns()`. +--- Set active namespace for highlights defined with `nvim_set_hl()`. This can be set for +--- a single window, see `nvim_win_set_hl_ns()`. --- --- @param ns_id integer the namespace to use function vim.api.nvim_set_hl_ns(ns_id) end ---- Set active namespace for highlights defined with `nvim_set_hl()` while ---- redrawing. +--- 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. +--- `nvim_set_decoration_provider()` on_win and on_line callbacks, which +--- are allowed to change the namespace during a redraw cycle. --- --- @param ns_id integer the namespace to activate function vim.api.nvim_set_hl_ns_fast(ns_id) end @@ -2022,37 +2211,34 @@ function vim.api.nvim_set_hl_ns_fast(ns_id) end --- --- To set a buffer-local mapping, use `nvim_buf_set_keymap()`. --- ---- Unlike `:map`, leading/trailing whitespace is accepted as part of the ---- {lhs} or {rhs}. Empty {rhs} is <Nop>. `keycodes` are replaced as usual. +--- Unlike `:map`, leading/trailing whitespace is accepted as part of the {lhs} or {rhs}. +--- Empty {rhs} is [<Nop>]. `keycodes` are replaced as usual. --- --- Example: --- --- ```vim ---- call nvim_set_keymap('n', ' <NL>', '', {'nowait': v:true}) +--- call nvim_set_keymap('n', ' <NL>', '', {'nowait': v:true}) --- ``` --- --- is equivalent to: --- --- ```vim ---- nmap <nowait> <Space><NL> <Nop> +--- 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 ---- "!a" for abbreviation in Insert mode, Cmdline mode, or both, ---- respectively +--- 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: ---- • "noremap" disables `recursive_mapping`, like `:noremap` ---- • "desc" human-readable description. ---- • "callback" Lua function called in place of {rhs}. ---- • "replace_keycodes" (boolean) When "expr" is true, replace ---- keycodes in the resulting string (see ---- `nvim_replace_termcodes()`). Returning nil from the Lua ---- "callback" is equivalent to returning an empty string. +--- @param opts vim.api.keyset.keymap Optional parameters map: Accepts all `:map-arguments` as keys 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}. +--- - "replace_keycodes" (boolean) When "expr" is true, replace keycodes in the +--- resulting string (see `nvim_replace_termcodes()`). Returning nil from the Lua +--- "callback" is equivalent to returning an empty string. function vim.api.nvim_set_keymap(mode, lhs, rhs, opts) end --- @deprecated @@ -2069,10 +2255,10 @@ function vim.api.nvim_set_option(name, value) end --- @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. @@ -2087,11 +2273,11 @@ function vim.api.nvim_set_var(name, value) end --- @param value any Variable value function vim.api.nvim_set_vvar(name, value) end ---- Calculates the number of display cells occupied by `text`. Control ---- characters including <Tab> count as one cell. +--- Calculates the number of display cells occupied by `text`. +--- Control characters including [<Tab>] count as one cell. --- --- @param text string Some text ---- @return integer +--- @return integer # Number of cells function vim.api.nvim_strwidth(text) end --- Removes a tab-scoped (t:) variable @@ -2103,32 +2289,32 @@ function vim.api.nvim_tabpage_del_var(tabpage, name) end --- Gets the tabpage number --- --- @param tabpage integer Tabpage handle, or 0 for current tabpage ---- @return integer +--- @return integer # Tabpage number function vim.api.nvim_tabpage_get_number(tabpage) end --- Gets a tab-scoped (t:) variable --- --- @param tabpage integer Tabpage handle, or 0 for current tabpage --- @param name string Variable name ---- @return any +--- @return any # Variable value function vim.api.nvim_tabpage_get_var(tabpage, name) end --- Gets the current window in a tabpage --- --- @param tabpage integer Tabpage handle, or 0 for current tabpage ---- @return integer +--- @return integer # Window handle function vim.api.nvim_tabpage_get_win(tabpage) end --- Checks if a tabpage is valid --- --- @param tabpage integer Tabpage handle, or 0 for current tabpage ---- @return boolean +--- @return boolean # true if the tabpage is valid, false otherwise function vim.api.nvim_tabpage_is_valid(tabpage) end --- Gets the windows in a tabpage --- --- @param tabpage integer Tabpage handle, or 0 for current tabpage ---- @return integer[] +--- @return integer[] # List of windows in `tabpage` function vim.api.nvim_tabpage_list_wins(tabpage) end --- Sets a tab-scoped (t:) variable @@ -2146,18 +2332,21 @@ function vim.api.nvim_tabpage_set_win(tabpage, win) end --- Calls a function with window as temporary current window. --- +--- +--- @see `:help win_execute()` +--- @see vim.api.nvim_buf_call --- @param window integer Window handle, or 0 for current window --- @param fun function Function to call inside the window (currently Lua callable ---- only) ---- @return any +--- only) +--- @return any # Return value of function. function vim.api.nvim_win_call(window, fun) end --- Closes the window (like `:close` with a `window-ID`). --- --- @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 @@ -2169,7 +2358,7 @@ function vim.api.nvim_win_del_var(window, name) end --- Gets the current buffer in a window --- --- @param window integer Window handle, or 0 for current window ---- @return integer +--- @return integer # Buffer handle function vim.api.nvim_win_get_buf(window) end --- Gets window configuration. @@ -2179,27 +2368,29 @@ function vim.api.nvim_win_get_buf(window) end --- `relative` is empty for normal windows. --- --- @param window integer Window handle, or 0 for current window ---- @return vim.api.keyset.win_config +--- @return vim.api.keyset.win_config # Map defining the window configuration, see |nvim_open_win()| function vim.api.nvim_win_get_config(window) end --- Gets the (1,0)-indexed, buffer-relative cursor position for a given window --- (different windows showing the same buffer have independent cursor --- positions). `api-indexing` --- +--- +--- @see `:help getcurpos()` --- @param window integer Window handle, or 0 for current window ---- @return integer[] +--- @return integer[] # (row, col) tuple function vim.api.nvim_win_get_cursor(window) end --- Gets the window height --- --- @param window integer Window handle, or 0 for current window ---- @return integer +--- @return integer # Height as a count of rows function vim.api.nvim_win_get_height(window) end --- Gets the window number --- --- @param window integer Window handle, or 0 for current window ---- @return integer +--- @return integer # Window number function vim.api.nvim_win_get_number(window) end --- @deprecated @@ -2211,34 +2402,34 @@ function vim.api.nvim_win_get_option(window, name) end --- Gets the window position in display cells. First position is zero. --- --- @param window integer Window handle, or 0 for current window ---- @return integer[] +--- @return integer[] # (row, col) tuple with the window position function vim.api.nvim_win_get_position(window) end --- Gets the window tabpage --- --- @param window integer Window handle, or 0 for current window ---- @return integer +--- @return integer # Tabpage that contains the window function vim.api.nvim_win_get_tabpage(window) end --- Gets a window-scoped (w:) variable --- --- @param window integer Window handle, or 0 for current window --- @param name string Variable name ---- @return any +--- @return any # Variable value function vim.api.nvim_win_get_var(window, name) end --- Gets the window width --- --- @param window integer Window handle, or 0 for current window ---- @return integer +--- @return integer # Width as a count of columns 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. +--- 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. --- --- @param window integer Window handle, or 0 for current window function vim.api.nvim_win_hide(window) end @@ -2246,7 +2437,7 @@ function vim.api.nvim_win_hide(window) end --- Checks if a window is valid --- --- @param window integer Window handle, or 0 for current window ---- @return boolean +--- @return boolean # true if the window is valid, false otherwise function vim.api.nvim_win_is_valid(window) end --- Sets the current buffer in a window, without side effects @@ -2261,12 +2452,15 @@ function vim.api.nvim_win_set_buf(window, buffer) end --- When reconfiguring a window, absent option keys will not be changed. --- `row`/`col` and `relative` must be reconfigured together. --- +--- +--- @see vim.api.nvim_open_win --- @param window integer Window handle, or 0 for current window ---- @param config vim.api.keyset.win_config Map defining the window configuration, see `nvim_open_win()` +--- @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 ---- scrolls the window even if it is not the current one. +--- Sets the (1,0)-indexed cursor position in the window. `api-indexing` +--- This scrolls the window even if it is not the current one. --- --- @param window integer Window handle, or 0 for current window --- @param pos integer[] (row, col) tuple representing the new position @@ -2278,9 +2472,9 @@ function vim.api.nvim_win_set_cursor(window, pos) end --- @param height integer Height as a count of rows 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. +--- 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. --- @@ -2308,28 +2502,32 @@ function vim.api.nvim_win_set_var(window, name, value) end --- @param width integer Width as a count of columns 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. +--- 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 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. +--- 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()`. --- +--- @see `:help virtcol()` for text width. --- @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. ---- @return table<string,any> +--- - 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> # Dict containing text height information, with these keys: +--- - all: The total number of screen lines occupied by the range. +--- - fill: The number of diff filler or virtual lines among them. +--- 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 f7cd92a3b2..2fe5c32faf 100644 --- a/runtime/lua/vim/_meta/api_keysets.lua +++ b/runtime/lua/vim/_meta/api_keysets.lua @@ -197,6 +197,9 @@ error('Cannot require a meta file') --- @field desc? string --- @field replace_keycodes? boolean +--- @class vim.api.keyset.ns_opts +--- @field wins? any[] + --- @class vim.api.keyset.open_term --- @field on_input? function --- @field force_crlf? boolean diff --git a/runtime/lua/vim/_meta/api_keysets_extra.lua b/runtime/lua/vim/_meta/api_keysets_extra.lua index 76b56b04e7..81bce50746 100644 --- a/runtime/lua/vim/_meta/api_keysets_extra.lua +++ b/runtime/lua/vim/_meta/api_keysets_extra.lua @@ -23,16 +23,16 @@ error('Cannot require a meta file') --- @field conceal? boolean --- @field spell? boolean --- @field ui_watched? boolean ---- @field url? boolean +--- @field url? string --- @field hl_mode? string --- ---- @field virt_text? {[1]: string, [2]: string}[] +--- @field virt_text? [string, 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? [string, string][][] --- @field virt_lines_above? boolean --- @field virt_lines_leftcol? boolean --- @@ -43,11 +43,17 @@ error('Cannot require a meta file') --- @field line_hl_group? string --- @field cursorline_hl_group? string ---- @class vim.api.keyset.get_extmark_item +--- @class vim.api.keyset.get_extmark_item_by_id --- @field [1] integer row --- @field [2] integer col --- @field [3] vim.api.keyset.extmark_details? +--- @class vim.api.keyset.get_extmark_item +--- @field [1] integer extmark_id +--- @field [2] integer row +--- @field [3] integer col +--- @field [4] vim.api.keyset.extmark_details? + --- @class vim.api.keyset.get_mark --- @field [1] integer row --- @field [2] integer col @@ -96,20 +102,29 @@ error('Cannot require a meta file') --- @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 + +--- @class vim.api.keyset.hl_info.cterm : vim.api.keyset.hl_info.base --- @field foreground? integer --- @field background? integer ---- @class vim.api.keyset.hl_info : vim.api.keyset.hl_info.base +--- @class vim.api.keyset.get_hl_info : vim.api.keyset.hl_info.base --- @field fg? integer --- @field bg? integer --- @field sp? integer --- @field default? true +--- @field blend? integer +--- @field cterm? vim.api.keyset.hl_info.cterm + +--- @class vim.api.keyset.set_hl_info : vim.api.keyset.hl_info.base +--- @field fg? integer|string +--- @field bg? integer|string +--- @field sp? integer|string +--- @field default? true --- @field link? string --- @field blend? integer +--- @field force? true --- @field cterm? vim.api.keyset.hl_info.cterm --- @class vim.api.keyset.get_mode diff --git a/runtime/lua/vim/_meta/builtin.lua b/runtime/lua/vim/_meta/builtin.lua index 75737bd040..13bd1c1294 100644 --- a/runtime/lua/vim/_meta/builtin.lua +++ b/runtime/lua/vim/_meta/builtin.lua @@ -121,6 +121,7 @@ function vim.stricmp(a, b) end --- @param str string --- @param index integer --- @param use_utf16? boolean +--- @return integer function vim.str_byteindex(str, index, use_utf16) end --- Gets a list of the starting byte positions of each UTF-8 codepoint in the given string. @@ -181,8 +182,8 @@ function vim.str_utf_end(str, index) end --- that sequence. --- @param str string --- @param index? integer ---- @return integer UTF-32 index ---- @return integer UTF-16 index +--- @return integer # UTF-32 index +--- @return integer # UTF-16 index function vim.str_utfindex(str, index) end --- The result is a String, which is the text {str} converted from @@ -247,7 +248,7 @@ function vim.schedule(fn) end --- - If {callback} errors, the error is raised. function vim.wait(time, callback, interval, fast_only) end ---- Attach to ui events, similar to |nvim_ui_attach()| but receive events +--- Attach to |ui-events|, similar to |nvim_ui_attach()| but receive events --- as Lua callback. Can be used to implement screen elements like --- popupmenu or message handling in Lua. --- @@ -281,6 +282,8 @@ function vim.wait(time, callback, interval, fast_only) end --- end) --- ``` --- +--- @since 0 +--- --- @param ns integer --- @param options table<string, any> --- @param callback fun() diff --git a/runtime/lua/vim/_meta/builtin_types.lua b/runtime/lua/vim/_meta/builtin_types.lua index 9f0d2e7038..aca6649957 100644 --- a/runtime/lua/vim/_meta/builtin_types.lua +++ b/runtime/lua/vim/_meta/builtin_types.lua @@ -25,7 +25,7 @@ --- @field variables table<string,any> --- @field windows integer[] ---- @alias vim.fn.getjumplist.ret {[1]: vim.fn.getjumplist.ret.item[], [2]: integer} +--- @alias vim.fn.getjumplist.ret [vim.fn.getjumplist.ret.item[], integer] --- @class vim.fn.getjumplist.ret.item --- @field bufnr integer @@ -34,6 +34,11 @@ --- @field filename? string --- @field lnum integer +--- @class vim.fn.getmarklist.ret.item +--- @field mark string +--- @field pos [integer, integer, integer, integer] +--- @field file string + --- @class vim.fn.getmousepos.ret --- @field screenrow integer --- @field screencol integer @@ -135,3 +140,69 @@ --- @field sid string --- @field variables? table<string, any> --- @field version 1 + +--- @class vim.fn.undotree.entry +--- +--- Undo sequence number. Same as what appears in +--- \|:undolist|. +--- @field seq integer +--- +--- Timestamp when the change happened. Use +--- \|strftime()| to convert to something readable. +--- @field time integer +--- +--- Only appears in the item that is the last one +--- that was added. This marks the last change +--- and where further changes will be added. +--- @field newhead? integer +--- +--- Only appears in the item that is the last one +--- that was undone. This marks the current +--- position in the undo tree, the block that will +--- be used by a redo command. When nothing was +--- undone after the last change this item will +--- not appear anywhere. +--- @field curhead? integer +--- +--- Only appears on the last block before a file +--- write. The number is the write count. The +--- first write has number 1, the last one the +--- "save_last" mentioned above. +--- @field save integer +--- +--- Alternate entry. This is again a List of undo +--- blocks. Each item may again have an "alt" +--- item. +--- @field alt vim.fn.undotree.entry[] + +--- @class vim.fn.undotree.ret +--- +--- The highest undo sequence number used. +--- @field seq_last integer +--- +--- The sequence number of the current position in +--- the undo tree. This differs from "seq_last" +--- when some changes were undone. +--- @field seq_cur integer +--- +--- Time last used for |:earlier| and related +--- commands. Use |strftime()| to convert to +--- something readable. +--- @field time_cur integer +--- +--- Number of the last file write. Zero when no +--- write yet. +--- @field save_last integer +--- +--- Number of the current position in the undo +--- tree. +--- @field save_cur integer +--- +--- Non-zero when the last undo block was synced. +--- This happens when waiting from input from the +--- user. See |undo-blocks|. +--- @field synced integer +--- +--- A list of dictionaries with information about +--- undo blocks. +--- @field entries vim.fn.undotree.entry[] diff --git a/runtime/lua/vim/_meta/diff.lua b/runtime/lua/vim/_meta/diff.lua index 617bc87f59..4803ed4775 100644 --- a/runtime/lua/vim/_meta/diff.lua +++ b/runtime/lua/vim/_meta/diff.lua @@ -11,19 +11,19 @@ --- - `count_a` (`integer`): Hunk size in {a}. --- - `start_b` (`integer`): Start line of hunk in {b}. --- - `count_b` (`integer`): Hunk size in {b}. ---- @field on_hunk fun(start_a: integer, count_a: integer, start_b: integer, count_b: integer): integer +--- @field on_hunk? fun(start_a: integer, count_a: integer, start_b: integer, count_b: integer): integer? --- --- Form of the returned diff: --- - `unified`: String in unified format. --- - `indices`: Array of hunk locations. --- Note: This option is ignored if `on_hunk` is used. --- (default: `'unified'`) ---- @field result_type 'unified'|'indices' +--- @field result_type? 'unified'|'indices' --- --- Run linematch on the resulting hunks from xdiff. When integer, only hunks --- upto this size in lines are run through linematch. --- Requires `result_type = indices`, ignored otherwise. ---- @field linematch boolean|integer +--- @field linematch? boolean|integer --- --- Diff algorithm to use. Values: --- - `myers`: the default algorithm @@ -31,15 +31,15 @@ --- - `patience`: patience diff algorithm --- - `histogram`: histogram diff algorithm --- (default: `'myers'`) ---- @field algorithm 'myers'|'minimal'|'patience'|'histogram' ---- @field ctxlen integer Context length ---- @field interhunkctxlen integer Inter hunk context length ---- @field ignore_whitespace boolean Ignore whitespace ---- @field ignore_whitespace_change boolean Ignore whitespace change ---- @field ignore_whitespace_change_at_eol boolean Ignore whitespace change at end-of-line. ---- @field ignore_cr_at_eol boolean Ignore carriage return at end-of-line ---- @field ignore_blank_lines boolean Ignore blank lines ---- @field indent_heuristic boolean Use the indent heuristic for the internal diff library. +--- @field algorithm? 'myers'|'minimal'|'patience'|'histogram' +--- @field ctxlen? integer Context length +--- @field interhunkctxlen? integer Inter hunk context length +--- @field ignore_whitespace? boolean Ignore whitespace +--- @field ignore_whitespace_change? boolean Ignore whitespace change +--- @field ignore_whitespace_change_at_eol? boolean Ignore whitespace change at end-of-line. +--- @field ignore_cr_at_eol? boolean Ignore carriage return at end-of-line +--- @field ignore_blank_lines? boolean Ignore blank lines +--- @field indent_heuristic? boolean Use the indent heuristic for the internal diff library. -- luacheck: no unused args @@ -65,7 +65,7 @@ --- ---@param a string First string to compare ---@param b string Second string to compare ----@param opts vim.diff.Opts +---@param opts? vim.diff.Opts ---@return string|integer[][]? --- See {opts.result_type}. `nil` if {opts.on_hunk} is given. function vim.diff(a, b, opts) end diff --git a/runtime/lua/vim/_meta/lpeg.lua b/runtime/lua/vim/_meta/lpeg.lua index 73b3375c82..d354de95df 100644 --- a/runtime/lua/vim/_meta/lpeg.lua +++ b/runtime/lua/vim/_meta/lpeg.lua @@ -2,7 +2,7 @@ error('Cannot require a meta file') -- These types were taken from https://github.com/LuaCATS/lpeg --- (based on revision e6789e28e5b91a4a277a2a03081d708c403a3e34) +-- (based on revision 33f4ff5343a64cf613a0634d70092fbc2b64291b) -- with types being renamed to include the vim namespace and with some descriptions made less verbose. --- @brief <pre>help @@ -22,17 +22,18 @@ vim.lpeg = {} --- @nodoc --- @class vim.lpeg.Pattern +--- @operator len: vim.lpeg.Pattern --- @operator unm: vim.lpeg.Pattern --- @operator add(vim.lpeg.Pattern): vim.lpeg.Pattern --- @operator sub(vim.lpeg.Pattern): vim.lpeg.Pattern --- @operator mul(vim.lpeg.Pattern): vim.lpeg.Pattern --- @operator mul(vim.lpeg.Capture): vim.lpeg.Pattern --- @operator div(string): vim.lpeg.Capture ---- @operator div(number): vim.lpeg.Capture +--- @operator div(integer): vim.lpeg.Capture --- @operator div(table): vim.lpeg.Capture --- @operator div(function): vim.lpeg.Capture ---- @operator pow(number): vim.lpeg.Pattern ---- @operator mod(function): nil +--- @operator pow(integer): vim.lpeg.Pattern +--- @operator mod(function): vim.lpeg.Capture local Pattern = {} --- @alias vim.lpeg.Capture vim.lpeg.Pattern @@ -55,11 +56,12 @@ local Pattern = {} --- assert(pattern:match('1 hello') == nil) --- ``` --- ---- @param pattern vim.lpeg.Pattern +--- @param pattern vim.lpeg.Pattern|string|integer|boolean|table|function --- @param subject string --- @param init? integer ---- @return integer|vim.lpeg.Capture|nil -function vim.lpeg.match(pattern, subject, init) end +--- @param ... any +--- @return any ... +function vim.lpeg.match(pattern, subject, init, ...) end --- Matches the given `pattern` against the `subject` string. If the match succeeds, returns the --- index in the subject of the first character after the match, or the captured values (if the @@ -81,8 +83,9 @@ function vim.lpeg.match(pattern, subject, init) end --- --- @param subject string --- @param init? integer ---- @return integer|vim.lpeg.Capture|nil -function Pattern:match(subject, init) end +--- @param ... any +--- @return any ... +function Pattern:match(subject, init, ...) end --- Returns the string `"pattern"` if the given value is a pattern, otherwise `nil`. --- @@ -123,7 +126,7 @@ function vim.lpeg.P(value) end --- 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. --- ---- @param pattern vim.lpeg.Pattern +--- @param pattern vim.lpeg.Pattern|string|integer|boolean|table --- @return vim.lpeg.Pattern function vim.lpeg.B(pattern) end @@ -163,7 +166,7 @@ function vim.lpeg.S(string) end --- assert(b:match('(') == nil) --- ``` --- ---- @param v string|integer +--- @param v boolean|string|number|function|table|thread|userdata|lightuserdata --- @return vim.lpeg.Pattern function vim.lpeg.V(v) end @@ -227,7 +230,7 @@ function vim.lpeg.locale(tab) end --- assert(c == 'c') --- ``` --- ---- @param patt vim.lpeg.Pattern +--- @param patt vim.lpeg.Pattern|string|integer|boolean|table|function --- @return vim.lpeg.Capture function vim.lpeg.C(patt) end @@ -258,7 +261,7 @@ function vim.lpeg.Cc(...) end --- `func(...func(func(C1, C2), C3)...,Cn)`, that is, it will fold (or accumulate, or reduce) the captures from --- `patt` using function `func`. This capture assumes that `patt` should produce at least one capture with at --- least one value (of any type), which becomes the initial value of an accumulator. (If you need a specific ---- initial value, you may prefix a constant captureto `patt`.) For each subsequent capture, LPeg calls `func` +--- initial value, you may prefix a constant capture to `patt`.) For each subsequent capture, LPeg calls `func` --- with this accumulator as the first argument and all values produced by the capture as extra arguments; --- the first result from this call becomes the new value for the accumulator. The final value of the accumulator --- becomes the captured value. @@ -273,7 +276,7 @@ function vim.lpeg.Cc(...) end --- assert(sum:match('10,30,43') == 83) --- ``` --- ---- @param patt vim.lpeg.Pattern +--- @param patt vim.lpeg.Pattern|string|integer|boolean|table|function --- @param func fun(acc, newvalue) --- @return vim.lpeg.Capture function vim.lpeg.Cf(patt, func) end @@ -282,7 +285,7 @@ function vim.lpeg.Cf(patt, func) end --- The group may be anonymous (if no name is given) or named with the given name (which --- can be any non-nil Lua value). --- ---- @param patt vim.lpeg.Pattern +--- @param patt vim.lpeg.Pattern|string|integer|boolean|table|function --- @param name? string --- @return vim.lpeg.Capture function vim.lpeg.Cg(patt, name) end @@ -320,7 +323,7 @@ function vim.lpeg.Cp() end --- assert(gsub('Hello, xxx!', 'xxx', 'World') == 'Hello, World!') --- ``` --- ---- @param patt vim.lpeg.Pattern +--- @param patt vim.lpeg.Pattern|string|integer|boolean|table|function --- @return vim.lpeg.Capture function vim.lpeg.Cs(patt) end @@ -329,7 +332,7 @@ function vim.lpeg.Cs(patt) end --- Moreover, for each named capture group created by `patt`, the first value of the group is put into --- the table with the group name as its key. The captured value is only the table. --- ---- @param patt vim.lpeg.Pattern|'' +--- @param patt vim.lpeg.Pattern|string|integer|boolean|table|function --- @return vim.lpeg.Capture function vim.lpeg.Ct(patt) end @@ -343,7 +346,7 @@ function vim.lpeg.Ct(patt) end --- (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 ---- @param fn function +--- @param patt vim.lpeg.Pattern|string|integer|boolean|table|function +--- @param fn fun(s: string, i: integer, ...: any): (position: boolean|integer, ...: any) --- @return vim.lpeg.Capture function vim.lpeg.Cmt(patt, fn) end diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua index 428b7c4d4f..ce3ff4f861 100644 --- a/runtime/lua/vim/_meta/options.lua +++ b/runtime/lua/vim/_meta/options.lua @@ -544,7 +544,7 @@ vim.wo.bri = vim.wo.breakindent --- applying 'breakindent', even if the resulting --- text should normally be narrower. This prevents --- text indented almost to the right window border ---- occupying lot of vertical space when broken. +--- occupying lots of vertical space when broken. --- (default: 20) --- shift:{n} After applying 'breakindent', the wrapped line's --- beginning will be shifted by the given number of @@ -558,9 +558,9 @@ vim.wo.bri = vim.wo.breakindent --- list:{n} Adds an additional indent for lines that match a --- numbered or bulleted list (using the --- 'formatlistpat' setting). ---- list:-1 Uses the length of a match with 'formatlistpat' ---- for indentation. --- (default: 0) +--- list:-1 Uses the width of a match with 'formatlistpat' for +--- indentation. --- column:{n} Indent at column {n}. Will overrule the other --- sub-options. Note: an additional indent may be --- added for the 'showbreak' setting. @@ -731,11 +731,12 @@ vim.go.cd = vim.go.cdpath --- The key used in Command-line Mode to open the command-line window. --- 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: +--- type. The preferred way is to use `key-notation` (e.g. <Up>, <C-F>) or +--- a letter preceded with a caret (e.g. `^F` is CTRL-F). Examples: --- --- ```vim ---- exe "set cedit=\\<C-Y>" ---- exe "set cedit=\\<Esc>" +--- set cedit=^Y +--- set cedit=<Esc> --- ``` --- `Nvi` also has this option, but it only uses the first character. --- See `cmdwin`. @@ -785,6 +786,20 @@ vim.bo.channel = vim.o.channel --- v:fname_in name of the input file --- v:fname_out name of the output file --- Note that v:fname_in and v:fname_out will never be the same. +--- +--- The advantage of using a function call without arguments is that it is +--- faster, see `expr-option-function`. +--- +--- If the 'charconvert' expression starts with s: or `<SID>`, then it is +--- replaced with the script ID (`local-function`). Example: +--- +--- ```vim +--- set charconvert=s:MyConvert() +--- set charconvert=<SID>SomeConvert() +--- ``` +--- Otherwise the expression is evaluated in the context of the script +--- where the option was set, thus script-local items are available. +--- --- This option cannot be set from a `modeline` or in the `sandbox`, for --- security reasons. --- @@ -899,11 +914,10 @@ vim.go.cb = vim.go.clipboard --- used. The command-line will cover the last line of the screen when --- shown. --- ---- WARNING: `cmdheight=0` is considered experimental. Expect some ---- unwanted behaviour. Some 'shortmess' flags and similar ---- mechanism might fail to take effect, causing unwanted hit-enter ---- prompts. Some informative messages, both from Nvim itself and ---- plugins, will not be displayed. +--- WARNING: `cmdheight=0` is EXPERIMENTAL. Expect some unwanted behaviour. +--- Some 'shortmess' flags and similar mechanism might fail to take effect, +--- causing unwanted hit-enter prompts. Some informative messages, both +--- from Nvim itself and plugins, will not be displayed. --- --- @type integer vim.o.cmdheight = 1 @@ -974,8 +988,8 @@ vim.bo.comments = vim.o.comments vim.bo.com = vim.bo.comments --- A template for a comment. The "%s" in the value is replaced with the ---- comment text. For example, C uses "/*%s*/". Used for `commenting` and to ---- add markers for folding, see `fold-marker`. +--- comment text, and should be padded with a space when possible. +--- Used for `commenting` and to add markers for folding, see `fold-marker`. --- --- @type string vim.o.commentstring = "" @@ -1040,6 +1054,19 @@ vim.o.cfu = vim.o.completefunc vim.bo.completefunc = vim.o.completefunc vim.bo.cfu = vim.bo.completefunc +--- A comma-separated list of `complete-items` that controls the alignment +--- and display order of items in the popup menu during Insert mode +--- completion. The supported values are abbr, kind, and menu. These +--- options allow to customize how the completion items are shown in the +--- popup menu. Note: must always contain those three values in any +--- order. +--- +--- @type string +vim.o.completeitemalign = "abbr,kind,menu" +vim.o.cia = vim.o.completeitemalign +vim.go.completeitemalign = vim.o.completeitemalign +vim.go.cia = vim.go.completeitemalign + --- A comma-separated list of options for Insert mode completion --- `ins-completion`. The supported values are: --- @@ -1061,6 +1088,10 @@ vim.bo.cfu = vim.bo.completefunc --- completion in the preview window. 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". +--- --- 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. @@ -1069,13 +1100,19 @@ vim.bo.cfu = vim.bo.completefunc --- 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". +--- fuzzy Enable `fuzzy-matching` for completion candidates. This +--- allows for more flexible and intuitive matching, where +--- characters can be skipped and matches can be found even +--- if the exact sequence is not typed. Only makes a +--- difference how completion candidates are reduced from the +--- list of alternatives, but not how the candidates are +--- collected (using different completion types). --- --- @type string vim.o.completeopt = "menu,preview" vim.o.cot = vim.o.completeopt +vim.bo.completeopt = vim.o.completeopt +vim.bo.cot = vim.bo.completeopt vim.go.completeopt = vim.o.completeopt vim.go.cot = vim.go.completeopt @@ -1805,9 +1842,12 @@ vim.go.ead = vim.go.eadirection --- When on all Unicode emoji characters are considered to be full width. --- This excludes "text emoji" characters, which are normally displayed as ---- single width. Unfortunately there is no good specification for this ---- and it has been determined on trial-and-error basis. Use the ---- `setcellwidths()` function to change the behavior. +--- single width. However, such "text emoji" are treated as full-width +--- emoji if they are followed by the U+FE0F variant selector. +--- +--- Unfortunately there is no good specification for this and it has been +--- determined on trial-and-error basis. Use the `setcellwidths()` +--- function to change the behavior. --- --- @type boolean vim.o.emoji = true @@ -2177,7 +2217,7 @@ vim.go.fic = vim.go.fileignorecase --- ``` --- `FileType` `filetypes` --- When a dot appears in the value then this separates two filetype ---- names. Example: >c +--- names, it should therefore not be used for a filetype. Example: >c --- /* vim: set filetype=c.doxygen : */ --- ``` --- This will use the "c" filetype first, then the "doxygen" filetype. @@ -2185,7 +2225,7 @@ vim.go.fic = vim.go.fileignorecase --- one dot may appear. --- This option is not copied to another buffer, independent of the 's' or --- 'S' flag in 'cpoptions'. ---- Only normal file name characters can be used, `/\*?[|<>` are illegal. +--- Only alphanumeric characters, '-' and '_' can be used. --- --- @type string vim.o.filetype = "" @@ -2500,6 +2540,9 @@ vim.wo.fdt = vim.wo.foldtext --- This will invoke the mylang#Format() function in the --- autoload/mylang.vim file in 'runtimepath'. `autoload` --- +--- The advantage of using a function call without arguments is that it is +--- faster, see `expr-option-function`. +--- --- The expression is also evaluated when 'textwidth' is set and adding --- text beyond that limit. This happens under the same conditions as --- when internal formatting is used. Make sure the cursor is kept in the @@ -3265,12 +3308,15 @@ vim.go.inc = vim.go.include --- the script ID (`local-function`). Example: --- --- ```vim ---- setlocal includeexpr=s:MyIncludeExpr(v:fname) ---- setlocal includeexpr=<SID>SomeIncludeExpr(v:fname) +--- setlocal includeexpr=s:MyIncludeExpr() +--- setlocal includeexpr=<SID>SomeIncludeExpr() --- ``` --- Otherwise, the expression is evaluated in the context of the script --- where the option was set, thus script-local items are available. --- +--- It is more efficient if the value is just a function call without +--- arguments, see `expr-option-function`. +--- --- The expression will be evaluated in the `sandbox` when set from a --- modeline, see `sandbox-option`. --- This option cannot be set in a modeline when 'modelineexpr' is off. @@ -3330,7 +3376,7 @@ vim.go.is = vim.go.incsearch --- in Insert mode as specified with the 'indentkeys' option. --- When this option is not empty, it overrules the 'cindent' and --- 'smartindent' indenting. When 'lisp' is set, this option is ---- is only used when 'lispoptions' contains "expr:1". +--- only used when 'lispoptions' contains "expr:1". --- The expression is evaluated with `v:lnum` set to the line number for --- which the indent is to be computed. The cursor is also in this line --- when the expression is evaluated (but it may be moved around). @@ -3345,6 +3391,9 @@ vim.go.is = vim.go.incsearch --- Otherwise, the expression is evaluated in the context of the script --- where the option was set, thus script-local items are available. --- +--- The advantage of using a function call without arguments is that it is +--- faster, see `expr-option-function`. +--- --- The expression must return the number of spaces worth of indent. It --- can return "-1" to keep the current indent (this means 'autoindent' is --- used for the indent). @@ -3541,8 +3590,11 @@ vim.go.js = vim.go.joinspaces --- |alternate-file` or using `mark-motions` try to --- restore the `mark-view` in which the action occurred. --- +--- clean Remove unloaded buffers from the jumplist. +--- EXPERIMENTAL: this flag may change in the future. +--- --- @type string -vim.o.jumpoptions = "" +vim.o.jumpoptions = "clean" vim.o.jop = vim.o.jumpoptions vim.go.jumpoptions = vim.o.jumpoptions vim.go.jop = vim.go.jumpoptions @@ -3551,7 +3603,7 @@ vim.go.jop = vim.go.jumpoptions --- Setting this option to a valid keymap name has the side effect of --- setting 'iminsert' to one, so that the keymap becomes effective. --- 'imsearch' is also set to one, unless it was -1 ---- Only normal file name characters can be used, `/\*?[|<>` are illegal. +--- Only alphanumeric characters, '.', '-' and '_' can be used. --- --- @type string vim.o.keymap = "" @@ -3628,7 +3680,7 @@ vim.go.kp = vim.go.keywordprg --- part can be in one of two forms: --- 1. A list of pairs. Each pair is a "from" character immediately --- followed by the "to" character. Examples: "aA", "aAbBcC". ---- 2. A list of "from" characters, a semi-colon and a list of "to" +--- 2. A list of "from" characters, a semicolon and a list of "to" --- characters. Example: "abc;ABC" --- Example: "aA,fgh;FGH,cCdDeE" --- Special characters need to be preceded with a backslash. These are @@ -3720,7 +3772,7 @@ vim.go.ls = vim.go.laststatus --- update use `:redraw`. --- This may occasionally cause display errors. It is only meant to be set --- temporarily when performing an operation where redrawing may cause ---- flickering or cause a slow down. +--- flickering or cause a slowdown. --- --- @type boolean vim.o.lazyredraw = false @@ -3820,6 +3872,9 @@ vim.go.lw = vim.go.lispwords --- between tabs and spaces and for trailing blanks. Further changed by --- the 'listchars' option. --- +--- When 'listchars' does not contain "tab" field, tabs are shown as "^I" +--- or "<09>", like how unprintable characters are displayed. +--- --- 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: @@ -4533,6 +4588,20 @@ vim.go.mouset = vim.go.mousetime --- (without "unsigned" it would become "9-2019"). --- Using CTRL-X on "0" or CTRL-A on "18446744073709551615" --- (2^64 - 1) has no effect, overflow is prevented. +--- blank If included, treat numbers as signed or unsigned based on +--- preceding whitespace. If a number with a leading dash has its +--- dash immediately preceded by a non-whitespace character (i.e., +--- not a tab or a " "), the negative sign won't be considered as +--- part of the number. For example: +--- Using CTRL-A on "14" in "Carbon-14" results in "Carbon-15" +--- (without "blank" it would become "Carbon-13"). +--- Using CTRL-X on "8" in "Carbon -8" results in "Carbon -9" +--- (because -8 is preceded by whitespace. If "unsigned" was +--- set, it would result in "Carbon -7"). +--- If this format is included, overflow is prevented as if +--- "unsigned" were set. If both this format and "unsigned" are +--- included, "unsigned" will take precedence. +--- --- Numbers which simply begin with a digit in the range 1-9 are always --- considered decimal. This also happens for numbers that are not --- recognized as octal or hex. @@ -4757,7 +4826,7 @@ vim.go.pm = vim.go.patchmode --- ``` --- 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: +--- names are separated with a semicolon: --- --- ```vim --- let &path = &path .. "," .. substitute($INCL, ';', ',', 'g') @@ -5200,6 +5269,9 @@ vim.wo.scr = vim.wo.scroll --- Minimum is 1, maximum is 100000. --- Only in `terminal` buffers. --- +--- Note: Lines that are not visible and kept in scrollback are not +--- reflown when the terminal buffer is resized horizontally. +--- --- @type integer vim.o.scrollback = -1 vim.o.scbk = vim.o.scrollback @@ -5764,7 +5836,7 @@ vim.bo.shiftwidth = vim.o.shiftwidth vim.bo.sw = vim.bo.shiftwidth --- This option helps to avoid all the `hit-enter` prompts caused by file ---- messages, for example with CTRL-G, and to avoid some other messages. +--- messages, for example with CTRL-G, and to avoid some other messages. --- It is a list of flags: --- flag meaning when present ~ --- l use "999L, 888B" instead of "999 lines, 888 bytes" *shm-l* @@ -5781,8 +5853,8 @@ vim.bo.sw = vim.bo.shiftwidth --- message; also for quickfix message (e.g., ":cn") --- s don't give "search hit BOTTOM, continuing at TOP" or *shm-s* --- "search hit TOP, continuing at BOTTOM" messages; when using ---- the search count do not show "W" after the count message (see ---- S below) +--- the search count do not show "W" before the count message +--- (see `shm-S` below) --- t truncate file message at the start if it is too long *shm-t* --- to fit on the command-line, "<" will appear in the left most --- column; ignored in Ex mode @@ -5804,7 +5876,11 @@ vim.bo.sw = vim.bo.shiftwidth --- `: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]" +--- "[1/5]". When the "S" flag is not present (e.g. search count +--- is shown), the "search hit BOTTOM, continuing at TOP" and +--- "search hit TOP, continuing at BOTTOM" messages are only +--- indicated by a "W" (Mnemonic: Wrapped) letter before the +--- search count statistics. --- --- This gives you the opportunity to avoid that a change between buffers --- requires you to hit <Enter>, but still gives as useful a message as @@ -6249,7 +6325,7 @@ vim.bo.spo = vim.bo.spelloptions --- minus two. --- --- timeout:{millisec} Limit the time searching for suggestions to ---- {millisec} milli seconds. Applies to the following +--- {millisec} milliseconds. Applies to the following --- methods. When omitted the limit is 5000. When --- negative there is no limit. --- @@ -6269,9 +6345,11 @@ vim.bo.spo = vim.bo.spelloptions --- The file is used for all languages. --- --- expr:{expr} Evaluate expression {expr}. Use a function to avoid ---- trouble with spaces. `v:val` holds the badly spelled ---- word. The expression must evaluate to a List of ---- Lists, each with a suggestion and a score. +--- trouble with spaces. Best is to call a function +--- without arguments, see `expr-option-function|. +--- |v:val` holds the badly spelled word. The expression +--- must evaluate to a List of Lists, each with a +--- suggestion and a score. --- Example: --- [['the', 33], ['that', 44]] ~ --- Set 'verbose' and use `z=` to see the scores that the @@ -6338,7 +6416,8 @@ vim.go.spr = vim.go.splitright --- non-blank of the line. When off the cursor is kept in the same column --- (if possible). This applies to the commands: --- - CTRL-D, CTRL-U, CTRL-B, CTRL-F, "G", "H", "M", "L", "gg" ---- - "d", "<<" and ">>" with a linewise operator +--- - "d", "<<", "==" and ">>" with a linewise operator +--- (`operator-resulting-pos`) --- - "%" with a count --- - buffer changing commands (CTRL-^, :bnext, :bNext, etc.) --- - Ex commands that only have a line number, e.g., ":25" or ":+". @@ -6351,7 +6430,6 @@ vim.o.sol = vim.o.startofline vim.go.startofline = vim.o.startofline vim.go.sol = vim.go.startofline ---- EXPERIMENTAL --- When non-empty, this option determines the content of the area to the --- side of a window, normally containing the fold, sign and number columns. --- The format of this option is like that of 'statusline'. @@ -6359,8 +6437,7 @@ vim.go.sol = vim.go.startofline --- Some of the items from the 'statusline' format are different for --- 'statuscolumn': --- ---- %l line number of currently drawn line ---- %r relative line number of currently drawn line +--- %l line number column for currently drawn line --- %s sign column for currently drawn line --- %C fold column for currently drawn line --- @@ -6389,11 +6466,8 @@ vim.go.sol = vim.go.startofline --- Examples: --- --- ```vim ---- " Relative number with bar separator and click handlers: ---- set statuscolumn=%@SignCb@%s%=%T%@NumCb@%r│%T ---- ---- " Right aligned relative cursor line number: ---- let &stc='%=%{v:relnum?v:relnum:v:lnum} ' +--- " Line number with bar separator and click handlers: +--- set statuscolumn=%@SignCb@%s%=%T%@NumCb@%l│%T --- --- " Line numbers in hexadecimal for non wrapped part of lines: --- let &stc='%=%{v:virtnum>0?"":printf("%x",v:lnum)} ' @@ -6799,7 +6873,7 @@ vim.bo.smc = vim.bo.synmaxcol --- Syntax autocommand event is triggered with the value as argument. --- This option is not copied to another buffer, independent of the 's' or --- 'S' flag in 'cpoptions'. ---- Only normal file name characters can be used, `/\*?[|<>` are illegal. +--- Only alphanumeric characters, '.', '-' and '_' can be used. --- --- @type string vim.o.syntax = "" @@ -6807,6 +6881,22 @@ vim.o.syn = vim.o.syntax vim.bo.syntax = vim.o.syntax vim.bo.syn = vim.bo.syntax +--- This option controls the behavior when closing tab pages (e.g., using +--- `:tabclose`). When empty Vim goes to the next (right) tab page. +--- +--- Possible values (comma-separated list): +--- left If included, go to the previous tab page instead of +--- the next one. +--- uselast If included, go to the previously used tab page if +--- possible. This option takes precedence over the +--- others. +--- +--- @type string +vim.o.tabclose = "" +vim.o.tcl = vim.o.tabclose +vim.go.tabclose = vim.o.tabclose +vim.go.tcl = vim.go.tabclose + --- When non-empty, this option determines the content of the tab pages --- line at the top of the Vim window. When empty Vim will use a default --- tab pages line. See `setting-tabline` for more info. @@ -7178,7 +7268,7 @@ vim.go.tm = vim.go.timeoutlen --- When on, the title of the window will be set to the value of --- 'titlestring' (if it is not empty), or to: ---- filename [+=-] (path) - NVIM +--- filename [+=-] (path) - Nvim --- Where: --- filename the name of the file being edited --- - indicates the file cannot be modified, 'ma' off @@ -7186,7 +7276,7 @@ vim.go.tm = vim.go.timeoutlen --- = indicates the file is read-only --- =+ indicates the file is read-only and modified --- (path) is the path of the file being edited ---- - NVIM the server name `v:servername` or "NVIM" +--- - Nvim the server name `v:servername` or "Nvim" --- --- @type boolean vim.o.title = false @@ -7607,9 +7697,14 @@ vim.go.ww = vim.go.whichwrap --- Some keys will not work, such as CTRL-C, <CR> and Enter. --- <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: +--- Although 'wc' is a number option, it can be specified as a number, a +--- single character, a `key-notation` (e.g. <Up>, <C-F>) or a letter +--- preceded with a caret (e.g. `^F` is CTRL-F): --- --- ```vim +--- :set wc=27 +--- :set wc=X +--- :set wc=^I --- set wc=<Tab> --- ``` --- diff --git a/runtime/lua/vim/_meta/regex.lua b/runtime/lua/vim/_meta/regex.lua index 595ad96319..9c9cd7d29b 100644 --- a/runtime/lua/vim/_meta/regex.lua +++ b/runtime/lua/vim/_meta/regex.lua @@ -5,9 +5,9 @@ --- @brief Vim regexes can be used directly from Lua. Currently they only allow --- matching within a single line. ---- Parse the Vim regex {re} and return a regex object. Regexes are "magic" ---- and case-sensitive by default, regardless of 'magic' and 'ignorecase'. ---- They can be controlled with flags, see |/magic| and |/ignorecase|. +--- Parses the Vim regex `re` and returns a regex object. Regexes are "magic" and case-sensitive by +--- default, regardless of 'magic' and 'ignorecase'. They can be controlled with flags, see |/magic| +--- and |/ignorecase|. --- @param re string --- @return vim.regex function vim.regex(re) end @@ -16,20 +16,22 @@ function vim.regex(re) end --- @class vim.regex local regex = {} -- luacheck: no unused ---- Match the string against the regex. If the string should match the regex ---- precisely, surround the regex with `^` and `$`. If there was a match, the ---- byte indices for the beginning and end of the match are returned. When ---- there is no match, `nil` is returned. Because any integer is "truthy", ---- `regex:match_str()` can be directly used as a condition in an if-statement. +--- Matches string `str` against this regex. To match the string precisely, surround the regex with +--- "^" and "$". Returns the byte indices for the start and end of the match, or `nil` if there is +--- no match. Because any integer is "truthy", `regex:match_str()` can be directly used as +--- a condition in an if-statement. --- @param str string +--- @return integer? # match start (byte index), or `nil` if no match +--- @return integer? # match end (byte index), or `nil` if no match function regex:match_str(str) end ---- Match line {line_idx} (zero-based) in buffer {bufnr}. If {start} and {end} ---- are supplied, match only this byte index range. Otherwise see ---- |regex:match_str()|. If {start} is used, then the returned byte indices ---- will be relative {start}. +--- Matches line at `line_idx` (zero-based) in buffer `bufnr`. Match is restricted to byte index +--- range `start` and `end_` if given, otherwise see |regex:match_str()|. Returned byte indices are +--- relative to `start` if given. --- @param bufnr integer --- @param line_idx integer --- @param start? integer --- @param end_? integer +--- @return integer? # match start (byte index) relative to `start`, or `nil` if no match +--- @return integer? # match end (byte index) relative to `start`, or `nil` if no match function regex:match_line(bufnr, line_idx, start, end_) end diff --git a/runtime/lua/vim/_meta/spell.lua b/runtime/lua/vim/_meta/spell.lua index c636db3b53..b4e3bf6ca4 100644 --- a/runtime/lua/vim/_meta/spell.lua +++ b/runtime/lua/vim/_meta/spell.lua @@ -20,7 +20,7 @@ --- ``` --- --- @param str string ---- @return {[1]: string, [2]: 'bad'|'rare'|'local'|'caps', [3]: integer}[] +--- @return [string, 'bad'|'rare'|'local'|'caps', integer][] --- List of tuples with three items: --- - The badly spelled word. --- - The type of the spelling error: diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua index f4daacfb7d..3f6deba092 100644 --- a/runtime/lua/vim/_meta/vimfn.lua +++ b/runtime/lua/vim/_meta/vimfn.lua @@ -15,7 +15,7 @@ error('Cannot require a meta file') --- echo abs(-4) --- < 4 --- ---- @param expr any +--- @param expr number --- @return number function vim.fn.abs(expr) end @@ -31,7 +31,7 @@ function vim.fn.abs(expr) end --- echo acos(-0.5) --- < 2.094395 --- ---- @param expr any +--- @param expr number --- @return number function vim.fn.acos(expr) end @@ -47,7 +47,7 @@ function vim.fn.acos(expr) end --- --- @param object any --- @param expr any ---- @return any +--- @return any # Resulting |List| or |Blob|, or 1 if {object} is not a |List| or a |Blob|. function vim.fn.add(object, expr) end --- Bitwise AND on the two arguments. The arguments are converted @@ -57,8 +57,8 @@ function vim.fn.add(object, expr) end --- let flag = and(bits, 0x80) --- < --- ---- @param expr any ---- @param expr1 any +--- @param expr number +--- @param expr1 number --- @return integer vim.fn['and'] = function(expr, expr1) end @@ -86,7 +86,7 @@ function vim.fn.api_info() end --- < --- --- @param lnum integer ---- @param text any +--- @param text string|string[] --- @return 0|1 function vim.fn.append(lnum, text) end @@ -110,7 +110,7 @@ function vim.fn.append(lnum, text) end --- <However, when {text} is an empty list then no error is given --- for an invalid {lnum}, since {lnum} isn't actually used. --- ---- @param buf any +--- @param buf integer|string --- @param lnum integer --- @param text string --- @return 0|1 @@ -195,7 +195,7 @@ function vim.fn.asin(expr) end --- Also see |assert_fails()|, |assert_nobeep()| and --- |assert-return|. --- ---- @param cmd any +--- @param cmd string --- @return 0|1 function vim.fn.assert_beeps(cmd) end @@ -203,16 +203,17 @@ function vim.fn.assert_beeps(cmd) end --- added to |v:errors| and 1 is returned. Otherwise zero is --- returned. |assert-return| --- The error is in the form "Expected {expected} but got ---- {actual}". When {msg} is present it is prefixed to that. +--- {actual}". When {msg} is present it is prefixed to that, +--- along with the location of the assert when run from a script. --- --- There is no automatic conversion, the String "4" is different --- from the Number 4. And the number 4 is different from the --- Float 4.0. The value of 'ignorecase' is not used here, case --- always matters. --- Example: >vim ---- assert_equal('foo', 'bar') ---- <Will result in a string to be added to |v:errors|: ---- test.vim line 12: Expected 'foo' but got 'bar' ~ +--- call assert_equal('foo', 'bar', 'baz') +--- <Will add the following to |v:errors|: +--- test.vim line 12: baz: Expected 'foo' but got 'bar' ~ --- --- @param expected any --- @param actual any @@ -226,8 +227,10 @@ function vim.fn.assert_equal(expected, actual, msg) end --- When {fname-one} or {fname-two} does not exist the error will --- mention that. --- +--- @param fname-one string +--- @param fname-two string --- @return 0|1 -function vim.fn.assert_equalfile() end +function vim.fn.assert_equalfile(fname-one, fname-two) end --- When v:exception does not contain the string {error} an error --- message is added to |v:errors|. Also see |assert-return|. @@ -254,25 +257,25 @@ function vim.fn.assert_exception(error, msg) end --- When {error} is a string it must be found literally in the --- first reported error. Most often this will be the error code, --- including the colon, e.g. "E123:". >vim ---- assert_fails('bad cmd', 'E987:') +--- call assert_fails('bad cmd', 'E987:') --- < --- When {error} is a |List| with one or two strings, these are --- used as patterns. The first pattern is matched against the --- first reported error: >vim ---- assert_fails('cmd', ['E987:.*expected bool']) +--- call assert_fails('cmd', ['E987:.*expected bool']) --- <The second pattern, if present, is matched against the last --- reported error. To only match the last error use an empty --- string for the first error: >vim ---- assert_fails('cmd', ['', 'E987:']) +--- call assert_fails('cmd', ['', 'E987:']) --- < --- If {msg} is empty then it is not used. Do this to get the --- default message when passing the {lnum} argument. ---- +--- *E1115* --- When {lnum} is present and not negative, and the {error} --- argument is present and matches, then this is compared with --- the line number at which the error was reported. That can be --- the line number in a function or in a script. ---- +--- *E1116* --- When {context} is present it is used as a pattern and matched --- against the context (script name or function name) where --- {lnum} is located in. @@ -280,7 +283,7 @@ function vim.fn.assert_exception(error, msg) end --- Note that beeping is not considered an error, and some failing --- commands only beep. Use |assert_beeps()| for those. --- ---- @param cmd any +--- @param cmd string --- @param error? any --- @param msg? any --- @param lnum? integer @@ -291,7 +294,8 @@ function vim.fn.assert_fails(cmd, error, msg, lnum, context) end --- When {actual} is not false an error message is added to --- |v:errors|, like with |assert_equal()|. --- The error is in the form "Expected False but got {actual}". ---- When {msg} is present it is prepended to that. +--- When {msg} is present it is prefixed to that, along with the +--- location of the assert when run from a script. --- Also see |assert-return|. --- --- A value is false when it is zero. When {actual} is not a @@ -309,17 +313,18 @@ function vim.fn.assert_false(actual, msg) end --- but got {actual}". When {msg} is present it is prefixed to --- that. --- ---- @param lower any ---- @param upper any ---- @param actual any ---- @param msg? any +--- @param lower number +--- @param upper number +--- @param actual number +--- @param msg? string --- @return 0|1 function vim.fn.assert_inrange(lower, upper, actual, msg) end --- When {pattern} does not match {actual} an error message is --- added to |v:errors|. Also see |assert-return|. --- The error is in the form "Pattern {pattern} does not match ---- {actual}". When {msg} is present it is prefixed to that. +--- {actual}". When {msg} is present it is prefixed to that, +--- along with the location of the assert when run from a script. --- --- {pattern} is used as with |expr-=~|: The matching is always done --- like 'magic' was set and 'cpoptions' is empty, no matter what @@ -330,13 +335,13 @@ function vim.fn.assert_inrange(lower, upper, actual, msg) end --- Use both to match the whole text. --- --- Example: >vim ---- assert_match('^f.*o$', 'foobar') +--- call assert_match('^f.*o$', 'foobar') --- <Will result in a string to be added to |v:errors|: --- test.vim line 12: Pattern '^f.*o$' does not match 'foobar' ~ --- ---- @param pattern any ---- @param actual any ---- @param msg? any +--- @param pattern string +--- @param actual string +--- @param msg? string --- @return 0|1 function vim.fn.assert_match(pattern, actual, msg) end @@ -344,7 +349,7 @@ function vim.fn.assert_match(pattern, actual, msg) end --- produces a beep or visual bell. --- Also see |assert_beeps()|. --- ---- @param cmd any +--- @param cmd string --- @return 0|1 function vim.fn.assert_nobeep(cmd) end @@ -362,16 +367,16 @@ function vim.fn.assert_notequal(expected, actual, msg) end --- |v:errors| when {pattern} matches {actual}. --- Also see |assert-return|. --- ---- @param pattern any ---- @param actual any ---- @param msg? any +--- @param pattern string +--- @param actual string +--- @param msg? string --- @return 0|1 function vim.fn.assert_notmatch(pattern, actual, msg) end --- Report a test failure directly, using String {msg}. --- Always returns one. --- ---- @param msg any +--- @param msg string --- @return 0|1 function vim.fn.assert_report(msg) end @@ -380,10 +385,11 @@ function vim.fn.assert_report(msg) end --- Also see |assert-return|. --- A value is |TRUE| when it is a non-zero number or |v:true|. --- When {actual} is not a number or |v:true| the assert fails. ---- When {msg} is given it precedes the default message. +--- When {msg} is given it is prefixed to the default message, +--- along with the location of the assert when run from a script. --- --- @param actual any ---- @param msg? any +--- @param msg? string --- @return 0|1 function vim.fn.assert_true(actual, msg) end @@ -397,7 +403,7 @@ function vim.fn.assert_true(actual, msg) end --- echo atan(-4.01) --- < -1.326405 --- ---- @param expr any +--- @param expr number --- @return number function vim.fn.atan(expr) end @@ -412,8 +418,8 @@ function vim.fn.atan(expr) end --- echo atan2(1, -1) --- < 2.356194 --- ---- @param expr1 any ---- @param expr2 any +--- @param expr1 number +--- @param expr2 number --- @return number function vim.fn.atan2(expr1, expr2) end @@ -439,9 +445,9 @@ function vim.fn.blob2list(blob) end --- something went wrong, or browsing is not possible. --- --- @param save any ---- @param title any ---- @param initdir any ---- @param default any +--- @param title string +--- @param initdir string +--- @param default string --- @return 0|1 function vim.fn.browse(save, title, initdir, default) end @@ -456,8 +462,8 @@ function vim.fn.browse(save, title, initdir, default) end --- When the "Cancel" button is hit, something went wrong, or --- browsing is not possible, an empty string is returned. --- ---- @param title any ---- @param initdir any +--- @param title string +--- @param initdir string --- @return 0|1 function vim.fn.browsedir(title, initdir) end @@ -729,7 +735,7 @@ function vim.fn.call(func, arglist, dict) end --- --- Returns 0.0 if {expr} is not a |Float| or a |Number|. --- ---- @param expr any +--- @param expr number --- @return number function vim.fn.ceil(expr) end @@ -742,8 +748,8 @@ function vim.fn.ceil(expr) end --- For a socket, there is only one stream, and {stream} should be --- omitted. --- ---- @param id any ---- @param stream? any +--- @param id integer +--- @param stream? string --- @return 0|1 function vim.fn.chanclose(id, stream) end @@ -775,8 +781,8 @@ function vim.fn.changenr() end --- was created with `"rpc":v:true` then the channel expects RPC --- messages, use |rpcnotify()| and |rpcrequest()| instead. --- ---- @param id any ---- @param data any +--- @param id number +--- @param data string|string[] --- @return 0|1 function vim.fn.chansend(id, data) end @@ -820,8 +826,9 @@ function vim.fn.charclass(string) end --- With the cursor on '세' in line 5 with text "여보세요": >vim --- echo charcol('.') " returns 3 --- echo col('.') " returns 7 +--- < --- ---- @param expr any +--- @param expr string|integer[] --- @param winid? integer --- @return integer function vim.fn.charcol(expr, winid) end @@ -861,8 +868,8 @@ function vim.fn.charcol(expr, winid) end --- --- @param string string --- @param idx integer ---- @param countcc? any ---- @param utf16? any +--- @param countcc? boolean +--- @param utf16? boolean --- @return integer function vim.fn.charidx(string, idx, countcc, utf16) end @@ -886,6 +893,7 @@ function vim.fn.charidx(string, idx, countcc, utf16) end --- " ... do some work --- call chdir(save_dir) --- endif +--- < --- --- @param dir string --- @return string @@ -907,37 +915,37 @@ function vim.fn.cindent(lnum) end --- If {win} is specified, use the window with this number or --- window ID instead of the current window. --- ---- @param win? any +--- @param win? integer function vim.fn.clearmatches(win) end --- The result is a Number, which is the byte index of the column ---- position given with {expr}. The accepted positions are: ---- . the cursor position ---- $ the end of the cursor line (the result is the ---- number of bytes in the cursor line plus one) ---- 'x position of mark x (if the mark is not set, 0 is ---- returned) ---- v In Visual mode: the start of the Visual area (the ---- cursor is the end). When not in Visual mode ---- returns the cursor position. Differs from |'<| in ---- that it's updated right away. +--- position given with {expr}. +--- For accepted positions see |getpos()|. +--- When {expr} is "$", it means the end of the cursor line, so +--- the result is the number of bytes in the cursor line plus one. --- Additionally {expr} can be [lnum, col]: a |List| with the line --- and column number. Most useful when the column is "$", to get --- the last column of a specific line. When "lnum" or "col" is --- out of range then col() returns zero. +--- --- With the optional {winid} argument the values are obtained for --- that window instead of the current window. +--- --- To get the line number use |line()|. To get both use --- |getpos()|. +--- --- For the screen column position use |virtcol()|. For the --- character position use |charcol()|. +--- --- Note that only marks in the current file can be used. +--- --- Examples: >vim --- echo col(".") " column of cursor --- echo col("$") " length of cursor line plus one --- echo col("'t") " column of mark t --- echo col("'" .. markname) " column of mark markname ---- <The first column is 1. Returns 0 if {expr} is invalid or when +--- < +--- The first column is 1. Returns 0 if {expr} is invalid or when --- the window with ID {winid} is not found. --- For an uppercase mark the column may actually be in another --- buffer. @@ -946,8 +954,9 @@ function vim.fn.clearmatches(win) end --- line. Also, when using a <Cmd> mapping the cursor isn't --- moved, this can be used to obtain the column in Insert mode: >vim --- imap <F2> <Cmd>echo col(".").."\n"<CR> +--- < --- ---- @param expr any +--- @param expr string|integer[] --- @param winid? integer --- @return integer function vim.fn.col(expr, winid) end @@ -981,8 +990,8 @@ function vim.fn.col(expr, winid) end --- <This isn't very useful, but it shows how it works. Note that --- an empty string is returned to avoid a zero being inserted. --- ---- @param startcol any ---- @param matches any +--- @param startcol integer +--- @param matches any[] function vim.fn.complete(startcol, matches) end --- Add {expr} to the list of matches. Only to be used by the @@ -1065,8 +1074,9 @@ function vim.fn.complete_check() end --- call complete_info(['mode']) --- " Get only 'mode' and 'pum_visible' --- call complete_info(['mode', 'pum_visible']) +--- < --- ---- @param what? any +--- @param what? any[] --- @return table function vim.fn.complete_info(what) end @@ -1121,10 +1131,10 @@ function vim.fn.complete_info(what) end --- don't fit, a vertical layout is used anyway. For some systems --- the horizontal layout is always used. --- ---- @param msg any ---- @param choices? any ---- @param default? any ---- @param type? any +--- @param msg string +--- @param choices? string +--- @param default? integer +--- @param type? string --- @return integer function vim.fn.confirm(msg, choices, default, type) end @@ -1150,7 +1160,7 @@ function vim.fn.copy(expr) end --- echo cos(-4.01) --- < -0.646043 --- ---- @param expr any +--- @param expr number --- @return number function vim.fn.cos(expr) end @@ -1164,7 +1174,7 @@ function vim.fn.cos(expr) end --- echo cosh(-0.5) --- < -1.127626 --- ---- @param expr any +--- @param expr number --- @return number function vim.fn.cosh(expr) end @@ -1180,10 +1190,10 @@ function vim.fn.cosh(expr) end --- occurrences of {expr} is returned. Zero is returned when --- {expr} is an empty string. --- ---- @param comp any +--- @param comp string|table|any[] --- @param expr any ---- @param ic? any ---- @param start? any +--- @param ic? boolean +--- @param start? integer --- @return integer function vim.fn.count(comp, expr, ic, start) end @@ -1191,7 +1201,7 @@ function vim.fn.count(comp, expr, ic, start) end --- from the top of the |context-stack| (see |context-dict|). --- If {index} is not given, it is assumed to be 0 (i.e.: top). --- ---- @param index? any +--- @param index? integer --- @return table function vim.fn.ctxget(index) end @@ -1207,7 +1217,7 @@ function vim.fn.ctxpop() end --- which |context-types| to include in the pushed context. --- Otherwise, all context types are included. --- ---- @param types? any +--- @param types? string[] --- @return any function vim.fn.ctxpush(types) end @@ -1216,8 +1226,8 @@ function vim.fn.ctxpush(types) end --- {context} is a Dictionary with context data (|context-dict|). --- If {index} is not given, it is assumed to be 0 (i.e.: top). --- ---- @param context any ---- @param index? any +--- @param context table +--- @param index? integer --- @return any function vim.fn.ctxset(context, index) end @@ -1228,7 +1238,7 @@ function vim.fn.ctxsize() end --- @param lnum integer --- @param col? integer ---- @param off? any +--- @param off? integer --- @return any function vim.fn.cursor(lnum, col, off) end @@ -1263,7 +1273,7 @@ function vim.fn.cursor(lnum, col, off) end --- position within a <Tab> or after the last character. --- Returns 0 when the position could be set, -1 otherwise. --- ---- @param list any +--- @param list integer[] --- @return any function vim.fn.cursor(list) end @@ -1275,7 +1285,7 @@ function vim.fn.cursor(list) end --- Returns |TRUE| if successfully interrupted the program. --- Otherwise returns |FALSE|. --- ---- @param pid any +--- @param pid integer --- @return any function vim.fn.debugbreak(pid) end @@ -1299,7 +1309,7 @@ function vim.fn.debugbreak(pid) end --- Also see |copy()|. --- --- @param expr any ---- @param noref? any +--- @param noref? boolean --- @return any function vim.fn.deepcopy(expr, noref) end @@ -1339,9 +1349,9 @@ function vim.fn.delete(fname, flags) end --- when using |line()| this refers to the current buffer. Use "$" --- to refer to the last line in buffer {buf}. --- ---- @param buf any ---- @param first any ---- @param last? any +--- @param buf integer|string +--- @param first integer|string +--- @param last? integer|string --- @return any function vim.fn.deletebufline(buf, first, last) end @@ -1384,9 +1394,9 @@ function vim.fn.deletebufline(buf, first, last) end --- This function can be used by plugins to implement options with --- validation and parsing logic. --- ---- @param dict any ---- @param pattern any ---- @param callback any +--- @param dict table +--- @param pattern string +--- @param callback function --- @return any function vim.fn.dictwatcheradd(dict, pattern, callback) end @@ -1395,8 +1405,8 @@ function vim.fn.dictwatcheradd(dict, pattern, callback) end --- order for the watcher to be successfully deleted. --- --- @param dict any ---- @param pattern any ---- @param callback any +--- @param pattern string +--- @param callback function --- @return any function vim.fn.dictwatcherdel(dict, pattern, callback) end @@ -1457,7 +1467,7 @@ function vim.fn.diff_hlID(lnum, col) end --- echo digraph_get('aa') " Returns 'あ' --- < --- ---- @param chars any +--- @param chars string --- @return any function vim.fn.digraph_get(chars) end @@ -1475,7 +1485,7 @@ function vim.fn.digraph_get(chars) end --- echo digraph_getlist(1) --- < --- ---- @param listall? any +--- @param listall? boolean --- @return any function vim.fn.digraph_getlist(listall) end @@ -1495,12 +1505,9 @@ function vim.fn.digraph_getlist(listall) end --- Example: >vim --- call digraph_set(' ', 'あ') --- < ---- Can be used as a |method|: >vim ---- GetString()->digraph_set('あ') ---- < --- ---- @param chars any ---- @param digraph any +--- @param chars string +--- @param digraph string --- @return any function vim.fn.digraph_set(chars, digraph) end @@ -1518,11 +1525,7 @@ function vim.fn.digraph_set(chars, digraph) end --- <Except that the function returns after the first error, --- following digraphs will not be added. --- ---- Can be used as a |method|: >vim ---- GetList()->digraph_setlist() ---- < ---- ---- @param digraphlist any +--- @param digraphlist table<integer,string[]> --- @return any function vim.fn.digraph_setlist(digraphlist) end @@ -1557,7 +1560,7 @@ function vim.fn.environ() end --- <Also see |shellescape()| and |fnameescape()|. --- --- @param string string ---- @param chars any +--- @param chars string --- @return any function vim.fn.escape(string, chars) end @@ -1582,25 +1585,32 @@ function vim.fn.eventhandler() end --- This function checks if an executable with the name {expr} --- exists. {expr} must be the name of the program without any --- arguments. +--- --- executable() uses the value of $PATH and/or the normal ---- searchpath for programs. *PATHEXT* +--- searchpath for programs. +--- *PATHEXT* --- On MS-Windows the ".exe", ".bat", etc. can optionally be --- included. Then the extensions in $PATHEXT are tried. Thus if --- "foo.exe" does not exist, "foo.exe.bat" can be found. If ---- $PATHEXT is not set then ".exe;.com;.bat;.cmd" is used. A dot +--- $PATHEXT is not set then ".com;.exe;.bat;.cmd" is used. A dot --- by itself can be used in $PATHEXT to try using the name --- without an extension. When 'shell' looks like a Unix shell, --- then the name is also tried without adding an extension. --- On MS-Windows it only checks if the file exists and is not a --- directory, not if it's really executable. ---- On Windows an executable in the same directory as Vim is ---- always found (it is added to $PATH at |startup|). +--- On MS-Windows an executable in the same directory as the Vim +--- executable is always found (it's added to $PATH at |startup|). +--- *NoDefaultCurrentDirectoryInExePath* +--- On MS-Windows an executable in Vim's current working directory +--- is also normally found, but this can be disabled by setting +--- the $NoDefaultCurrentDirectoryInExePath environment variable. +--- --- The result is a Number: --- 1 exists --- 0 does not exist --- |exepath()| can be used to get the full path of an executable. --- ---- @param expr any +--- @param expr string --- @return 0|1 function vim.fn.executable(expr) end @@ -1641,8 +1651,8 @@ function vim.fn.execute(command, silent) end --- Returns empty string otherwise. --- If {expr} starts with "./" the |current-directory| is used. --- ---- @param expr any ---- @return any +--- @param expr string +--- @return string function vim.fn.exepath(expr) end --- The result is a Number, which is |TRUE| if {expr} is @@ -1733,7 +1743,7 @@ function vim.fn.exepath(expr) end --- <This doesn't check for existence of the "bufcount" variable, --- but gets the value of "bufcount", and checks if that exists. --- ---- @param expr any +--- @param expr string --- @return 0|1 function vim.fn.exists(expr) end @@ -1747,7 +1757,7 @@ function vim.fn.exists(expr) end --- echo exp(-1) --- < 0.367879 --- ---- @param expr any +--- @param expr number --- @return any function vim.fn.exp(expr) end @@ -1916,9 +1926,9 @@ function vim.fn.expandcmd(string, options) end --- fails. --- Returns {expr1}. Returns 0 on error. --- ---- @param expr1 any ---- @param expr2 any ---- @param expr3? any +--- @param expr1 table +--- @param expr2 table +--- @param expr3? table --- @return any function vim.fn.extend(expr1, expr2, expr3) end @@ -1926,9 +1936,9 @@ function vim.fn.extend(expr1, expr2, expr3) end --- List or Dictionary is created and returned. {expr1} remains --- unchanged. --- ---- @param expr1 any ---- @param expr2 any ---- @param expr3? any +--- @param expr1 table +--- @param expr2 table +--- @param expr3? table --- @return any function vim.fn.extendnew(expr1, expr2, expr3) end @@ -1989,6 +1999,19 @@ function vim.fn.feedkeys(string, mode) end --- @return any function vim.fn.file_readable(file) end +--- Copy the file pointed to by the name {from} to {to}. The +--- result is a Number, which is |TRUE| if the file was copied +--- successfully, and |FALSE| when it failed. +--- If a file with name {to} already exists, it will fail. +--- Note that it does not handle directories (yet). +--- +--- This function is not available in the |sandbox|. +--- +--- @param from string +--- @param to string +--- @return 0|1 +function vim.fn.filecopy(from, to) end + --- The result is a Number, which is |TRUE| when a file with the --- name {file} exists, and can be read. If {file} doesn't exist, --- or is a directory, the result is |FALSE|. {file} is any @@ -2070,8 +2093,8 @@ function vim.fn.filewritable(file) end --- 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 +--- @param expr1 string|table +--- @param expr2 string|function --- @return any function vim.fn.filter(expr1, expr2) end @@ -2094,7 +2117,7 @@ function vim.fn.filter(expr1, expr2) end --- --- @param name string --- @param path? string ---- @param count? any +--- @param count? integer --- @return any function vim.fn.finddir(name, path, count) end @@ -2129,15 +2152,15 @@ function vim.fn.findfile(name, path, count) end --- echo flatten([1, [2, [3, 4]], 5], 1) --- < [1, 2, [3, 4], 5] --- ---- @param list any ---- @param maxdepth? any +--- @param list any[] +--- @param maxdepth? integer --- @return any[]|0 function vim.fn.flatten(list, maxdepth) end --- Like |flatten()| but first make a copy of {list}. --- ---- @param list any ---- @param maxdepth? any +--- @param list any[] +--- @param maxdepth? integer --- @return any[]|0 function vim.fn.flattennew(list, maxdepth) end @@ -2162,7 +2185,7 @@ function vim.fn.flattennew(list, maxdepth) end --- echo float2nr(1.0e-100) --- < 0 --- ---- @param expr any +--- @param expr number --- @return any function vim.fn.float2nr(expr) end @@ -2178,7 +2201,7 @@ function vim.fn.float2nr(expr) end --- echo floor(4.0) --- < 4.0 --- ---- @param expr any +--- @param expr number --- @return any function vim.fn.floor(expr) end @@ -2197,8 +2220,8 @@ function vim.fn.floor(expr) end --- echo fmod(-12.33, 1.22) --- < -0.13 --- ---- @param expr1 any ---- @param expr2 any +--- @param expr1 number +--- @param expr2 number --- @return any function vim.fn.fmod(expr1, expr2) end @@ -2343,8 +2366,8 @@ function vim.fn.foldtextresult(lnum) end --- 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 +--- @param expr1 string|table +--- @param expr2 string|function --- @return any function vim.fn.foreach(expr1, expr2) end @@ -2486,7 +2509,7 @@ vim.fn['function'] = function(name, arglist, dict) end --- it's safe to perform. This is when waiting for the user to --- type a character. --- ---- @param atexit? any +--- @param atexit? boolean --- @return any function vim.fn.garbagecollect(atexit) end @@ -2523,12 +2546,25 @@ function vim.fn.get(blob, idx, default) end --- @return any function vim.fn.get(dict, key, default) end ---- Get item {what} from Funcref {func}. Possible values for +--- Get item {what} from |Funcref| {func}. Possible values for --- {what} are: ---- "name" The function name ---- "func" The function ---- "dict" The dictionary ---- "args" The list with arguments +--- "name" The function name +--- "func" The function +--- "dict" The dictionary +--- "args" The list with arguments +--- "arity" A dictionary with information about the number of +--- arguments accepted by the function (minus the +--- {arglist}) with the following fields: +--- required the number of positional arguments +--- optional the number of optional arguments, +--- in addition to the required ones +--- varargs |TRUE| if the function accepts a +--- variable number of arguments |...| +--- +--- Note: There is no error, if the {arglist} of +--- the Funcref contains more arguments than the +--- Funcref expects, it's not validated. +--- --- Returns zero on error. --- --- @param func function @@ -2634,8 +2670,9 @@ function vim.fn.getbufinfo(dict) end --- --- Example: >vim --- let lines = getbufline(bufnr("myfile"), 1, "$") +--- < --- ---- @param buf any +--- @param buf integer|string --- @param lnum integer --- @param end_? integer --- @return any @@ -2669,7 +2706,7 @@ function vim.fn.getbufoneline(buf, lnum) end --- let bufmodified = getbufvar(1, "&mod") --- echo "todo myvar = " .. getbufvar("todo", "myvar") --- ---- @param buf any +--- @param buf integer|string --- @param varname string --- @param def? any --- @return any @@ -2766,8 +2803,9 @@ function vim.fn.getchangelist(buf) end --- endfunction --- < --- +--- @param expr? 0|1 --- @return integer -function vim.fn.getchar() end +function vim.fn.getchar(expr) end --- The result is a Number which is the state of the modifiers for --- the last obtained character with getchar() or in another way. @@ -2800,7 +2838,7 @@ function vim.fn.getcharmod() end --- getpos('.') returns [0, 5, 7, 0] --- < --- ---- @param expr any +--- @param expr string --- @return integer[] function vim.fn.getcharpos(expr) end @@ -2823,7 +2861,7 @@ function vim.fn.getcharpos(expr) end --- nnoremap <expr> , getcharsearch().forward ? ',' : ';' --- <Also see |setcharsearch()|. --- ---- @return table[] +--- @return table function vim.fn.getcharsearch() end --- Get a single character from the user or input stream as a @@ -2837,27 +2875,28 @@ function vim.fn.getcharsearch() end --- Otherwise this works like |getchar()|, except that a number --- result is converted to a string. --- +--- @param expr? 0|1 --- @return string -function vim.fn.getcharstr() end +function vim.fn.getcharstr(expr) end --- Return the type of the current command-line completion. --- Only works when the command line is being edited, thus --- requires use of |c_CTRL-\_e| or |c_CTRL-R_=|. --- See |:command-completion| for the return string. ---- Also see |getcmdtype()|, |setcmdpos()|, |getcmdline()| and ---- |setcmdline()|. +--- Also see |getcmdtype()|, |setcmdpos()|, |getcmdline()|, +--- |getcmdprompt()| and |setcmdline()|. --- Returns an empty string when completion is not defined. --- --- @return string function vim.fn.getcmdcompltype() end ---- Return the current command-line. Only works when the command ---- line is being edited, thus requires use of |c_CTRL-\_e| or ---- |c_CTRL-R_=|. +--- Return the current command-line input. Only works when the +--- command line is being edited, thus requires use of +--- |c_CTRL-\_e| or |c_CTRL-R_=|. --- Example: >vim --- cmap <F7> <C-\>eescape(getcmdline(), ' \')<CR> ---- <Also see |getcmdtype()|, |getcmdpos()|, |setcmdpos()| and ---- |setcmdline()|. +--- <Also see |getcmdtype()|, |getcmdpos()|, |setcmdpos()|, +--- |getcmdprompt()| and |setcmdline()|. --- Returns an empty string when entering a password or using --- |inputsecret()|. --- @@ -2869,12 +2908,22 @@ function vim.fn.getcmdline() end --- Only works when editing the command line, thus requires use of --- |c_CTRL-\_e| or |c_CTRL-R_=| or an expression mapping. --- Returns 0 otherwise. ---- Also see |getcmdtype()|, |setcmdpos()|, |getcmdline()| and ---- |setcmdline()|. +--- Also see |getcmdtype()|, |setcmdpos()|, |getcmdline()|, +--- |getcmdprompt()| and |setcmdline()|. --- --- @return integer function vim.fn.getcmdpos() end +--- Return the current command-line prompt when using functions +--- like |input()| or |confirm()|. +--- Only works when the command line is being edited, thus +--- requires use of |c_CTRL-\_e| or |c_CTRL-R_=|. +--- Also see |getcmdtype()|, |getcmdline()|, |getcmdpos()|, +--- |setcmdpos()| and |setcmdline()|. +--- +--- @return string +function vim.fn.getcmdprompt() end + --- Return the screen position of the cursor in the command line --- as a byte count. The first column is 1. --- Instead of |getcmdpos()|, it adds the prompt position. @@ -2927,6 +2976,7 @@ function vim.fn.getcmdwintype() end --- customlist,{func} custom completion, defined via {func} --- diff_buffer |:diffget| and |:diffput| completion --- dir directory names +--- dir_in_path directory names in |'cdpath'| --- environment environment variable names --- event autocommand events --- expression Vim expression @@ -2979,9 +3029,9 @@ function vim.fn.getcmdwintype() end --- If there are no matches, an empty list is returned. An --- invalid value for {type} produces an error. --- ---- @param pat any ---- @param type any ---- @param filtered? any +--- @param pat string +--- @param type string +--- @param filtered? boolean --- @return string[] function vim.fn.getcompletion(pat, type, filtered) end @@ -3227,7 +3277,7 @@ function vim.fn.getline(lnum, end_) end --- < --- --- @param nr integer ---- @param what? any +--- @param what? table --- @return any function vim.fn.getloclist(nr, what) end @@ -3249,8 +3299,8 @@ function vim.fn.getloclist(nr, what) end --- Refer to |getpos()| for getting information about a specific --- mark. --- ---- @param buf? any ---- @return any +--- @param buf? integer? +--- @return vim.fn.getmarklist.ret.item[] function vim.fn.getmarklist(buf) end --- Returns a |List| with all matches previously defined for the @@ -3284,7 +3334,7 @@ function vim.fn.getmarklist(buf) end --- unlet m --- < --- ---- @param win? any +--- @param win? integer --- @return any function vim.fn.getmatches(win) end @@ -3327,9 +3377,34 @@ function vim.fn.getmousepos() end --- @return integer function vim.fn.getpid() end ---- Get the position for String {expr}. For possible values of ---- {expr} see |line()|. For getting the cursor position see ---- |getcurpos()|. +--- Get the position for String {expr}. +--- The accepted values for {expr} are: +--- . The cursor position. +--- $ The last line in the current buffer. +--- 'x Position of mark x (if the mark is not set, 0 is +--- returned for all values). +--- w0 First line visible in current window (one if the +--- display isn't updated, e.g. in silent Ex mode). +--- w$ Last line visible in current window (this is one +--- less than "w0" if no lines are visible). +--- v When not in Visual mode, returns the cursor +--- position. In Visual mode, returns the other end +--- of the Visual area. A good way to think about +--- this is that in Visual mode "v" and "." complement +--- each other. While "." refers to the cursor +--- position, "v" refers to where |v_o| would move the +--- cursor. As a result, you can use "v" and "." +--- together to work on all of a selection in +--- characterwise Visual mode. If the cursor is at +--- the end of a characterwise Visual area, "v" refers +--- to the start of the same Visual area. And if the +--- cursor is at the start of a characterwise Visual +--- area, "v" refers to the end of the same Visual +--- area. "v" differs from |'<| and |'>| in that it's +--- updated right away. +--- Note that a mark in another file can be used. The line number +--- then applies to another buffer. +--- --- The result is a |List| with four numbers: --- [bufnum, lnum, col, off] --- "bufnum" is zero, unless a mark like '0 or 'A is used, then it @@ -3340,20 +3415,25 @@ function vim.fn.getpid() end --- it is the offset in screen columns from the start of the --- character. E.g., a position within a <Tab> or after the last --- character. ---- Note that for '< and '> Visual mode matters: when it is "V" ---- (visual line mode) the column of '< is zero and the column of ---- '> is a large number equal to |v:maxcol|. +--- +--- For getting the cursor position see |getcurpos()|. --- The column number in the returned List is the byte position --- within the line. To get the character position in the line, --- use |getcharpos()|. +--- +--- Note that for '< and '> Visual mode matters: when it is "V" +--- (visual line mode) the column of '< is zero and the column of +--- '> is a large number equal to |v:maxcol|. --- A very large column number equal to |v:maxcol| can be returned, --- in which case it means "after the end of the line". --- If {expr} is invalid, returns a list with all zeros. +--- --- This can be used to save and restore the position of a mark: >vim --- let save_a_mark = getpos("'a") --- " ... --- call setpos("'a", save_a_mark) ---- <Also see |getcharpos()|, |getcurpos()| and |setpos()|. +--- < +--- Also see |getcharpos()|, |getcurpos()| and |setpos()|. --- --- @param expr string --- @return integer[] @@ -3462,7 +3542,7 @@ function vim.fn.getpos(expr) end --- echo getqflist({'lines' : ["F1:10:L10"]}) --- < --- ---- @param what? any +--- @param what? table --- @return any function vim.fn.getqflist(what) end @@ -3536,14 +3616,14 @@ function vim.fn.getreginfo(regname) end --- 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 +--- type Specify the region's selection type. +--- See |getregtype()| for possible values, +--- except that the width can be omitted +--- and an empty string cannot be used. +--- (default: "v") --- --- exclusive If |TRUE|, use exclusive selection ---- for the end position +--- for the end position. --- (default: follow 'selection') --- --- You can get the last selection type by |visualmode()|. @@ -3569,8 +3649,8 @@ function vim.fn.getreginfo(regname) end --- difference if the buffer is displayed in a window with --- different 'virtualedit' or 'list' values. --- ---- Examples: > ---- :xnoremap <CR> +--- Examples: >vim +--- xnoremap <CR> --- \ <Cmd>echom getregion( --- \ getpos('v'), getpos('.'), #{ type: mode() })<CR> --- < @@ -3777,7 +3857,7 @@ function vim.fn.gettagstack(winnr) end --- xgettext does not understand escaping in single quoted --- strings. --- ---- @param text any +--- @param text string --- @return any function vim.fn.gettext(text) end @@ -3904,10 +3984,10 @@ function vim.fn.getwinvar(winnr, varname, def) end --- See |expand()| for expanding special Vim variables. See --- |system()| for getting the raw output of an external command. --- ---- @param expr any +--- @param expr string --- @param nosuf? boolean ---- @param list? any ---- @param alllinks? any +--- @param list? boolean +--- @param alllinks? boolean --- @return any function vim.fn.glob(expr, nosuf, list, alllinks) end @@ -3965,10 +4045,10 @@ function vim.fn.glob2regpat(string) end --- supported, thus using 'path' will not always work properly. --- --- @param path string ---- @param expr any +--- @param expr string --- @param nosuf? boolean ---- @param list? any ---- @param allinks? any +--- @param list? boolean +--- @param allinks? boolean --- @return any function vim.fn.globpath(path, expr, nosuf, list, allinks) end @@ -4039,7 +4119,7 @@ function vim.fn.globpath(path, expr, nosuf, list, allinks) end --- endif --- < --- ---- @param feature any +--- @param feature string --- @return 0|1 function vim.fn.has(feature) end @@ -4047,8 +4127,8 @@ function vim.fn.has(feature) end --- has an entry with key {key}. FALSE otherwise. The {key} --- argument is a string. --- ---- @param dict any ---- @param key any +--- @param dict table +--- @param key string --- @return 0|1 function vim.fn.has_key(dict, key) end @@ -4105,7 +4185,7 @@ function vim.fn.haslocaldir(winnr, tabnr) end --- --- @param what any --- @param mode? string ---- @param abbr? any +--- @param abbr? boolean --- @return 0|1 function vim.fn.hasmapto(what, mode, abbr) end @@ -4143,7 +4223,7 @@ function vim.fn.highlight_exists(name) end --- let date=input("Enter date: ") --- <This function is not available in the |sandbox|. --- ---- @param history any +--- @param history string --- @param item any --- @return 0|1 function vim.fn.histadd(history, item) end @@ -4180,7 +4260,7 @@ function vim.fn.histadd(history, item) end --- let \@/ = histget("search", -1) --- < --- ---- @param history any +--- @param history string --- @param item? any --- @return 0|1 function vim.fn.histdel(history, item) end @@ -4200,8 +4280,8 @@ function vim.fn.histdel(history, item) end --- command -nargs=1 H execute histget("cmd", 0+<args>) --- < --- ---- @param history any ---- @param index? any +--- @param history string +--- @param index? integer|string --- @return string function vim.fn.histget(history, index) end @@ -4211,8 +4291,9 @@ function vim.fn.histget(history, index) end --- --- Example: >vim --- let inp_index = histnr("expr") +--- < --- ---- @param history any +--- @param history string --- @return integer function vim.fn.histnr(history) end @@ -4258,8 +4339,8 @@ function vim.fn.hostname() end --- cannot use UCS-2 in a string anyway, because of the NUL bytes. --- --- @param string string ---- @param from any ---- @param to any +--- @param from string +--- @param to string --- @return any function vim.fn.iconv(string, from, to) end @@ -4270,7 +4351,7 @@ function vim.fn.iconv(string, from, to) end --- Note that `v:_null_string`, `v:_null_list`, `v:_null_dict` and --- `v:_null_blob` have the same `id()` with different types --- because they are internally represented as NULL pointers. ---- `id()` returns a hexadecimal representanion of the pointers to +--- `id()` returns a hexadecimal representation of the pointers to --- the containers (i.e. like `0x994a40`), same as `printf("%p", --- {expr})`, but it is advised against counting on the exact --- format of the return value. @@ -4318,11 +4399,12 @@ function vim.fn.indent(lnum) end --- if index(numbers, 123) >= 0 --- " ... --- endif +--- < --- --- @param object any --- @param expr any ---- @param start? any ---- @param ic? any +--- @param start? integer +--- @param ic? boolean --- @return any function vim.fn.index(object, expr, start, ic) end @@ -4362,6 +4444,7 @@ function vim.fn.index(object, expr, start, ic) end --- echo indexof(l, "v:val.n == 20") --- echo indexof(l, {i, v -> v.n == 30}) --- echo indexof(l, "v:val.n == 20", #{startidx: 1}) +--- < --- --- @param object any --- @param expr any @@ -4370,9 +4453,9 @@ function vim.fn.index(object, expr, start, ic) end function vim.fn.indexof(object, expr, opts) end --- ---- @param prompt any ---- @param text? any ---- @param completion? any +--- @param prompt string +--- @param text? string +--- @param completion? string --- @return any function vim.fn.input(prompt, text, completion) end @@ -4484,6 +4567,7 @@ function vim.fn.input(prompt, text, completion) end --- let g:Foo = input("enter search pattern: ") --- call inputrestore() --- endfunction +--- < --- --- @param opts table --- @return any @@ -4512,7 +4596,7 @@ function vim.fn.inputdialog(...) end --- let color = inputlist(['Select color:', '1. red', --- \ '2. green', '3. blue']) --- ---- @param textlist any +--- @param textlist string[] --- @return any function vim.fn.inputlist(textlist) end @@ -4544,8 +4628,8 @@ function vim.fn.inputsave() end --- typed on the command-line in response to the issued prompt. --- NOTE: Command-line completion is not supported. --- ---- @param prompt any ---- @param text? any +--- @param prompt string +--- @param text? string --- @return any function vim.fn.inputsecret(prompt, text) end @@ -4592,16 +4676,34 @@ function vim.fn.interrupt() end --- let bits = invert(bits) --- < --- ---- @param expr any +--- @param expr number --- @return any function vim.fn.invert(expr) end +--- The result is a Number, which is |TRUE| when {path} is an +--- absolute path. +--- On Unix, a path is considered absolute when it starts with '/'. +--- On MS-Windows, it is considered absolute when it starts with an +--- optional drive prefix and is followed by a '\' or '/'. UNC paths +--- are always absolute. +--- Example: >vim +--- echo isabsolutepath('/usr/share/') " 1 +--- echo isabsolutepath('./foobar') " 0 +--- echo isabsolutepath('C:\Windows') " 1 +--- echo isabsolutepath('foobar') " 0 +--- echo isabsolutepath('\\remote\file') " 1 +--- < +--- +--- @param path string +--- @return 0|1 +function vim.fn.isabsolutepath(path) end + --- The result is a Number, which is |TRUE| when a directory --- with the name {directory} exists. If {directory} doesn't --- exist, or isn't a directory, the result is |FALSE|. {directory} --- is any expression, which is used as a String. --- ---- @param directory any +--- @param directory string --- @return 0|1 function vim.fn.isdirectory(directory) end @@ -4612,7 +4714,7 @@ function vim.fn.isdirectory(directory) end --- echo isinf(-1.0 / 0.0) --- < -1 --- ---- @param expr any +--- @param expr number --- @return 1|0|-1 function vim.fn.isinf(expr) end @@ -4637,7 +4739,7 @@ function vim.fn.islocked(expr) end --- echo isnan(0.0 / 0.0) --- < 1 --- ---- @param expr any +--- @param expr number --- @return 0|1 function vim.fn.isnan(expr) end @@ -4649,6 +4751,10 @@ function vim.fn.isnan(expr) end --- for [key, value] in items(mydict) --- echo key .. ': ' .. value --- endfor +--- < +--- A List or a String argument is also supported. In these +--- cases, items() returns a List with the index and the value at +--- the index. --- --- @param dict any --- @return any @@ -4663,7 +4769,7 @@ function vim.fn.jobclose(...) end --- Return the PID (process id) of |job-id| {job}. --- ---- @param job any +--- @param job integer --- @return integer function vim.fn.jobpid(job) end @@ -4671,7 +4777,7 @@ function vim.fn.jobpid(job) end --- columns and {height} rows. --- Fails if the job was not started with `"pty":v:true`. --- ---- @param job any +--- @param job integer --- @param width integer --- @param height integer --- @return any @@ -4769,7 +4875,7 @@ function vim.fn.jobsend(...) end --- - -1 if {cmd}[0] is not executable. --- See also |job-control|, |channel|, |msgpack-rpc|. --- ---- @param cmd any +--- @param cmd string|string[] --- @param opts? table --- @return any function vim.fn.jobstart(cmd, opts) end @@ -4783,7 +4889,7 @@ function vim.fn.jobstart(cmd, opts) end --- Returns 1 for valid job id, 0 for invalid id, including jobs have --- exited or stopped. --- ---- @param id any +--- @param id integer --- @return any function vim.fn.jobstop(id) end @@ -4807,7 +4913,7 @@ function vim.fn.jobstop(id) end --- -2 if the job was interrupted (by |CTRL-C|) --- -3 if the job-id is invalid --- ---- @param jobs any +--- @param jobs integer[] --- @param timeout? integer --- @return any function vim.fn.jobwait(jobs, timeout) end @@ -4822,8 +4928,8 @@ function vim.fn.jobwait(jobs, timeout) end --- converted into a string like with |string()|. --- The opposite function is |split()|. --- ---- @param list any ---- @param sep? any +--- @param list any[] +--- @param sep? string --- @return any function vim.fn.join(list, sep) end @@ -4863,7 +4969,7 @@ function vim.fn.json_encode(expr) end --- Return a |List| with all the keys of {dict}. The |List| is in --- arbitrary order. Also see |items()| and |values()|. --- ---- @param dict any +--- @param dict table --- @return any function vim.fn.keys(dict) end @@ -4958,28 +5064,16 @@ function vim.fn.libcall(libname, funcname, argument) end --- @return any function vim.fn.libcallnr(libname, funcname, argument) end ---- The result is a Number, which is the line number of the file ---- position given with {expr}. The {expr} argument is a string. ---- The accepted positions are: ---- . the cursor position ---- $ the last line in the current buffer ---- 'x position of mark x (if the mark is not set, 0 is ---- returned) ---- w0 first line visible in current window (one if the ---- display isn't updated, e.g. in silent Ex mode) ---- w$ last line visible in current window (this is one ---- less than "w0" if no lines are visible) ---- v In Visual mode: the start of the Visual area (the ---- cursor is the end). When not in Visual mode ---- returns the cursor position. Differs from |'<| in ---- that it's updated right away. ---- Note that a mark in another file can be used. The line number ---- then applies to another buffer. +--- See |getpos()| for accepted positions. +--- --- To get the column number use |col()|. To get both use --- |getpos()|. +--- --- With the optional {winid} argument the values are obtained for --- that window instead of the current window. +--- --- Returns 0 for invalid values of {expr} and {winid}. +--- --- Examples: >vim --- echo line(".") " line number of the cursor --- echo line(".", winid) " idem, in window "winid" @@ -4989,7 +5083,7 @@ function vim.fn.libcallnr(libname, funcname, argument) end --- To jump to the last known position when opening a file see --- |last-position-jump|. --- ---- @param expr any +--- @param expr string|integer[] --- @param winid? integer --- @return integer function vim.fn.line(expr, winid) end @@ -5029,7 +5123,7 @@ function vim.fn.lispindent(lnum) end --- --- |blob2list()| does the opposite. --- ---- @param list any +--- @param list any[] --- @return any function vim.fn.list2blob(list) end @@ -5048,8 +5142,8 @@ function vim.fn.list2blob(list) end --- < --- Returns an empty string on error. --- ---- @param list any ---- @param utf8? any +--- @param list any[] +--- @param utf8? boolean --- @return any function vim.fn.list2str(list, utf8) end @@ -5069,7 +5163,7 @@ function vim.fn.localtime() end --- echo log(exp(5)) --- < 5.0 --- ---- @param expr any +--- @param expr number --- @return any function vim.fn.log(expr) end @@ -5082,7 +5176,7 @@ function vim.fn.log(expr) end --- echo log10(0.01) --- < -2.0 --- ---- @param expr any +--- @param expr number --- @return any function vim.fn.log10(expr) end @@ -5139,8 +5233,8 @@ function vim.fn.log10(expr) end --- 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 +--- @param expr1 string|table|any[] +--- @param expr2 string|function --- @return any function vim.fn.map(expr1, expr2) end @@ -5182,6 +5276,7 @@ function vim.fn.map(expr1, expr2) end --- "lhsrawalt" The {lhs} of the mapping as raw bytes, alternate --- form, only present when it differs from "lhsraw" --- "rhs" The {rhs} of the mapping as typed. +--- "callback" Lua function, if RHS was defined as such. --- "silent" 1 for a |:map-silent| mapping, else 0. --- "noremap" 1 if the {rhs} of the mapping is not remappable. --- "script" 1 if mapping was defined with <script>. @@ -5214,6 +5309,7 @@ function vim.fn.map(expr1, expr2) end --- This function can be used to map a key even when it's already --- mapped, and have it do the original mapping too. Sketch: >vim --- exe 'nnoremap <Tab> ==' .. maparg('<Tab>', 'n') +--- < --- --- @param name string --- @param mode? string @@ -5263,7 +5359,7 @@ function vim.fn.maparg(name, mode, abbr, dict) end --- --- @param name string --- @param mode? string ---- @param abbr? any +--- @param abbr? boolean --- @return any function vim.fn.mapcheck(name, mode, abbr) end @@ -5296,9 +5392,11 @@ function vim.fn.mapcheck(name, mode, abbr) end --- \ {_, m -> m.lhs == 'xyzzy'})[0].mode_bits --- ounmap xyzzy --- echo printf("Operator-pending mode bit: 0x%x", op_bit) +--- < --- ---- @return any -function vim.fn.maplist() end +--- @param abbr? 0|1 +--- @return table[] +function vim.fn.maplist(abbr) end --- Like |map()| but instead of replacing items in {expr1} a new --- List or Dictionary is created and returned. {expr1} remains @@ -5311,8 +5409,8 @@ function vim.fn.maplist() end function vim.fn.mapnew(expr1, expr2) end --- @param mode string ---- @param abbr? any ---- @param dict? any +--- @param abbr? boolean +--- @param dict? boolean --- @return any function vim.fn.mapset(mode, abbr, dict) end @@ -5350,8 +5448,9 @@ function vim.fn.mapset(mode, abbr, dict) end --- for d in save_maps --- call mapset(d) --- endfor +--- < --- ---- @param dict any +--- @param dict boolean --- @return any function vim.fn.mapset(dict) end @@ -5417,10 +5516,10 @@ function vim.fn.mapset(dict) end --- zero matches at the start instead of a number of matches --- further down in the text. --- ---- @param expr any ---- @param pat any ---- @param start? any ---- @param count? any +--- @param expr string|any[] +--- @param pat string +--- @param start? integer +--- @param count? integer --- @return any function vim.fn.match(expr, pat, start, count) end @@ -5481,20 +5580,20 @@ function vim.fn.match(expr, pat, start, count) end --- available from |getmatches()|. All matches can be deleted in --- one operation by |clearmatches()|. --- ---- @param group any ---- @param pattern any ---- @param priority? any ---- @param id? any ---- @param dict? any +--- @param group integer|string +--- @param pattern string +--- @param priority? integer +--- @param id? integer +--- @param dict? string --- @return any function vim.fn.matchadd(group, pattern, priority, id, dict) end --- Same as |matchadd()|, but requires a list of positions {pos} --- instead of a pattern. This command is faster than |matchadd()| ---- because it does not require to handle regular expressions and ---- sets buffer line boundaries to redraw screen. It is supposed ---- to be used when fast match additions and deletions are ---- required, for example to highlight matching parentheses. +--- because it does not handle regular expressions and it sets +--- buffer line boundaries to redraw screen. It is supposed to be +--- used when fast match additions and deletions are required, for +--- example to highlight matching parentheses. --- *E5030* *E5031* --- {pos} is a list of positions. Each position can be one of --- these: @@ -5525,11 +5624,11 @@ function vim.fn.matchadd(group, pattern, priority, id, dict) end --- <Matches added by |matchaddpos()| are returned by --- |getmatches()|. --- ---- @param group any ---- @param pos any ---- @param priority? any ---- @param id? any ---- @param dict? any +--- @param group integer|string +--- @param pos any[] +--- @param priority? integer +--- @param id? integer +--- @param dict? string --- @return any function vim.fn.matchaddpos(group, pos, priority, id, dict) end @@ -5575,19 +5674,19 @@ function vim.fn.matcharg(nr) end --- --- Examples: >vim --- " Assuming line 3 in buffer 5 contains "a" ---- :echo matchbufline(5, '\<\k\+\>', 3, 3) ---- [{'lnum': 3, 'byteidx': 0, 'text': 'a'}] +--- echo matchbufline(5, '\<\k\+\>', 3, 3) +--- < `[{'lnum': 3, 'byteidx': 0, 'text': 'a'}]` >vim --- " 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'}] ---- < +--- 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 +--- 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 +--- < `[{'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. --- @@ -5606,8 +5705,8 @@ function vim.fn.matchbufline(buf, pat, lnum, end_, dict) end --- If {win} is specified, use the window with this number or --- window ID instead of the current window. --- ---- @param id any ---- @param win? any +--- @param id integer +--- @param win? integer --- @return any function vim.fn.matchdelete(id, win) end @@ -5630,9 +5729,9 @@ function vim.fn.matchdelete(id, win) end --- When {expr} is a |List| the result is equal to |match()|. --- --- @param expr any ---- @param pat any ---- @param start? any ---- @param count? any +--- @param pat string +--- @param start? integer +--- @param count? integer --- @return any function vim.fn.matchend(expr, pat, start, count) end @@ -5698,9 +5797,9 @@ function vim.fn.matchend(expr, pat, start, count) end --- \ {'matchseq': 1}) --- <results in `['two one']`. --- ---- @param list any ---- @param str any ---- @param dict? any +--- @param list any[] +--- @param str string +--- @param dict? string --- @return any function vim.fn.matchfuzzy(list, str, dict) end @@ -5725,9 +5824,9 @@ function vim.fn.matchfuzzy(list, str, dict) end --- \ ->matchfuzzypos('ll', {'key' : 'text'}) --- <results in `[[{"id": 10, "text": "hello"}], [[2, 3]], [127]]` --- ---- @param list any ---- @param str any ---- @param dict? any +--- @param list any[] +--- @param str string +--- @param dict? string --- @return any function vim.fn.matchfuzzypos(list, str, dict) end @@ -5743,9 +5842,9 @@ function vim.fn.matchfuzzypos(list, str, dict) end --- You can pass in a List, but that is not very useful. --- --- @param expr any ---- @param pat any ---- @param start? any ---- @param count? any +--- @param pat string +--- @param start? integer +--- @param count? integer --- @return any function vim.fn.matchlist(expr, pat, start, count) end @@ -5762,9 +5861,9 @@ function vim.fn.matchlist(expr, pat, start, count) end --- The type isn't changed, it's not necessarily a String. --- --- @param expr any ---- @param pat any ---- @param start? any ---- @param count? any +--- @param pat string +--- @param start? integer +--- @param count? integer --- @return any function vim.fn.matchstr(expr, pat, start, count) end @@ -5786,17 +5885,17 @@ function vim.fn.matchstr(expr, pat, start, count) end --- 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'}] ---- < +--- echo matchstrlist(['tik tok'], '\<\k\+\>') +--- < `[{'idx': 0, 'byteidx': 0, 'text': 'tik'}, {'idx': 0, 'byteidx': 4, 'text': 'tok'}]` >vim +--- 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\)\?\(.*\)', +--- 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 +--- < `[{'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. --- @@ -5824,9 +5923,9 @@ function vim.fn.matchstrlist(list, pat, dict) end --- The type isn't changed, it's not necessarily a String. --- --- @param expr any ---- @param pat any ---- @param start? any ---- @param count? any +--- @param pat string +--- @param start? integer +--- @param count? integer --- @return any function vim.fn.matchstrpos(expr, pat, start, count) end @@ -5889,7 +5988,7 @@ function vim.fn.max(expr) end --- < --- --- @param path string ---- @param modes? any +--- @param modes? string --- @return any function vim.fn.menu_get(path, modes) end @@ -5986,17 +6085,14 @@ function vim.fn.min(expr) end --- When {flags} is present it must be a string. An empty string --- has no effect. --- ---- If {flags} contains "p" then intermediate directories are ---- created as necessary. +--- {flags} can contain these character flags: +--- "p" intermediate directories will be created as necessary +--- "D" {name} will be deleted at the end of the current +--- function, but not recursively |:defer| +--- "R" {name} will be deleted recursively at the end of the +--- current function |:defer| --- ---- If {flags} contains "D" then {name} is deleted at the end of ---- the current function, as with: >vim ---- defer delete({name}, 'd') ---- < ---- If {flags} contains "R" then {name} is deleted recursively at ---- the end of the current function, as with: >vim ---- defer delete({name}, 'rf') ---- <Note that when {name} has more than one part and "p" is used +--- Note that when {name} has more than one part and "p" is used --- some directories may already exist. Only the first one that --- is created and what it contains is scheduled to be deleted. --- E.g. when using: >vim @@ -6025,7 +6121,7 @@ function vim.fn.min(expr) end --- --- @param name string --- @param flags? string ---- @param prot? any +--- @param prot? string --- @return any function vim.fn.mkdir(name, flags, prot) end @@ -6159,12 +6255,7 @@ function vim.fn.msgpackdump(list, type) end --- C parser does not support such values. --- float |Float|. This value cannot possibly appear in --- |msgpackparse()| output. ---- string |readfile()|-style list of strings. This value will ---- appear in |msgpackparse()| output if string contains ---- zero byte or if string is a mapping key and mapping is ---- being represented as special dictionary for other ---- reasons. ---- binary |String|, or |Blob| if binary string contains zero +--- string |String|, or |Blob| if binary string contains zero --- byte. This value cannot appear in |msgpackparse()| --- output since blobs were introduced. --- array |List|. This value cannot appear in |msgpackparse()| @@ -6211,8 +6302,8 @@ function vim.fn.nextnonblank(lnum) end --- characters. nr2char(0) is a real NUL and terminates the --- string, thus results in an empty string. --- ---- @param expr any ---- @param utf8? any +--- @param expr integer +--- @param utf8? boolean --- @return any function vim.fn.nr2char(expr, utf8) end @@ -6227,8 +6318,8 @@ function vim.fn.nr2char(expr, utf8) end --- to separate commands. In many places it would not be clear if --- "|" is an operator or a command separator. --- ---- @param expr any ---- @param expr1 any +--- @param expr number +--- @param expr1 number --- @return any vim.fn['or'] = function(expr, expr1) end @@ -6246,7 +6337,7 @@ vim.fn['or'] = function(expr, expr1) end --- Returns an empty string on error. --- --- @param path string ---- @param len? any +--- @param len? integer --- @return any function vim.fn.pathshorten(path, len) end @@ -6279,8 +6370,8 @@ function vim.fn.perleval(expr) end --- echo pow(32, 0.20) --- < 2.0 --- ---- @param x any ---- @param y any +--- @param x number +--- @param y number --- @return any function vim.fn.pow(x, y) end @@ -6618,7 +6709,7 @@ function vim.fn.prevnonblank(lnum) end --- into this, copying the exact format string and parameters that --- were used. --- ---- @param fmt any +--- @param fmt string --- @param expr1? any --- @return string function vim.fn.printf(fmt, expr1) end @@ -6629,7 +6720,7 @@ function vim.fn.printf(fmt, expr1) end --- If the buffer doesn't exist or isn't a prompt buffer, an empty --- string is returned. --- ---- @param buf any +--- @param buf integer|string --- @return any function vim.fn.prompt_getprompt(buf) end @@ -6664,8 +6755,8 @@ function vim.fn.prompt_getprompt(buf) end --- endfunc --- call prompt_setcallback(bufnr(), function('s:TextEntered')) --- ---- @param buf any ---- @param expr any +--- @param buf integer|string +--- @param expr string|function --- @return any function vim.fn.prompt_setcallback(buf, expr) end @@ -6677,8 +6768,8 @@ function vim.fn.prompt_setcallback(buf, expr) end --- mode. Without setting a callback Vim will exit Insert mode, --- as in any buffer. --- ---- @param buf any ---- @param expr any +--- @param buf integer|string +--- @param expr string|function --- @return any function vim.fn.prompt_setinterrupt(buf, expr) end @@ -6689,8 +6780,8 @@ function vim.fn.prompt_setinterrupt(buf, expr) end --- call prompt_setprompt(bufnr(''), 'command: ') --- < --- ---- @param buf any ---- @param text any +--- @param buf integer|string +--- @param text string --- @return any function vim.fn.prompt_setprompt(buf, text) end @@ -6766,7 +6857,7 @@ function vim.fn.pyxeval(expr) end --- echo rand(seed) % 16 " random number 0 - 15 --- < --- ---- @param expr? any +--- @param expr? number --- @return any function vim.fn.rand(expr) end @@ -6789,8 +6880,8 @@ function vim.fn.rand(expr) end --- < --- --- @param expr any ---- @param max? any ---- @param stride? any +--- @param max? integer +--- @param stride? integer --- @return any function vim.fn.range(expr, max, stride) end @@ -6818,8 +6909,8 @@ function vim.fn.range(expr, max, stride) end --- Also see |readfile()| and |writefile()|. --- --- @param fname string ---- @param offset? any ---- @param size? any +--- @param offset? integer +--- @param size? integer --- @return any function vim.fn.readblob(fname, offset, size) end @@ -6852,8 +6943,8 @@ function vim.fn.readblob(fname, offset, size) end --- < --- Returns an empty List on error. --- ---- @param directory any ---- @param expr? any +--- @param directory string +--- @param expr? integer --- @return any function vim.fn.readdir(directory, expr) end @@ -6890,8 +6981,8 @@ function vim.fn.readdir(directory, expr) end --- Also see |writefile()|. --- --- @param fname string ---- @param type? any ---- @param max? any +--- @param type? string +--- @param max? integer --- @return any function vim.fn.readfile(fname, type, max) end @@ -6913,7 +7004,7 @@ function vim.fn.readfile(fname, type, max) end --- < --- --- @param object any ---- @param func any +--- @param func function --- @param initial? any --- @return any function vim.fn.reduce(object, func, initial) end @@ -7019,9 +7110,9 @@ function vim.fn.remove(list, idx) end --- < --- Use |delete()| to remove a file. --- ---- @param list any +--- @param list any[] --- @param idx integer ---- @param end_? any +--- @param end_? integer --- @return any function vim.fn.remove(list, idx, end_) end @@ -7044,7 +7135,7 @@ function vim.fn.remove(blob, idx) end --- --- @param blob any --- @param idx integer ---- @param end_? any +--- @param end_? integer --- @return any function vim.fn.remove(blob, idx, end_) end @@ -7055,7 +7146,7 @@ function vim.fn.remove(blob, idx, end_) end --- Returns zero on error. --- --- @param dict any ---- @param key any +--- @param key string --- @return any function vim.fn.remove(dict, key) end @@ -7066,8 +7157,8 @@ function vim.fn.remove(dict, key) end --- NOTE: If {to} exists it is overwritten without warning. --- This function is not available in the |sandbox|. --- ---- @param from any ---- @param to any +--- @param from string +--- @param to string --- @return any function vim.fn.rename(from, to) end @@ -7081,7 +7172,7 @@ function vim.fn.rename(from, to) end --- <Results in ['a', 'b', 'a', 'b', 'a', 'b']. --- --- @param expr any ---- @param count any +--- @param count integer --- @return any vim.fn['repeat'] = function(expr, count) end @@ -7097,7 +7188,7 @@ vim.fn['repeat'] = function(expr, count) end --- current directory (provided the result is still a relative --- path name) and also keeps a trailing path separator. --- ---- @param filename any +--- @param filename string --- @return any function vim.fn.resolve(filename) end @@ -7128,7 +7219,7 @@ function vim.fn.reverse(object) end --- echo round(-4.5) --- < -5.0 --- ---- @param expr any +--- @param expr number --- @return any function vim.fn.round(expr) end @@ -7138,8 +7229,8 @@ function vim.fn.round(expr) end --- au VimLeave call rpcnotify(0, "leaving") --- < --- ---- @param channel any ---- @param event any +--- @param channel integer +--- @param event string --- @param args? any --- @return any function vim.fn.rpcnotify(channel, event, args) end @@ -7150,19 +7241,20 @@ function vim.fn.rpcnotify(channel, event, args) end --- let result = rpcrequest(rpc_chan, "func", 1, 2, 3) --- < --- ---- @param channel any ---- @param method any +--- @param channel integer +--- @param method string --- @param args? any --- @return any function vim.fn.rpcrequest(channel, method, args) end +--- @deprecated --- Deprecated. Replace >vim --- let id = rpcstart('prog', ['arg1', 'arg2']) --- <with >vim --- let id = jobstart(['prog', 'arg1', 'arg2'], {'rpc': v:true}) --- < --- ---- @param prog any +--- @param prog string --- @param argv? any --- @return any function vim.fn.rpcstart(prog, argv) end @@ -7195,7 +7287,7 @@ function vim.fn.rubyeval(expr) end --- attribute at other positions. --- Returns -1 when row or col is out of range. --- ---- @param row any +--- @param row integer --- @param col integer --- @return any function vim.fn.screenattr(row, col) end @@ -7209,7 +7301,7 @@ function vim.fn.screenattr(row, col) end --- This is mainly to be used for testing. --- Returns -1 when row or col is out of range. --- ---- @param row any +--- @param row integer --- @param col integer --- @return any function vim.fn.screenchar(row, col) end @@ -7220,7 +7312,7 @@ function vim.fn.screenchar(row, col) end --- This is mainly to be used for testing. --- Returns an empty List when row or col is out of range. --- ---- @param row any +--- @param row integer --- @param col integer --- @return any function vim.fn.screenchars(row, col) end @@ -7236,7 +7328,7 @@ function vim.fn.screenchars(row, col) end --- the following mappings: >vim --- nnoremap <expr> GG ":echom " .. screencol() .. "\n" --- nnoremap <silent> GG :echom screencol()<CR> ---- noremap GG <Cmd>echom screencol()<Cr> +--- noremap GG <Cmd>echom screencol()<CR> --- < --- --- @return any @@ -7288,7 +7380,7 @@ function vim.fn.screenrow() end --- This is mainly to be used for testing. --- Returns an empty String when row or col is out of range. --- ---- @param row any +--- @param row integer --- @param col integer --- @return any function vim.fn.screenstring(row, col) end @@ -7347,6 +7439,9 @@ function vim.fn.screenstring(row, col) end --- The value must not be negative. A zero value is like not --- giving the argument. --- +--- Note: the timeout is only considered when searching, not +--- while evaluating the {skip} expression. +--- --- If the {skip} expression is given it is evaluated with the --- cursor positioned on the start of a match. If it evaluates to --- non-zero this match is skipped. This can be used, for @@ -7394,11 +7489,11 @@ function vim.fn.screenstring(row, col) end --- without the 'e' flag if the cursor is on the "f" of "if". --- The 'n' flag tells the function not to move the cursor. --- ---- @param pattern any +--- @param pattern string --- @param flags? string ---- @param stopline? any +--- @param stopline? integer --- @param timeout? integer ---- @param skip? any +--- @param skip? string|function --- @return any function vim.fn.search(pattern, flags, stopline, timeout, skip) end @@ -7545,8 +7640,8 @@ function vim.fn.searchcount(options) end --- < --- --- @param name string ---- @param global? any ---- @param thisblock? any +--- @param global? boolean +--- @param thisblock? boolean --- @return any function vim.fn.searchdecl(name, global, thisblock) end @@ -7634,8 +7729,15 @@ function vim.fn.searchdecl(name, global, thisblock) end --- \ 'synIDattr(synID(line("."), col("."), 0), "name") =~? "string"') --- < --- ---- @return any -function vim.fn.searchpair() end +--- @param start string +--- @param middle string +--- @param end_ string +--- @param flags? string +--- @param skip? string|function +--- @param stopline? integer +--- @param timeout? integer +--- @return integer +function vim.fn.searchpair(start, middle, end_, flags, skip, stopline, timeout) end --- Same as |searchpair()|, but returns a |List| with the line and --- column position of the match. The first element of the |List| @@ -7647,8 +7749,15 @@ function vim.fn.searchpair() end --- < --- See |match-parens| for a bigger and more useful example. --- ---- @return any -function vim.fn.searchpairpos() end +--- @param start string +--- @param middle string +--- @param end_ string +--- @param flags? string +--- @param skip? string|function +--- @param stopline? integer +--- @param timeout? integer +--- @return [integer, integer] +function vim.fn.searchpairpos(start, middle, end_, flags, skip, stopline, timeout) end --- Same as |search()|, but returns a |List| with the line and --- column position of the match. The first element of the |List| @@ -7664,11 +7773,11 @@ function vim.fn.searchpairpos() end --- <In this example "submatch" is 2 when a lowercase letter is --- found |/\l|, 3 when an uppercase letter is found |/\u|. --- ---- @param pattern any +--- @param pattern string --- @param flags? string ---- @param stopline? any +--- @param stopline? integer --- @param timeout? integer ---- @param skip? any +--- @param skip? string|function --- @return any function vim.fn.searchpos(pattern, flags, stopline, timeout, skip) end @@ -7714,7 +7823,7 @@ function vim.fn.serverlist() end --- echo serverstart('::1:12345') --- < --- ---- @param address? any +--- @param address? string --- @return any function vim.fn.serverstart(address) end @@ -7723,7 +7832,7 @@ function vim.fn.serverstart(address) end --- If |v:servername| is stopped it is set to the next available --- address in |serverlist()|. --- ---- @param address any +--- @param address string --- @return any function vim.fn.serverstop(address) end @@ -7751,9 +7860,9 @@ function vim.fn.serverstop(address) end --- If {buf} is not a valid buffer or {lnum} is not valid, an --- error message is given. --- ---- @param buf any +--- @param buf integer|string --- @param lnum integer ---- @param text any +--- @param text string|string[] --- @return any function vim.fn.setbufline(buf, lnum, text) end @@ -7770,7 +7879,7 @@ function vim.fn.setbufline(buf, lnum, text) end --- call setbufvar("todo", "myvar", "foobar") --- <This function is not available in the |sandbox|. --- ---- @param buf any +--- @param buf integer|string --- @param varname string --- @param val any --- @return any @@ -7803,13 +7912,13 @@ function vim.fn.setbufvar(buf, varname, val) end --- To clear the overrides pass an empty {list}: >vim --- call setcellwidths([]) --- ---- <You can use the script $VIMRUNTIME/tools/emoji_list.vim to see +--- <You can use the script $VIMRUNTIME/tools/emoji_list.lua to see --- the effect for known emoji characters. Move the cursor --- through the text to check if the cell widths of your terminal --- match with what Vim knows about each emoji. If it doesn't --- look right you need to adjust the {list} argument. --- ---- @param list any +--- @param list any[] --- @return any function vim.fn.setcellwidths(list) end @@ -7823,8 +7932,8 @@ function vim.fn.setcellwidths(list) end --- call setpos('.', [0, 8, 4, 0]) --- <positions the cursor on the second character '보'. --- ---- @param expr any ---- @param list any +--- @param expr string +--- @param list integer[] --- @return any function vim.fn.setcharpos(expr, list) end @@ -7847,7 +7956,7 @@ function vim.fn.setcharpos(expr, list) end --- call setcharsearch(prevsearch) --- <Also see |getcharsearch()|. --- ---- @param dict any +--- @param dict string --- @return any function vim.fn.setcharsearch(dict) end @@ -7857,8 +7966,8 @@ function vim.fn.setcharsearch(dict) end --- Returns 0 when successful, 1 when not editing the command --- line. --- ---- @param str any ---- @param pos? any +--- @param str string +--- @param pos? integer --- @return any function vim.fn.setcmdline(str, pos) end @@ -7876,13 +7985,13 @@ function vim.fn.setcmdline(str, pos) end --- Returns 0 when successful, 1 when not editing the command --- line. --- ---- @param pos any +--- @param pos integer --- @return any function vim.fn.setcmdpos(pos) end --- @param lnum integer --- @param col? integer ---- @param off? any +--- @param off? integer --- @return any function vim.fn.setcursorcharpos(lnum, col, off) end @@ -7896,7 +8005,7 @@ function vim.fn.setcursorcharpos(lnum, col, off) end --- call cursor(4, 3) --- <positions the cursor on the first character '여'. --- ---- @param list any +--- @param list integer[] --- @return any function vim.fn.setcursorcharpos(list) end @@ -7907,7 +8016,7 @@ function vim.fn.setcursorcharpos(list) end --- See also |expr-env|. --- --- @param name string ---- @param val any +--- @param val string --- @return any function vim.fn.setenv(name, val) end @@ -7981,8 +8090,8 @@ function vim.fn.setline(lnum, text) end --- --- @param nr integer --- @param list any ---- @param action? any ---- @param what? any +--- @param action? string +--- @param what? table --- @return any function vim.fn.setloclist(nr, list, action, what) end @@ -7994,7 +8103,7 @@ function vim.fn.setloclist(nr, list, action, what) end --- window ID instead of the current window. --- --- @param list any ---- @param win? any +--- @param win? integer --- @return any function vim.fn.setmatches(list, win) end @@ -8046,8 +8155,8 @@ function vim.fn.setmatches(list, win) end --- also set the preferred column. Also see the "curswant" key in --- |winrestview()|. --- ---- @param expr any ---- @param list any +--- @param expr string +--- @param list integer[] --- @return any function vim.fn.setpos(expr, list) end @@ -8164,9 +8273,9 @@ function vim.fn.setpos(expr, list) end --- independent of the 'errorformat' setting. Use a command like --- `:cc 1` to jump to the first position. --- ---- @param list any ---- @param action? any ---- @param what? any +--- @param list any[] +--- @param action? string +--- @param what? table --- @return any function vim.fn.setqflist(list, action, what) end @@ -8301,7 +8410,7 @@ function vim.fn.settabwinvar(tabnr, winnr, varname, val) end --- --- @param nr integer --- @param dict any ---- @param action? any +--- @param action? string --- @return any function vim.fn.settagstack(nr, dict, action) end @@ -8355,7 +8464,7 @@ function vim.fn.sha256(string) end --- <See also |::S|. --- --- @param string string ---- @param special? any +--- @param special? boolean --- @return any function vim.fn.shellescape(string, special) end @@ -8400,6 +8509,7 @@ function vim.fn.sign_define(name, dict) end --- icon full path to the bitmap file for the sign. --- linehl highlight group used for the whole line the --- sign is placed in. +--- priority default priority value of the sign --- numhl highlight group used for the line number where --- the sign is placed. --- text text that is displayed when there is no icon @@ -8450,6 +8560,7 @@ function vim.fn.sign_define(list) end --- linehl highlight group used for the whole line the --- sign is placed in; not present if not set. --- name name of the sign +--- priority default priority value of the sign --- numhl highlight group used for the line number where --- the sign is placed; not present if not set. --- text text that is displayed when there is no icon @@ -8536,7 +8647,7 @@ function vim.fn.sign_getdefined(name) end --- echo sign_getplaced() --- < --- ---- @param buf? any +--- @param buf? integer|string --- @param dict? vim.fn.sign_getplaced.dict --- @return vim.fn.sign_getplaced.ret.item[] function vim.fn.sign_getplaced(buf, dict) end @@ -8610,10 +8721,10 @@ function vim.fn.sign_jump(id, group, buf) end --- \ {'lnum' : 40, 'priority' : 90}) --- < --- ---- @param id any ---- @param group any +--- @param id integer +--- @param group string --- @param name string ---- @param buf any +--- @param buf integer|string --- @param dict? vim.fn.sign_place.dict --- @return integer function vim.fn.sign_place(id, group, name, buf, dict) end @@ -8641,7 +8752,8 @@ function vim.fn.sign_place(id, group, name, buf, dict) end --- priority Priority of the sign. When multiple signs are --- placed on a line, the sign with the highest --- priority is used. If not specified, the ---- default value of 10 is used. See +--- default value of 10 is used, unless specified +--- otherwise by the sign definition. See --- |sign-priority| for more information. --- --- If {id} refers to an existing sign, then the existing sign is @@ -8804,7 +8916,7 @@ function vim.fn.sign_unplacelist(list) end --- directory. In order to resolve all the involved symbolic --- links before simplifying the path name, use |resolve()|. --- ---- @param filename any +--- @param filename string --- @return any function vim.fn.simplify(filename) end @@ -8817,7 +8929,7 @@ function vim.fn.simplify(filename) end --- echo sin(-4.01) --- < 0.763301 --- ---- @param expr any +--- @param expr number --- @return any function vim.fn.sin(expr) end @@ -8831,7 +8943,7 @@ function vim.fn.sin(expr) end --- echo sinh(-0.9) --- < -1.026517 --- ---- @param expr any +--- @param expr number --- @return any function vim.fn.sinh(expr) end @@ -8845,8 +8957,8 @@ function vim.fn.sinh(expr) end --- Returns an empty value if {start} or {end} are invalid. --- --- @param expr any ---- @param start any ---- @param end_? any +--- @param start integer +--- @param end_? integer --- @return any function vim.fn.slice(expr, start, end_) end @@ -8875,7 +8987,7 @@ function vim.fn.slice(expr, start, end_) end --- - 0 on invalid arguments or connection failure. --- --- @param mode string ---- @param address any +--- @param address string --- @param opts? table --- @return any function vim.fn.sockconnect(mode, address, opts) end @@ -8953,7 +9065,7 @@ function vim.fn.sockconnect(mode, address, opts) end --- < --- --- @param list any ---- @param how? any +--- @param how? string|function --- @param dict? any --- @return any function vim.fn.sort(list, how, dict) end @@ -8965,7 +9077,7 @@ function vim.fn.sort(list, how, dict) end --- This can be used for making spelling suggestions. Note that --- the method can be quite slow. --- ---- @param word any +--- @param word string --- @return any function vim.fn.soundfold(word) end @@ -8992,7 +9104,7 @@ function vim.fn.soundfold(word) end --- The spelling information for the current window and the value --- of 'spelllang' are used. --- ---- @param sentence? any +--- @param sentence? string --- @return any function vim.fn.spellbadword(sentence) end @@ -9016,15 +9128,15 @@ function vim.fn.spellbadword(sentence) end --- The spelling information for the current window is used. The --- values of 'spelllang' and 'spellsuggest' are used. --- ---- @param word any ---- @param max? any ---- @param capital? any +--- @param word string +--- @param max? integer +--- @param capital? boolean --- @return any function vim.fn.spellsuggest(word, max, capital) end --- Make a |List| out of {string}. When {pattern} is omitted or ---- empty each white-separated sequence of characters becomes an ---- item. +--- empty each white space separated sequence of characters +--- becomes an item. --- Otherwise the string is split where {pattern} matches, --- removing the matched characters. 'ignorecase' is not used --- here, add \c to ignore case. |/\c| @@ -9047,8 +9159,8 @@ function vim.fn.spellsuggest(word, max, capital) end --- <The opposite function is |join()|. --- --- @param string string ---- @param pattern? any ---- @param keepempty? any +--- @param pattern? string +--- @param keepempty? boolean --- @return any function vim.fn.split(string, pattern, keepempty) end @@ -9064,7 +9176,7 @@ function vim.fn.split(string, pattern, keepempty) end --- < str2float("nan") --- NaN may be different, it depends on system libraries. --- ---- @param expr any +--- @param expr number --- @return any function vim.fn.sqrt(expr) end @@ -9082,7 +9194,7 @@ function vim.fn.sqrt(expr) end --- echo rand(seed) --- < --- ---- @param expr? any +--- @param expr? number --- @return any function vim.fn.srand(expr) end @@ -9187,7 +9299,7 @@ function vim.fn.stdpath(what) end --- Returns 0.0 if the conversion fails. --- --- @param string string ---- @param quoted? any +--- @param quoted? boolean --- @return any function vim.fn.str2float(string, quoted) end @@ -9203,7 +9315,7 @@ function vim.fn.str2float(string, quoted) end --- echo str2list("á") " returns [97, 769] --- --- @param string string ---- @param utf8? any +--- @param utf8? boolean --- @return any function vim.fn.str2list(string, utf8) end @@ -9226,7 +9338,7 @@ function vim.fn.str2list(string, utf8) end --- Returns 0 if {string} is empty or on error. --- --- @param string string ---- @param base? any +--- @param base? integer --- @return any function vim.fn.str2nr(string, base) end @@ -9257,10 +9369,10 @@ function vim.fn.strcharlen(string) end --- --- Returns an empty string on error. --- ---- @param src any ---- @param start any ---- @param len? any ---- @param skipcc? any +--- @param src string +--- @param start integer +--- @param len? integer +--- @param skipcc? boolean --- @return any function vim.fn.strcharpart(src, start, len, skipcc) end @@ -9293,7 +9405,7 @@ function vim.fn.strcharpart(src, start, len, skipcc) end --- < --- --- @param string string ---- @param skipcc? any +--- @param skipcc? boolean --- @return integer function vim.fn.strchars(string, skipcc) end @@ -9331,8 +9443,8 @@ function vim.fn.strdisplaywidth(string, col) end --- echo strftime("%c", getftime("file.c")) --- " Show mod time of file.c. --- ---- @param format any ---- @param time? any +--- @param format string +--- @param time? number --- @return string function vim.fn.strftime(format, time) end @@ -9796,7 +9908,7 @@ function vim.fn.synIDtrans(synID) end --- --- @param lnum integer --- @param col integer ---- @return {[1]: integer, [2]: string, [3]: integer} +--- @return [integer, string, integer] function vim.fn.synconcealed(lnum, col) end --- Return a |List|, which is the stack of syntax items at the @@ -9906,7 +10018,7 @@ function vim.fn.systemlist(cmd, input, keepempty) end --- endfor --- <Note that a buffer may appear in more than one window. --- ---- @param arg? any +--- @param arg? integer --- @return any function vim.fn.tabpagebuflist(arg) end @@ -10049,7 +10161,7 @@ function vim.fn.tempname() end --- except $TERM is set to "xterm-256color". Full behavior is --- described in |terminal|. --- ---- @param cmd any +--- @param cmd string|string[] --- @param opts? table --- @return any function vim.fn.termopen(cmd, opts) end @@ -10068,7 +10180,7 @@ function vim.fn.termopen(cmd, opts) end --- -1 means forever --- "callback" the callback --- ---- @param id? any +--- @param id? integer --- @return any function vim.fn.timer_info(id) end @@ -10084,8 +10196,8 @@ function vim.fn.timer_info(id) end --- String, then the timer is paused, otherwise it is unpaused. --- See |non-zero-arg|. --- ---- @param timer any ---- @param paused any +--- @param timer integer +--- @param paused boolean --- @return any function vim.fn.timer_pause(timer, paused) end @@ -10118,8 +10230,8 @@ function vim.fn.timer_pause(timer, paused) end --- \ {'repeat': 3}) --- <This invokes MyHandler() three times at 500 msec intervals. --- ---- @param time any ---- @param callback any +--- @param time number +--- @param callback string|function --- @param options? table --- @return any function vim.fn.timer_start(time, callback, options) end @@ -10128,7 +10240,7 @@ function vim.fn.timer_start(time, callback, options) end --- {timer} is an ID returned by timer_start(), thus it must be a --- Number. If {timer} does not exist there is no error. --- ---- @param timer any +--- @param timer integer --- @return any function vim.fn.timer_stop(timer) end @@ -10143,7 +10255,7 @@ function vim.fn.timer_stopall() end --- characters turned into lowercase (just like applying |gu| to --- the string). Returns an empty string on error. --- ---- @param expr any +--- @param expr string --- @return string function vim.fn.tolower(expr) end @@ -10151,7 +10263,7 @@ function vim.fn.tolower(expr) end --- characters turned into uppercase (just like applying |gU| to --- the string). Returns an empty string on error. --- ---- @param expr any +--- @param expr string --- @return string function vim.fn.toupper(expr) end @@ -10203,7 +10315,7 @@ function vim.fn.tr(src, fromstr, tostr) end --- echo trim(" vim ", " ", 2) --- <returns " vim" --- ---- @param text any +--- @param text string --- @param mask? string --- @param dir? 0|1|2 --- @return string @@ -10221,7 +10333,7 @@ function vim.fn.trim(text, mask, dir) end --- echo trunc(4.0) --- < 4.0 --- ---- @param expr any +--- @param expr number --- @return integer function vim.fn.trunc(expr) end @@ -10250,6 +10362,7 @@ function vim.fn.trunc(expr) end --- if myvar is v:null | endif --- <To check if the v:t_ variables exist use this: >vim --- if exists('v:t_number') | endif +--- < --- --- @param expr any --- @return integer @@ -10313,7 +10426,7 @@ function vim.fn.undofile(name) end --- item. --- --- @param buf? integer|string ---- @return any +--- @return vim.fn.undotree.ret function vim.fn.undotree(buf) end --- Remove second and succeeding copies of repeated adjacent @@ -10360,8 +10473,8 @@ function vim.fn.uniq(list, func, dict) end --- --- @param string string --- @param idx integer ---- @param countcc? any ---- @param charidx? any +--- @param countcc? boolean +--- @param charidx? boolean --- @return integer function vim.fn.utf16idx(string, idx, countcc, charidx) end @@ -10382,7 +10495,9 @@ function vim.fn.values(dict) end --- set to 8, it returns 8. |conceal| is ignored. --- For the byte position use |col()|. --- ---- For the use of {expr} see |col()|. +--- For the use of {expr} see |getpos()| and |col()|. +--- When {expr} is "$", it means the end of the cursor line, so +--- the result is the number of cells in the cursor line plus one. --- --- When 'virtualedit' is used {expr} can be [lnum, col, off], --- where "off" is the offset in screen columns from the start of @@ -10392,18 +10507,6 @@ function vim.fn.values(dict) end --- beyond the end of the line can be returned. Also see --- |'virtualedit'| --- ---- The accepted positions are: ---- . the cursor position ---- $ the end of the cursor line (the result is the ---- number of displayed characters in the cursor line ---- plus one) ---- 'x position of mark x (if the mark is not set, 0 is ---- returned) ---- v In Visual mode: the start of the Visual area (the ---- cursor is the end). When not in Visual mode ---- returns the cursor position. Differs from |'<| in ---- that it's updated right away. ---- --- If {list} is present and non-zero then virtcol() returns a --- List with the first and last screen position occupied by the --- character. @@ -10422,13 +10525,16 @@ function vim.fn.values(dict) end --- " With text " there", with 't at 'h': --- --- echo virtcol("'t") " returns 6 ---- <The first column is 1. 0 or [0, 0] is returned for an error. +--- < +--- The first column is 1. 0 or [0, 0] is returned for an error. +--- --- A more advanced example that echoes the maximum length of --- all lines: >vim --- echo max(map(range(1, line('$')), "virtcol([v:val, '$'])")) +--- < --- ---- @param expr any ---- @param list? any +--- @param expr string|integer[] +--- @param list? boolean --- @param winid? integer --- @return any function vim.fn.virtcol(expr, list, winid) end @@ -10477,7 +10583,7 @@ function vim.fn.virtcol2col(winid, lnum, col) end --- a non-empty String, then the Visual mode will be cleared and --- the old value is returned. See |non-zero-arg|. --- ---- @param expr? any +--- @param expr? boolean --- @return any function vim.fn.visualmode(expr) end @@ -10498,7 +10604,7 @@ function vim.fn.visualmode(expr) end --- --- @param timeout integer --- @param condition any ---- @param interval? any +--- @param interval? number --- @return any function vim.fn.wait(timeout, condition, interval) end @@ -10528,8 +10634,8 @@ function vim.fn.wildmenumode() end --- When window {id} does not exist then no error is given and --- an empty string is returned. --- ---- @param id any ---- @param command any +--- @param id integer +--- @param command string --- @param silent? boolean --- @return any function vim.fn.win_execute(id, command, silent) end @@ -10537,7 +10643,7 @@ function vim.fn.win_execute(id, command, silent) end --- Returns a |List| with |window-ID|s for windows that contain --- buffer {bufnr}. When there is none the list is empty. --- ---- @param bufnr any +--- @param bufnr integer --- @return integer[] function vim.fn.win_findbuf(bufnr) end @@ -10549,8 +10655,8 @@ function vim.fn.win_findbuf(bufnr) end --- number {tab}. The first tab has number one. --- Return zero if the window cannot be found. --- ---- @param win? any ---- @param tab? any +--- @param win? integer +--- @param tab? integer --- @return integer function vim.fn.win_getid(win, tab) end @@ -10579,7 +10685,7 @@ function vim.fn.win_gettype(nr) end --- tabpage. --- Return TRUE if successful, FALSE if the window cannot be found. --- ---- @param expr any +--- @param expr integer --- @return 0|1 function vim.fn.win_gotoid(expr) end @@ -10587,14 +10693,14 @@ function vim.fn.win_gotoid(expr) end --- with ID {expr}: [tabnr, winnr]. --- Return [0, 0] if the window cannot be found. --- ---- @param expr any +--- @param expr integer --- @return any function vim.fn.win_id2tabwin(expr) end --- Return the window number of window with ID {expr}. --- Return 0 if the window cannot be found in the current tabpage. --- ---- @param expr any +--- @param expr integer --- @return any function vim.fn.win_id2win(expr) end @@ -10613,7 +10719,7 @@ function vim.fn.win_id2win(expr) end --- Only works for the current tab page. *E1308* --- --- @param nr integer ---- @param offset any +--- @param offset integer --- @return any function vim.fn.win_move_separator(nr, offset) end @@ -10629,7 +10735,7 @@ function vim.fn.win_move_separator(nr, offset) end --- Only works for the current tab page. --- --- @param nr integer ---- @param offset any +--- @param offset integer --- @return any function vim.fn.win_move_statusline(nr, offset) end @@ -10664,7 +10770,7 @@ function vim.fn.win_screenpos(nr) end --- 'splitright' are used. --- --- @param nr integer ---- @param target any +--- @param target integer --- @param options? table --- @return any function vim.fn.win_splitmove(nr, target, options) end @@ -10706,6 +10812,7 @@ function vim.fn.windowsversion() end --- This excludes any window toolbar line. --- Examples: >vim --- echo "The current window has " .. winheight(0) .. " lines." +--- < --- --- @param nr integer --- @return integer @@ -10789,8 +10896,9 @@ function vim.fn.winline() end --- let window_count = winnr('$') --- let prev_window = winnr('#') --- let wnum = winnr('3k') +--- < --- ---- @param arg? any +--- @param arg? string|integer --- @return any function vim.fn.winnr(arg) end @@ -10939,6 +11047,7 @@ function vim.fn.wordcount() end --- To copy a file byte for byte: >vim --- let fl = readfile("foo", "b") --- call writefile(fl, "foocopy", "b") +--- < --- --- @param object any --- @param fname string @@ -10953,7 +11062,7 @@ function vim.fn.writefile(object, fname, flags) end --- let bits = xor(bits, 0x80) --- < --- ---- @param expr any ---- @param expr1 any +--- @param expr number +--- @param expr1 number --- @return any function vim.fn.xor(expr, expr1) end diff --git a/runtime/lua/vim/_options.lua b/runtime/lua/vim/_options.lua index b41e298dd7..a61fa61256 100644 --- a/runtime/lua/vim/_options.lua +++ b/runtime/lua/vim/_options.lua @@ -95,7 +95,6 @@ local api = vim.api -- TODO(tjdevries): Improve option metadata so that this doesn't have to be hardcoded. --- Can be done in a separate PR. local key_value_options = { fillchars = true, fcs = true, @@ -175,6 +174,11 @@ local function new_buf_opt_accessor(bufnr) end local function new_win_opt_accessor(winid, bufnr) + -- TODO(lewis6991): allow passing both buf and win to nvim_get_option_value + if bufnr ~= nil and bufnr ~= 0 then + error('only bufnr=0 is supported') + end + return setmetatable({}, { __index = function(_, k) if bufnr == nil and type(k) == 'number' then @@ -185,11 +189,6 @@ local function new_win_opt_accessor(winid, bufnr) end end - if bufnr ~= nil and bufnr ~= 0 then - error('only bufnr=0 is supported') - end - - -- TODO(lewis6991): allow passing both buf and win to nvim_get_option_value return api.nvim_get_option_value(k, { scope = bufnr and 'local' or nil, win = winid or 0, @@ -197,7 +196,6 @@ local function new_win_opt_accessor(winid, bufnr) end, __newindex = function(_, k, v) - -- TODO(lewis6991): allow passing both buf and win to nvim_set_option_value return api.nvim_set_option_value(k, v, { scope = bufnr and 'local' or nil, win = winid or 0, diff --git a/runtime/lua/vim/_watch.lua b/runtime/lua/vim/_watch.lua index 02b3f536c2..11f6742941 100644 --- a/runtime/lua/vim/_watch.lua +++ b/runtime/lua/vim/_watch.lua @@ -30,6 +30,8 @@ M.FileChangeType = { --- @class vim._watch.watch.Opts : vim._watch.Opts --- @field uvflags? uv.fs_event_start.flags +--- Decides if `path` should be skipped. +--- --- @param path string --- @param opts? vim._watch.Opts local function skip(path, opts) @@ -69,7 +71,7 @@ function M.watch(path, opts, callback) local uvflags = opts and opts.uvflags or {} local handle = assert(uv.new_fs_event()) - local _, start_err = handle:start(path, uvflags, function(err, filename, events) + local _, start_err, start_errname = handle:start(path, uvflags, function(err, filename, events) assert(not err, err) local fullpath = path if filename then @@ -96,7 +98,15 @@ function M.watch(path, opts, callback) callback(fullpath, change_type) end) - assert(not start_err, start_err) + if start_err then + if start_errname == 'ENOENT' then + -- Server may send "workspace/didChangeWatchedFiles" with nonexistent `baseUri` path. + -- This is mostly a placeholder until we have `nvim_log` API. + vim.notify_once(('watch.watch: %s'):format(start_err), vim.log.levels.INFO) + end + -- TODO(justinmk): log important errors once we have `nvim_log` API. + return function() end + end return function() local _, stop_err = handle:stop() @@ -193,7 +203,18 @@ function M.watchdirs(path, opts, callback) local root_handle = assert(uv.new_fs_event()) handles[path] = root_handle - root_handle:start(path, {}, create_on_change(path)) + local _, start_err, start_errname = root_handle:start(path, {}, create_on_change(path)) + + if start_err then + if start_errname == 'ENOENT' then + -- Server may send "workspace/didChangeWatchedFiles" with nonexistent `baseUri` path. + -- This is mostly a placeholder until we have `nvim_log` API. + vim.notify_once(('watch.watchdirs: %s'):format(start_err), vim.log.levels.INFO) + end + -- TODO(justinmk): log important errors once we have `nvim_log` API. + + -- Continue. vim.fs.dir() will return nothing, so the code below is harmless. + end --- "640K ought to be enough for anyone" --- Who has folders this deep? @@ -227,11 +248,12 @@ end --- @param data string --- @param opts vim._watch.Opts? --- @param callback vim._watch.Callback -local function fswatch_output_handler(data, opts, callback) +local function on_inotifywait_output(data, opts, callback) local d = vim.split(data, '%s+') -- only consider the last reported event - local fullpath, event = d[1], d[#d] + local path, event, file = d[1], d[2], d[#d] + local fullpath = vim.fs.joinpath(path, file) if skip(fullpath, opts) then return @@ -240,20 +262,16 @@ local function fswatch_output_handler(data, opts, callback) --- @type integer local change_type - if event == 'Created' then + if event == 'CREATE' then change_type = M.FileChangeType.Created - elseif event == 'Removed' then + elseif event == 'DELETE' then change_type = M.FileChangeType.Deleted - elseif event == 'Updated' then + elseif event == 'MODIFY' 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 + elseif event == 'MOVED_FROM' then + change_type = M.FileChangeType.Deleted + elseif event == 'MOVED_TO' then + change_type = M.FileChangeType.Created end if change_type then @@ -265,24 +283,22 @@ end --- @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 - +function M.inotify(path, opts, callback) local obj = vim.system({ - 'fswatch', - '--event=Created', - '--event=Removed', - '--event=Updated', - '--event=Renamed', - '--event-flags', + 'inotifywait', + '--quiet', -- suppress startup messages + '--no-dereference', -- don't follow symlinks + '--monitor', -- keep listening for events forever '--recursive', - '--latency=' .. tostring(latency), - '--exclude', - '/.git/', + '--event', + 'create', + '--event', + 'delete', + '--event', + 'modify', + '--event', + 'move', + string.format('@%s/.git', path), -- ignore git directory path, }, { stderr = function(err, data) @@ -292,11 +308,11 @@ function M.fswatch(path, opts, callback) if data and #vim.trim(data) > 0 then vim.schedule(function() - if vim.fn.has('linux') == 1 and vim.startswith(data, 'Event queue overflow') then - data = 'inotify(7) limit reached, see :h fswatch-limitations for more info.' + if vim.fn.has('linux') == 1 and vim.startswith(data, 'Failed to watch') then + data = 'inotify(7) limit reached, see :h inotify-limitations for more info.' end - vim.notify('fswatch: ' .. data, vim.log.levels.ERROR) + vim.notify('inotify: ' .. data, vim.log.levels.ERROR) end) end end, @@ -306,7 +322,7 @@ function M.fswatch(path, opts, callback) end for line in vim.gsplit(data or '', '\n', { plain = true, trimempty = true }) do - fswatch_output_handler(line, opts, callback) + on_inotifywait_output(line, opts, callback) end end, -- --latency is locale dependent but tostring() isn't and will always have '.' as decimal point. diff --git a/runtime/lua/vim/deprecated/health.lua b/runtime/lua/vim/deprecated/health.lua index 0f6b1f578c..eed889d90a 100644 --- a/runtime/lua/vim/deprecated/health.lua +++ b/runtime/lua/vim/deprecated/health.lua @@ -1,7 +1,7 @@ local M = {} local health = vim.health -local deprecated = {} +local deprecated = {} ---@type [string, table, string][] function M.check() if next(deprecated) == nil then diff --git a/runtime/lua/vim/diagnostic.lua b/runtime/lua/vim/diagnostic.lua index 348204abb7..ef00a1fa51 100644 --- a/runtime/lua/vim/diagnostic.lua +++ b/runtime/lua/vim/diagnostic.lua @@ -42,7 +42,7 @@ local M = {} --- --- @field namespace? integer ---- Each of the configuration options below accepts one of the following: +--- Many of the configuration options below accept 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. @@ -78,6 +78,9 @@ local M = {} --- - {reverse}? (boolean) Reverse sort order --- (default: `false`) --- @field severity_sort? boolean|{reverse?:boolean} +--- +--- Default values for |vim.diagnostic.jump()|. See |vim.diagnostic.Opts.Jump|. +--- @field jump? vim.diagnostic.Opts.Jump --- @class (private) vim.diagnostic.OptsResolved --- @field float vim.diagnostic.Opts.Float @@ -105,7 +108,7 @@ local M = {} --- 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} +--- @field pos? integer|[integer,integer] --- --- Sort diagnostics by severity. --- Overrides the setting from |vim.diagnostic.config()|. @@ -119,7 +122,7 @@ local M = {} --- String to use as the header for the floating window. If a table, it is --- interpreted as a `[text, hl_group]` tuple. --- Overrides the setting from |vim.diagnostic.config()|. ---- @field header? string|{[1]:string,[2]:any} +--- @field header? string|[string,any] --- --- Include the diagnostic source in the message. --- Use "if_many" to only show sources if there is more than one source of @@ -200,7 +203,7 @@ local M = {} --- @field hl_mode? 'replace'|'combine'|'blend' --- --- See |nvim_buf_set_extmark()|. ---- @field virt_text? {[1]:string,[2]:any}[] +--- @field virt_text? [string,any][] --- --- See |nvim_buf_set_extmark()|. --- @field virt_text_pos? 'eol'|'overlay'|'right_align'|'inline' @@ -241,6 +244,22 @@ local M = {} --- whole line the sign is placed in. --- @field linehl? table<vim.diagnostic.Severity,string> +--- @class vim.diagnostic.Opts.Jump +--- +--- Default value of the {float} parameter of |vim.diagnostic.jump()|. +--- (default: false) +--- @field float? boolean|vim.diagnostic.Opts.Float +--- +--- Default value of the {wrap} parameter of |vim.diagnostic.jump()|. +--- (default: true) +--- @field wrap? boolean +--- +--- Default value of the {severity} parameter of |vim.diagnostic.jump()|. +--- @field severity? vim.diagnostic.SeverityFilter +--- +--- Default value of the {_highest} parameter of |vim.diagnostic.jump()|. +--- @field package _highest? boolean + -- TODO: inherit from `vim.diagnostic.Opts`, implement its fields. --- Optional filters |kwargs|, or `nil` for all. --- @class vim.diagnostic.Filter @@ -284,6 +303,13 @@ local global_diagnostic_options = { float = true, update_in_insert = false, severity_sort = false, + jump = { + -- Do not show floating window + float = false, + + -- Wrap around buffer + wrap = true, + }, } --- @class (private) vim.diagnostic.Handler @@ -835,21 +861,36 @@ local function filter_highest(diagnostics) end end ---- @param position {[1]: integer, [2]: integer} --- @param search_forward boolean ---- @param bufnr integer ---- @param opts vim.diagnostic.GotoOpts ---- @param namespace integer[]|integer +--- @param opts vim.diagnostic.JumpOpts? --- @return vim.Diagnostic? -local function next_diagnostic(position, search_forward, bufnr, opts, namespace) +local function next_diagnostic(search_forward, opts) + opts = opts or {} + + -- Support deprecated win_id alias + if opts.win_id then + vim.deprecate('opts.win_id', 'opts.winid', '0.13') + opts.winid = opts.win_id + opts.win_id = nil + end + + -- Support deprecated cursor_position alias + if opts.cursor_position then + vim.deprecate('opts.cursor_position', 'opts.pos', '0.13') + opts.pos = opts.cursor_position + opts.cursor_position = nil + end + + local winid = opts.winid or api.nvim_get_current_win() + local bufnr = api.nvim_win_get_buf(winid) + local position = opts.pos or api.nvim_win_get_cursor(winid) + + -- Adjust row to be 0-indexed position[1] = position[1] - 1 - bufnr = get_bufnr(bufnr) - local wrap = if_nil(opts.wrap, true) - local get_opts = vim.deepcopy(opts) - get_opts.namespace = get_opts.namespace or namespace + local wrap = if_nil(opts.wrap, true) - local diagnostics = get_diagnostics(bufnr, get_opts, true) + local diagnostics = get_diagnostics(bufnr, opts, true) if opts._highest then filter_highest(diagnostics) @@ -902,32 +943,40 @@ 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 = if_nil(opts.float, true) - local win_id = opts.win_id or api.nvim_get_current_win() - - if not pos then +--- Move the cursor to the given diagnostic. +--- +--- @param diagnostic vim.Diagnostic? +--- @param opts vim.diagnostic.JumpOpts? +local function goto_diagnostic(diagnostic, opts) + if not diagnostic then api.nvim_echo({ { 'No more valid diagnostics to move to', 'WarningMsg' } }, true, {}) return end - api.nvim_win_call(win_id, function() + opts = opts or {} + + -- Support deprecated win_id alias + if opts.win_id then + vim.deprecate('opts.win_id', 'opts.winid', '0.13') + opts.winid = opts.win_id + opts.win_id = nil + end + + local winid = opts.winid or api.nvim_get_current_win() + + vim._with({ win = winid }, function() -- Save position in the window's jumplist vim.cmd("normal! m'") - api.nvim_win_set_cursor(win_id, { pos[1] + 1, pos[2] }) + api.nvim_win_set_cursor(winid, { diagnostic.lnum + 1, diagnostic.col }) -- Open folds under the cursor vim.cmd('normal! zv') end) - if float then - local float_opts = type(float) == 'table' and float or {} + if opts.float then + local float_opts = type(opts.float) == 'table' and opts.float or {} vim.schedule(function() M.open_float(vim.tbl_extend('keep', float_opts, { - bufnr = api.nvim_win_get_buf(win_id), + bufnr = api.nvim_win_get_buf(winid), scope = 'cursor', focus = false, })) @@ -1114,24 +1163,24 @@ end --- Get the previous diagnostic closest to the cursor position. --- ----@param opts? vim.diagnostic.GotoOpts +---@param opts? vim.diagnostic.JumpOpts ---@return vim.Diagnostic? : Previous diagnostic function M.get_prev(opts) - opts = opts or {} - - local win_id = opts.win_id or api.nvim_get_current_win() - local bufnr = api.nvim_win_get_buf(win_id) - local cursor_position = opts.cursor_position or api.nvim_win_get_cursor(win_id) - - return next_diagnostic(cursor_position, false, bufnr, opts, opts.namespace) + return next_diagnostic(false, opts) end --- Return the position of the previous diagnostic in the current buffer. --- ----@param opts? vim.diagnostic.GotoOpts +---@param opts? vim.diagnostic.JumpOpts ---@return table|false: Previous diagnostic position as a `(row, col)` tuple --- or `false` if there is no prior diagnostic. +---@deprecated function M.get_prev_pos(opts) + vim.deprecate( + 'vim.diagnostic.get_prev_pos()', + 'access the lnum and col fields from get_prev() instead', + '0.13' + ) local prev = M.get_prev(opts) if not prev then return false @@ -1141,31 +1190,35 @@ function M.get_prev_pos(opts) end --- Move to the previous diagnostic in the current buffer. ----@param opts? vim.diagnostic.GotoOpts +---@param opts? vim.diagnostic.JumpOpts +---@deprecated function M.goto_prev(opts) - return diagnostic_move_pos(opts, M.get_prev_pos(opts)) + vim.deprecate('vim.diagnostic.goto_prev()', 'vim.diagnostic.jump()', '0.13') + opts = opts or {} + opts.float = if_nil(opts.float, true) + goto_diagnostic(M.get_prev(opts), opts) end --- Get the next diagnostic closest to the cursor position. --- ----@param opts? vim.diagnostic.GotoOpts +---@param opts? vim.diagnostic.JumpOpts ---@return vim.Diagnostic? : Next diagnostic function M.get_next(opts) - opts = opts or {} - - local win_id = opts.win_id or api.nvim_get_current_win() - local bufnr = api.nvim_win_get_buf(win_id) - local cursor_position = opts.cursor_position or api.nvim_win_get_cursor(win_id) - - return next_diagnostic(cursor_position, true, bufnr, opts, opts.namespace) + return next_diagnostic(true, opts) end --- Return the position of the next diagnostic in the current buffer. --- ----@param opts? vim.diagnostic.GotoOpts +---@param opts? vim.diagnostic.JumpOpts ---@return table|false : Next diagnostic position as a `(row, col)` tuple or false if no next --- diagnostic. +---@deprecated function M.get_next_pos(opts) + vim.deprecate( + 'vim.diagnostic.get_next_pos()', + 'access the lnum and col fields from get_next() instead', + '0.13' + ) local next = M.get_next(opts) if not next then return false @@ -1186,13 +1239,23 @@ end --- See |diagnostic-severity|. --- @field severity? vim.diagnostic.SeverityFilter ---- Configuration table with the following keys: ---- @class vim.diagnostic.GotoOpts : vim.diagnostic.GetOpts +--- Configuration table with the keys listed below. Some parameters can have their default values +--- changed with |vim.diagnostic.config()|. +--- @class vim.diagnostic.JumpOpts : vim.diagnostic.GetOpts +--- +--- The diagnostic to jump to. Mutually exclusive with {count}, {namespace}, +--- and {severity}. +--- @field diagnostic? vim.Diagnostic --- ---- Cursor position as a `(row, col)` tuple. ---- See |nvim_win_get_cursor()|. ---- (default: current cursor position) ---- @field cursor_position? {[1]:integer,[2]:integer} +--- The number of diagnostics to move by, starting from {pos}. A positive +--- integer moves forward by {count} diagnostics, while a negative integer moves +--- backward by {count} diagnostics. Mutually exclusive with {diagnostic}. +--- @field count? integer +--- +--- Cursor position as a `(row, col)` tuple. See |nvim_win_get_cursor()|. Used +--- to find the nearest diagnostic when {count} is used. Only used when {count} +--- is non-nil. Default is the current cursor position. +--- @field pos? [integer,integer] --- --- Whether to loop around file or not. Similar to 'wrapscan'. --- (default: `true`) @@ -1209,18 +1272,78 @@ end --- 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`) +--- (default: `false`) --- @field float? boolean|vim.diagnostic.Opts.Float --- --- Window ID --- (default: `0`) ---- @field win_id? integer +--- @field winid? integer + +--- Move to a diagnostic. +--- +--- @param opts vim.diagnostic.JumpOpts +--- @return vim.Diagnostic? # The diagnostic that was moved to. +function M.jump(opts) + vim.validate('opts', opts, 'table') + + -- One of "diagnostic" or "count" must be provided + assert( + opts.diagnostic or opts.count, + 'One of "diagnostic" or "count" must be specified in the options to vim.diagnostic.jump()' + ) + + -- Apply configuration options from vim.diagnostic.config() + opts = vim.tbl_deep_extend('keep', opts, global_diagnostic_options.jump) + + if opts.diagnostic then + goto_diagnostic(opts.diagnostic, opts) + return opts.diagnostic + end + + local count = opts.count + if count == 0 then + return nil + end + + -- Support deprecated cursor_position alias + if opts.cursor_position then + vim.deprecate('opts.cursor_position', 'opts.pos', '0.13') + opts.pos = opts.cursor_position + opts.cursor_position = nil + end + + local diag = nil + while count ~= 0 do + local next = next_diagnostic(count > 0, opts) + if not next then + break + end + + -- Update cursor position + opts.pos = { next.lnum + 1, next.col } + + if count > 0 then + count = count - 1 + else + count = count + 1 + end + diag = next + end + + goto_diagnostic(diag, opts) + + return diag +end --- Move to the next diagnostic. --- ----@param opts? vim.diagnostic.GotoOpts +---@param opts? vim.diagnostic.JumpOpts +---@deprecated function M.goto_next(opts) - diagnostic_move_pos(opts, M.get_next_pos(opts)) + vim.deprecate('vim.diagnostic.goto_next()', 'vim.diagnostic.jump()', '0.13') + opts = opts or {} + opts.float = if_nil(opts.float, true) + goto_diagnostic(M.get_next(opts), opts) end M.handlers.signs = { @@ -1239,6 +1362,10 @@ M.handlers.signs = { bufnr = get_bufnr(bufnr) opts = opts or {} + if not api.nvim_buf_is_loaded(bufnr) then + return + end + if opts.signs and opts.signs.severity then diagnostics = filter_by_severity(opts.signs.severity, diagnostics) end @@ -1321,8 +1448,10 @@ M.handlers.signs = { local numhl = opts.signs.numhl or {} local linehl = opts.signs.linehl or {} + local line_count = api.nvim_buf_line_count(bufnr) + for _, diagnostic in ipairs(diagnostics) do - if api.nvim_buf_is_loaded(diagnostic.bufnr) then + if diagnostic.lnum <= line_count 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], @@ -1688,7 +1817,7 @@ end --- ---@param opts vim.diagnostic.Opts.Float? ---@return integer? float_bufnr ----@return integer? win_id +---@return integer? winid function M.open_float(opts, ...) -- Support old (bufnr, opts) signature local bufnr --- @type integer? @@ -1739,16 +1868,19 @@ function M.open_float(opts, ...) if scope == 'line' then --- @param d vim.Diagnostic diagnostics = vim.tbl_filter(function(d) - return lnum >= d.lnum and lnum <= d.end_lnum + return lnum >= d.lnum + and lnum <= d.end_lnum + and (d.lnum == d.end_lnum or lnum ~= d.end_lnum or d.end_col ~= 0) end, diagnostics) elseif scope == 'cursor' then - -- LSP servers can send diagnostics with `end_col` past the length of the line + -- If `col` is past the end of the line, show if the cursor is on the last char in 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 - and (d.end_col >= col or d.end_lnum > lnum) + return lnum >= d.lnum + and lnum <= d.end_lnum + and (lnum ~= d.lnum or col >= math.min(d.col, line_length - 1)) + and ((d.lnum == d.end_lnum and d.col == d.end_col) or lnum ~= d.end_lnum or col < d.end_col) end, diagnostics) end @@ -1946,7 +2078,7 @@ end --- @field title? string --- --- See |diagnostic-severity|. ---- @field severity? vim.diagnostic.Severity +--- @field severity? vim.diagnostic.SeverityFilter --- Add all diagnostics to the quickfix list. --- @@ -1974,7 +2106,7 @@ end --- @field title? string --- --- See |diagnostic-severity|. ---- @field severity? vim.diagnostic.Severity +--- @field severity? vim.diagnostic.SeverityFilter --- Add buffer diagnostics to the location list. --- diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index d1fdd0aa16..d3910e26eb 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -4,12 +4,13 @@ local fn = vim.fn local M = {} --- @alias vim.filetype.mapfn fun(path:string,bufnr:integer, ...):string?, fun(b:integer)? ---- @alias vim.filetype.maptbl {[1]:string|vim.filetype.mapfn, [2]:{priority:integer}} +--- @alias vim.filetype.mapopts { parent: string, priority: number } +--- @alias vim.filetype.maptbl [string|vim.filetype.mapfn, vim.filetype.mapopts] --- @alias vim.filetype.mapping.value string|vim.filetype.mapfn|vim.filetype.maptbl --- @alias vim.filetype.mapping table<string,vim.filetype.mapping.value> --- @param ft string|vim.filetype.mapfn ---- @param opts? {priority:integer} +--- @param opts? vim.filetype.mapopts --- @return vim.filetype.maptbl local function starsetf(ft, opts) return { @@ -26,6 +27,9 @@ local function starsetf(ft, opts) end end, { + -- Allow setting "parent" to be reused in closures, but don't have default as it will be + -- assigned later from grouping + parent = opts and opts.parent, -- Starset matches should have lowest priority by default priority = (opts and opts.priority) or -math.huge, }, @@ -144,6 +148,9 @@ end local function detect_noext(path, bufnr) local root = fn.fnamemodify(path, ':r') + if root == path then + return + end return M.match({ buf = bufnr, filename = root }) end @@ -170,9 +177,15 @@ end -- luacheck: push no unused args -- luacheck: push ignore 122 --- Filetypes based on file extension +-- Filetype detection logic is encoded in three tables: +-- 1. `extension` for literal extension lookup +-- 2. `filename` for literal full path or basename lookup; +-- 3. `pattern` for matching filenames or paths against Lua patterns, +-- optimized for fast lookup. +-- See `:h dev-vimpatch-filetype` for guidance when porting Vim filetype patches. + ---@diagnostic disable: unused-local ---- @type vim.filetype.mapping +---@type vim.filetype.mapping local extension = { -- BEGIN EXTENSION ['8th'] = '8th', @@ -190,6 +203,7 @@ local extension = { aidl = 'aidl', aml = 'aml', run = 'ampl', + g4 = 'antlr4', scpt = 'applescript', ino = 'arduino', pde = 'arduino', @@ -203,12 +217,17 @@ local extension = { return 'aspvbs' end, asm = detect.asm, + s = detect.asm, + S = detect.asm, + a = detect.asm, + A = detect.asm, lst = detect.asm, mac = detect.asm, asn1 = 'asn', asn = 'asn', asp = detect.asp, astro = 'astro', + asy = 'asy', atl = 'atlas', as = 'atlas', zed = 'authzed', @@ -232,6 +251,7 @@ local extension = { db = detect.bindzone, bicep = 'bicep', bicepparam = 'bicep', + zone = 'bindzone', bb = 'bitbake', bbappend = 'bitbake', bbclass = 'bitbake', @@ -257,10 +277,14 @@ local extension = { cdc = 'cdc', cdl = 'cdl', toc = detect_line1('\\contentsline', 'tex', 'cdrtoc'), + cedar = 'cedar', cfc = 'cf', cfm = 'cf', cfi = 'cf', hgrc = 'cfg', + cfg = detect.cfg, + Cfg = detect.cfg, + CFG = detect.cfg, chf = 'ch', chai = 'chaiscript', ch = detect.change, @@ -281,7 +305,6 @@ local extension = { cook = 'cook', cmake = 'cmake', cmod = 'cmod', - lib = 'cobol', cob = 'cobol', cbl = 'cobol', atg = 'coco', @@ -344,6 +367,9 @@ local extension = { dart = 'dart', drt = 'dart', ds = 'datascript', + dat = detect.dat, + Dat = detect.dat, + DAT = detect.dat, dcd = 'dcd', decl = detect.decl, dec = detect.decl, @@ -366,6 +392,9 @@ local extension = { gv = 'dot', drac = 'dracula', drc = 'dracula', + lvs = 'dracula', + lpe = 'dracula', + dsp = detect.dsp, dtd = 'dtd', d = detect.dtrace, dts = 'dts', @@ -417,6 +446,7 @@ local extension = { fal = 'falcon', fan = 'fan', fwt = 'fan', + lib = 'faust', fnl = 'fennel', m4gl = 'fgl', ['4gl'] = 'fgl', @@ -468,8 +498,21 @@ local extension = { gmi = 'gemtext', gemini = 'gemtext', gift = 'gift', + prettierignore = 'gitignore', gleam = 'gleam', + vert = 'glsl', + tesc = 'glsl', + tese = 'glsl', glsl = 'glsl', + geom = 'glsl', + frag = 'glsl', + comp = 'glsl', + rgen = 'glsl', + rmiss = 'glsl', + rchit = 'glsl', + rahit = 'glsl', + rint = 'glsl', + rcall = 'glsl', gn = 'gn', gni = 'gn', gnuplot = 'gnuplot', @@ -535,6 +578,7 @@ local extension = { stm = detect.html, htt = 'httest', htb = 'httest', + http = 'http', hurl = 'hurl', hw = detect.hw, module = detect.hw, @@ -572,6 +616,7 @@ local extension = { jsx = 'javascriptreact', clp = 'jess', jgr = 'jgraph', + jinja = 'jinja', jjdescription = 'jj', j73 = 'jovial', jov = 'jovial', @@ -582,6 +627,7 @@ local extension = { json = 'json', jsonp = 'json', geojson = 'json', + mcmeta = 'json', webmanifest = 'json', ipynb = 'json', ['jupyterlab-settings'] = 'json', @@ -606,6 +652,9 @@ local extension = { kts = 'kotlin', kt = 'kotlin', ktm = 'kotlin', + sub = 'krl', + Sub = 'krl', + SUB = 'krl', ks = 'kscript', k = 'kwt', ACE = 'lace', @@ -639,6 +688,9 @@ local extension = { lt = 'lite', lite = 'lite', livemd = 'livebook', + log = detect.log, + Log = detect.log, + LOG = detect.log, lgt = 'logtalk', lotos = 'lotos', lot = detect_line1('\\contentsline', 'tex', 'lotos'), @@ -664,9 +716,8 @@ local extension = { return not (path:find('html%.m4$') or path:find('fvwm2rc')) and 'm4' or nil end, eml = 'mail', - mk = 'make', - mak = 'make', - dsp = 'make', + mk = detect.make, + mak = detect.make, page = 'mallard', map = 'map', mws = 'maple', @@ -679,7 +730,6 @@ local extension = { markdown = detect.markdown, mdown = detect.markdown, mhtml = 'mason', - comp = 'mason', mason = 'mason', master = 'master', mas = 'master', @@ -689,6 +739,8 @@ local extension = { dm3 = 'maxima', dmt = 'maxima', wxm = 'maxima', + mw = 'mediawiki', + wiki = 'mediawiki', mel = 'mel', mmd = 'mermaid', mmdc = 'mermaid', @@ -702,9 +754,17 @@ local extension = { mixal = 'mix', mm = detect.mm, nb = 'mma', + wl = 'mma', mmp = 'mmp', mms = detect.mms, + mod = detect.mod, + Mod = detect.mod, + MOD = detect.mod, DEF = 'modula2', + m3 = 'modula3', + i3 = 'modula3', + mg = 'modula3', + ig = 'modula3', lm3 = 'modula3', mojo = 'mojo', ['🔥'] = 'mojo', -- 🙄 @@ -729,6 +789,14 @@ local extension = { n1ql = 'n1ql', nql = 'n1ql', nanorc = 'nanorc', + NSA = 'natural', + NSC = 'natural', + NSG = 'natural', + NSL = 'natural', + NSM = 'natural', + NSN = 'natural', + NSP = 'natural', + NSS = 'natural', ncf = 'ncf', nginx = 'nginx', nim = 'nim', @@ -738,6 +806,15 @@ local extension = { nix = 'nix', norg = 'norg', nqc = 'nqc', + ['1'] = detect.nroff, + ['2'] = detect.nroff, + ['3'] = detect.nroff, + ['4'] = detect.nroff, + ['5'] = detect.nroff, + ['6'] = detect.nroff, + ['7'] = detect.nroff, + ['8'] = detect.nroff, + ['9'] = detect.nroff, roff = 'nroff', tmac = 'nroff', man = 'nroff', @@ -769,6 +846,14 @@ local extension = { ['or'] = 'openroad', scad = 'openscad', ovpn = 'openvpn', + opl = 'opl', + opL = 'opl', + oPl = 'opl', + oPL = 'opl', + Opl = 'opl', + OpL = 'opl', + OPl = 'opl', + OPL = 'opl', ora = 'ora', org = 'org', org_archive = 'org', @@ -800,6 +885,16 @@ local extension = { ctp = 'php', php = 'php', phpt = 'php', + php0 = 'php', + php1 = 'php', + php2 = 'php', + php3 = 'php', + php4 = 'php', + php5 = 'php', + php6 = 'php', + php7 = 'php', + php8 = 'php', + php9 = 'php', phtml = 'php', theme = 'php', pike = 'pike', @@ -832,6 +927,9 @@ local extension = { it = 'ppwiz', ih = 'ppwiz', action = 'privoxy', + prg = detect.prg, + Prg = detect.prg, + PRG = detect.prg, pc = 'proc', pdb = 'prolog', pml = 'promela', @@ -873,6 +971,19 @@ local extension = { t6 = 'raku', p6 = 'raku', raml = 'raml', + sysx = 'rapid', + sysX = 'rapid', + Sysx = 'rapid', + SysX = 'rapid', + SYSX = 'rapid', + SYSx = 'rapid', + modx = 'rapid', + modX = 'rapid', + Modx = 'rapid', + ModX = 'rapid', + MODX = 'rapid', + MODx = 'rapid', + rasi = 'rasi', rbs = 'rbs', rego = 'rego', rem = 'remind', @@ -929,6 +1040,7 @@ local extension = { rake = 'ruby', rs = 'rust', sage = 'sage', + sls = 'salt', sas = 'sas', sass = 'sass', sa = 'sather', @@ -952,6 +1064,7 @@ local extension = { ebuild = detect.bash, eclass = detect.bash, env = detect.sh, + envrc = detect.sh, ksh = detect.ksh, sh = detect.sh, mdd = 'sh', @@ -984,6 +1097,7 @@ local extension = { smt = 'smith', smithy = 'smithy', sml = 'sml', + smk = 'snakemake', spt = 'snobol4', sno = 'snobol4', sln = 'solution', @@ -1006,6 +1120,9 @@ local extension = { sqi = 'sqr', sqr = 'sqr', nut = 'squirrel', + src = detect.src, + Src = detect.src, + SRC = detect.src, s28 = 'srec', s37 = 'srec', srec = 'srec', @@ -1030,8 +1147,12 @@ local extension = { svelte = 'svelte', svg = 'svg', swift = 'swift', + swiftinterface = 'swift', swig = 'swig', swg = 'swig', + sys = detect.sys, + Sys = detect.sys, + SYS = detect.sys, svh = 'systemverilog', sv = 'systemverilog', cmm = 'trace32', @@ -1060,6 +1181,7 @@ local extension = { nls = 'tex', thm = 'tex', eps_tex = 'tex', + pdf_tex = 'tex', pygtex = 'tex', pygstyle = 'tex', clo = 'tex', @@ -1153,6 +1275,8 @@ local extension = { wbt = 'winbatch', wit = 'wit', wml = 'wml', + wsf = 'wsh', + wsc = 'wsh', wsml = 'wsml', ad = 'xdefaults', xhtml = 'xhtml', @@ -1206,6 +1330,8 @@ local extension = { z8a = 'z8a', zig = 'zig', zon = 'zig', + ziggy = 'ziggy', + ['ziggy-schema'] = 'ziggy_schema', zu = 'zimbu', zut = 'zimbutempl', zs = 'zserio', @@ -1261,8 +1387,7 @@ local extension = { ['dpkg-new'] = detect_noext, ['in'] = function(path, bufnr) if vim.fs.basename(path) ~= 'configure.in' then - local root = fn.fnamemodify(path, ':r') - return M.match({ buf = bufnr, filename = root }) + return detect_noext(path, bufnr) end end, new = detect_noext, @@ -1275,7 +1400,7 @@ local extension = { -- END EXTENSION } ---- @type vim.filetype.mapping +---@type vim.filetype.mapping local filename = { -- BEGIN FILENAME ['a2psrc'] = 'a2ps', @@ -1290,7 +1415,17 @@ local filename = { ['/.aptitude/config'] = 'aptconf', ['=tagging-method'] = 'arch', ['.arch-inventory'] = 'arch', + ['makefile.am'] = 'automake', + ['Makefile.am'] = 'automake', ['GNUmakefile.am'] = 'automake', + ['.bash_aliases'] = detect.bash, + ['.bash-aliases'] = detect.bash, + ['.bash_history'] = detect.bash, + ['.bash-history'] = detect.bash, + ['.bash_logout'] = detect.bash, + ['.bash-logout'] = detect.bash, + ['.bash_profile'] = detect.bash, + ['.bash-profile'] = detect.bash, ['named.root'] = 'bindzone', WORKSPACE = 'bzl', ['WORKSPACE.bzlmod'] = 'bzl', @@ -1373,6 +1508,7 @@ local filename = { jbuild = 'dune', ['dune-workspace'] = 'dune', ['dune-project'] = 'dune', + ['dune-file'] = 'dune', Earthfile = 'earthfile', ['.editorconfig'] = 'editorconfig', ['elinks.conf'] = 'elinks', @@ -1409,6 +1545,7 @@ local filename = { gnashpluginrc = 'gnash', gnashrc = 'gnash', ['.gnuplot_history'] = 'gnuplot', + ['goaccess.conf'] = 'goaccess', ['go.sum'] = 'gosum', ['go.work.sum'] = 'gosum', ['go.work'] = 'gowork', @@ -1445,11 +1582,15 @@ local filename = { ['ipf.conf'] = 'ipfilter', ['ipf6.conf'] = 'ipfilter', ['ipf.rules'] = 'ipfilter', + ['.bun_repl_history'] = 'javascript', ['.node_repl_history'] = 'javascript', + ['deno_history.txt'] = 'javascript', ['Pipfile.lock'] = 'json', ['.firebaserc'] = 'json', ['.prettierrc'] = 'json', ['.stylelintrc'] = 'json', + ['.lintstagedrc'] = 'json', + ['deno.lock'] = 'json', ['flake.lock'] = 'json', ['.babelrc'] = 'jsonc', ['.eslintrc'] = 'jsonc', @@ -1461,9 +1602,14 @@ local filename = { ['.swrc'] = 'jsonc', ['.vsconfig'] = 'jsonc', ['.justfile'] = 'just', + ['justfile'] = 'just', + ['Justfile'] = 'just', Kconfig = 'kconfig', ['Kconfig.debug'] = 'kconfig', ['Config.in'] = 'kconfig', + ['ldaprc'] = 'ldapconf', + ['.ldaprc'] = 'ldapconf', + ['ldap.conf'] = 'ldapconf', ['lftp.conf'] = 'lftp', ['.lftprc'] = 'lftp', ['/.libao'] = 'libao', @@ -1509,6 +1655,8 @@ local filename = { mrxvtrc = 'mrxvtrc', ['.mrxvtrc'] = 'mrxvtrc', ['.msmtprc'] = 'msmtp', + ['Muttngrc'] = 'muttrc', + ['Muttrc'] = 'muttrc', ['.mysql_history'] = 'mysql', ['/etc/nanorc'] = 'nanorc', Neomuttrc = 'neomuttrc', @@ -1520,6 +1668,7 @@ local filename = { ['octave.conf'] = 'octave', ['.ondirrc'] = 'ondir', opam = 'opam', + ['opam.locked'] = 'opam', ['pacman.log'] = 'pacmanlog', ['/etc/pam.conf'] = 'pamconf', ['pam_env.conf'] = 'pamenv', @@ -1532,6 +1681,9 @@ local filename = { ['/etc/shadow-'] = 'passwd', ['/etc/shadow'] = 'passwd', ['/etc/passwd.edit'] = 'passwd', + ['.gitolite.rc'] = 'perl', + ['gitolite.rc'] = 'perl', + ['example.gitolite.rc'] = 'perl', ['latexmkrc'] = 'perl', ['.latexmkrc'] = 'perl', ['pf.conf'] = 'pf', @@ -1587,6 +1739,10 @@ local filename = { irbrc = 'ruby', ['.irb_history'] = 'ruby', irb_history = 'ruby', + ['rakefile'] = 'ruby', + ['Rakefile'] = 'ruby', + ['rantfile'] = 'ruby', + ['Rantfile'] = 'ruby', Vagrantfile = 'ruby', ['smb.conf'] = 'samba', screenrc = 'screen', @@ -1597,6 +1753,8 @@ local filename = { ['/etc/serial.conf'] = 'setserial', ['/etc/udev/cdsymlinks.conf'] = 'sh', ['.ash_history'] = 'sh', + ['.devscripts'] = 'sh', + ['devscripts.conf'] = 'sh', ['makepkg.conf'] = 'sh', ['.makepkg.conf'] = 'sh', ['user-dirs.dirs'] = 'sh', @@ -1618,6 +1776,7 @@ local filename = { ['/etc/slp.spi'] = 'slpspi', ['.slrnrc'] = 'slrnrc', ['sendmail.cf'] = 'sm', + Snakefile = 'snakemake', ['.sqlite_history'] = 'sql', ['squid.conf'] = 'squid', ['ssh_config'] = 'sshconfig', @@ -1637,7 +1796,7 @@ local filename = { ['.xsdbcmdhistory'] = 'tcl', ['texmf.cnf'] = 'texmf', COPYING = 'text', - README = 'text', + README = detect_seq(detect.haredoc, 'text'), LICENSE = 'text', AUTHORS = 'text', tfrc = 'tf', @@ -1703,528 +1862,568 @@ local filename = { } -- Re-use closures as much as possible -local detect_apache = starsetf('apache') -local detect_muttrc = starsetf('muttrc') -local detect_neomuttrc = starsetf('neomuttrc') +local detect_apache_diretc = starsetf('apache', { parent = '/etc/' }) +local detect_apache_dotconf = starsetf('apache', { parent = '%.conf' }) +local detect_muttrc = starsetf('muttrc', { parent = 'utt' }) +local detect_neomuttrc = starsetf('neomuttrc', { parent = 'utt' }) +local detect_xkb = starsetf('xkb', { parent = '/usr/' }) ---- @type vim.filetype.mapping +---@type table<string,vim.filetype.mapping> local pattern = { -- BEGIN PATTERN - ['.*/etc/a2ps/.*%.cfg'] = 'a2ps', - ['.*/etc/a2ps%.cfg'] = 'a2ps', - ['.*/usr/share/alsa/alsa%.conf'] = 'alsaconf', - ['.*/etc/asound%.conf'] = 'alsaconf', - ['.*/etc/apache2/sites%-.*/.*%.com'] = 'apache', - ['.*/etc/httpd/.*%.conf'] = 'apache', - ['.*/etc/apache2/.*%.conf.*'] = detect_apache, - ['.*/etc/apache2/conf%..*/.*'] = detect_apache, - ['.*/etc/apache2/mods%-.*/.*'] = detect_apache, - ['.*/etc/apache2/sites%-.*/.*'] = detect_apache, - ['access%.conf.*'] = detect_apache, - ['apache%.conf.*'] = detect_apache, - ['apache2%.conf.*'] = detect_apache, - ['httpd%.conf.*'] = detect_apache, - ['srm%.conf.*'] = detect_apache, - ['.*/etc/httpd/conf%..*/.*'] = detect_apache, - ['.*/etc/httpd/conf%.d/.*%.conf.*'] = detect_apache, - ['.*/etc/httpd/mods%-.*/.*'] = detect_apache, - ['.*/etc/httpd/sites%-.*/.*'] = detect_apache, - ['.*/etc/proftpd/.*%.conf.*'] = starsetf('apachestyle'), - ['.*/etc/proftpd/conf%..*/.*'] = starsetf('apachestyle'), - ['proftpd%.conf.*'] = starsetf('apachestyle'), - ['.*asterisk/.*%.conf.*'] = starsetf('asterisk'), - ['.*asterisk.*/.*voicemail%.conf.*'] = starsetf('asteriskvm'), - ['.*/%.aptitude/config'] = 'aptconf', - ['.*%.[aA]'] = detect.asm, - ['.*%.[sS]'] = detect.asm, - ['[mM]akefile%.am'] = 'automake', - ['.*/bind/db%..*'] = starsetf('bindzone'), - ['.*/named/db%..*'] = starsetf('bindzone'), - ['.*/build/conf/.*%.conf'] = 'bitbake', - ['.*/meta/conf/.*%.conf'] = 'bitbake', - ['.*/meta%-.*/conf/.*%.conf'] = 'bitbake', - ['.*%.blade%.php'] = 'blade', - ['bzr_log%..*'] = 'bzr', - ['.*enlightenment/.*%.cfg'] = 'c', - ['.*/%.cabal/config'] = 'cabalconfig', - ['.*/cabal/config'] = 'cabalconfig', - ['cabal%.project%..*'] = starsetf('cabalproject'), - ['.*/%.calendar/.*'] = starsetf('calendar'), - ['.*/share/calendar/.*/calendar%..*'] = starsetf('calendar'), - ['.*/share/calendar/calendar%..*'] = starsetf('calendar'), - ['sgml%.catalog.*'] = starsetf('catalog'), - ['.*/etc/defaults/cdrdao'] = 'cdrdaoconf', - ['.*/etc/cdrdao%.conf'] = 'cdrdaoconf', - ['.*/etc/default/cdrdao'] = 'cdrdaoconf', - ['.*hgrc'] = 'cfg', - ['.*%.[Cc][Ff][Gg]'] = { - detect.cfg, - -- Decrease priority to avoid conflicts with more specific patterns - -- such as '.*/etc/a2ps/.*%.cfg', '.*enlightenment/.*%.cfg', etc. - { priority = -1 }, + ['/debian/'] = { + ['/debian/changelog$'] = 'debchangelog', + ['/debian/control$'] = 'debcontrol', + ['/debian/copyright$'] = 'debcopyright', + ['/debian/patches/'] = detect.dep3patch, + }, + ['/etc/'] = { + ['/etc/a2ps/.*%.cfg$'] = 'a2ps', + ['/etc/a2ps%.cfg$'] = 'a2ps', + ['/etc/asound%.conf$'] = 'alsaconf', + ['/etc/apache2/sites%-.*/.*%.com$'] = 'apache', + ['/etc/httpd/.*%.conf$'] = 'apache', + ['/etc/apache2/.*%.conf'] = detect_apache_diretc, + ['/etc/apache2/conf%..*/'] = detect_apache_diretc, + ['/etc/apache2/mods%-.*/'] = detect_apache_diretc, + ['/etc/apache2/sites%-.*/'] = detect_apache_diretc, + ['/etc/httpd/conf%..*/'] = detect_apache_diretc, + ['/etc/httpd/conf%.d/.*%.conf'] = detect_apache_diretc, + ['/etc/httpd/mods%-.*/'] = detect_apache_diretc, + ['/etc/httpd/sites%-.*/'] = detect_apache_diretc, + ['/etc/proftpd/.*%.conf'] = starsetf('apachestyle'), + ['/etc/proftpd/conf%..*/'] = starsetf('apachestyle'), + ['/etc/cdrdao%.conf$'] = 'cdrdaoconf', + ['/etc/default/cdrdao$'] = 'cdrdaoconf', + ['/etc/defaults/cdrdao$'] = 'cdrdaoconf', + ['/etc/translate%-shell$'] = 'clojure', + ['/etc/hostname%.'] = starsetf('config'), + ['/etc/cron%.d/'] = starsetf('crontab'), + ['/etc/apt/sources%.list%.d/.*%.sources$'] = 'deb822sources', + ['/etc/apt/sources%.list%.d/.*%.list$'] = 'debsources', + ['/etc/apt/sources%.list$'] = 'debsources', + ['/etc/DIR_COLORS$'] = 'dircolors', + ['/etc/dnsmasq%.conf$'] = 'dnsmasq', + ['/etc/dnsmasq%.d/'] = starsetf('dnsmasq'), + ['/etc/yum%.conf$'] = 'dosini', + ['/etc/yum%.repos%.d/'] = starsetf('dosini'), + ['/etc/gitconfig%.d/'] = starsetf('gitconfig'), + ['/etc/gitconfig$'] = 'gitconfig', + ['/etc/gitattributes$'] = 'gitattributes', + ['/etc/group$'] = 'group', + ['/etc/group%-$'] = 'group', + ['/etc/group%.edit$'] = 'group', + ['/etc/gshadow%-$'] = 'group', + ['/etc/gshadow%.edit$'] = 'group', + ['/etc/gshadow$'] = 'group', + ['/etc/grub%.conf$'] = 'grub', + ['/etc/host%.conf$'] = 'hostconf', + ['/etc/hosts%.allow$'] = 'hostsaccess', + ['/etc/hosts%.deny$'] = 'hostsaccess', + ['/etc/initng/.*/.*%.i$'] = 'initng', + ['/etc/libao%.conf$'] = 'libao', + ['/etc/.*limits%.conf$'] = 'limits', + ['/etc/.*limits%.d/.*%.conf$'] = 'limits', + ['/etc/limits$'] = 'limits', + ['/etc/logcheck/.*%.d.*/'] = starsetf('logcheck'), + ['/etc/login%.access$'] = 'loginaccess', + ['/etc/login%.defs$'] = 'logindefs', + ['/etc/aliases$'] = 'mailaliases', + ['/etc/mail/aliases$'] = 'mailaliases', + ['/etc/man%.conf$'] = 'manconf', + ['/etc/conf%.modules$'] = 'modconf', + ['/etc/modprobe%.'] = starsetf('modconf'), + ['/etc/modules%.conf$'] = 'modconf', + ['/etc/modules$'] = 'modconf', + ['/etc/modutils/'] = starsetf(function(path, bufnr) + if fn.executable(fn.expand(path)) ~= 1 then + return 'modconf' + end + end), + ['/etc/Muttrc%.d/'] = starsetf('muttrc'), + ['/etc/nanorc$'] = 'nanorc', + ['/etc/nginx/'] = 'nginx', + ['/etc/pam%.conf$'] = 'pamconf', + ['/etc/pam%.d/'] = starsetf('pamconf'), + ['/etc/passwd%-$'] = 'passwd', + ['/etc/shadow$'] = 'passwd', + ['/etc/shadow%.edit$'] = 'passwd', + ['/etc/passwd$'] = 'passwd', + ['/etc/passwd%.edit$'] = 'passwd', + ['/etc/shadow%-$'] = 'passwd', + ['/etc/pinforc$'] = 'pinfo', + ['/etc/protocols$'] = 'protocols', + ['/etc/sensors%.d/[^.]'] = starsetf('sensors'), + ['/etc/sensors%.conf$'] = 'sensors', + ['/etc/sensors3%.conf$'] = 'sensors', + ['/etc/services$'] = 'services', + ['/etc/serial%.conf$'] = 'setserial', + ['/etc/udev/cdsymlinks%.conf$'] = 'sh', + ['/etc/profile$'] = detect.sh, + ['/etc/slp%.conf$'] = 'slpconf', + ['/etc/slp%.reg$'] = 'slpreg', + ['/etc/slp%.spi$'] = 'slpspi', + ['/etc/sudoers%.d/'] = starsetf('sudoers'), + ['/etc/ssh/ssh_config%.d/.*%.conf$'] = 'sshconfig', + ['/etc/ssh/sshd_config%.d/.*%.conf$'] = 'sshdconfig', + ['/etc/sudoers$'] = 'sudoers', + ['/etc/sysctl%.conf$'] = 'sysctl', + ['/etc/sysctl%.d/.*%.conf$'] = 'sysctl', + ['/etc/systemd/.*%.conf%.d/.*%.conf$'] = 'systemd', + ['/etc/systemd/system/.*%.d/.*%.conf$'] = 'systemd', + ['/etc/systemd/system/.*%.d/%.#'] = 'systemd', + ['/etc/systemd/system/%.#'] = 'systemd', + ['/etc/config/'] = starsetf(detect.uci), + ['/etc/udev/udev%.conf$'] = 'udevconf', + ['/etc/udev/permissions%.d/.*%.permissions$'] = 'udevperm', + ['/etc/updatedb%.conf$'] = 'updatedb', + ['/etc/init/.*%.conf$'] = 'upstart', + ['/etc/init/.*%.override$'] = 'upstart', + ['/etc/xinetd%.conf$'] = 'xinetd', + ['/etc/xinetd%.d/'] = starsetf('xinetd'), + ['/etc/blkid%.tab%.old$'] = 'xml', + ['/etc/blkid%.tab$'] = 'xml', + ['/etc/xdg/menus/.*%.menu$'] = 'xml', + ['/etc/zprofile$'] = 'zsh', }, - ['[cC]hange[lL]og.*'] = starsetf(detect.changelog), - ['.*%.%.ch'] = 'chill', - ['.*/etc/translate%-shell'] = 'clojure', - ['.*%.cmake%.in'] = 'cmake', - -- */cmus/rc and */.cmus/rc - ['.*/%.?cmus/rc'] = 'cmusrc', - -- */cmus/*.theme and */.cmus/*.theme - ['.*/%.?cmus/.*%.theme'] = 'cmusrc', - ['.*/%.cmus/autosave'] = 'cmusrc', - ['.*/%.cmus/command%-history'] = 'cmusrc', - ['.*/etc/hostname%..*'] = starsetf('config'), - ['crontab%..*'] = starsetf('crontab'), - ['.*/etc/cron%.d/.*'] = starsetf('crontab'), - ['%.cshrc.*'] = detect.csh, - ['%.login.*'] = detect.csh, - ['cvs%d+'] = 'cvs', - ['.*%.[Dd][Aa][Tt]'] = detect.dat, - ['.*/debian/patches/.*'] = detect.dep3patch, - ['.*/etc/dnsmasq%.d/.*'] = starsetf('dnsmasq'), - ['Containerfile%..*'] = starsetf('dockerfile'), - ['Dockerfile%..*'] = starsetf('dockerfile'), - ['.*/etc/yum%.repos%.d/.*'] = starsetf('dosini'), - ['drac%..*'] = starsetf('dracula'), - ['.*/debian/changelog'] = 'debchangelog', - ['.*/debian/control'] = 'debcontrol', - ['.*/debian/copyright'] = 'debcopyright', - ['.*/etc/apt/sources%.list%.d/.*%.list'] = 'debsources', - ['.*/etc/apt/sources%.list'] = 'debsources', - ['.*/etc/apt/sources%.list%.d/.*%.sources'] = 'deb822sources', - ['.*%.directory'] = 'desktop', - ['.*%.desktop'] = 'desktop', - ['dictd.*%.conf'] = 'dictdconf', - ['.*/etc/DIR_COLORS'] = 'dircolors', - ['.*/etc/dnsmasq%.conf'] = 'dnsmasq', - ['php%.ini%-.*'] = 'dosini', - ['.*/%.aws/config'] = 'confini', - ['.*/%.aws/credentials'] = 'confini', - ['.*/etc/yum%.conf'] = 'dosini', - ['.*/lxqt/.*%.conf'] = 'dosini', - ['.*/screengrab/.*%.conf'] = 'dosini', - ['.*/bpython/config'] = 'dosini', - ['.*/mypy/config'] = 'dosini', - ['.*/flatpak/repo/config'] = 'dosini', - ['.*lvs'] = 'dracula', - ['.*lpe'] = 'dracula', - ['.*/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, - ['[a-zA-Z].*Properties%..*'] = detect.foam, - ['.*Transport%..*'] = detect.foam, - ['.*/constant/g'] = detect.foam, - ['.*/0/.*'] = detect.foam, - ['.*/0%.orig/.*'] = detect.foam, - ['.*/%.fvwm/.*'] = starsetf('fvwm'), - ['.*fvwmrc.*'] = starsetf(detect.fvwm_v1), - ['.*fvwm95.*%.hook'] = starsetf(detect.fvwm_v1), - ['.*fvwm2rc.*'] = starsetf(detect.fvwm_v2), - ['.*/tmp/lltmp.*'] = starsetf('gedcom'), - ['.*/etc/gitconfig%.d/.*'] = starsetf('gitconfig'), - ['.*/gitolite%-admin/conf/.*'] = starsetf('gitolite'), - ['tmac%..*'] = starsetf('nroff'), - ['.*/%.gitconfig%.d/.*'] = starsetf('gitconfig'), - ['.*%.git/.*'] = { - detect.git, - -- Decrease priority to run after simple pattern checks - { priority = -1 }, + ['/log/'] = { + ['/log/auth%.crit$'] = 'messages', + ['/log/auth%.err$'] = 'messages', + ['/log/auth%.info$'] = 'messages', + ['/log/auth%.log$'] = 'messages', + ['/log/auth%.notice$'] = 'messages', + ['/log/auth%.warn$'] = 'messages', + ['/log/auth$'] = 'messages', + ['/log/cron%.crit$'] = 'messages', + ['/log/cron%.err$'] = 'messages', + ['/log/cron%.info$'] = 'messages', + ['/log/cron%.log$'] = 'messages', + ['/log/cron%.notice$'] = 'messages', + ['/log/cron%.warn$'] = 'messages', + ['/log/cron$'] = 'messages', + ['/log/daemon%.crit$'] = 'messages', + ['/log/daemon%.err$'] = 'messages', + ['/log/daemon%.info$'] = 'messages', + ['/log/daemon%.log$'] = 'messages', + ['/log/daemon%.notice$'] = 'messages', + ['/log/daemon%.warn$'] = 'messages', + ['/log/daemon$'] = 'messages', + ['/log/debug%.crit$'] = 'messages', + ['/log/debug%.err$'] = 'messages', + ['/log/debug%.info$'] = 'messages', + ['/log/debug%.log$'] = 'messages', + ['/log/debug%.notice$'] = 'messages', + ['/log/debug%.warn$'] = 'messages', + ['/log/debug$'] = 'messages', + ['/log/kern%.crit$'] = 'messages', + ['/log/kern%.err$'] = 'messages', + ['/log/kern%.info$'] = 'messages', + ['/log/kern%.log$'] = 'messages', + ['/log/kern%.notice$'] = 'messages', + ['/log/kern%.warn$'] = 'messages', + ['/log/kern$'] = 'messages', + ['/log/lpr%.crit$'] = 'messages', + ['/log/lpr%.err$'] = 'messages', + ['/log/lpr%.info$'] = 'messages', + ['/log/lpr%.log$'] = 'messages', + ['/log/lpr%.notice$'] = 'messages', + ['/log/lpr%.warn$'] = 'messages', + ['/log/lpr$'] = 'messages', + ['/log/mail%.crit$'] = 'messages', + ['/log/mail%.err$'] = 'messages', + ['/log/mail%.info$'] = 'messages', + ['/log/mail%.log$'] = 'messages', + ['/log/mail%.notice$'] = 'messages', + ['/log/mail%.warn$'] = 'messages', + ['/log/mail$'] = 'messages', + ['/log/messages%.crit$'] = 'messages', + ['/log/messages%.err$'] = 'messages', + ['/log/messages%.info$'] = 'messages', + ['/log/messages%.log$'] = 'messages', + ['/log/messages%.notice$'] = 'messages', + ['/log/messages%.warn$'] = 'messages', + ['/log/messages$'] = 'messages', + ['/log/news/news%.crit$'] = 'messages', + ['/log/news/news%.err$'] = 'messages', + ['/log/news/news%.info$'] = 'messages', + ['/log/news/news%.log$'] = 'messages', + ['/log/news/news%.notice$'] = 'messages', + ['/log/news/news%.warn$'] = 'messages', + ['/log/news/news$'] = 'messages', + ['/log/syslog%.crit$'] = 'messages', + ['/log/syslog%.err$'] = 'messages', + ['/log/syslog%.info$'] = 'messages', + ['/log/syslog%.log$'] = 'messages', + ['/log/syslog%.notice$'] = 'messages', + ['/log/syslog%.warn$'] = 'messages', + ['/log/syslog$'] = 'messages', + ['/log/user%.crit$'] = 'messages', + ['/log/user%.err$'] = 'messages', + ['/log/user%.info$'] = 'messages', + ['/log/user%.log$'] = 'messages', + ['/log/user%.notice$'] = 'messages', + ['/log/user%.warn$'] = 'messages', + ['/log/user$'] = 'messages', + }, + ['/systemd/'] = { + ['/%.config/systemd/user/%.#'] = 'systemd', + ['/%.config/systemd/user/.*%.d/%.#'] = 'systemd', + ['/%.config/systemd/user/.*%.d/.*%.conf$'] = 'systemd', + ['/systemd/.*%.automount$'] = 'systemd', + ['/systemd/.*%.dnssd$'] = 'systemd', + ['/systemd/.*%.link$'] = 'systemd', + ['/systemd/.*%.mount$'] = 'systemd', + ['/systemd/.*%.netdev$'] = 'systemd', + ['/systemd/.*%.network$'] = 'systemd', + ['/systemd/.*%.nspawn$'] = 'systemd', + ['/systemd/.*%.path$'] = 'systemd', + ['/systemd/.*%.service$'] = 'systemd', + ['/systemd/.*%.slice$'] = 'systemd', + ['/systemd/.*%.socket$'] = 'systemd', + ['/systemd/.*%.swap$'] = 'systemd', + ['/systemd/.*%.target$'] = 'systemd', + ['/systemd/.*%.timer$'] = 'systemd', + }, + ['/usr/'] = { + ['/usr/share/alsa/alsa%.conf$'] = 'alsaconf', + ['/usr/.*/gnupg/options%.skel$'] = 'gpg', + ['/usr/share/upstart/.*%.conf$'] = 'upstart', + ['/usr/share/upstart/.*%.override$'] = 'upstart', + ['/usr/share/X11/xkb/compat/'] = detect_xkb, + ['/usr/share/X11/xkb/geometry/'] = detect_xkb, + ['/usr/share/X11/xkb/keycodes/'] = detect_xkb, + ['/usr/share/X11/xkb/symbols/'] = detect_xkb, + ['/usr/share/X11/xkb/types/'] = detect_xkb, + }, + ['/var/'] = { + ['/var/backups/group%.bak$'] = 'group', + ['/var/backups/gshadow%.bak$'] = 'group', + ['/var/backups/passwd%.bak$'] = 'passwd', + ['/var/backups/shadow%.bak$'] = 'passwd', + }, + ['/conf'] = { + ['/%.aptitude/config$'] = 'aptconf', + ['/build/conf/.*%.conf$'] = 'bitbake', + ['/meta%-.*/conf/.*%.conf$'] = 'bitbake', + ['/meta/conf/.*%.conf$'] = 'bitbake', + ['/%.cabal/config$'] = 'cabalconfig', + ['/cabal/config$'] = 'cabalconfig', + ['/%.aws/config$'] = 'confini', + ['/bpython/config$'] = 'dosini', + ['/flatpak/repo/config$'] = 'dosini', + ['/mypy/config$'] = 'dosini', + ['^${HOME}/%.config/notmuch/.*/config$'] = 'dosini', + ['^${XDG_CONFIG_HOME}/notmuch/.*/config$'] = 'dosini', + ['^${XDG_CONFIG_HOME}/git/config$'] = 'gitconfig', + ['%.git/config%.worktree$'] = 'gitconfig', + ['%.git/config$'] = 'gitconfig', + ['%.git/modules/.*/config$'] = 'gitconfig', + ['%.git/modules/config$'] = 'gitconfig', + ['%.git/worktrees/.*/config%.worktree$'] = 'gitconfig', + ['/%.config/git/config$'] = 'gitconfig', + ['/gitolite%-admin/conf/'] = starsetf('gitolite'), + ['/%.i3/config$'] = 'i3config', + ['/i3/config$'] = 'i3config', + ['/supertux2/config$'] = 'lisp', + ['/%.mplayer/config$'] = 'mplayerconf', + ['/neofetch/config%.conf$'] = 'sh', + ['/%.ssh/config$'] = 'sshconfig', + ['/%.sway/config$'] = 'swayconfig', + ['/sway/config$'] = 'swayconfig', + ['/%.cargo/config$'] = 'toml', + ['/%.bundle/config$'] = 'yaml', + }, + ['/%.'] = { + ['/%.aws/credentials$'] = 'confini', + ['/%.gitconfig%.d/'] = starsetf('gitconfig'), + ['/%.gnupg/gpg%.conf$'] = 'gpg', + ['/%.gnupg/options$'] = 'gpg', + ['/%.icewm/menu$'] = 'icemenu', + ['/%.libao$'] = 'libao', + ['/%.pinforc$'] = 'pinfo', + ['/%.cargo/credentials$'] = 'toml', + ['/%.init/.*%.override$'] = 'upstart', + }, + ['calendar/'] = { + ['/%.calendar/'] = starsetf('calendar'), + ['/share/calendar/.*/calendar%.'] = starsetf('calendar'), + ['/share/calendar/calendar%.'] = starsetf('calendar'), + }, + ['cmus/'] = { + -- */cmus/*.theme and */.cmus/*.theme + ['/%.?cmus/.*%.theme$'] = 'cmusrc', + -- */cmus/rc and */.cmus/rc + ['/%.?cmus/rc$'] = 'cmusrc', + ['/%.cmus/autosave$'] = 'cmusrc', + ['/%.cmus/command%-history$'] = 'cmusrc', + }, + ['git/'] = { + ['%.git/'] = { + detect.git, + -- Decrease priority to run after simple pattern checks + { priority = -1 }, + }, + ['^${XDG_CONFIG_HOME}/git/attributes$'] = 'gitattributes', + ['%.git/info/attributes$'] = 'gitattributes', + ['/%.config/git/attributes$'] = 'gitattributes', + ['^${XDG_CONFIG_HOME}/git/ignore$'] = 'gitignore', + ['%.git/info/exclude$'] = 'gitignore', + ['/%.config/git/ignore$'] = 'gitignore', + }, + ['%.cfg'] = { + ['enlightenment/.*%.cfg$'] = 'c', + ['Eterm/.*%.cfg$'] = 'eterm', + ['baseq[2-3]/.*%.cfg$'] = 'quake', + ['id1/.*%.cfg$'] = 'quake', + ['quake[1-3]/.*%.cfg$'] = 'quake', + ['/tex/latex/.*%.cfg$'] = 'tex', + }, + ['%.conf'] = { + ['^proftpd%.conf'] = starsetf('apachestyle'), + ['^access%.conf'] = detect_apache_dotconf, + ['^apache%.conf'] = detect_apache_dotconf, + ['^apache2%.conf'] = detect_apache_dotconf, + ['^httpd%.conf'] = detect_apache_dotconf, + ['^srm%.conf'] = detect_apache_dotconf, + ['asterisk/.*%.conf'] = starsetf('asterisk'), + ['asterisk.*/.*voicemail%.conf'] = starsetf('asteriskvm'), + ['^dictd.*%.conf$'] = 'dictdconf', + ['/lxqt/.*%.conf$'] = 'dosini', + ['/screengrab/.*%.conf$'] = 'dosini', + ['^${GNUPGHOME}/gpg%.conf$'] = 'gpg', + ['/boot/grub/grub%.conf$'] = 'grub', + ['^lilo%.conf'] = starsetf('lilo'), + ['^named.*%.conf$'] = 'named', + ['^rndc.*%.conf$'] = 'named', + ['/openvpn/.*/.*%.conf$'] = 'openvpn', + ['/%.ssh/.*%.conf$'] = 'sshconfig', + ['^%.?tmux.*%.conf$'] = 'tmux', + ['^%.?tmux.*%.conf'] = { 'tmux', { priority = -1 } }, + ['/%.config/upstart/.*%.conf$'] = 'upstart', + ['/%.config/upstart/.*%.override$'] = 'upstart', + ['/%.init/.*%.conf$'] = 'upstart', + ['/xorg%.conf%.d/.*%.conf$'] = detect.xfree86_v4, + }, + ['sst%.meta'] = { + ['%.%-sst%.meta$'] = 'sisu', + ['%._sst%.meta$'] = 'sisu', + ['%.sst%.meta$'] = 'sisu', + }, + ['file'] = { + ['^Containerfile%.'] = starsetf('dockerfile'), + ['^Dockerfile%.'] = starsetf('dockerfile'), + ['[mM]akefile$'] = detect.make, + ['^[mM]akefile'] = starsetf('make'), + ['^[rR]akefile'] = starsetf('ruby'), + ['^%.profile'] = detect.sh, + }, + ['fvwm'] = { + ['/%.fvwm/'] = starsetf('fvwm'), + ['fvwmrc'] = starsetf(detect.fvwm_v1), + ['fvwm95.*%.hook$'] = starsetf(detect.fvwm_v1), + ['fvwm2rc'] = starsetf(detect.fvwm_v2), + }, + ['nginx'] = { + ['/nginx/.*%.conf$'] = 'nginx', + ['/usr/local/nginx/conf/'] = 'nginx', + ['nginx%.conf$'] = 'nginx', + ['^nginx.*%.conf$'] = 'nginx', + }, + ['require'] = { + ['%-requirements%.txt$'] = 'requirements', + ['^requirements/.*%.txt$'] = 'requirements', + ['^requires/.*%.txt$'] = 'requirements', + }, + ['s6'] = { + ['s6.*/down$'] = 'execline', + ['s6.*/finish$'] = 'execline', + ['s6.*/run$'] = 'execline', + ['s6.*/up$'] = 'execline', + ['^s6%-'] = 'execline', + }, + ['utt'] = { + ['^mutt%-.*%-%w+$'] = 'mail', + ['^mutt' .. string.rep('[%w_-]', 6) .. '$'] = 'mail', + ['^muttng%-.*%-%w+$'] = 'mail', + ['^neomutt%-.*%-%w+$'] = 'mail', + ['^neomutt' .. string.rep('[%w_-]', 6) .. '$'] = 'mail', + -- muttngrc* and .muttngrc* + ['^%.?muttngrc'] = detect_muttrc, + -- muttrc* and .muttrc* + ['^%.?muttrc'] = detect_muttrc, + ['/%.mutt/muttrc'] = detect_muttrc, + ['/%.muttng/muttngrc'] = detect_muttrc, + ['/%.muttng/muttrc'] = detect_muttrc, + ['^Muttngrc'] = detect_muttrc, + ['^Muttrc'] = detect_muttrc, + -- neomuttrc* and .neomuttrc* + ['^%.?neomuttrc'] = detect_neomuttrc, + ['/%.neomutt/neomuttrc'] = detect_neomuttrc, + ['^Neomuttrc'] = detect_neomuttrc, + }, + ['^%.'] = { + ['^%.cshrc'] = detect.csh, + ['^%.login'] = detect.csh, + ['^%.notmuch%-config%.'] = 'dosini', + ['^%.gitsendemail%.msg%.......$'] = 'gitsendemail', + ['^%.kshrc'] = detect.ksh, + ['^%.article%.%d+$'] = 'mail', + ['^%.letter%.%d+$'] = 'mail', + ['^%.reminders'] = starsetf('remind'), + ['^%.tcshrc'] = detect.tcsh, + ['^%.zcompdump'] = starsetf('zsh'), + }, + ['proj%.user$'] = { + ['%.csproj%.user$'] = 'xml', + ['%.fsproj%.user$'] = 'xml', + ['%.vbproj%.user$'] = 'xml', + }, + [''] = { + ['^bash%-fc[%-%.]'] = detect.bash, + ['/bind/db%.'] = starsetf('bindzone'), + ['/named/db%.'] = starsetf('bindzone'), + ['%.blade%.php$'] = 'blade', + ['^bzr_log%.'] = 'bzr', + ['^cabal%.project%.'] = starsetf('cabalproject'), + ['^sgml%.catalog'] = starsetf('catalog'), + ['hgrc$'] = 'cfg', + ['^[cC]hange[lL]og'] = starsetf(detect.changelog), + ['%.%.ch$'] = 'chill', + ['%.cmake%.in$'] = 'cmake', + ['^crontab%.'] = starsetf('crontab'), + ['^cvs%d+$'] = 'cvs', + ['^php%.ini%-'] = 'dosini', + ['^drac%.'] = starsetf('dracula'), + ['/dtrace/.*%.d$'] = 'dtrace', + ['esmtprc$'] = 'esmtprc', + ['/0%.orig/'] = detect.foam, + ['/0/'] = detect.foam, + ['/constant/g$'] = detect.foam, + ['Transport%.'] = detect.foam, + ['^[a-zA-Z0-9].*Dict%.'] = detect.foam, + ['^[a-zA-Z0-9].*Dict$'] = detect.foam, + ['^[a-zA-Z].*Properties%.'] = detect.foam, + ['^[a-zA-Z].*Properties$'] = detect.foam, + ['/tmp/lltmp'] = starsetf('gedcom'), + ['^gkrellmrc_.$'] = 'gkrellmrc', + ['^${GNUPGHOME}/options$'] = 'gpg', + ['/boot/grub/menu%.lst$'] = 'grub', + -- gtkrc* and .gtkrc* + ['^%.?gtkrc'] = starsetf('gtkrc'), + ['^${VIMRUNTIME}/doc/.*%.txt$'] = 'help', + ['^hg%-editor%-.*%.txt$'] = 'hgcommit', + ['%.html%.m4$'] = 'htmlm4', + ['^JAM.*%.'] = starsetf('jam'), + ['^Prl.*%.'] = starsetf('jam'), + ['%.properties_..$'] = 'jproperties', + ['%.properties_.._..$'] = 'jproperties', + ['%.properties_.._.._'] = starsetf('jproperties'), + ['^org%.eclipse%..*%.prefs$'] = 'jproperties', + ['^[jt]sconfig.*%.json$'] = 'jsonc', + ['^Config%.in%.'] = starsetf('kconfig'), + ['^Kconfig%.'] = starsetf('kconfig'), + ['/ldscripts/'] = 'ld', + ['lftp/rc$'] = 'lftp', + ['/LiteStep/.*/.*%.rc$'] = 'litestep', + ['^/tmp/SLRN[0-9A-Z.]+$'] = 'mail', + ['^ae%d+%.txt$'] = 'mail', + ['^pico%.%d+$'] = 'mail', + ['^reportbug%-'] = starsetf('mail'), + ['^snd%.%d+$'] = 'mail', + ['^rndc.*%.key$'] = 'named', + ['^tmac%.'] = starsetf('nroff'), + ['%.ml%.cppo$'] = 'ocaml', + ['%.mli%.cppo$'] = 'ocaml', + ['/octave/history$'] = 'octave', + ['%.opam%.locked$'] = 'opam', + ['%.opam%.template$'] = 'opam', + ['printcap'] = starsetf(function(path, bufnr) + return require('vim.filetype.detect').printcap('print') + end), + ['/queries/.*%.scm$'] = 'query', -- treesitter queries (Neovim only) + [',v$'] = 'rcs', + ['^svn%-commit.*%.tmp$'] = 'svn', + ['%.swift%.gyb$'] = 'swiftgyb', + ['termcap'] = starsetf(function(path, bufnr) + return require('vim.filetype.detect').printcap('term') + end), + ['%.t%.html$'] = 'tilde', + ['%.vhdl_[0-9]'] = starsetf('vhdl'), + ['vimrc'] = starsetf('vim'), + ['/Xresources/'] = starsetf('xdefaults'), + ['/app%-defaults/'] = starsetf('xdefaults'), + ['^Xresources'] = starsetf('xdefaults'), + -- Increase priority to run before the pattern below + ['^XF86Config%-4'] = starsetf(detect.xfree86_v4, { priority = -math.huge + 1 }), + ['^XF86Config'] = starsetf(detect.xfree86_v3), + ['Xmodmap$'] = 'xmodmap', + ['xmodmap'] = starsetf('xmodmap'), + -- .zlog* and zlog* + ['^%.?zlog'] = starsetf('zsh'), + -- .zsh* and zsh* + ['^%.?zsh'] = starsetf('zsh'), + -- Ignored extension + ['~$'] = function(path, bufnr) + local short = path:gsub('~+$', '', 1) + if path ~= short and short ~= '' then + return M.match({ buf = bufnr, filename = fn.fnameescape(short) }) + end + end, }, - ['.*%.git/modules/.*/config'] = 'gitconfig', - ['.*%.git/modules/config'] = 'gitconfig', - ['.*%.git/config'] = 'gitconfig', - ['.*/etc/gitconfig'] = 'gitconfig', - ['.*/%.config/git/config'] = 'gitconfig', - ['.*%.git/config%.worktree'] = 'gitconfig', - ['.*%.git/worktrees/.*/config%.worktree'] = 'gitconfig', - ['${XDG_CONFIG_HOME}/git/config'] = 'gitconfig', - ['.*%.git/info/attributes'] = 'gitattributes', - ['.*/etc/gitattributes'] = 'gitattributes', - ['.*/%.config/git/attributes'] = 'gitattributes', - ['${XDG_CONFIG_HOME}/git/attributes'] = 'gitattributes', - ['.*%.git/info/exclude'] = 'gitignore', - ['.*/%.config/git/ignore'] = 'gitignore', - ['${XDG_CONFIG_HOME}/git/ignore'] = 'gitignore', - ['%.gitsendemail%.msg%.......'] = 'gitsendemail', - ['gkrellmrc_.'] = 'gkrellmrc', - ['.*/usr/.*/gnupg/options%.skel'] = 'gpg', - ['.*/%.gnupg/options'] = 'gpg', - ['.*/%.gnupg/gpg%.conf'] = 'gpg', - ['${GNUPGHOME}/options'] = 'gpg', - ['${GNUPGHOME}/gpg%.conf'] = 'gpg', - ['.*/etc/group'] = 'group', - ['.*/etc/gshadow'] = 'group', - ['.*/etc/group%.edit'] = 'group', - ['.*/var/backups/gshadow%.bak'] = 'group', - ['.*/etc/group%-'] = 'group', - ['.*/etc/gshadow%-'] = 'group', - ['.*/var/backups/group%.bak'] = 'group', - ['.*/etc/gshadow%.edit'] = 'group', - ['.*/boot/grub/grub%.conf'] = 'grub', - ['.*/boot/grub/menu%.lst'] = 'grub', - ['.*/etc/grub%.conf'] = 'grub', - -- gtkrc* and .gtkrc* - ['%.?gtkrc.*'] = starsetf('gtkrc'), - ['${VIMRUNTIME}/doc/.*%.txt'] = 'help', - ['hg%-editor%-.*%.txt'] = 'hgcommit', - ['.*/etc/host%.conf'] = 'hostconf', - ['.*/etc/hosts%.deny'] = 'hostsaccess', - ['.*/etc/hosts%.allow'] = 'hostsaccess', - ['.*%.html%.m4'] = 'htmlm4', - ['.*/%.i3/config'] = 'i3config', - ['.*/i3/config'] = 'i3config', - ['.*/%.icewm/menu'] = 'icemenu', - ['.*/etc/initng/.*/.*%.i'] = 'initng', - ['JAM.*%..*'] = starsetf('jam'), - ['Prl.*%..*'] = starsetf('jam'), - ['.*%.properties_..'] = 'jproperties', - ['.*%.properties_.._..'] = 'jproperties', - ['org%.eclipse%..*%.prefs'] = 'jproperties', - ['.*%.properties_.._.._.*'] = starsetf('jproperties'), - ['[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'), - ['.*/ldscripts/.*'] = 'ld', - ['.*lftp/rc'] = 'lftp', - ['.*/%.libao'] = 'libao', - ['.*/etc/libao%.conf'] = 'libao', - ['.*/etc/.*limits%.conf'] = 'limits', - ['.*/etc/limits'] = 'limits', - ['.*/etc/.*limits%.d/.*%.conf'] = 'limits', - ['.*/supertux2/config'] = 'lisp', - ['.*/LiteStep/.*/.*%.rc'] = 'litestep', - ['.*/etc/login%.access'] = 'loginaccess', - ['.*/etc/login%.defs'] = 'logindefs', - ['%.letter%.%d+'] = 'mail', - ['%.article%.%d+'] = 'mail', - ['/tmp/SLRN[0-9A-Z.]+'] = 'mail', - ['ae%d+%.txt'] = 'mail', - ['pico%.%d+'] = 'mail', - ['mutt%-.*%-%w+'] = 'mail', - ['muttng%-.*%-%w+'] = 'mail', - ['neomutt%-.*%-%w+'] = 'mail', - ['mutt' .. string.rep('[%w_-]', 6)] = 'mail', - ['neomutt' .. string.rep('[%w_-]', 6)] = 'mail', - ['snd%.%d+'] = 'mail', - ['reportbug%-.*'] = starsetf('mail'), - ['.*/etc/mail/aliases'] = 'mailaliases', - ['.*/etc/aliases'] = 'mailaliases', - ['.*[mM]akefile'] = 'make', - ['[mM]akefile.*'] = starsetf('make'), - ['.*/etc/man%.conf'] = 'manconf', - ['.*/log/auth'] = 'messages', - ['.*/log/cron'] = 'messages', - ['.*/log/daemon'] = 'messages', - ['.*/log/debug'] = 'messages', - ['.*/log/kern'] = 'messages', - ['.*/log/lpr'] = 'messages', - ['.*/log/mail'] = 'messages', - ['.*/log/messages'] = 'messages', - ['.*/log/news/news'] = 'messages', - ['.*/log/syslog'] = 'messages', - ['.*/log/user'] = 'messages', - ['.*/log/auth%.log'] = 'messages', - ['.*/log/cron%.log'] = 'messages', - ['.*/log/daemon%.log'] = 'messages', - ['.*/log/debug%.log'] = 'messages', - ['.*/log/kern%.log'] = 'messages', - ['.*/log/lpr%.log'] = 'messages', - ['.*/log/mail%.log'] = 'messages', - ['.*/log/messages%.log'] = 'messages', - ['.*/log/news/news%.log'] = 'messages', - ['.*/log/syslog%.log'] = 'messages', - ['.*/log/user%.log'] = 'messages', - ['.*/log/auth%.err'] = 'messages', - ['.*/log/cron%.err'] = 'messages', - ['.*/log/daemon%.err'] = 'messages', - ['.*/log/debug%.err'] = 'messages', - ['.*/log/kern%.err'] = 'messages', - ['.*/log/lpr%.err'] = 'messages', - ['.*/log/mail%.err'] = 'messages', - ['.*/log/messages%.err'] = 'messages', - ['.*/log/news/news%.err'] = 'messages', - ['.*/log/syslog%.err'] = 'messages', - ['.*/log/user%.err'] = 'messages', - ['.*/log/auth%.info'] = 'messages', - ['.*/log/cron%.info'] = 'messages', - ['.*/log/daemon%.info'] = 'messages', - ['.*/log/debug%.info'] = 'messages', - ['.*/log/kern%.info'] = 'messages', - ['.*/log/lpr%.info'] = 'messages', - ['.*/log/mail%.info'] = 'messages', - ['.*/log/messages%.info'] = 'messages', - ['.*/log/news/news%.info'] = 'messages', - ['.*/log/syslog%.info'] = 'messages', - ['.*/log/user%.info'] = 'messages', - ['.*/log/auth%.warn'] = 'messages', - ['.*/log/cron%.warn'] = 'messages', - ['.*/log/daemon%.warn'] = 'messages', - ['.*/log/debug%.warn'] = 'messages', - ['.*/log/kern%.warn'] = 'messages', - ['.*/log/lpr%.warn'] = 'messages', - ['.*/log/mail%.warn'] = 'messages', - ['.*/log/messages%.warn'] = 'messages', - ['.*/log/news/news%.warn'] = 'messages', - ['.*/log/syslog%.warn'] = 'messages', - ['.*/log/user%.warn'] = 'messages', - ['.*/log/auth%.crit'] = 'messages', - ['.*/log/cron%.crit'] = 'messages', - ['.*/log/daemon%.crit'] = 'messages', - ['.*/log/debug%.crit'] = 'messages', - ['.*/log/kern%.crit'] = 'messages', - ['.*/log/lpr%.crit'] = 'messages', - ['.*/log/mail%.crit'] = 'messages', - ['.*/log/messages%.crit'] = 'messages', - ['.*/log/news/news%.crit'] = 'messages', - ['.*/log/syslog%.crit'] = 'messages', - ['.*/log/user%.crit'] = 'messages', - ['.*/log/auth%.notice'] = 'messages', - ['.*/log/cron%.notice'] = 'messages', - ['.*/log/daemon%.notice'] = 'messages', - ['.*/log/debug%.notice'] = 'messages', - ['.*/log/kern%.notice'] = 'messages', - ['.*/log/lpr%.notice'] = 'messages', - ['.*/log/mail%.notice'] = 'messages', - ['.*/log/messages%.notice'] = 'messages', - ['.*/log/news/news%.notice'] = 'messages', - ['.*/log/syslog%.notice'] = 'messages', - ['.*/log/user%.notice'] = 'messages', - ['.*%.[Mm][Oo][Dd]'] = detect.mod, - ['.*/etc/modules%.conf'] = 'modconf', - ['.*/etc/conf%.modules'] = 'modconf', - ['.*/etc/modules'] = 'modconf', - ['.*/etc/modprobe%..*'] = starsetf('modconf'), - ['.*/etc/modutils/.*'] = starsetf(function(path, bufnr) - if fn.executable(fn.expand(path)) ~= 1 then - return 'modconf' - end - end), - ['.*%.[mi][3g]'] = 'modula3', - ['Muttrc'] = 'muttrc', - ['Muttngrc'] = 'muttrc', - ['.*/etc/Muttrc%.d/.*'] = starsetf('muttrc'), - ['.*/%.mplayer/config'] = 'mplayerconf', - ['Muttrc.*'] = detect_muttrc, - ['Muttngrc.*'] = detect_muttrc, - -- muttrc* and .muttrc* - ['%.?muttrc.*'] = detect_muttrc, - -- muttngrc* and .muttngrc* - ['%.?muttngrc.*'] = detect_muttrc, - ['.*/%.mutt/muttrc.*'] = detect_muttrc, - ['.*/%.muttng/muttrc.*'] = detect_muttrc, - ['.*/%.muttng/muttngrc.*'] = detect_muttrc, - ['rndc.*%.conf'] = 'named', - ['rndc.*%.key'] = 'named', - ['named.*%.conf'] = 'named', - ['.*/etc/nanorc'] = 'nanorc', - ['.*%.NS[ACGLMNPS]'] = 'natural', - ['Neomuttrc.*'] = detect_neomuttrc, - -- neomuttrc* and .neomuttrc* - ['%.?neomuttrc.*'] = detect_neomuttrc, - ['.*/%.neomutt/neomuttrc.*'] = detect_neomuttrc, - ['nginx.*%.conf'] = 'nginx', - ['.*/etc/nginx/.*'] = 'nginx', - ['.*nginx%.conf'] = 'nginx', - ['.*/nginx/.*%.conf'] = 'nginx', - ['.*/usr/local/nginx/conf/.*'] = 'nginx', - ['.*%.[1-9]'] = detect.nroff, - ['.*%.ml%.cppo'] = 'ocaml', - ['.*%.mli%.cppo'] = 'ocaml', - ['.*/octave/history'] = 'octave', - ['.*%.opam%.template'] = 'opam', - ['.*/openvpn/.*/.*%.conf'] = 'openvpn', - ['.*%.[Oo][Pp][Ll]'] = 'opl', - ['.*/etc/pam%.conf'] = 'pamconf', - ['.*/etc/pam%.d/.*'] = starsetf('pamconf'), - ['.*/etc/passwd%-'] = 'passwd', - ['.*/etc/shadow'] = 'passwd', - ['.*/etc/shadow%.edit'] = 'passwd', - ['.*/var/backups/shadow%.bak'] = 'passwd', - ['.*/var/backups/passwd%.bak'] = 'passwd', - ['.*/etc/passwd'] = 'passwd', - ['.*/etc/passwd%.edit'] = 'passwd', - ['.*/etc/shadow%-'] = 'passwd', - ['%.?gitolite%.rc'] = 'perl', - ['example%.gitolite%.rc'] = 'perl', - ['.*%.php%d'] = 'php', - ['.*/%.pinforc'] = 'pinfo', - ['.*/etc/pinforc'] = 'pinfo', - ['.*%.[Pp][Rr][Gg]'] = detect.prg, - ['.*/etc/protocols'] = 'protocols', - ['.*printcap.*'] = starsetf(function(path, bufnr) - return require('vim.filetype.detect').printcap('print') - end), - ['.*baseq[2-3]/.*%.cfg'] = 'quake', - ['.*quake[1-3]/.*%.cfg'] = 'quake', - ['.*id1/.*%.cfg'] = 'quake', - ['.*/queries/.*%.scm'] = 'query', -- treesitter queries (Neovim only) - ['.*,v'] = 'rcs', - ['%.reminders.*'] = starsetf('remind'), - ['.*%-requirements%.txt'] = 'requirements', - ['requirements/.*%.txt'] = 'requirements', - ['requires/.*%.txt'] = 'requirements', - ['[rR]akefile.*'] = starsetf('ruby'), - ['[rR]antfile'] = 'ruby', - ['[rR]akefile'] = 'ruby', - ['.*/etc/sensors%.d/[^.].*'] = starsetf('sensors'), - ['.*/etc/sensors%.conf'] = 'sensors', - ['.*/etc/sensors3%.conf'] = 'sensors', - ['.*/etc/services'] = 'services', - ['.*/etc/serial%.conf'] = 'setserial', - ['.*/etc/udev/cdsymlinks%.conf'] = 'sh', - ['.*/neofetch/config%.conf'] = 'sh', - ['%.bash[_%-]aliases'] = detect.bash, - ['%.bash[_%-]history'] = detect.bash, - ['%.bash[_%-]logout'] = detect.bash, - ['%.bash[_%-]profile'] = detect.bash, - ['%.kshrc.*'] = detect.ksh, - ['%.profile.*'] = detect.sh, - ['.*/etc/profile'] = detect.sh, - ['bash%-fc[%-%.].*'] = detect.bash, - ['%.tcshrc.*'] = detect.tcsh, - ['.*/etc/sudoers%.d/.*'] = starsetf('sudoers'), - ['.*%._sst%.meta'] = 'sisu', - ['.*%.%-sst%.meta'] = 'sisu', - ['.*%.sst%.meta'] = 'sisu', - ['.*/etc/slp%.conf'] = 'slpconf', - ['.*/etc/slp%.reg'] = 'slpreg', - ['.*/etc/slp%.spi'] = 'slpspi', - ['.*/etc/ssh/ssh_config%.d/.*%.conf'] = 'sshconfig', - ['.*/%.ssh/config'] = 'sshconfig', - ['.*/%.ssh/.*%.conf'] = 'sshconfig', - ['.*/etc/ssh/sshd_config%.d/.*%.conf'] = 'sshdconfig', - ['.*%.[Ss][Rr][Cc]'] = detect.src, - ['.*/etc/sudoers'] = 'sudoers', - ['svn%-commit.*%.tmp'] = 'svn', - ['.*/sway/config'] = 'swayconfig', - ['.*/%.sway/config'] = 'swayconfig', - ['.*%.swift%.gyb'] = 'swiftgyb', - ['.*%.[Ss][Yy][Ss]'] = detect.sys, - ['.*/etc/sysctl%.conf'] = 'sysctl', - ['.*/etc/sysctl%.d/.*%.conf'] = 'sysctl', - ['.*/systemd/.*%.automount'] = 'systemd', - ['.*/systemd/.*%.dnssd'] = 'systemd', - ['.*/systemd/.*%.link'] = 'systemd', - ['.*/systemd/.*%.mount'] = 'systemd', - ['.*/systemd/.*%.netdev'] = 'systemd', - ['.*/systemd/.*%.network'] = 'systemd', - ['.*/systemd/.*%.nspawn'] = 'systemd', - ['.*/systemd/.*%.path'] = 'systemd', - ['.*/systemd/.*%.service'] = 'systemd', - ['.*/systemd/.*%.slice'] = 'systemd', - ['.*/systemd/.*%.socket'] = 'systemd', - ['.*/systemd/.*%.swap'] = 'systemd', - ['.*/systemd/.*%.target'] = 'systemd', - ['.*/systemd/.*%.timer'] = 'systemd', - ['.*/etc/systemd/.*%.conf%.d/.*%.conf'] = 'systemd', - ['.*/%.config/systemd/user/.*%.d/.*%.conf'] = 'systemd', - ['.*/etc/systemd/system/.*%.d/.*%.conf'] = 'systemd', - ['.*/etc/systemd/system/.*%.d/%.#.*'] = 'systemd', - ['.*/etc/systemd/system/%.#.*'] = 'systemd', - ['.*/%.config/systemd/user/.*%.d/%.#.*'] = 'systemd', - ['.*/%.config/systemd/user/%.#.*'] = 'systemd', - ['.*termcap.*'] = starsetf(function(path, bufnr) - return require('vim.filetype.detect').printcap('term') - end), - ['.*/tex/latex/.*%.cfg'] = 'tex', - ['.*%.t%.html'] = 'tilde', - ['%.?tmux.*%.conf'] = 'tmux', - ['%.?tmux.*%.conf.*'] = { 'tmux', { priority = -1 } }, - ['.*/%.cargo/config'] = 'toml', - ['.*/%.cargo/credentials'] = 'toml', - ['.*/etc/udev/udev%.conf'] = 'udevconf', - ['.*/etc/udev/permissions%.d/.*%.permissions'] = 'udevperm', - ['.*/etc/updatedb%.conf'] = 'updatedb', - ['.*/%.init/.*%.override'] = 'upstart', - ['.*/usr/share/upstart/.*%.conf'] = 'upstart', - ['.*/%.config/upstart/.*%.override'] = 'upstart', - ['.*/etc/init/.*%.conf'] = 'upstart', - ['.*/etc/init/.*%.override'] = 'upstart', - ['.*/%.config/upstart/.*%.conf'] = 'upstart', - ['.*/%.init/.*%.conf'] = 'upstart', - ['.*/usr/share/upstart/.*%.override'] = 'upstart', - ['.*%.[Ll][Oo][Gg]'] = detect.log, - ['.*/etc/config/.*'] = starsetf(detect.uci), - ['.*%.vhdl_[0-9].*'] = starsetf('vhdl'), - ['.*%.ws[fc]'] = 'wsh', - ['.*/Xresources/.*'] = starsetf('xdefaults'), - ['.*/app%-defaults/.*'] = starsetf('xdefaults'), - ['.*/etc/xinetd%.conf'] = 'xinetd', - ['.*/usr/share/X11/xkb/compat/.*'] = starsetf('xkb'), - ['.*/usr/share/X11/xkb/geometry/.*'] = starsetf('xkb'), - ['.*/usr/share/X11/xkb/keycodes/.*'] = starsetf('xkb'), - ['.*/usr/share/X11/xkb/symbols/.*'] = starsetf('xkb'), - ['.*/usr/share/X11/xkb/types/.*'] = starsetf('xkb'), - ['.*/etc/blkid%.tab'] = 'xml', - ['.*/etc/blkid%.tab%.old'] = 'xml', - ['.*%.vbproj%.user'] = 'xml', - ['.*%.fsproj%.user'] = 'xml', - ['.*%.csproj%.user'] = 'xml', - ['.*/etc/xdg/menus/.*%.menu'] = 'xml', - ['.*Xmodmap'] = 'xmodmap', - ['.*/etc/zprofile'] = 'zsh', - ['.*vimrc.*'] = starsetf('vim'), - ['Xresources.*'] = starsetf('xdefaults'), - ['.*/etc/xinetd%.d/.*'] = starsetf('xinetd'), - ['.*xmodmap.*'] = starsetf('xmodmap'), - ['.*/xorg%.conf%.d/.*%.conf'] = detect.xfree86_v4, - -- Increase priority to run before the pattern below - ['XF86Config%-4.*'] = starsetf(detect.xfree86_v4, { priority = -math.huge + 1 }), - ['XF86Config.*'] = starsetf(detect.xfree86_v3), - ['.*/%.bundle/config'] = 'yaml', - ['%.zcompdump.*'] = starsetf('zsh'), - -- .zlog* and zlog* - ['%.?zlog.*'] = starsetf('zsh'), - -- .zsh* and zsh* - ['%.?zsh.*'] = starsetf('zsh'), - -- Ignored extension - ['.*~'] = function(path, bufnr) - local short = path:gsub('~+$', '', 1) - if path ~= short and short ~= '' then - return M.match({ buf = bufnr, filename = fn.fnameescape(short) }) - end - end, -- END PATTERN } -- luacheck: pop -- luacheck: pop ---- @param t vim.filetype.mapping +--- Lookup table/cache for patterns +--- @alias vim.filetype.pattern_cache { has_env: boolean, has_slash: boolean } +--- @type table<string,vim.filetype.pattern_cache> +local pattern_lookup = {} + +local function compare_by_priority(a, b) + return a[next(a)][2].priority > b[next(b)][2].priority +end + +--- @param pat string +--- @return { has_env: boolean, has_slash: boolean } +local function parse_pattern(pat) + return { has_env = pat:find('%$%b{}') ~= nil, has_slash = pat:find('/') ~= nil } +end + +--- @param t table<string,vim.filetype.mapping> +--- @return vim.filetype.mapping[] --- @return vim.filetype.mapping[] local function sort_by_priority(t) - local sorted = {} --- @type vim.filetype.mapping[] - for k, v in pairs(t) do - local ft = type(v) == 'table' and v[1] or v - assert( - type(ft) == 'string' or type(ft) == 'function', - 'Expected string or function for filetype' - ) - - local opts = (type(v) == 'table' and type(v[2]) == 'table') and v[2] or {} - if not opts.priority then - opts.priority = 0 + -- Separate patterns with non-negative and negative priority because they + -- will be processed separately + local pos = {} --- @type vim.filetype.mapping[] + local neg = {} --- @type vim.filetype.mapping[] + for parent, ft_map in pairs(t) do + pattern_lookup[parent] = pattern_lookup[parent] or parse_pattern(parent) + for pat, maptbl in pairs(ft_map) do + local ft = type(maptbl) == 'table' and maptbl[1] or maptbl + assert( + type(ft) == 'string' or type(ft) == 'function', + 'Expected string or function for filetype' + ) + + -- Parse pattern for common data and cache it once + pattern_lookup[pat] = pattern_lookup[pat] or parse_pattern(pat) + + local opts = (type(maptbl) == 'table' and type(maptbl[2]) == 'table') and maptbl[2] or {} + opts.parent = opts.parent or parent + opts.priority = opts.priority or 0 + + table.insert(opts.priority >= 0 and pos or neg, { [pat] = { ft, opts } }) end - table.insert(sorted, { [k] = { ft, opts } }) end - table.sort(sorted, function(a, b) - return a[next(a)][2].priority > b[next(b)][2].priority - end) - return sorted + + table.sort(pos, compare_by_priority) + table.sort(neg, compare_by_priority) + return pos, neg end -local pattern_sorted = sort_by_priority(pattern) +local pattern_sorted_pos, pattern_sorted_neg = sort_by_priority(pattern) --- @param path string --- @param as_pattern? true @@ -2302,7 +2501,7 @@ end --- ['.*/etc/foo/.*%.conf'] = { 'dosini', { priority = 10 } }, --- -- A pattern containing an environment variable --- ['${XDG_CONFIG_HOME}/foo/git'] = 'git', ---- ['README.(%a+)$'] = function(path, bufnr, ext) +--- ['.*README.(%a+)'] = function(path, bufnr, ext) --- if ext == 'md' then --- return 'markdown' --- elseif ext == 'rst' then @@ -2344,11 +2543,16 @@ function M.add(filetypes) end for k, v in pairs(filetypes.pattern or {}) do - pattern[normalize_path(k, true)] = v + -- Add to "match all" parent pattern (might be better to optimize later or document + -- supplying `opts.parent` directly) + -- User patterns are assumed to be implicitly anchored (as in Vim) + pattern['']['^' .. normalize_path(k, true) .. '$'] = v end if filetypes.pattern then - pattern_sorted = sort_by_priority(pattern) + -- TODO: full resorting might be expensive with a lot of separate `vim.filetype.add()` calls. + -- Consider inserting new patterns precisely into already sorted lists of built-in patterns. + pattern_sorted_pos, pattern_sorted_neg = sort_by_priority(pattern) end end @@ -2392,46 +2596,81 @@ local function dispatch(ft, path, bufnr, ...) return ft0, on_detect end ---- Lookup table/cache for patterns that contain an environment variable pattern, e.g. ${SOME_VAR}. ---- @type table<string,boolean> -local expand_env_lookup = {} +--- @param pat string +--- @return boolean +--- @return string +local function expand_envvar_pattern(pat) + local some_env_missing = false + local expanded = pat:gsub('%${(%S-)}', function(env) + local val = vim.env[env] --- @type string? + some_env_missing = some_env_missing or val == nil + return vim.pesc(val or '') + end) + return some_env_missing, expanded +end --- @param name string --- @param path string --- @param tail string --- @param pat string ---- @return string|false? -local function match_pattern(name, path, tail, pat) - if expand_env_lookup[pat] == nil then - expand_env_lookup[pat] = pat:find('%${') ~= nil - end - if expand_env_lookup[pat] then - local return_early --- @type true? - --- @type string - pat = pat:gsub('%${(%S-)}', function(env) - -- If an environment variable is present in the pattern but not set, there is no match - if not vim.env[env] then - return_early = true - return nil - end - return vim.pesc(vim.env[env]) - end) - if return_early then - return false +--- @param try_all_candidates boolean +--- @return string? +local function match_pattern(name, path, tail, pat, try_all_candidates) + local pat_cache = pattern_lookup[pat] + local has_slash = pat_cache.has_slash + + if pat_cache.has_env then + local some_env_missing, expanded = expand_envvar_pattern(pat) + -- If any environment variable is present in the pattern but not set, there is no match + if some_env_missing then + return nil end + pat, has_slash = expanded, expanded:find('/') ~= nil end - -- If the pattern contains a / match against the full path, otherwise just the tail - local fullpat = '^' .. pat .. '$' + -- Try all possible candidates to make parent patterns not depend on slash presence + if try_all_candidates then + return (path:match(pat) or name:match(pat) or tail:match(pat)) + end - if pat:find('/') then + -- If the pattern contains a / match against the full path, otherwise just the tail + if has_slash then -- Similar to |autocmd-pattern|, if the pattern contains a '/' then check for a match against -- both the short file name (as typed) and the full file name (after expanding to full path -- and resolving symlinks) - return (name:match(fullpat) or path:match(fullpat)) + return (name:match(pat) or path:match(pat)) end - return (tail:match(fullpat)) + return (tail:match(pat)) +end + +--- @param name string +--- @param path string +--- @param tail string +--- @param pattern_sorted vim.filetype.mapping[] +--- @param parent_matches table<string,boolean> +--- @param bufnr integer? +local function match_pattern_sorted(name, path, tail, pattern_sorted, parent_matches, bufnr) + for i = 1, #pattern_sorted do + local pat, ft_data = next(pattern_sorted[i]) + + local parent = ft_data[2].parent + local parent_is_matched = parent_matches[parent] + if parent_is_matched == nil then + parent_matches[parent] = match_pattern(name, path, tail, parent, true) ~= nil + parent_is_matched = parent_matches[parent] + end + + if parent_is_matched then + local matches = match_pattern(name, path, tail, pat, false) + if matches then + local ft, on_detect = dispatch(ft_data[1], path, bufnr, matches) + if ft then + return ft, on_detect + end + end + end + end end --- @class vim.filetype.match.args @@ -2527,23 +2766,12 @@ function M.match(args) end -- Next, check the file path against available patterns with non-negative priority - local j = 1 - for i, v in ipairs(pattern_sorted) do - local k = next(v) - local opts = v[k][2] - if opts.priority < 0 then - j = i - break - end - - local filetype = v[k][1] - local matches = match_pattern(name, path, tail, k) - if matches then - ft, on_detect = dispatch(filetype, path, bufnr, matches) - if ft then - return ft, on_detect - end - end + -- Cache match results of all parent patterns to improve performance + local parent_matches = {} + ft, on_detect = + match_pattern_sorted(name, path, tail, pattern_sorted_pos, parent_matches, bufnr) + if ft then + return ft, on_detect end -- Next, check file extension @@ -2556,18 +2784,10 @@ function M.match(args) end -- Next, check patterns with negative priority - for i = j, #pattern_sorted do - local v = pattern_sorted[i] - local k = next(v) - - local filetype = v[k][1] - local matches = match_pattern(name, path, tail, k) - if matches then - ft, on_detect = dispatch(filetype, path, bufnr, matches) - if ft then - return ft, on_detect - end - end + ft, on_detect = + match_pattern_sorted(name, path, tail, pattern_sorted_neg, parent_matches, bufnr) + if ft then + return ft, on_detect end end @@ -2583,20 +2803,24 @@ function M.match(args) contents = M._getlines(bufnr) end end - -- If name is nil, catch any errors from the contents filetype detection function. - -- If the function tries to use the filename that is nil then it will fail, - -- but this enables checks which do not need a filename to still work. - local ok - ok, ft, on_detect = pcall( - require('vim.filetype.detect').match_contents, - contents, - name, - function(ext) - return dispatch(extension[ext], name, bufnr) + + -- Match based solely on content only if there is any content (for performance) + if not (#contents == 1 and contents[1] == '') then + -- If name is nil, catch any errors from the contents filetype detection function. + -- If the function tries to use the filename that is nil then it will fail, + -- but this enables checks which do not need a filename to still work. + local ok + ok, ft, on_detect = pcall( + require('vim.filetype.detect').match_contents, + contents, + name, + function(ext) + return dispatch(extension[ext], name, bufnr) + end + ) + if ok then + return ft, on_detect end - ) - if ok then - return ft, on_detect end end end @@ -2615,6 +2839,7 @@ end --- Note: this uses |nvim_get_option_value()| but caches the result. --- This means |ftplugin| and |FileType| autocommands are only --- triggered once and may not reflect later changes. +--- @since 11 --- @param filetype string Filetype --- @param option string Option name --- @return string|boolean|integer: Option value diff --git a/runtime/lua/vim/filetype/detect.lua b/runtime/lua/vim/filetype/detect.lua index ba86d8de5a..1cc81b177f 100644 --- a/runtime/lua/vim/filetype/detect.lua +++ b/runtime/lua/vim/filetype/detect.lua @@ -450,7 +450,7 @@ local function modula2(bufnr) return 'modula2', function(b) - vim.api.nvim_buf_call(b, function() + vim._with({ buf = b }, function() fn['modula2#SetDialect'](dialect, extension) end) end @@ -472,6 +472,41 @@ function M.def(_, bufnr) end --- @type vim.filetype.mapfn +function M.dsp(path, bufnr) + if vim.g.filetype_dsp then + return vim.g.filetype_dsp + end + + -- Test the filename + local file_name = fn.fnamemodify(path, ':t') + if file_name:find('^[mM]akefile.*$') then + return 'make' + end + + -- Test the file contents + for _, line in ipairs(getlines(bufnr, 1, 200)) do + if + findany(line, { + -- Check for comment style + [[#.*]], + -- Check for common lines + [[^.*Microsoft Developer Studio Project File.*$]], + [[^!MESSAGE This is not a valid makefile\..+$]], + -- Check for keywords + [[^!(IF,ELSEIF,ENDIF).*$]], + -- Check for common assignments + [[^SOURCE=.*$]], + }) + then + return 'make' + end + end + + -- Otherwise, assume we have a Faust file + return 'faust' +end + +--- @type vim.filetype.mapfn function M.e(_, bufnr) if vim.g.filetype_euphoria then return vim.g.filetype_euphoria @@ -594,7 +629,7 @@ function M.frm(_, bufnr) end --- @type vim.filetype.mapfn -function M.fvwm_1(_, _) +function M.fvwm_v1(_, _) return 'fvwm', function(bufnr) vim.b[bufnr].fvwm_version = 1 end @@ -650,13 +685,58 @@ function M.header(_, bufnr) end end +--- Recursively search for Hare source files in a directory and any +--- subdirectories, up to a given depth. +--- @param dir string +--- @param depth number +--- @return boolean +local function is_hare_module(dir, depth) + depth = math.max(depth, 0) + for name, _ in vim.fs.dir(dir, { depth = depth + 1 }) do + if name:find('%.ha$') then + return true + end + end + return false +end + +--- @type vim.filetype.mapfn +function M.haredoc(path, _) + if vim.g.filetype_haredoc then + if is_hare_module(vim.fs.dirname(path), vim.g.haredoc_search_depth or 1) then + return 'haredoc' + end + end +end + --- @type vim.filetype.mapfn function M.html(_, bufnr) - for _, line in ipairs(getlines(bufnr, 1, 10)) do - if matchregex(line, [[\<DTD\s\+XHTML\s]]) then + -- Disabled for the reasons mentioned here: + -- https://github.com/vim/vim/pull/13594#issuecomment-1834465890 + -- local filename = fn.fnamemodify(path, ':t') + -- if filename:find('%.component%.html$') then + -- return 'htmlangular' + -- end + + for _, line in ipairs(getlines(bufnr, 1, 40)) do + if + matchregex( + line, + [[@\(if\|for\|defer\|switch\)\|\*\(ngIf\|ngFor\|ngSwitch\|ngTemplateOutlet\)\|ng-template\|ng-content\|{{.*}}]] + ) + then + return 'htmlangular' + elseif matchregex(line, [[\<DTD\s\+XHTML\s]]) then return 'xhtml' - elseif matchregex(line, [[\c{%\s*\(extends\|block\|load\)\>\|{#\s\+]]) then + elseif + matchregex( + line, + [[\c{%\s*\(autoescape\|block\|comment\|csrf_token\|cycle\|debug\|extends\|filter\|firstof\|for\|if\|ifchanged\|include\|load\|lorem\|now\|query_string\|regroup\|resetcycle\|spaceless\|templatetag\|url\|verbatim\|widthratio\|with\)\>\|{#\s\+]] + ) + then return 'htmldjango' + elseif findany(line, { '<extend', '<super>' }) then + return 'superhtml' end end return 'html' @@ -894,6 +974,24 @@ local function m4(contents) end end +--- Check if it is a Microsoft Makefile +--- @type vim.filetype.mapfn +function M.make(_, bufnr) + vim.b.make_microsoft = nil + for _, line in ipairs(getlines(bufnr, 1, 1000)) do + if matchregex(line, [[\c^\s*!\s*\(ifn\=\(def\)\=\|include\|message\|error\)\>]]) then + vim.b.make_microsoft = 1 + break + elseif + matchregex(line, [[^ *ifn\=\(eq\|def\)\>]]) + or findany(line, { '^ *[-s]?%s', '^ *%w+%s*[!?:+]=' }) + then + break + end + end + return 'make' +end + --- @type vim.filetype.mapfn function M.markdown(_, _) return vim.g.filetype_md or 'markdown' @@ -1038,6 +1136,8 @@ function M.perl(path, bufnr) end end +local prolog_patterns = { '^%s*:%-', '^%s*%%+%s', '^%s*%%+$', '^%s*/%*', '%.%s*$' } + --- @type vim.filetype.mapfn function M.pl(_, bufnr) if vim.g.filetype_pl then @@ -1046,11 +1146,7 @@ function M.pl(_, bufnr) -- Recognize Prolog by specific text in the first non-empty line; -- require a blank after the '%' because Perl uses "%list" and "%translate" local line = nextnonblank(bufnr, 1) - if - line and line:find(':%-') - or matchregex(line, [[\c\<prolog\>]]) - or findany(line, { '^%s*%%+%s', '^%s*%%+$', '^%s*/%*' }) - then + if line and matchregex(line, [[\c\<prolog\>]]) or findany(line, prolog_patterns) then return 'prolog' else return 'perl' @@ -1154,11 +1250,7 @@ function M.proto(_, bufnr) -- Recognize Prolog by specific text in the first non-empty line; -- require a blank after the '%' because Perl uses "%list" and "%translate" local line = nextnonblank(bufnr, 1) - if - line and line:find(':%-') - or matchregex(line, [[\c\<prolog\>]]) - or findany(line, { '^%s*%%+%s', '^%s*%%+$', '^%s*/%*' }) - then + if line and matchregex(line, [[\c\<prolog\>]]) or findany(line, prolog_patterns) then return 'prolog' end end @@ -1331,7 +1423,7 @@ end function M.sgml(_, bufnr) local lines = table.concat(getlines(bufnr, 1, 5)) if lines:find('linuxdoc') then - return 'smgllnx' + return 'sgmllnx' elseif lines:find('<!DOCTYPE.*DocBook') then return 'docbk', function(b) @@ -1533,7 +1625,7 @@ function M.tex(path, bufnr) end end --- Determine if a *.tf file is TF mud client or terraform +-- Determine if a *.tf file is TF (TinyFugue) mud client or terraform --- @type vim.filetype.mapfn function M.tf(_, bufnr) for _, line in ipairs(getlines(bufnr)) do @@ -1759,6 +1851,7 @@ local patterns_hashbang = { ['^janet\\>'] = { 'janet', { vim_regex = true } }, ['^dart\\>'] = { 'dart', { vim_regex = true } }, ['^execlineb\\>'] = { 'execline', { vim_regex = true } }, + ['^vim\\>'] = { 'vim', { vim_regex = true } }, } ---@private diff --git a/runtime/lua/vim/fs.lua b/runtime/lua/vim/fs.lua index b05220ee2c..ccddf826f7 100644 --- a/runtime/lua/vim/fs.lua +++ b/runtime/lua/vim/fs.lua @@ -1,6 +1,10 @@ +local uv = vim.uv + local M = {} -local iswin = vim.uv.os_uname().sysname == 'Windows_NT' +-- Can't use `has('win32')` because the `nvim -ll` test runner doesn't support `vim.fn` yet. +local sysname = uv.os_uname().sysname:lower() +local iswin = not not (sysname:find('windows') or sysname:find('mingw')) local os_sep = iswin and '\\' or '/' --- Iterate over all the parents of the given path. @@ -21,6 +25,7 @@ local os_sep = iswin and '\\' or '/' --- end --- ``` --- +---@since 10 ---@param start (string) Initial path. ---@return fun(_, dir: string): string? # Iterator ---@return nil @@ -40,6 +45,7 @@ end --- Return the parent directory of the given path --- +---@since 10 ---@generic T : string|nil ---@param file T Path ---@return T Parent directory of {file} @@ -69,6 +75,7 @@ end --- Return the basename of the given path --- +---@since 10 ---@generic T : string|nil ---@param file T Path ---@return T Basename of {file} @@ -89,6 +96,7 @@ end --- Concatenate directories and/or file paths into a single path with normalization --- (e.g., `"foo/"` and `"bar"` get joined to `"foo/bar"`) --- +---@since 12 ---@param ... string ---@return string function M.joinpath(...) @@ -99,6 +107,7 @@ end --- Return an iterator over the items located in {path} --- +---@since 10 ---@param path (string) An absolute or relative path to the directory to iterate --- over. The path is first normalized |vim.fs.normalize()|. --- @param opts table|nil Optional keyword arguments: @@ -122,12 +131,12 @@ function M.dir(path, opts) path = M.normalize(path) if not opts.depth or opts.depth == 1 then - local fs = vim.uv.fs_scandir(path) + local fs = uv.fs_scandir(path) return function() if not fs then return end - return vim.uv.fs_scandir_next(fs) + return uv.fs_scandir_next(fs) end end @@ -138,9 +147,9 @@ function M.dir(path, opts) --- @type string, integer local dir0, level = unpack(table.remove(dirs, 1)) local dir = level == 1 and dir0 or M.joinpath(path, dir0) - local fs = vim.uv.fs_scandir(dir) + local fs = uv.fs_scandir(dir) while fs do - local name, t = vim.uv.fs_scandir_next(fs) + local name, t = uv.fs_scandir_next(fs) if not name then break end @@ -210,6 +219,7 @@ end --- end, {limit = math.huge, type = 'file'}) --- ``` --- +---@since 10 ---@param names (string|string[]|fun(name: string, path: string): boolean) Names of the items to find. --- Must be base names, paths and globs are not supported when {names} is a string or a table. --- If {names} is a function, it is called for each traversed item with args: @@ -234,7 +244,7 @@ function M.find(names, opts) names = { names } end - local path = opts.path or assert(vim.uv.cwd()) + local path = opts.path or assert(uv.cwd()) local stop = opts.stop local limit = opts.limit or 1 @@ -265,7 +275,7 @@ function M.find(names, opts) local t = {} --- @type string[] for _, name in ipairs(names) do local f = M.joinpath(p, name) - local stat = vim.uv.fs_stat(f) + local stat = uv.fs_stat(f) if stat and (not opts.type or opts.type == stat.type) then t[#t + 1] = f end @@ -349,6 +359,7 @@ end --- end) --- ``` --- +--- @since 12 --- @param source integer|string Buffer number (0 for current buffer) or file path (absolute or --- relative to the |current-directory|) to begin the search from. --- @param marker (string|string[]|fun(name: string, path: string): boolean) A marker, or list @@ -365,7 +376,7 @@ function M.root(source, marker) path = source elseif type(source) == 'number' then if vim.bo[source].buftype ~= '' then - path = assert(vim.uv.cwd()) + path = assert(uv.cwd()) else path = vim.api.nvim_buf_get_name(source) end @@ -528,6 +539,7 @@ end --- [[\\?\UNC\server\share\foo\..\..\..\bar]] => "//?/UNC/server/share/bar" --- ``` --- +---@since 10 ---@param path (string) Path to normalize ---@param opts? vim.fs.normalize.Opts ---@return (string) : Normalized path @@ -552,7 +564,7 @@ function M.normalize(path, opts) -- Expand ~ to users home directory if vim.startswith(path, '~') then - local home = vim.uv.os_homedir() or '~' + local home = uv.os_homedir() or '~' if home:sub(-1) == os_sep_local then home = home:sub(1, -2) end @@ -561,7 +573,7 @@ function M.normalize(path, opts) -- Expand environment variables if `opts.expand_env` isn't `false` if opts.expand_env == nil or opts.expand_env then - path = path:gsub('%$([%w_]+)', vim.uv.os_getenv) + path = path:gsub('%$([%w_]+)', uv.os_getenv) end if win then @@ -609,4 +621,56 @@ function M.normalize(path, opts) return path end +--- @param path string Path to remove +--- @param ty string type of path +--- @param recursive? boolean +--- @param force? boolean +local function rm(path, ty, recursive, force) + --- @diagnostic disable-next-line:no-unknown + local rm_fn + + if ty == 'directory' then + if recursive then + for file, fty in vim.fs.dir(path) do + rm(M.joinpath(path, file), fty, true, force) + end + elseif not force then + error(string.format('%s is a directory', path)) + end + + rm_fn = uv.fs_rmdir + else + rm_fn = uv.fs_unlink + end + + local ret, err, errnm = rm_fn(path) + if ret == nil and (not force or errnm ~= 'ENOENT') then + error(err) + end +end + +--- @class vim.fs.rm.Opts +--- @inlinedoc +--- +--- Remove directories and their contents recursively +--- @field recursive? boolean +--- +--- Ignore nonexistent files and arguments +--- @field force? boolean + +--- Remove files or directories +--- @since 13 +--- @param path string Path to remove +--- @param opts? vim.fs.rm.Opts +function M.rm(path, opts) + opts = opts or {} + + local stat, err, errnm = uv.fs_stat(path) + if stat then + rm(path, stat.type, opts.recursive, opts.force) + elseif not opts.force or errnm ~= 'ENOENT' then + error(err) + end +end + return M diff --git a/runtime/lua/vim/glob.lua b/runtime/lua/vim/glob.lua index ad4a915a94..22073b15c8 100644 --- a/runtime/lua/vim/glob.lua +++ b/runtime/lua/vim/glob.lua @@ -1,6 +1,6 @@ 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 C, Cc, Ct, Cf, Cmt = lpeg.C, lpeg.Cc, lpeg.Ct, lpeg.Cf, lpeg.Cmt local M = {} @@ -29,8 +29,10 @@ function M.to_lpeg(pattern) return patt end - local function add(acc, a) - return acc + a + local function condlist(conds, after) + return vim.iter(conds):fold(P(false), function(acc, cond) + return acc + cond * after + end) end local function mul(acc, m) @@ -45,13 +47,22 @@ function M.to_lpeg(pattern) return (-after * P(1)) ^ 0 * after end + -- luacheck: push ignore s + local function cut(s, idx, match) + return idx, match + end + -- luacheck: pop + 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 + Elem = Cmt( + Cf( + (V('DStar') + V('Star') + V('Ques') + V('Class') + V('CondList') + V('Literal')) + * (V('Elem') + V('End')), + mul + ), + cut ), DStar = (B(pathsep) + -B(P(1))) * P('**') @@ -63,15 +74,14 @@ function M.to_lpeg(pattern) * 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('}'), + CondList = P('{') * Ct(V('Cond') * (P(',') * V('Cond')) ^ 0) * P('}') * V('Pattern') / condlist, -- 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)), + Cond = Cmt(Cf((V('Ques') + V('Class') + V('Literal') - S(',}')) ^ 1, mul), cut) + Cc(P(0)), Literal = P(1) / P, End = P(-1) * Cc(P(-1)), }) diff --git a/runtime/lua/vim/health.lua b/runtime/lua/vim/health.lua index f40f04a064..52a7a13966 100644 --- a/runtime/lua/vim/health.lua +++ b/runtime/lua/vim/health.lua @@ -1,6 +1,6 @@ --- @brief ---<pre>help ---- health.vim is a minimal framework to help users troubleshoot configuration and +--- vim.health is a minimal framework to help users troubleshoot configuration and --- any other environment conditions that a plugin might care about. Nvim ships --- with healthchecks for configuration, performance, python support, ruby --- support, clipboard support, and more. @@ -39,7 +39,7 @@ --- :checkhealth vim* --- < --- ---- Create a healthcheck *health-dev* *vim.health* +--- Create a healthcheck *health-dev* --- --- Healthchecks are functions that check the user environment, configuration, or --- any other prerequisites that a plugin cares about. Nvim ships with @@ -104,10 +104,10 @@ local function filepath_to_healthcheck(path) local subpath = path:gsub('.*lua/', '') if vim.fs.basename(subpath) == 'health.lua' then -- */health.lua - name = assert(vim.fs.dirname(subpath)) + name = vim.fs.dirname(subpath) else -- */health/init.lua - name = assert(vim.fs.dirname(assert(vim.fs.dirname(subpath)))) + name = vim.fs.dirname(vim.fs.dirname(subpath)) end name = name:gsub('/', '.') @@ -275,114 +275,6 @@ function M.error(msg, ...) collect_output(input) 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.uv.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.uv.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" @@ -393,8 +285,8 @@ local path2name = function(path) -- Remove everything up to the last /lua/ folder path = path:gsub('^.*/lua/', '') - -- Remove the filename (health.lua) - path = vim.fs.dirname(path) + -- Remove the filename (health.lua) or (health/init.lua) + path = vim.fs.dirname(path:gsub('/init%.lua$', '')) -- Change slashes to dots path = path:gsub('/', '.') @@ -409,11 +301,13 @@ end local PATTERNS = { '/autoload/health/*.vim', '/lua/**/**/health.lua', '/lua/**/**/health/init.lua' } --- :checkhealth completion function used by cmdexpand.c get_healthcheck_names() M._complete = function() - local unique = vim + local unique = vim ---@type table<string,boolean> + ---@param pattern string .iter(vim.tbl_map(function(pattern) return vim.tbl_map(path2name, vim.api.nvim_get_runtime_file(pattern, true)) end, PATTERNS)) :flatten() + ---@param t table<string,boolean> :fold({}, function(t, name) t[name] = true -- Remove duplicates return t @@ -472,7 +366,7 @@ function M._check(mods, plugin_names) vim.fn.call(func, {}) else local f = assert(loadstring(func)) - local ok, output = pcall(f) + local ok, output = pcall(f) ---@type boolean, string if not ok then M.error( string.format('Failed to run healthcheck for "%s" plugin. Exception:\n%s\n', name, output) @@ -485,7 +379,14 @@ function M._check(mods, plugin_names) s_output = {} M.error('The healthcheck report for "' .. name .. '" plugin is empty.') end - local header = { string.rep('=', 78), name .. ': ' .. func, '' } + + local header = { + string.rep('=', 78), + -- Example: `foo.health: [ …] require("foo.health").check()` + ('%s: %s%s'):format(name, (' '):rep(76 - name:len() - func:len()), func), + '', + } + -- remove empty line after header from report_start if s_output[1] == '' then local tmp = {} ---@type string[] @@ -499,7 +400,7 @@ function M._check(mods, plugin_names) end s_output[#s_output + 1] = '' s_output = vim.list_extend(header, s_output) - vim.fn.append('$', s_output) + vim.fn.append(vim.fn.line('$'), s_output) vim.cmd.redraw() end diff --git a/runtime/lua/vim/health/health.lua b/runtime/lua/vim/health/health.lua index 5bc03199ee..d226f35f9a 100644 --- a/runtime/lua/vim/health/health.lua +++ b/runtime/lua/vim/health/health.lua @@ -11,10 +11,14 @@ local function check_runtime() health.start('Runtime') -- Files from an old installation. local bad_files = { - ['plugin/health.vim'] = false, ['autoload/health/nvim.vim'] = false, ['autoload/health/provider.vim'] = false, ['autoload/man.vim'] = false, + ['lua/provider/node/health.lua'] = false, + ['lua/provider/perl/health.lua'] = false, + ['lua/provider/python/health.lua'] = false, + ['lua/provider/ruby/health.lua'] = false, + ['plugin/health.vim'] = false, ['plugin/man.vim'] = false, ['queries/help/highlights.scm'] = false, ['queries/help/injections.scm'] = false, @@ -39,7 +43,7 @@ local function check_runtime() 'Found old files in $VIMRUNTIME (this can cause weird behavior):\n%s', bad_files_msg ), - { 'Delete the $VIMRUNTIME directory (or uninstall Nvim), then reinstall Nvim.' } + { 'Delete the $VIMRUNTIME directory, then reinstall Nvim.' } ) end end @@ -50,11 +54,11 @@ local function check_config() local init_lua = vim.fn.stdpath('config') .. '/init.lua' local init_vim = vim.fn.stdpath('config') .. '/init.vim' - local vimrc = vim.env.MYVIMRC and vim.fn.expand(vim.env.MYVIMRC) or init_lua + local vimrc = vim.env.MYVIMRC and vim.fs.normalize(vim.env.MYVIMRC) or init_lua if vim.fn.filereadable(vimrc) == 0 and vim.fn.filereadable(init_vim) == 0 then ok = false - local has_vim = vim.fn.filereadable(vim.fn.expand('~/.vimrc')) == 1 + local has_vim = vim.fn.filereadable(vim.fs.normalize('~/.vimrc')) == 1 health.warn( ('%s user config file: %s'):format( -1 == vim.fn.getfsize(vimrc) and 'Missing' or 'Unreadable', @@ -114,7 +118,7 @@ local function check_config() ) shadafile = ( vim.o.shadafile == '' - and (shadafile == '' and vim.fn.stdpath('state') .. '/shada/main.shada' or vim.fn.expand( + and (shadafile == '' and vim.fn.stdpath('state') .. '/shada/main.shada' or vim.fs.normalize( shadafile )) or (vim.o.shadafile == 'NONE' and '' or vim.o.shadafile) @@ -239,6 +243,7 @@ local function check_tmux() return end + ---@param option string local get_tmux_option = function(option) local cmd = 'tmux show-option -qvg ' .. option -- try global scope local out = vim.fn.system(vim.fn.split(cmd)) @@ -378,7 +383,7 @@ local function check_terminal() 'SSH_TTY', }) do if vim.env[env_var] then - health.info(vim.fn.printf('$%s="%s"', env_var, vim.env[env_var])) + health.info(string.format('$%s="%s"', env_var, vim.env[env_var])) end end end diff --git a/runtime/lua/vim/highlight.lua b/runtime/lua/vim/highlight.lua index f278bd357f..a8d88db372 100644 --- a/runtime/lua/vim/highlight.lua +++ b/runtime/lua/vim/highlight.lua @@ -20,8 +20,8 @@ M.priorities = { --- @class vim.highlight.range.Opts --- @inlinedoc --- ---- Type of range. See [setreg()] ---- (default: `'charwise'`) +--- Type of range. See [getregtype()] +--- (default: `'v'` i.e. charwise) --- @field regtype? string --- --- Indicates whether the range is end-inclusive @@ -31,8 +31,6 @@ M.priorities = { --- Indicates priority of highlight --- (default: `vim.highlight.priorities.user`) --- @field priority? integer ---- ---- @field package _scoped? boolean --- Apply highlight group to range of text. --- @@ -47,25 +45,72 @@ 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 - -- supported now - local region = vim.region(bufnr, start, finish, regtype, inclusive) - for linenr, cols in pairs(region) do - local end_row - if cols[2] == -1 then - end_row = linenr + 1 - cols[2] = 0 + + local v_maxcol = vim.v.maxcol + + local pos1 = type(start) == 'string' and vim.fn.getpos(start) + or { + bufnr, + start[1] + 1, + start[2] ~= -1 and start[2] ~= v_maxcol and start[2] + 1 or v_maxcol, + 0, + } + local pos2 = type(finish) == 'string' and vim.fn.getpos(finish) + or { + bufnr, + finish[1] + 1, + finish[2] ~= -1 and start[2] ~= v_maxcol and finish[2] + 1 or v_maxcol, + 0, + } + + local buf_line_count = vim.api.nvim_buf_line_count(bufnr) + pos1[2] = math.min(pos1[2], buf_line_count) + pos2[2] = math.min(pos2[2], buf_line_count) + + if pos1[2] <= 0 or pos1[3] <= 0 or pos2[2] <= 0 or pos2[3] <= 0 then + return + end + + vim._with({ buf = bufnr }, function() + if pos1[3] ~= v_maxcol then + local max_col1 = vim.fn.col({ pos1[2], '$' }) + pos1[3] = math.min(pos1[3], max_col1) + end + if pos2[3] ~= v_maxcol then + local max_col2 = vim.fn.col({ pos2[2], '$' }) + pos2[3] = math.min(pos2[3], max_col2) end - api.nvim_buf_set_extmark(bufnr, ns, linenr, cols[1], { + end) + + local region = vim.fn.getregionpos(pos1, pos2, { + type = regtype, + exclusive = not inclusive, + eol = true, + }) + -- For non-blockwise selection, use a single extmark. + if regtype == 'v' or regtype == 'V' then + region = { { region[1][1], region[#region][2] } } + if + regtype == 'V' + or region[1][2][2] == pos1[2] and pos1[3] == v_maxcol + or region[1][2][2] == pos2[2] and pos2[3] == v_maxcol + then + region[1][2][2] = region[1][2][2] + 1 + region[1][2][3] = 0 + end + end + + for _, res in ipairs(region) do + local start_row = res[1][2] - 1 + local start_col = res[1][3] - 1 + local end_row = res[2][2] - 1 + local end_col = res[2][3] + api.nvim_buf_set_extmark(bufnr, ns, start_row, start_col, { hl_group = higroup, end_row = end_row, - end_col = cols[2], + end_col = end_col, priority = priority, strict = false, - scoped = scoped, }) end end @@ -129,19 +174,18 @@ function M.on_yank(opts) yank_cancel() end - vim.api.nvim__win_add_ns(winid, yank_ns) + vim.api.nvim__ns_set(yank_ns, { wins = { winid } }) M.range(bufnr, yank_ns, higroup, "'[", "']", { regtype = event.regtype, inclusive = event.inclusive, priority = opts.priority or M.priorities.user, - _scoped = true, }) yank_cancel = function() yank_timer = nil yank_cancel = nil pcall(vim.api.nvim_buf_clear_namespace, bufnr, yank_ns, 0, -1) - pcall(vim.api.nvim__win_del_ns, winid, yank_ns) + pcall(vim.api.nvim__ns_set, { wins = {} }) end yank_timer = vim.defer_fn(yank_cancel, timeout) diff --git a/runtime/lua/vim/iter.lua b/runtime/lua/vim/iter.lua index 1093759efe..4bbcaf16db 100644 --- a/runtime/lua/vim/iter.lua +++ b/runtime/lua/vim/iter.lua @@ -6,10 +6,12 @@ --- of each pipeline stage is input to the next stage. The first stage depends on the type passed to --- `vim.iter()`: --- ---- - List tables (arrays, |lua-list|) yield only the value of each element. ---- - Holes (nil values) are allowed. +--- - Lists or arrays (|lua-list|) yield only the value of each element. +--- - Holes (nil values) are allowed (but discarded). +--- - Use pairs() to treat array/list tables as dicts (preserve holes and non-contiguous integer +--- keys): `vim.iter(pairs(…))`. --- - Use |Iter:enumerate()| to also pass the index to the next stage. ---- - Or initialize with ipairs(): `vim.iter(ipairs(…))`. +--- - Or initialize with ipairs(): `vim.iter(ipairs(…))`. --- - Non-list tables (|lua-dict|) yield both the key and value of each element. --- - Function |iterator|s yield all values returned by the underlying function. --- - Tables with a |__call()| metamethod are treated as function iterators. @@ -276,7 +278,7 @@ end --- -- { 6, 12 } --- ``` --- ----@param f fun(...):any Mapping function. Takes all values returned from +---@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 @@ -1034,7 +1036,7 @@ function Iter.new(src, ...) if type(k) ~= 'number' or k <= 0 or math.floor(k) ~= k then return Iter.new(pairs(src)) end - t[#t + 1] = v + t[#t + 1] = v -- Coerce to list-like table. end return ArrayIter.new(t) end diff --git a/runtime/lua/vim/keymap.lua b/runtime/lua/vim/keymap.lua index ec00c56c7a..50ca0d2d0e 100644 --- a/runtime/lua/vim/keymap.lua +++ b/runtime/lua/vim/keymap.lua @@ -15,30 +15,28 @@ local keymap = {} --- (Default: `false`) --- @field remap? boolean ---- Adds a new |mapping|. +--- Defines a |mapping| of |keycodes| to a function or keycodes. +--- --- Examples: --- --- ```lua ---- -- Map to a Lua function: ---- vim.keymap.set('n', 'lhs', function() print("real lua function") end) ---- -- Map to multiple modes: ---- vim.keymap.set({'n', 'v'}, '<leader>lr', vim.lsp.buf.references, { buffer = true }) ---- -- Buffer-local mapping: ---- vim.keymap.set('n', '<leader>w', "<cmd>w<cr>", { silent = true, buffer = 5 }) ---- -- Expr mapping: +--- -- Map "x" to a Lua function: +--- vim.keymap.set('n', 'x', function() print("real lua function") end) +--- -- Map "<leader>x" to multiple modes for the current buffer: +--- vim.keymap.set({'n', 'v'}, '<leader>x', vim.lsp.buf.references, { buffer = true }) +--- -- Map <Tab> to an expression (|:map-<expr>|): --- vim.keymap.set('i', '<Tab>', function() --- return vim.fn.pumvisible() == 1 and "<C-n>" or "<Tab>" --- end, { expr = true }) ---- -- <Plug> mapping: +--- -- Map "[%%" to a <Plug> mapping: --- vim.keymap.set('n', '[%%', '<Plug>(MatchitNormalMultiBackward)') --- ``` --- ----@param mode string|string[] Mode short-name, see |nvim_set_keymap()|. ---- Can also be list of modes to create mapping on multiple modes. +---@param mode string|string[] Mode "short-name" (see |nvim_set_keymap()|), or a list thereof. ---@param lhs string Left-hand side |{lhs}| of the mapping. ---@param rhs string|function Right-hand side |{rhs}| of the mapping, can be a Lua function. ---- ---@param opts? vim.keymap.set.Opts +--- ---@see |nvim_set_keymap()| ---@see |maparg()| ---@see |mapcheck()| diff --git a/runtime/lua/vim/loader.lua b/runtime/lua/vim/loader.lua index ea77a22416..e86d33bf53 100644 --- a/runtime/lua/vim/loader.lua +++ b/runtime/lua/vim/loader.lua @@ -200,7 +200,7 @@ function Loader.loader(modname) return chunk or error(err) end Loader._hashes = nil - return '\ncache_loader: module ' .. modname .. ' not found' + return ("\n\tcache_loader: module '%s' not found"):format(modname) end --- The `package.loaders` loader for libs @@ -208,8 +208,7 @@ end ---@return string|function ---@private 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 is_win = vim.fn.has('win32') == 1 local ret = M.find(modname, { patterns = is_win and { '.dll' } or { '.so' } })[1] if ret then -- Making function name in Lua 5.1 (see src/loadlib.c:mkfuncname) is @@ -222,7 +221,7 @@ function Loader.loader_lib(modname) local chunk, err = package.loadlib(ret.modpath, 'luaopen_' .. funcname:gsub('%.', '_')) return chunk or error(err) end - return '\ncache_loader_lib: module ' .. modname .. ' not found' + return ("\n\tcache_loader_lib: module '%s' not found"):format(modname) end --- `loadfile` using the cache @@ -290,6 +289,9 @@ function Loader.load(modpath, opts) end --- Finds Lua modules for the given module name. +--- +--- @since 0 +--- ---@param modname string Module name, or `"*"` to find the top-level modules instead ---@param opts? vim.loader.find.Opts Options for finding a module: ---@return vim.loader.ModuleInfo[] @@ -378,8 +380,10 @@ function M.find(modname, opts) return results end ---- Resets the cache for the path, or all the paths ---- if path is nil. +--- Resets the cache for the path, or all the paths if path is nil. +--- +--- @since 0 +--- ---@param path string? path to reset function M.reset(path) if path then @@ -399,6 +403,8 @@ end --- * adds the Lua loader using the byte-compilation cache --- * adds the libs loader --- * removes the default Nvim loader +--- +--- @since 0 function M.enable() if M.enabled then return @@ -422,6 +428,8 @@ end --- Disables the experimental Lua module loader: --- * removes the loaders --- * adds the default Nvim loader +--- +--- @since 0 function M.disable() if not M.enabled then return diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index 1592fd3151..60677554ce 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -3,7 +3,6 @@ local validate = vim.validate 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' @@ -11,6 +10,7 @@ local lsp = vim._defer_require('vim.lsp', { buf = ..., --- @module 'vim.lsp.buf' client = ..., --- @module 'vim.lsp.client' codelens = ..., --- @module 'vim.lsp.codelens' + completion = ..., --- @module 'vim.lsp.completion' diagnostic = ..., --- @module 'vim.lsp.diagnostic' handlers = ..., --- @module 'vim.lsp.handlers' inlay_hint = ..., --- @module 'vim.lsp.inlay_hint' @@ -33,43 +33,50 @@ 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 = { - [ms.textDocument_hover] = { 'hoverProvider' }, - [ms.textDocument_signatureHelp] = { 'signatureHelpProvider' }, - [ms.textDocument_definition] = { 'definitionProvider' }, - [ms.textDocument_implementation] = { 'implementationProvider' }, - [ms.textDocument_declaration] = { 'declarationProvider' }, - [ms.textDocument_typeDefinition] = { 'typeDefinitionProvider' }, - [ms.textDocument_documentSymbol] = { 'documentSymbolProvider' }, - [ms.textDocument_prepareCallHierarchy] = { 'callHierarchyProvider' }, [ms.callHierarchy_incomingCalls] = { 'callHierarchyProvider' }, [ms.callHierarchy_outgoingCalls] = { 'callHierarchyProvider' }, - [ms.textDocument_prepareTypeHierarchy] = { 'typeHierarchyProvider' }, - [ms.typeHierarchy_subtypes] = { 'typeHierarchyProvider' }, - [ms.typeHierarchy_supertypes] = { 'typeHierarchyProvider' }, - [ms.textDocument_rename] = { 'renameProvider' }, - [ms.textDocument_prepareRename] = { 'renameProvider', 'prepareProvider' }, + [ms.codeAction_resolve] = { 'codeActionProvider', 'resolveProvider' }, + [ms.codeLens_resolve] = { 'codeLensProvider', 'resolveProvider' }, + [ms.documentLink_resolve] = { 'documentLinkProvider', 'resolveProvider' }, + [ms.inlayHint_resolve] = { 'inlayHintProvider', 'resolveProvider' }, [ms.textDocument_codeAction] = { 'codeActionProvider' }, [ms.textDocument_codeLens] = { 'codeLensProvider' }, - [ms.codeLens_resolve] = { 'codeLensProvider', 'resolveProvider' }, - [ms.codeAction_resolve] = { 'codeActionProvider', 'resolveProvider' }, - [ms.workspace_executeCommand] = { 'executeCommandProvider' }, - [ms.workspace_symbol] = { 'workspaceSymbolProvider' }, - [ms.textDocument_references] = { 'referencesProvider' }, - [ms.textDocument_rangeFormatting] = { 'documentRangeFormattingProvider' }, - [ms.textDocument_formatting] = { 'documentFormattingProvider' }, [ms.textDocument_completion] = { 'completionProvider' }, - [ms.textDocument_documentHighlight] = { 'documentHighlightProvider' }, - [ms.textDocument_semanticTokens_full] = { 'semanticTokensProvider' }, - [ms.textDocument_semanticTokens_full_delta] = { 'semanticTokensProvider' }, - [ms.textDocument_inlayHint] = { 'inlayHintProvider' }, + [ms.textDocument_declaration] = { 'declarationProvider' }, + [ms.textDocument_definition] = { 'definitionProvider' }, [ms.textDocument_diagnostic] = { 'diagnosticProvider' }, - [ms.inlayHint_resolve] = { 'inlayHintProvider', 'resolveProvider' }, - [ms.textDocument_documentLink] = { 'documentLinkProvider' }, - [ms.documentLink_resolve] = { 'documentLinkProvider', 'resolveProvider' }, [ms.textDocument_didClose] = { 'textDocumentSync', 'openClose' }, [ms.textDocument_didOpen] = { 'textDocumentSync', 'openClose' }, - [ms.textDocument_willSave] = { 'textDocumentSync', 'willSave' }, + [ms.textDocument_documentColor] = { 'colorProvider' }, + [ms.textDocument_documentHighlight] = { 'documentHighlightProvider' }, + [ms.textDocument_documentLink] = { 'documentLinkProvider' }, + [ms.textDocument_documentSymbol] = { 'documentSymbolProvider' }, + [ms.textDocument_formatting] = { 'documentFormattingProvider' }, + [ms.textDocument_hover] = { 'hoverProvider' }, + [ms.textDocument_implementation] = { 'implementationProvider' }, + [ms.textDocument_inlayHint] = { 'inlayHintProvider' }, + [ms.textDocument_inlineValue] = { 'inlineValueProvider' }, + [ms.textDocument_linkedEditingRange] = { 'linkedEditingRangeProvider' }, + [ms.textDocument_moniker] = { 'monikerProvider' }, + [ms.textDocument_onTypeFormatting] = { 'documentOnTypeFormattingProvider' }, + [ms.textDocument_prepareCallHierarchy] = { 'callHierarchyProvider' }, + [ms.textDocument_prepareRename] = { 'renameProvider', 'prepareProvider' }, + [ms.textDocument_prepareTypeHierarchy] = { 'typeHierarchyProvider' }, + [ms.textDocument_rangeFormatting] = { 'documentRangeFormattingProvider' }, + [ms.textDocument_rangesFormatting] = { 'documentRangeFormattingProvider', 'rangesSupport' }, + [ms.textDocument_references] = { 'referencesProvider' }, + [ms.textDocument_rename] = { 'renameProvider' }, + [ms.textDocument_selectionRange] = { 'selectionRangeProvider' }, + [ms.textDocument_semanticTokens_full] = { 'semanticTokensProvider' }, + [ms.textDocument_semanticTokens_full_delta] = { 'semanticTokensProvider' }, + [ms.textDocument_signatureHelp] = { 'signatureHelpProvider' }, + [ms.textDocument_typeDefinition] = { 'typeDefinitionProvider' }, [ms.textDocument_willSaveWaitUntil] = { 'textDocumentSync', 'willSaveWaitUntil' }, + [ms.textDocument_willSave] = { 'textDocumentSync', 'willSave' }, + [ms.typeHierarchy_subtypes] = { 'typeHierarchyProvider' }, + [ms.typeHierarchy_supertypes] = { 'typeHierarchyProvider' }, + [ms.workspace_executeCommand] = { 'executeCommandProvider' }, + [ms.workspace_symbol] = { 'workspaceSymbolProvider' }, } -- TODO improve handling of scratch buffers with LSP attached. @@ -201,10 +208,10 @@ end --- Predicate used to decide if a client should be re-used. Used on all --- running clients. The default implementation re-uses a client if name and --- root_dir matches. ---- @field reuse_client fun(client: vim.lsp.Client, config: vim.lsp.ClientConfig): boolean +--- @field reuse_client? fun(client: vim.lsp.Client, config: vim.lsp.ClientConfig): boolean --- --- Buffer handle to attach to if starting or re-using a client (0 for current). ---- @field bufnr integer +--- @field bufnr? integer --- --- Suppress error reporting if the LSP server fails to start (default false). --- @field silent? boolean @@ -351,7 +358,7 @@ function lsp._set_defaults(client, bufnr) then vim.bo[bufnr].formatexpr = 'v:lua.vim.lsp.formatexpr()' end - api.nvim_buf_call(bufnr, function() + vim._with({ buf = bufnr }, function() if client.supports_method(ms.textDocument_hover) and is_empty_or_default(bufnr, 'keywordprg') @@ -377,9 +384,9 @@ local function reset_defaults(bufnr) if vim.bo[bufnr].formatexpr == 'v:lua.vim.lsp.formatexpr()' then vim.bo[bufnr].formatexpr = nil end - api.nvim_buf_call(bufnr, function() + vim._with({ buf = bufnr }, function() local keymap = vim.fn.maparg('K', 'n', false, true) - if keymap and keymap.callback == vim.lsp.buf.hover then + if keymap and keymap.callback == vim.lsp.buf.hover and keymap.buffer == 1 then vim.keymap.del('n', 'K', { buffer = bufnr }) end end) @@ -391,9 +398,9 @@ end local function on_client_exit(code, signal, client_id) local client = all_clients[client_id] - for bufnr in pairs(client.attached_buffers) do - vim.schedule(function() - if client and client.attached_buffers[bufnr] then + vim.schedule(function() + for bufnr in pairs(client.attached_buffers) do + if client and client.attached_buffers[bufnr] and api.nvim_buf_is_valid(bufnr) then api.nvim_exec_autocmds('LspDetach', { buffer = bufnr, modeline = false, @@ -401,15 +408,16 @@ local function on_client_exit(code, signal, client_id) }) end - local namespace = vim.lsp.diagnostic.get_namespace(client_id) - vim.diagnostic.reset(namespace, bufnr) client.attached_buffers[bufnr] = nil if #lsp.get_clients({ bufnr = bufnr, _uninitialized = true }) == 0 then reset_defaults(bufnr) end - end) - end + end + + local namespace = vim.lsp.diagnostic.get_namespace(client_id) + vim.diagnostic.reset(namespace) + end) local name = client.name or 'unknown' @@ -519,7 +527,6 @@ local function buf_detach_client(bufnr, client) end client.attached_buffers[bufnr] = nil - util.buf_versions[bufnr] = nil local namespace = lsp.diagnostic.get_namespace(client.id) vim.diagnostic.reset(namespace, bufnr) @@ -577,7 +584,8 @@ local function buf_attach(bufnr) api.nvim_buf_attach(bufnr, false, { on_lines = function(_, _, changedtick, firstline, lastline, new_lastline) if #lsp.get_clients({ bufnr = bufnr }) == 0 then - return true -- detach + -- detach if there are no clients + return #lsp.get_clients({ bufnr = bufnr, _uninitialized = true }) == 0 end util.buf_versions[bufnr] = changedtick changetracking.send_changes(bufnr, firstline, lastline, new_lastline) @@ -603,6 +611,7 @@ local function buf_attach(bufnr) buf_detach_client(bufnr, client) end attached_buffers[bufnr] = nil + util.buf_versions[bufnr] = nil end, -- TODO if we know all of the potential clients ahead of time, then we @@ -852,17 +861,20 @@ api.nvim_create_autocmd('VimLeavePre', { ---@param params table|nil Parameters to send to the server ---@param handler? lsp.Handler See |lsp-handler| --- If nil, follows resolution strategy defined in |lsp-handler-configuration| ---- +---@param on_unsupported? fun() +--- The function to call when the buffer has no clients that support the given method. +--- Defaults to an `ERROR` level notification. ---@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 ---cancel all the requests. You could instead ---iterate all clients and call their `cancel_request()` methods. -function lsp.buf_request(bufnr, method, params, handler) +function lsp.buf_request(bufnr, method, params, handler, on_unsupported) validate({ bufnr = { bufnr, 'n', true }, method = { method, 's' }, handler = { handler, 'f', true }, + on_unsupported = { on_unsupported, 'f', true }, }) bufnr = resolve_bufnr(bufnr) @@ -884,7 +896,11 @@ 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) + if on_unsupported == nil then + vim.notify(lsp._unsupported_method(method), vim.log.levels.ERROR) + else + on_unsupported() + end vim.cmd.redraw() return {}, function() end end @@ -1002,8 +1018,7 @@ 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) - log.debug('omnifunc.findstart', { findstart = findstart, base = base }) - return vim.lsp._completion.omnifunc(findstart, base) + return vim.lsp.completion._omnifunc(findstart, base) end --- @class vim.lsp.formatexpr.Opts @@ -1016,7 +1031,7 @@ end --- 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` +--- `setlocal formatexpr=v:lua.vim.lsp.formatexpr()` or (more typically) in `on_attach` --- via `vim.bo[bufnr].formatexpr = 'v:lua.vim.lsp.formatexpr(#{timeout_ms:250})'`. --- ---@param opts? vim.lsp.formatexpr.Opts diff --git a/runtime/lua/vim/lsp/_completion.lua b/runtime/lua/vim/lsp/_completion.lua deleted file mode 100644 index a169f96565..0000000000 --- a/runtime/lua/vim/lsp/_completion.lua +++ /dev/null @@ -1,276 +0,0 @@ -local M = {} -local api = vim.api -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 vim.lsp._snippet_grammar.parse(input) - end) - return ok and tostring(parsed) or input -end - ---- Returns text that should be inserted when selecting completion item. The ---- precedence is as follows: textEdit.newText > insertText > label ---- ---- See https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion ---- ----@param item lsp.CompletionItem ----@return string -local function get_completion_word(item) - if item.textEdit ~= nil and item.textEdit.newText ~= nil and item.textEdit.newText ~= '' then - if item.insertTextFormat == protocol.InsertTextFormat.PlainText then - return item.textEdit.newText - else - return parse_snippet(item.textEdit.newText) - end - elseif item.insertText ~= nil and item.insertText ~= '' then - if item.insertTextFormat == protocol.InsertTextFormat.PlainText then - return item.insertText - else - return parse_snippet(item.insertText) - end - end - return item.label -end - ---- 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 -end - ---- Turns the result of a `textDocument/completion` request into vim-compatible ---- |complete-items|. ---- ----@param result vim.lsp.CompletionResult Result of `textDocument/completion` ----@param prefix string prefix to filter the completion items ----@return table[] ----@see complete-items -function M._lsp_to_complete_items(result, prefix) - local items = get_items(result) - if vim.tbl_isempty(items) then - return {} - end - - local function matches_prefix(item) - return vim.startswith(get_completion_word(item), prefix) - end - - items = vim.tbl_filter(matches_prefix, items) --[[@as lsp.CompletionItem[]|]] - table.sort(items, function(a, b) - return (a.sortText or a.label) < (b.sortText or b.label) - end) - - local matches = {} - for _, item in ipairs(items) do - local info = '' - local documentation = item.documentation - if documentation then - if type(documentation) == 'string' and documentation ~= '' then - info = documentation - elseif type(documentation) == 'table' and type(documentation.value) == 'string' then - info = documentation.value - else - vim.notify( - ('invalid documentation value %s'):format(vim.inspect(documentation)), - vim.log.levels.WARN - ) - end - end - local word = get_completion_word(item) - table.insert(matches, { - word = word, - abbr = item.label, - kind = protocol.CompletionItemKind[item.kind] or 'Unknown', - menu = item.detail or '', - info = #info > 0 and info or nil, - icase = 1, - dup = 1, - empty = 1, - user_data = { - nvim = { - lsp = { - completion_item = item, - }, - }, - }, - }) - end - return matches -end - ----@param lnum integer 0-indexed ----@param items lsp.CompletionItem[] -local function adjust_start_col(lnum, line, items, encoding) - local min_start_char = nil - for _, item in pairs(items) do - if item.textEdit and item.textEdit.range.start.line == lnum then - if min_start_char and min_start_char ~= item.textEdit.range.start.character then - return nil - end - min_start_char = item.textEdit.range.start.character - end - end - if min_start_char then - return vim.lsp.util._str_byteindex_enc(line, min_start_char, encoding) - else - return nil - end -end - ----@private ----@param line string line content ----@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 vim.lsp.CompletionResult ----@param encoding string ----@return table[] matches ----@return integer? server_start_boundary -function M._convert_results( - line, - lnum, - cursor_col, - client_start_boundary, - server_start_boundary, - result, - encoding -) - -- Completion response items may be relative to a position different than `client_start_boundary`. - -- Concrete example, with lua-language-server: - -- - -- require('plenary.asy| - -- ▲ ▲ ▲ - -- │ │ └── cursor_pos: 20 - -- │ └────── client_start_boundary: 17 - -- └────────────── textEdit.range.start.character: 9 - -- .newText = 'plenary.async' - -- ^^^ - -- prefix (We'd remove everything not starting with `asy`, - -- so we'd eliminate the `plenary.async` result - -- - -- `adjust_start_col` is used to prefer the language server boundary. - -- - local candidates = get_items(result) - local curstartbyte = adjust_start_col(lnum, line, candidates, encoding) - if server_start_boundary == nil then - server_start_boundary = curstartbyte - elseif curstartbyte ~= nil and curstartbyte ~= server_start_boundary then - server_start_boundary = client_start_boundary - end - local prefix = line:sub((server_start_boundary or client_start_boundary) + 1, cursor_col) - local matches = M._lsp_to_complete_items(result, prefix) - return matches, server_start_boundary -end - ----@param findstart integer 0 or 1, decides behavior ----@param base integer findstart=0, text to match against ----@return integer|table Decided by {findstart}: ---- - findstart=0: column where the completion starts, or -2 or -3 ---- - findstart=1: list of matches (actually just calls |complete()|) -function M.omnifunc(findstart, base) - assert(base) -- silence luals - local bufnr = api.nvim_get_current_buf() - local clients = lsp.get_clients({ bufnr = bufnr, method = ms.textDocument_completion }) - local remaining = #clients - if remaining == 0 then - return findstart == 1 and -1 or {} - end - - local win = api.nvim_get_current_win() - local cursor = api.nvim_win_get_cursor(win) - local lnum = cursor[1] - 1 - local cursor_col = cursor[2] - local line = api.nvim_get_current_line() - local line_to_cursor = line:sub(1, cursor_col) - local client_start_boundary = vim.fn.match(line_to_cursor, '\\k*$') --[[@as integer]] - local server_start_boundary = nil - local items = {} - - local function on_done() - local mode = api.nvim_get_mode()['mode'] - if mode == 'i' or mode == 'ic' then - vim.fn.complete((server_start_boundary or client_start_boundary) + 1, items) - end - end - - local util = vim.lsp.util - for _, client in ipairs(clients) do - local params = util.make_position_params(win, client.offset_encoding) - client.request(ms.textDocument_completion, params, function(err, result) - if err then - vim.lsp.log.warn(err.message) - end - if result and vim.fn.mode() == 'i' then - local matches - matches, server_start_boundary = M._convert_results( - line, - lnum, - cursor_col, - client_start_boundary, - server_start_boundary, - result, - client.offset_encoding - ) - vim.list_extend(items, matches) - end - remaining = remaining - 1 - if remaining == 0 then - vim.schedule(on_done) - end - end, bufnr) - end - - -- Return -2 to signal that we should continue completion so that we can - -- async complete. - return -2 -end - -return M diff --git a/runtime/lua/vim/lsp/_dynamic.lua b/runtime/lua/vim/lsp/_dynamic.lua index 819b03a63a..27113c0e74 100644 --- a/runtime/lua/vim/lsp/_dynamic.lua +++ b/runtime/lua/vim/lsp/_dynamic.lua @@ -24,7 +24,6 @@ function M:supports_registration(method) end --- @param registrations lsp.Registration[] ---- @package function M:register(registrations) -- remove duplicates self:unregister(registrations) @@ -38,7 +37,6 @@ function M:register(registrations) end --- @param unregisterations lsp.Unregistration[] ---- @package function M:unregister(unregisterations) for _, unreg in ipairs(unregisterations) do local method = unreg.method @@ -58,7 +56,6 @@ end --- @param method string --- @param opts? {bufnr: integer?} --- @return lsp.Registration? (table|nil) the registration if found ---- @package function M:get(method, opts) opts = opts or {} opts.bufnr = opts.bufnr or vim.api.nvim_get_current_buf() @@ -78,7 +75,6 @@ end --- @param method string --- @param opts? {bufnr: integer?} ---- @package function M:supports(method, opts) return self:get(method, opts) ~= nil end diff --git a/runtime/lua/vim/lsp/_meta/protocol.lua b/runtime/lua/vim/lsp/_meta/protocol.lua index 9a11972007..d83c40a09f 100644 --- a/runtime/lua/vim/lsp/_meta/protocol.lua +++ b/runtime/lua/vim/lsp/_meta/protocol.lua @@ -134,7 +134,7 @@ error('Cannot require a meta file') ---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 +---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 @@ -681,6 +681,11 @@ error('Cannot require a meta file') ---of a notebook cell. ---@field cellTextDocuments lsp.TextDocumentItem[] +---Registration options specific to a notebook. +--- +---@since 3.17.0 +---@class lsp.NotebookDocumentSyncRegistrationOptions: lsp.NotebookDocumentSyncOptions, lsp.StaticRegistrationOptions + ---The params sent in a change notebook document notification. --- ---@since 3.17.0 @@ -789,7 +794,7 @@ error('Cannot require a meta file') ---Information about the server. --- ---@since 3.15.0 ----@field serverInfo? lsp._anonym1.serverInfo +---@field serverInfo? lsp.ServerInfo ---The data type of the ResponseError if the ---initialize request fails. @@ -1115,7 +1120,7 @@ error('Cannot require a meta file') ---capability. --- ---@since 3.17.0 ----@field itemDefaults? lsp._anonym2.itemDefaults +---@field itemDefaults? lsp.CompletionItemDefaults --- ---The completion items. ---@field items lsp.CompletionItem[] @@ -1171,7 +1176,7 @@ error('Cannot require a meta file') --- ---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 +---if the client specifies the client capability ---`textDocument.signatureHelp.noActiveParameterSupport === true` --- ---If omitted or the value lies outside the range of @@ -1307,6 +1312,12 @@ error('Cannot require a meta file') ---Title of the command, like `save`. ---@field title string --- +---An optional tooltip. +--- +---@since 3.18.0 +---@proposed +---@field tooltip? string +--- ---The identifier of the actual command handler. ---@field command string --- @@ -1355,7 +1366,7 @@ error('Cannot require a meta file') --- error message with `reason` in the editor. --- ---@since 3.16.0 ----@field disabled? lsp._anonym4.disabled +---@field disabled? lsp.CodeActionDisabled --- ---The workspace edit this code action performs. ---@field edit? lsp.WorkspaceEdit @@ -1379,6 +1390,12 @@ error('Cannot require a meta file') --- ---A query string to filter symbols by. Clients may send an empty ---string here to request all symbols. +--- +---The `query`-parameter should be interpreted in a *relaxed way* as editors +---will apply their own highlighting and scoring on the results. A good rule +---of thumb is to match case-insensitive and to simply check that the +---characters of *query* appear in their order in a candidate symbol. +---Servers shouldn't use prefix, substring, or similar strict matching. ---@field query string ---A special workspace symbol that supports locations without a range. @@ -1393,7 +1410,7 @@ error('Cannot require a meta file') ---capability `workspace.symbol.resolveSupport`. --- ---See SymbolInformation#location for more details. ----@field location lsp.Location|lsp._anonym5.location +---@field location lsp.Location|lsp.LocationUriOnly --- ---A data entry field that is preserved on a workspace symbol between a ---workspace symbol request and a workspace symbol resolve request. @@ -1566,6 +1583,12 @@ error('Cannot require a meta file') --- ---The edits to apply. ---@field edit lsp.WorkspaceEdit +--- +---Additional data about the edit. +--- +---@since 3.18.0 +---@proposed +---@field metadata? lsp.WorkspaceEditMetadata ---The result returned from the apply workspace edit request. --- @@ -1650,7 +1673,7 @@ error('Cannot require a meta file') ---@class lsp.SetTraceParams --- ----@field value lsp.TraceValues +---@field value lsp.TraceValue ---@class lsp.LogTraceParams --- @@ -1848,10 +1871,10 @@ error('Cannot require a meta file') --- ---Server supports providing semantic tokens for a specific range ---of a document. ----@field range? boolean|lsp._anonym6.range +---@field range? boolean|lsp._anonym1.range --- ---Server supports providing semantic tokens for a full document. ----@field full? boolean|lsp._anonym7.full +---@field full? boolean|lsp.SemanticTokensFullDelta ---@since 3.16.0 ---@class lsp.SemanticTokensEdit @@ -1888,7 +1911,10 @@ error('Cannot require a meta file') --- ---@since 3.16.0 - support for AnnotatedTextEdit. This is guarded using a ---client capability. ----@field edits (lsp.TextEdit|lsp.AnnotatedTextEdit)[] +--- +---@since 3.18.0 - support for SnippetTextEdit. This is guarded using a +---client capability. +---@field edits (lsp.TextEdit|lsp.AnnotatedTextEdit|lsp.SnippetTextEdit)[] ---Create file operation. ---@class lsp.CreateFile: lsp.ResourceOperation @@ -2235,7 +2261,7 @@ error('Cannot require a meta file') ---@field uri lsp.DocumentUri --- ---The text document's language identifier. ----@field languageId string +---@field languageId lsp.LanguageKind --- ---The version number of this document (it will increase after each ---change, including undo/redo). @@ -2244,6 +2270,28 @@ error('Cannot require a meta file') ---The content of the opened text document. ---@field text string +---Options specific to a notebook plus its cells +---to be synced to the server. +--- +---If a selector provides a notebook document +---filter but no cell selector all cells of a +---matching notebook document will be synced. +--- +---If a selector provides no notebook document +---filter but only a cell selector all notebook +---document that contain at least one matching +---cell will be synced. +--- +---@since 3.17.0 +---@class lsp.NotebookDocumentSyncOptions +--- +---The notebooks to be synced +---@field notebookSelector (lsp.NotebookDocumentFilterWithNotebook|lsp.NotebookDocumentFilterWithCells)[] +--- +---Whether save notification should be forwarded to +---the server. Will only be honored if mode === `notebook`. +---@field save? boolean + ---A versioned notebook document identifier. --- ---@since 3.17.0 @@ -2266,7 +2314,7 @@ error('Cannot require a meta file') ---@field metadata? lsp.LSPObject --- ---Changes to cells ----@field cells? lsp._anonym8.cells +---@field cells? lsp.NotebookDocumentCellChanges ---A literal to identify a notebook document in the client. --- @@ -2348,7 +2396,7 @@ error('Cannot require a meta file') ---Information about the client --- ---@since 3.15.0 ----@field clientInfo? lsp._anonym11.clientInfo +---@field clientInfo? lsp.ClientInfo --- ---The locale the client is currently showing the user interface ---in. This must not necessarily be the locale of the operating @@ -2380,7 +2428,7 @@ error('Cannot require a meta file') ---@field initializationOptions? lsp.LSPAny --- ---The initial trace setting. If omitted trace is disabled ('off'). ----@field trace? lsp.TraceValues +---@field trace? lsp.TraceValue ---@class lsp.WorkspaceFoldersInitializeParams --- @@ -2534,18 +2582,24 @@ error('Cannot require a meta file') ---@proposed ---@field inlineCompletionProvider? boolean|lsp.InlineCompletionOptions --- ----Text document specific server capabilities. ---- ----@since 3.18.0 ----@proposed ----@field textDocument? lsp._anonym12.textDocument ---- ---Workspace specific server capabilities. ----@field workspace? lsp._anonym14.workspace +---@field workspace? lsp.WorkspaceOptions --- ---Experimental server capabilities. ---@field experimental? lsp.LSPAny +---Information about the server +--- +---@since 3.15.0 +---@since 3.18.0 ServerInfo type name added. +---@class lsp.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 + ---A text document identifier to denote a specific version of a text document. ---@class lsp.VersionedTextDocumentIdentifier: lsp.TextDocumentIdentifier --- @@ -2586,8 +2640,9 @@ error('Cannot require a meta file') ---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. +---The diagnostic's severity. To avoid interpretation mismatches when a +---server is used with different clients it is highly recommended that servers +---always provide a severity value. ---@field severity? lsp.DiagnosticSeverity --- ---The diagnostic's code, which usually appear in the user interface. @@ -2604,10 +2659,8 @@ error('Cannot require a meta file') ---appears in the user interface. ---@field source? string --- ----The diagnostic's message. It usually appears in the user interface. ---- ----@since 3.18.0 - support for `MarkupContent`. This is guarded by the client capability `textDocument.diagnostic.markupMessageSupport`. ----@field message string|lsp.MarkupContent +---The diagnostic's message. It usually appears in the user interface +---@field message string --- ---Additional metadata about the diagnostic. --- @@ -2661,6 +2714,46 @@ error('Cannot require a meta file') ---The range if the replace is requested. ---@field replace lsp.Range +---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 +---be used if a completion item itself doesn't specify the value. +--- +---If a completion list specifies a default value and a completion item +---also specifies a corresponding value the one from the item is used. +--- +---Servers are only allowed to return default values if the client +---signals support for this via the `completionList.itemDefaults` +---capability. +--- +---@since 3.17.0 +---@class lsp.CompletionItemDefaults +--- +---A default commit character set. +--- +---@since 3.17.0 +---@field commitCharacters? string[] +--- +---A default edit range. +--- +---@since 3.17.0 +---@field editRange? lsp.Range|lsp.EditRangeWithInsertReplace +--- +---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 + ---Completion options. ---@class lsp.CompletionOptions: lsp.WorkDoneProgressOptions --- @@ -2692,7 +2785,7 @@ error('Cannot require a meta file') ---capabilities. --- ---@since 3.17.0 ----@field completionItem? lsp._anonym15.completionItem +---@field completionItem? lsp.ServerCompletionItemOptions ---Hover options. ---@class lsp.HoverOptions: lsp.WorkDoneProgressOptions @@ -2742,7 +2835,7 @@ error('Cannot require a meta file') --- ---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 +---if the client specifies the client capability ---`textDocument.signatureHelp.noActiveParameterSupport === true` --- ---If provided (or `null`), this is used in place of @@ -2819,8 +2912,6 @@ error('Cannot require a meta file') ---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. ---- ----Note that the client should check the `textDocument.diagnostic.markupMessageSupport` server capability before sending diagnostics with markup messages to a server. ---@field diagnostics lsp.Diagnostic[] --- ---Requested kind of actions to return. @@ -2834,6 +2925,16 @@ error('Cannot require a meta file') ---@since 3.17.0 ---@field triggerKind? lsp.CodeActionTriggerKind +---Captures why the code action is currently disabled. +--- +---@since 3.18.0 +---@class lsp.CodeActionDisabled +--- +---Human readable description of why the code action is currently disabled. +--- +---This is displayed in the code actions UI. +---@field reason string + ---Provider options for a {@link CodeActionRequest}. ---@class lsp.CodeActionOptions: lsp.WorkDoneProgressOptions --- @@ -2843,12 +2944,36 @@ error('Cannot require a meta file') ---may list out every specific kind they provide. ---@field codeActionKinds? lsp.CodeActionKind[] --- +---Static documentation for a class of code actions. +--- +---Documentation from the provider should be shown in the code actions menu if either: +--- +---- Code actions of `kind` are requested by the editor. In this case, the editor will show the documentation that +--- most closely matches the requested code action kind. For example, if a provider has documentation for +--- both `Refactor` and `RefactorExtract`, when the user requests code actions for `RefactorExtract`, +--- the editor will use the documentation for `RefactorExtract` instead of the documentation for `Refactor`. +--- +---- Any code actions of `kind` are returned by the provider. +--- +---At most one documentation entry should be shown per provider. +--- +---@since 3.18.0 +---@proposed +---@field documentation? lsp.CodeActionKindDocumentation[] +--- ---The server provides support to resolve additional ---information for a code action. --- ---@since 3.16.0 ---@field resolveProvider? boolean +---Location with only uri and does not include range. +--- +---@since 3.18.0 +---@class lsp.LocationUriOnly +--- +---@field uri lsp.DocumentUri + ---Server capabilities for a {@link WorkspaceSymbolRequest}. ---@class lsp.WorkspaceSymbolOptions: lsp.WorkDoneProgressOptions --- @@ -2923,12 +3048,33 @@ error('Cannot require a meta file') ---@since version 3.12.0 ---@field prepareProvider? boolean +---@since 3.18.0 +---@class lsp.PrepareRenamePlaceholder +--- +---@field range lsp.Range +--- +---@field placeholder string + +---@since 3.18.0 +---@class lsp.PrepareRenameDefaultBehavior +--- +---@field defaultBehavior boolean + ---The server capabilities of a {@link ExecuteCommandRequest}. ---@class lsp.ExecuteCommandOptions: lsp.WorkDoneProgressOptions --- ---The commands to be executed on the server ---@field commands string[] +---Additional data about a workspace edit. +--- +---@since 3.18.0 +---@proposed +---@class lsp.WorkspaceEditMetadata +--- +---Signal to the editor that this edit is a refactoring. +---@field isRefactoring? boolean + ---@since 3.16.0 ---@class lsp.SemanticTokensLegend --- @@ -2938,6 +3084,14 @@ error('Cannot require a meta file') ---The token modifiers a server uses. ---@field tokenModifiers string[] +---Semantic tokens options to support deltas for full documents +--- +---@since 3.18.0 +---@class lsp.SemanticTokensFullDelta +--- +---The server supports deltas for full documents. +---@field delta? boolean + ---A text document identifier to optionally denote a specific version of a text document. ---@class lsp.OptionalVersionedTextDocumentIdentifier: lsp.TextDocumentIdentifier --- @@ -2956,6 +3110,21 @@ error('Cannot require a meta file') ---The actual identifier of the change annotation ---@field annotationId lsp.ChangeAnnotationIdentifier +---An interactive text edit. +--- +---@since 3.18.0 +---@proposed +---@class lsp.SnippetTextEdit +--- +---The range of the text document to be manipulated. +---@field range lsp.Range +--- +---The snippet to be inserted. +---@field snippet lsp.StringValue +--- +---The actual identifier of the snippet edit. +---@field annotationId? lsp.ChangeAnnotationIdentifier + ---A generic resource operation. ---@class lsp.ResourceOperation --- @@ -3066,20 +3235,43 @@ error('Cannot require a meta file') ---if supported by the client. ---@field executionSummary? lsp.ExecutionSummary ----A change describing how to move a `NotebookCell` ----array from state S to S'. +---@since 3.18.0 +---@class lsp.NotebookDocumentFilterWithNotebook --- ----@since 3.17.0 ----@class lsp.NotebookCellArrayChange +---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 start oftest of the cell that changed. ----@field start uinteger +---The cells of the matching notebook to be synced. +---@field cells? lsp.NotebookCellLanguage[] + +---@since 3.18.0 +---@class lsp.NotebookDocumentFilterWithCells --- ----The deleted cells ----@field deleteCount uinteger +---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 new cells, if any ----@field cells? lsp.NotebookCell[] +---The cells of the matching notebook to be synced. +---@field cells lsp.NotebookCellLanguage[] + +---Cell changes to a notebook document. +--- +---@since 3.18.0 +---@class lsp.NotebookDocumentCellChanges +--- +---Changes to the cell structure to add or +---remove cells. +---@field structure? lsp.NotebookDocumentCellChangeStructure +--- +---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? lsp.NotebookDocumentCellContentChanges[] ---Describes the currently selected completion item. --- @@ -3093,6 +3285,18 @@ error('Cannot require a meta file') ---The text the range will be replaced with if this completion is accepted. ---@field text string +---Information about the client +--- +---@since 3.15.0 +---@since 3.18.0 ClientInfo type name added. +---@class lsp.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 + ---Defines the capabilities provided by the client. ---@class lsp.ClientCapabilities --- @@ -3140,69 +3344,40 @@ error('Cannot require a meta file') ---sent. ---@field save? boolean|lsp.SaveOptions ----Options specific to a notebook plus its cells ----to be synced to the server. +---Defines workspace specific capabilities of the server. --- ----If a selector provides a notebook document ----filter but no cell selector all cells of a ----matching notebook document will be synced. +---@since 3.18.0 +---@class lsp.WorkspaceOptions --- ----If a selector provides no notebook document ----filter but only a cell selector all notebook ----document that contain at least one matching ----cell will be synced. +---The server supports workspace folder. --- ----@since 3.17.0 ----@class lsp.NotebookDocumentSyncOptions +---@since 3.6.0 +---@field workspaceFolders? lsp.WorkspaceFoldersServerCapabilities --- ----The notebooks to be synced ----@field notebookSelector (lsp._anonym16.notebookSelector|lsp._anonym18.notebookSelector)[] +---The server is interested in notifications/requests for operations on files. --- ----Whether save notification should be forwarded to ----the server. Will only be honored if mode === `notebook`. ----@field save? boolean +---@since 3.16.0 +---@field fileOperations? lsp.FileOperationOptions ----Registration options specific to a notebook. +---@since 3.18.0 +---@class lsp.TextDocumentContentChangePartial --- ----@since 3.17.0 ----@class lsp.NotebookDocumentSyncRegistrationOptions: lsp.NotebookDocumentSyncOptions, lsp.StaticRegistrationOptions - ----@class lsp.WorkspaceFoldersServerCapabilities +---The range of the document that changed. +---@field range lsp.Range --- ----The server has support for workspace folders ----@field supported? boolean +---The optional length of the range that got replaced. --- ----Whether the server wants to receive workspace folder ----change notifications. +---@deprecated use range instead. +---@field rangeLength? uinteger --- ----If a string is provided the string is treated as an ID ----under which the notification is registered on the client ----side. The ID can be used to unregister for these events ----using the `client/unregisterCapability` request. ----@field changeNotifications? string|boolean +---The new text for the provided range. +---@field text string ----Options for notifications/requests for user operations on files. ---- ----@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 +---@since 3.18.0 +---@class lsp.TextDocumentContentChangeWholeDocument --- ----The server is interested in receiving willDeleteFiles file requests. ----@field willDelete? lsp.FileOperationRegistrationOptions +---The new text of the whole document. +---@field text string ---Structure to capture a description for an error code. --- @@ -3223,6 +3398,33 @@ error('Cannot require a meta file') ---The message of this related diagnostic information. ---@field message string +---Edit range variant that includes ranges for insert and replace operations. +--- +---@since 3.18.0 +---@class lsp.EditRangeWithInsertReplace +--- +---@field insert lsp.Range +--- +---@field replace lsp.Range + +---@since 3.18.0 +---@class lsp.ServerCompletionItemOptions +--- +---The server has support for completion item label +---details (see also `CompletionItemLabelDetails`) when +---receiving a completion item in a resolve call. +--- +---@since 3.17.0 +---@field labelDetailsSupport? boolean + +---@since 3.18.0 +---@deprecated use MarkupContent instead. +---@class lsp.MarkedStringWithLanguage +--- +---@field language string +--- +---@field value string + ---Represents a parameter of a callable-signature. A parameter can ---have a label and a doc-comment. ---@class lsp.ParameterInformation @@ -3233,14 +3435,36 @@ error('Cannot require a meta file') ---signature label. (see SignatureInformation.label). The offsets are based on a UTF-16 ---string representation as `Position` and `Range` does. --- +---To avoid ambiguities a server should use the [start, end] offset value instead of using +---a substring. Whether a client support this is controlled via `labelOffsetSupport` client +---capability. +--- ---*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 } +---@field label string|[uinteger, uinteger] --- ---The human-readable doc-comment of this parameter. Will be shown ---in the UI but can be omitted. ---@field documentation? string|lsp.MarkupContent +---Documentation for a class of code actions. +--- +---@since 3.18.0 +---@proposed +---@class lsp.CodeActionKindDocumentation +--- +---The kind of the code action being documented. +--- +---If the kind is generic, such as `CodeActionKind.Refactor`, the documentation will be shown whenever any +---refactorings are returned. If the kind if more specific, such as `CodeActionKind.RefactorExtract`, the +---documentation will only be shown when extract refactoring code actions are returned. +---@field kind lsp.CodeActionKind +--- +---Command that is ued to display the documentation to the user. +--- +---The title of this documentation code action is taken from {@linkcode Command.title} +---@field command lsp.Command + ---A notebook cell text document filter denotes a cell text ---document by different properties. --- @@ -3278,6 +3502,34 @@ error('Cannot require a meta file') ---not if known by the client. ---@field success? boolean +---@since 3.18.0 +---@class lsp.NotebookCellLanguage +--- +---@field language string + +---Structural changes to cells in a notebook document. +--- +---@since 3.18.0 +---@class lsp.NotebookDocumentCellChangeStructure +--- +---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[] + +---Content changes to a cell in a notebook document. +--- +---@since 3.18.0 +---@class lsp.NotebookDocumentCellContentChanges +--- +---@field document lsp.VersionedTextDocumentIdentifier +--- +---@field changes lsp.TextDocumentContentChangeEvent[] + ---Workspace specific client capabilities. ---@class lsp.WorkspaceClientCapabilities --- @@ -3524,7 +3776,7 @@ error('Cannot require a meta file') ---anymore since the information is outdated). --- ---@since 3.17.0 ----@field staleRequestSupport? lsp._anonym20.staleRequestSupport +---@field staleRequestSupport? lsp.StaleRequestSupportOptions --- ---Client capabilities specific to regular expressions. --- @@ -3556,6 +3808,43 @@ error('Cannot require a meta file') ---@since 3.17.0 ---@field positionEncodings? lsp.PositionEncodingKind[] +---@class lsp.WorkspaceFoldersServerCapabilities +--- +---The server has support for workspace folders +---@field supported? boolean +--- +---Whether the server wants to receive workspace folder +---change notifications. +--- +---If a string is provided the string is treated as an ID +---under which the notification is registered on the client +---side. The ID can be used to unregister for these events +---using the `client/unregisterCapability` request. +---@field changeNotifications? string|boolean + +---Options for notifications/requests for user operations on files. +--- +---@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 + ---A relative pattern is a helper to construct glob patterns that are matched ---relatively to a base URI. The common value for a `baseUri` is a workspace ---folder root, but it can be another absolute URI as well. @@ -3570,6 +3859,111 @@ error('Cannot require a meta file') ---The actual glob pattern; ---@field pattern lsp.Pattern +---A document filter where `language` is required field. +--- +---@since 3.18.0 +---@class lsp.TextDocumentFilterLanguage +--- +---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}. See TextDocumentFilter for examples. +--- +---@since 3.18.0 - support for relative patterns. +---@field pattern? lsp.GlobPattern + +---A document filter where `scheme` is required field. +--- +---@since 3.18.0 +---@class lsp.TextDocumentFilterScheme +--- +---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}. See TextDocumentFilter for examples. +--- +---@since 3.18.0 - support for relative patterns. +---@field pattern? lsp.GlobPattern + +---A document filter where `pattern` is required field. +--- +---@since 3.18.0 +---@class lsp.TextDocumentFilterPattern +--- +---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}. See TextDocumentFilter for examples. +--- +---@since 3.18.0 - support for relative patterns. +---@field pattern lsp.GlobPattern + +---A notebook document filter where `notebookType` is required field. +--- +---@since 3.18.0 +---@class lsp.NotebookDocumentFilterNotebookType +--- +---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? lsp.GlobPattern + +---A notebook document filter where `scheme` is required field. +--- +---@since 3.18.0 +---@class lsp.NotebookDocumentFilterScheme +--- +---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? lsp.GlobPattern + +---A notebook document filter where `pattern` is required field. +--- +---@since 3.18.0 +---@class lsp.NotebookDocumentFilterPattern +--- +---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 lsp.GlobPattern + +---A change describing how to move a `NotebookCell` +---array from state S to S'. +--- +---@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[] + ---@class lsp.WorkspaceEditClientCapabilities --- ---The client supports versioned document changes in `WorkspaceEdit`s @@ -3600,7 +3994,19 @@ error('Cannot require a meta file') ---create file, rename file and delete file changes. --- ---@since 3.16.0 ----@field changeAnnotationSupport? lsp._anonym21.changeAnnotationSupport +---@field changeAnnotationSupport? lsp.ChangeAnnotationsSupportOptions +--- +---Whether the client supports `WorkspaceEditMetadata` in `WorkspaceEdit`s. +--- +---@since 3.18.0 +---@proposed +---@field metadataSupport? boolean +--- +---Whether the client supports snippets as text edits. +--- +---@since 3.18.0 +---@proposed +---@field snippetEditSupport? boolean ---@class lsp.DidChangeConfigurationClientCapabilities --- @@ -3627,20 +4033,20 @@ error('Cannot require a meta file') ---@field dynamicRegistration? boolean --- ---Specific capabilities for the `SymbolKind` in the `workspace/symbol` request. ----@field symbolKind? lsp._anonym22.symbolKind +---@field symbolKind? lsp.ClientSymbolKindOptions --- ---The client supports tags on `SymbolInformation`. ---Clients supporting tags have to handle unknown tags gracefully. --- ---@since 3.16.0 ----@field tagSupport? lsp._anonym23.tagSupport +---@field tagSupport? lsp.ClientSymbolTagOptions --- ---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? lsp._anonym24.resolveSupport +---@field resolveSupport? lsp.ClientSymbolResolveOptions ---The client capabilities of a {@link ExecuteCommandRequest}. ---@class lsp.ExecuteCommandClientCapabilities @@ -3785,9 +4191,9 @@ error('Cannot require a meta file') --- ---The client supports the following `CompletionItem` specific ---capabilities. ----@field completionItem? lsp._anonym25.completionItem +---@field completionItem? lsp.ClientCompletionItemOptions --- ----@field completionItemKind? lsp._anonym29.completionItemKind +---@field completionItemKind? lsp.ClientCompletionItemOptionsKind --- ---Defines how the client handles whitespace and indentation ---when accepting a completion item that uses multi line @@ -3804,7 +4210,7 @@ error('Cannot require a meta file') ---capabilities. --- ---@since 3.17.0 ----@field completionList? lsp._anonym30.completionList +---@field completionList? lsp.CompletionListCapabilities ---@class lsp.HoverClientCapabilities --- @@ -3823,7 +4229,7 @@ error('Cannot require a meta file') --- ---The client supports the following `SignatureInformation` ---specific properties. ----@field signatureInformation? lsp._anonym31.signatureInformation +---@field signatureInformation? lsp.ClientSignatureInformationOptions --- ---The client supports to send additional context information for a ---`textDocument/signatureHelp` request. A client that opts into @@ -3901,7 +4307,7 @@ error('Cannot require a meta file') --- ---Specific capabilities for the `SymbolKind` in the ---`textDocument/documentSymbol` request. ----@field symbolKind? lsp._anonym33.symbolKind +---@field symbolKind? lsp.ClientSymbolKindOptions --- ---The client supports hierarchical document symbols. ---@field hierarchicalDocumentSymbolSupport? boolean @@ -3911,7 +4317,7 @@ error('Cannot require a meta file') ---Clients supporting tags have to handle unknown tags gracefully. --- ---@since 3.16.0 ----@field tagSupport? lsp._anonym34.tagSupport +---@field tagSupport? lsp.ClientSymbolTagOptions --- ---The client supports an additional label presented in the UI when ---registering a document symbol provider. @@ -3930,7 +4336,7 @@ error('Cannot require a meta file') ---set the request can only return `Command` literals. --- ---@since 3.8.0 ----@field codeActionLiteralSupport? lsp._anonym35.codeActionLiteralSupport +---@field codeActionLiteralSupport? lsp.ClientCodeActionLiteralOptions --- ---Whether code action supports the `isPreferred` property. --- @@ -3953,7 +4359,7 @@ error('Cannot require a meta file') ---properties via a separate `codeAction/resolve` request. --- ---@since 3.16.0 ----@field resolveSupport? lsp._anonym37.resolveSupport +---@field resolveSupport? lsp.ClientCodeActionResolveOptions --- ---Whether the client honors the change annotations in ---text edits and resource operations returned via the @@ -3963,12 +4369,25 @@ error('Cannot require a meta file') --- ---@since 3.16.0 ---@field honorsChangeAnnotations? boolean +--- +---Whether the client supports documentation for a class of +---code actions. +--- +---@since 3.18.0 +---@proposed +---@field documentationSupport? boolean ---The client capabilities of a {@link CodeLensRequest}. ---@class lsp.CodeLensClientCapabilities --- ---Whether code lens supports dynamic registration. ---@field dynamicRegistration? boolean +--- +---Whether the client supports resolving additional code lens +---properties via a separate `codeLens/resolve` request. +--- +---@since 3.18.0 +---@field resolveSupport? lsp.ClientCodeLensResolveOptions ---The client capabilities of a {@link DocumentLinkRequest}. ---@class lsp.DocumentLinkClientCapabilities @@ -4061,12 +4480,12 @@ error('Cannot require a meta file') ---Specific options for the folding range kind. --- ---@since 3.17.0 ----@field foldingRangeKind? lsp._anonym38.foldingRangeKind +---@field foldingRangeKind? lsp.ClientFoldingRangeKindOptions --- ---Specific options for the folding range. --- ---@since 3.17.0 ----@field foldingRange? lsp._anonym39.foldingRange +---@field foldingRange? lsp.ClientFoldingRangeOptions ---@class lsp.SelectionRangeClientCapabilities --- @@ -4076,34 +4495,13 @@ error('Cannot require a meta file') ---@field dynamicRegistration? boolean ---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? lsp._anonym40.tagSupport +---@class lsp.PublishDiagnosticsClientCapabilities: lsp.DiagnosticsCapabilities --- ---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. ---- ----@since 3.16.0 ----@field dataSupport? boolean ---@since 3.16.0 ---@class lsp.CallHierarchyClientCapabilities @@ -4129,7 +4527,7 @@ 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 lsp._anonym41.requests +---@field requests lsp.ClientSemanticTokensRequestOptions --- ---The token types that the client supports. ---@field tokenTypes string[] @@ -4212,12 +4610,12 @@ error('Cannot require a meta file') --- ---Indicates which properties a client can resolve lazily on an inlay ---hint. ----@field resolveSupport? lsp._anonym44.resolveSupport +---@field resolveSupport? lsp.ClientInlayHintResolveOptions ---Client capabilities specific to diagnostic pull requests. --- ---@since 3.17.0 ----@class lsp.DiagnosticClientCapabilities +---@class lsp.DiagnosticClientCapabilities: lsp.DiagnosticsCapabilities --- ---Whether implementation supports dynamic registration. If this is set to `true` ---the client supports the new `(TextDocumentRegistrationOptions & StaticRegistrationOptions)` @@ -4226,9 +4624,6 @@ error('Cannot require a meta file') --- ---Whether the clients supports related documents for document diagnostic pulls. ---@field relatedDocumentSupport? boolean ---- ----Whether the client supports `MarkupContent` in diagnostic messages. ----@field markupMessageSupport? boolean ---Client capabilities specific to inline completions. --- @@ -4257,7 +4652,7 @@ error('Cannot require a meta file') ---@class lsp.ShowMessageRequestClientCapabilities --- ---Capabilities specific to the `MessageActionItem` type. ----@field messageActionItem? lsp._anonym45.messageActionItem +---@field messageActionItem? lsp.ClientShowMessageActionItemOptions ---Client capabilities for the showDocument request. --- @@ -4268,13 +4663,24 @@ error('Cannot require a meta file') ---request. ---@field support boolean +---@since 3.18.0 +---@class lsp.StaleRequestSupportOptions +--- +---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[] + ---Client capabilities specific to regular expressions. --- ---@since 3.16.0 ---@class lsp.RegularExpressionsClientCapabilities --- ---The engine's name. ----@field engine string +---@field engine lsp.RegularExpressionEngineKind --- ---The engine's version. ---@field version? string @@ -4296,6 +4702,285 @@ error('Cannot require a meta file') ---@since 3.17.0 ---@field allowedTags? string[] +---@since 3.18.0 +---@class lsp.ChangeAnnotationsSupportOptions +--- +---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 + +---@since 3.18.0 +---@class lsp.ClientSymbolKindOptions +--- +---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 +---to a default value when unknown. +--- +---If this property is not present the client only supports +---the symbol kinds from `File` to `Array` as defined in +---the initial version of the protocol. +---@field valueSet? lsp.SymbolKind[] + +---@since 3.18.0 +---@class lsp.ClientSymbolTagOptions +--- +---The tags supported by the client. +---@field valueSet lsp.SymbolTag[] + +---@since 3.18.0 +---@class lsp.ClientSymbolResolveOptions +--- +---The properties that a client can resolve lazily. Usually +---`location.range` +---@field properties string[] + +---@since 3.18.0 +---@class lsp.ClientCompletionItemOptions +--- +---Client supports snippets as insert text. +--- +---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. 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? lsp.CompletionItemTagOptions +--- +---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? lsp.ClientCompletionItemResolveOptions +--- +---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? lsp.ClientCompletionItemInsertTextModeOptions +--- +---The client has support for completion item label +---details (see also `CompletionItemLabelDetails`). +--- +---@since 3.17.0 +---@field labelDetailsSupport? boolean + +---@since 3.18.0 +---@class lsp.ClientCompletionItemOptionsKind +--- +---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 +---to a default value when unknown. +--- +---If this property is not present the client only supports +---the completion items kinds from `Text` to `Reference` as defined in +---the initial version of the protocol. +---@field valueSet? lsp.CompletionItemKind[] + +---The client supports the following `CompletionList` specific +---capabilities. +--- +---@since 3.17.0 +---@class lsp.CompletionListCapabilities +--- +---The client supports the following itemDefaults on +---a completion list. +--- +---The value lists the supported property names of the +---`CompletionList.itemDefaults` object. If omitted +---no properties are supported. +--- +---@since 3.17.0 +---@field itemDefaults? string[] + +---@since 3.18.0 +---@class lsp.ClientSignatureInformationOptions +--- +---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? lsp.ClientSignatureParameterInformationOptions +--- +---The client supports the `activeParameter` property on `SignatureInformation` +---literal. +--- +---@since 3.16.0 +---@field activeParameterSupport? boolean +--- +---The client supports the `activeParameter` property on +---`SignatureHelp`/`SignatureInformation` being set to `null` to +---indicate that no parameter should be active. +--- +---@since 3.18.0 +---@proposed +---@field noActiveParameterSupport? boolean + +---@since 3.18.0 +---@class lsp.ClientCodeActionLiteralOptions +--- +---The code action kind is support with the following value +---set. +---@field codeActionKind lsp.ClientCodeActionKindOptions + +---@since 3.18.0 +---@class lsp.ClientCodeActionResolveOptions +--- +---The properties that a client can resolve lazily. +---@field properties string[] + +---@since 3.18.0 +---@class lsp.ClientCodeLensResolveOptions +--- +---The properties that a client can resolve lazily. +---@field properties string[] + +---@since 3.18.0 +---@class lsp.ClientFoldingRangeKindOptions +--- +---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[] + +---@since 3.18.0 +---@class lsp.ClientFoldingRangeOptions +--- +---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 + +---General diagnostics capabilities for pull and push model. +---@class lsp.DiagnosticsCapabilities +--- +---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? lsp.ClientDiagnosticsTagOptions +--- +---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. +--- +---@since 3.16.0 +---@field dataSupport? boolean + +---@since 3.18.0 +---@class lsp.ClientSemanticTokensRequestOptions +--- +---The client will send the `textDocument/semanticTokens/range` request if +---the server provides a corresponding handler. +---@field range? boolean|lsp._anonym2.range +--- +---The client will send the `textDocument/semanticTokens/full` request if +---the server provides a corresponding handler. +---@field full? boolean|lsp.ClientSemanticTokensRequestFullDelta + +---@since 3.18.0 +---@class lsp.ClientInlayHintResolveOptions +--- +---The properties that a client can resolve lazily. +---@field properties string[] + +---@since 3.18.0 +---@class lsp.ClientShowMessageActionItemOptions +--- +---Whether the client supports additional attributes which +---are preserved and send back to the server in the +---request's response. +---@field additionalPropertiesSupport? boolean + +---@since 3.18.0 +---@class lsp.CompletionItemTagOptions +--- +---The tags supported by the client. +---@field valueSet lsp.CompletionItemTag[] + +---@since 3.18.0 +---@class lsp.ClientCompletionItemResolveOptions +--- +---The properties that a client can resolve lazily. +---@field properties string[] + +---@since 3.18.0 +---@class lsp.ClientCompletionItemInsertTextModeOptions +--- +---@field valueSet lsp.InsertTextMode[] + +---@since 3.18.0 +---@class lsp.ClientSignatureParameterInformationOptions +--- +---The client supports processing label offsets instead of a +---simple label string. +--- +---@since 3.14.0 +---@field labelOffsetSupport? boolean + +---@since 3.18.0 +---@class lsp.ClientCodeActionKindOptions +--- +---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[] + +---@since 3.18.0 +---@class lsp.ClientDiagnosticsTagOptions +--- +---The tags supported by the client. +---@field valueSet lsp.DiagnosticTag[] + +---@since 3.18.0 +---@class lsp.ClientSemanticTokensRequestFullDelta +--- +---The client will send the `textDocument/semanticTokens/full/delta` request if +---the server provides a corresponding handler. +---@field delta? boolean + ---A set of predefined token types. This set is not fixed ---an clients can specify additional token types via the ---corresponding client capabilities. @@ -4325,6 +5010,7 @@ error('Cannot require a meta file') ---| "regexp" # regexp ---| "operator" # operator ---| "decorator" # decorator +---| "label" # label ---A set of predefined token modifiers. This set is not fixed ---an clients can specify additional token types via the @@ -4515,12 +5201,14 @@ error('Cannot require a meta file') ---| "refactor" # Refactor ---| "refactor.extract" # RefactorExtract ---| "refactor.inline" # RefactorInline +---| "refactor.move" # RefactorMove ---| "refactor.rewrite" # RefactorRewrite ---| "source" # Source ---| "source.organizeImports" # SourceOrganizeImports ---| "source.fixAll" # SourceFixAll +---| "notebook" # Notebook ----@alias lsp.TraceValues +---@alias lsp.TraceValue ---| "off" # Off ---| "messages" # Messages ---| "verbose" # Verbose @@ -4534,13 +5222,79 @@ error('Cannot require a meta file') ---| "plaintext" # PlainText ---| "markdown" # Markdown +---Predefined Language kinds +---@since 3.18.0 +---@proposed +---@alias lsp.LanguageKind +---| "abap" # ABAP +---| "bat" # WindowsBat +---| "bibtex" # BibTeX +---| "clojure" # Clojure +---| "coffeescript" # Coffeescript +---| "c" # C +---| "cpp" # CPP +---| "csharp" # CSharp +---| "css" # CSS +---| "d" # D +---| "pascal" # Delphi +---| "diff" # Diff +---| "dart" # Dart +---| "dockerfile" # Dockerfile +---| "elixir" # Elixir +---| "erlang" # Erlang +---| "fsharp" # FSharp +---| "git-commit" # GitCommit +---| "rebase" # GitRebase +---| "go" # Go +---| "groovy" # Groovy +---| "handlebars" # Handlebars +---| "haskell" # Haskell +---| "html" # HTML +---| "ini" # Ini +---| "java" # Java +---| "javascript" # JavaScript +---| "javascriptreact" # JavaScriptReact +---| "json" # JSON +---| "latex" # LaTeX +---| "less" # Less +---| "lua" # Lua +---| "makefile" # Makefile +---| "markdown" # Markdown +---| "objective-c" # ObjectiveC +---| "objective-cpp" # ObjectiveCPP +---| "pascal" # Pascal +---| "perl" # Perl +---| "perl6" # Perl6 +---| "php" # PHP +---| "powershell" # Powershell +---| "jade" # Pug +---| "python" # Python +---| "r" # R +---| "razor" # Razor +---| "ruby" # Ruby +---| "rust" # Rust +---| "scss" # SCSS +---| "sass" # SASS +---| "scala" # Scala +---| "shaderlab" # ShaderLab +---| "shellscript" # ShellScript +---| "sql" # SQL +---| "swift" # Swift +---| "typescript" # TypeScript +---| "typescriptreact" # TypeScriptReact +---| "tex" # TeX +---| "vb" # VisualBasic +---| "xml" # XML +---| "xsl" # XSL +---| "yaml" # YAML + ---Describes how an {@link InlineCompletionItemProvider inline completion provider} was triggered. --- ---@since 3.18.0 ---@proposed ---@alias lsp.InlineCompletionTriggerKind ----| 0 # Invoked ----| 1 # Automatic +---| 1 # Invoked +---| 2 # Automatic ---A set of predefined position encoding kinds. --- @@ -4684,7 +5438,7 @@ error('Cannot require a meta file') ---@since 3.17.0 ---@alias lsp.DocumentDiagnosticReport lsp.RelatedFullDocumentDiagnosticReport|lsp.RelatedUnchangedDocumentDiagnosticReport ----@alias lsp.PrepareRenameResult lsp.Range|lsp._anonym46.PrepareRenameResult|lsp._anonym47.PrepareRenameResult +---@alias lsp.PrepareRenameResult lsp.Range|lsp.PrepareRenamePlaceholder|lsp.PrepareRenameDefaultBehavior ---A document selector is the combination of one or many document filters. --- @@ -4705,7 +5459,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 lsp._anonym48.TextDocumentContentChangeEvent|lsp._anonym49.TextDocumentContentChangeEvent +---@alias lsp.TextDocumentContentChangeEvent lsp.TextDocumentContentChangePartial|lsp.TextDocumentContentChangeWholeDocument ---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 @@ -4719,7 +5473,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|lsp._anonym50.MarkedString +---@alias lsp.MarkedString string|lsp.MarkedStringWithLanguage ---A document filter describes a top level text document or ---a notebook cell document. @@ -4752,14 +5506,14 @@ error('Cannot require a meta file') ---\@sample A language filter that applies to all package.json paths: `{ language: 'json', pattern: '**package.json' }` --- ---@since 3.17.0 ----@alias lsp.TextDocumentFilter lsp._anonym51.TextDocumentFilter|lsp._anonym52.TextDocumentFilter|lsp._anonym53.TextDocumentFilter +---@alias lsp.TextDocumentFilter lsp.TextDocumentFilterLanguage|lsp.TextDocumentFilterScheme|lsp.TextDocumentFilterPattern ---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 lsp._anonym54.NotebookDocumentFilter|lsp._anonym55.NotebookDocumentFilter|lsp._anonym56.NotebookDocumentFilter +---@alias lsp.NotebookDocumentFilter lsp.NotebookDocumentFilterNotebookType|lsp.NotebookDocumentFilterScheme|lsp.NotebookDocumentFilterPattern ---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 @@ -4772,512 +5526,8 @@ error('Cannot require a meta file') ---@since 3.17.0 ---@alias lsp.Pattern string ----@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 lsp._anonym3.itemDefaults.editRange ---- ----@field insert lsp.Range ---- ----@field replace lsp.Range - ----@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|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 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 lsp._anonym5.location ---- ----@field uri lsp.DocumentUri - ----@class lsp._anonym6.range - ----@class lsp._anonym7.full ---- ----The server supports deltas for full documents. ----@field delta? boolean - ----@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 lsp._anonym10.cells.textContent ---- ----@field document lsp.VersionedTextDocumentIdentifier ---- ----@field changes lsp.TextDocumentContentChangeEvent[] - ----@class lsp._anonym8.cells ---- ----Changes to the cell structure to add or ----remove cells. ----@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? lsp._anonym10.cells.textContent[] - ----@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 lsp._anonym13.textDocument.diagnostic ---- ----Whether the server supports `MarkupContent` in diagnostic messages. ----@field markupMessageSupport? boolean - ----@class lsp._anonym12.textDocument ---- ----Capabilities specific to the diagnostic pull model. ---- ----@since 3.18.0 ----@field diagnostic? lsp._anonym13.textDocument.diagnostic - ----@class lsp._anonym14.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 lsp._anonym15.completionItem ---- ----The server has support for completion item label ----details (see also `CompletionItemLabelDetails`) when ----receiving a completion item in a resolve call. ---- ----@since 3.17.0 ----@field labelDetailsSupport? boolean - ----@class lsp._anonym17.notebookSelector.cells ---- ----@field language string - ----@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? lsp._anonym17.notebookSelector.cells[] - ----@class lsp._anonym19.notebookSelector.cells ---- ----@field language string - ----@class lsp._anonym18.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 lsp._anonym19.notebookSelector.cells[] - ----@class lsp._anonym20.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 lsp._anonym21.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 +---@alias lsp.RegularExpressionEngineKind string ----@class lsp._anonym22.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 ----to a default value when unknown. ---- ----If this property is not present the client only supports ----the symbol kinds from `File` to `Array` as defined in ----the initial version of the protocol. ----@field valueSet? lsp.SymbolKind[] +---@class lsp._anonym1.range ----@class lsp._anonym23.tagSupport ---- ----The tags supported by the client. ----@field valueSet lsp.SymbolTag[] - ----@class lsp._anonym24.resolveSupport ---- ----The properties that a client can resolve lazily. Usually ----`location.range` ----@field properties string[] - ----@class lsp._anonym26.completionItem.tagSupport ---- ----The tags supported by the client. ----@field valueSet lsp.CompletionItemTag[] - ----@class lsp._anonym27.completionItem.resolveSupport ---- ----The properties that a client can resolve lazily. ----@field properties string[] - ----@class lsp._anonym28.completionItem.insertTextModeSupport ---- ----@field valueSet lsp.InsertTextMode[] - ----@class lsp._anonym25.completionItem ---- ----Client supports snippets as insert text. ---- ----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. 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? lsp._anonym26.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? lsp._anonym27.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? lsp._anonym28.completionItem.insertTextModeSupport ---- ----The client has support for completion item label ----details (see also `CompletionItemLabelDetails`). ---- ----@since 3.17.0 ----@field labelDetailsSupport? boolean - ----@class lsp._anonym29.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 ----to a default value when unknown. ---- ----If this property is not present the client only supports ----the completion items kinds from `Text` to `Reference` as defined in ----the initial version of the protocol. ----@field valueSet? lsp.CompletionItemKind[] - ----@class lsp._anonym30.completionList ---- ----The client supports the following itemDefaults on ----a completion list. ---- ----The value lists the supported property names of the ----`CompletionList.itemDefaults` object. If omitted ----no properties are supported. ---- ----@since 3.17.0 ----@field itemDefaults? string[] - ----@class lsp._anonym32.signatureInformation.parameterInformation ---- ----The client supports processing label offsets instead of a ----simple label string. ---- ----@since 3.14.0 ----@field labelOffsetSupport? boolean - ----@class lsp._anonym31.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? lsp._anonym32.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 lsp._anonym33.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 ----to a default value when unknown. ---- ----If this property is not present the client only supports ----the symbol kinds from `File` to `Array` as defined in ----the initial version of the protocol. ----@field valueSet? lsp.SymbolKind[] - ----@class lsp._anonym34.tagSupport ---- ----The tags supported by the client. ----@field valueSet lsp.SymbolTag[] - ----@class lsp._anonym36.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 lsp._anonym35.codeActionLiteralSupport ---- ----The code action kind is support with the following value ----set. ----@field codeActionKind lsp._anonym36.codeActionLiteralSupport.codeActionKind - ----@class lsp._anonym37.resolveSupport ---- ----The properties that a client can resolve lazily. ----@field properties string[] - ----@class lsp._anonym38.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 lsp._anonym39.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 lsp._anonym40.tagSupport ---- ----The tags supported by the client. ----@field valueSet lsp.DiagnosticTag[] - ----@class lsp._anonym42.requests.range - ----@class lsp._anonym43.requests.full ---- ----The client will send the `textDocument/semanticTokens/full/delta` request if ----the server provides a corresponding handler. ----@field delta? boolean - ----@class lsp._anonym41.requests ---- ----The client will send the `textDocument/semanticTokens/range` request if ----the server provides a corresponding handler. ----@field range? boolean|lsp._anonym42.requests.range ---- ----The client will send the `textDocument/semanticTokens/full` request if ----the server provides a corresponding handler. ----@field full? boolean|lsp._anonym43.requests.full - ----@class lsp._anonym44.resolveSupport ---- ----The properties that a client can resolve lazily. ----@field properties string[] - ----@class lsp._anonym45.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 lsp._anonym46.PrepareRenameResult ---- ----@field range lsp.Range ---- ----@field placeholder string - ----@class lsp._anonym47.PrepareRenameResult ---- ----@field defaultBehavior boolean - ----@class lsp._anonym48.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 lsp._anonym49.TextDocumentContentChangeEvent ---- ----The new text of the whole document. ----@field text string - ----@class lsp._anonym50.MarkedString ---- ----@field language string ---- ----@field value string - ----@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}. See TextDocumentFilter for examples. ----@field pattern? string - ----@class lsp._anonym52.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}. See TextDocumentFilter for examples. ----@field pattern? string - ----@class lsp._anonym53.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}. See TextDocumentFilter for examples. ----@field pattern string - ----@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 - ----@class lsp._anonym55.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 lsp._anonym56.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 lsp._anonym2.range diff --git a/runtime/lua/vim/lsp/_watchfiles.lua b/runtime/lua/vim/lsp/_watchfiles.lua index 49328fbe9b..98e9818bcd 100644 --- a/runtime/lua/vim/lsp/_watchfiles.lua +++ b/runtime/lua/vim/lsp/_watchfiles.lua @@ -9,8 +9,8 @@ local M = {} 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 +elseif vim.fn.executable('inotifywait') == 1 then + M._watchfunc = watch.inotify else M._watchfunc = watch.watchdirs end diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua index 49833eaeec..301c1f0cb6 100644 --- a/runtime/lua/vim/lsp/buf.lua +++ b/runtime/lua/vim/lsp/buf.lua @@ -29,7 +29,12 @@ local function request(method, params, handler) end --- Displays hover information about the symbol under the cursor in a floating ---- window. Calling the function twice will jump into the floating window. +--- window. The window will be dismissed on cursor move. +--- Calling the function twice will jump into the floating window +--- (thus by default, "KK" will open the hover window and focus it). +--- In the floating window, all commands and mappings are available as usual, +--- except that "q" dismisses the window. +--- You can scroll the contents the same as you would any other buffer. function M.hover() local params = util.make_position_params() request(ms.textDocument_hover, params) @@ -135,7 +140,7 @@ end ---@param mode "v"|"V" ---@return table {start={row,col}, end={row,col}} using (1, 0) indexing local function range_from_selection(bufnr, mode) - -- TODO: Use `vim.region()` instead https://github.com/neovim/neovim/pull/13896 + -- TODO: Use `vim.fn.getregionpos()` instead. -- [bufnum, lnum, col, off]; both row and column 1-indexed local start = vim.fn.getpos('v') @@ -205,9 +210,11 @@ end --- Range to format. --- Table must contain `start` and `end` keys with {row,col} tuples using --- (1,0) indexing. +--- Can also be a list of tables that contain `start` and `end` keys as described above, +--- in which case `textDocument/rangesFormatting` support is required. --- (Default: current selection in visual mode, `nil` in other modes, --- formatting the full buffer) ---- @field range? {start:integer[],end:integer[]} +--- @field range? {start:[integer,integer],end:[integer, integer]}|{start:[integer,integer],end:[integer,integer]}[] --- Formats a buffer using the attached (and optionally filtered) language --- server clients. @@ -218,10 +225,20 @@ function M.format(opts) local bufnr = opts.bufnr or api.nvim_get_current_buf() local mode = api.nvim_get_mode().mode local range = opts.range + -- Try to use visual selection if no range is given if not range and mode == 'v' or mode == 'V' then range = range_from_selection(bufnr, mode) end - local method = range and ms.textDocument_rangeFormatting or ms.textDocument_formatting + + local passed_multiple_ranges = (range and #range ~= 0 and type(range[1]) == 'table') + local method ---@type string + if passed_multiple_ranges then + method = ms.textDocument_rangesFormatting + elseif range then + method = ms.textDocument_rangeFormatting + else + method = ms.textDocument_formatting + end local clients = vim.lsp.get_clients({ id = opts.id, @@ -241,10 +258,14 @@ function M.format(opts) --- @param params lsp.DocumentFormattingParams --- @return lsp.DocumentFormattingParams local function set_range(client, params) - if range then - local range_params = - util.make_given_range_params(range.start, range['end'], bufnr, client.offset_encoding) - params.range = range_params.range + local to_lsp_range = function(r) ---@return lsp.DocumentRangeFormattingParams|lsp.DocumentRangesFormattingParams + return util.make_given_range_params(r.start, r['end'], bufnr, client.offset_encoding).range + end + + if passed_multiple_ranges then + params.ranges = vim.tbl_map(to_lsp_range, range) + elseif range then + params.range = to_lsp_range(range) end return params end @@ -431,11 +452,9 @@ function M.document_symbol(opts) request_with_opts(ms.textDocument_documentSymbol, params, opts) end ---- @param call_hierarchy_items lsp.CallHierarchyItem[]? +--- @param call_hierarchy_items lsp.CallHierarchyItem[] +--- @return lsp.CallHierarchyItem? local function pick_call_hierarchy_item(call_hierarchy_items) - if not call_hierarchy_items then - return - end if #call_hierarchy_items == 1 then return call_hierarchy_items[1] end @@ -448,7 +467,7 @@ local function pick_call_hierarchy_item(call_hierarchy_items) if choice < 1 or choice > #items then return end - return choice + return call_hierarchy_items[choice] end --- @param method string @@ -460,7 +479,7 @@ local function call_hierarchy(method) vim.notify(err.message, vim.log.levels.WARN) return end - if not result then + if not result or vim.tbl_isempty(result) then vim.notify('No item resolved', vim.log.levels.WARN) return end @@ -836,14 +855,10 @@ function M.code_action(opts) if opts.diagnostics or opts.only then opts = { options = opts } end - local context = opts.context or {} + local context = opts.context and vim.deepcopy(opts.context) or {} if not context.triggerKind then context.triggerKind = vim.lsp.protocol.CodeActionTriggerKind.Invoked end - if not context.diagnostics then - local bufnr = api.nvim_get_current_buf() - context.diagnostics = vim.lsp.diagnostic.get_line_diagnostics(bufnr) - end local mode = api.nvim_get_mode().mode local bufnr = api.nvim_get_current_buf() local win = api.nvim_get_current_win() @@ -885,7 +900,23 @@ function M.code_action(opts) else params = util.make_range_params(win, client.offset_encoding) end - params.context = context + if context.diagnostics then + params.context = context + else + local ns_push = vim.lsp.diagnostic.get_namespace(client.id, false) + local ns_pull = vim.lsp.diagnostic.get_namespace(client.id, true) + local diagnostics = {} + local lnum = api.nvim_win_get_cursor(0)[1] - 1 + vim.list_extend(diagnostics, vim.diagnostic.get(bufnr, { namespace = ns_pull, lnum = lnum })) + vim.list_extend(diagnostics, vim.diagnostic.get(bufnr, { namespace = ns_push, lnum = lnum })) + params.context = vim.tbl_extend('force', context, { + ---@diagnostic disable-next-line: no-unknown + diagnostics = vim.tbl_map(function(d) + return d.user_data.lsp + end, diagnostics), + }) + end + client.request(ms.textDocument_codeAction, params, on_result, bufnr) end end diff --git a/runtime/lua/vim/lsp/client.lua b/runtime/lua/vim/lsp/client.lua index 4beb7fefda..e3c82f4169 100644 --- a/runtime/lua/vim/lsp/client.lua +++ b/runtime/lua/vim/lsp/client.lua @@ -182,7 +182,7 @@ local validate = vim.validate --- It can be `null` if the client supports workspace folders but none are --- configured. --- @field workspace_folders lsp.WorkspaceFolder[]? ---- @field root_dir string +--- @field root_dir string? --- --- @field attached_buffers table<integer,true> --- @@ -233,11 +233,11 @@ local validate = vim.validate --- --- 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` +--- Returns: { err=err, result=result }, a dict, 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 +--- @field request_sync fun(method: string, params: table?, timeout_ms: integer?, bufnr: integer): {err: lsp.ResponseError|nil, result:any}|nil, string|nil err # a dict --- --- Sends a notification to an LSP server. --- Returns: a boolean to indicate if the notification was successful. If @@ -436,7 +436,7 @@ local function ensure_list(x) return { x } end ---- @package +--- @nodoc --- @param config vim.lsp.ClientConfig --- @return vim.lsp.Client? function Client.create(config) @@ -470,7 +470,6 @@ function Client.create(config) _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. @@ -536,7 +535,7 @@ function Client:_run_callbacks(cbs, error_id, ...) end end ---- @package +--- @nodoc function Client:initialize() local config = self.config @@ -657,7 +656,7 @@ end --- @param method string LSP method name. --- @param params? table LSP request params. --- @param handler? lsp.Handler Response |lsp-handler| for this method. ---- @param bufnr? integer Buffer handle (0 for current). +--- @param bufnr integer Buffer handle (0 for current). --- @return boolean status, integer? 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 @@ -739,7 +738,7 @@ end --- @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 +--- @return {err: lsp.ResponseError|nil, result:any}|nil, string|nil err # a dict, 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 @@ -862,14 +861,14 @@ 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) +--- @param on_unsupported? function handler invoked when the command is not supported by the client. +function Client:_exec_cmd(command, context, handler, on_unsupported) context = vim.deepcopy(context or {}, true) --[[@as lsp.HandlerContext]] context.bufnr = context.bufnr or api.nvim_get_current_buf() context.client_id = self.id @@ -883,14 +882,18 @@ function Client:_exec_cmd(command, context, handler) 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 - ) + if on_unsupported then + on_unsupported() + else + 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 + ) + end return end -- Not using command directly to exclude extra properties, @@ -902,7 +905,6 @@ function Client:_exec_cmd(command, context, handler) 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 @@ -914,18 +916,16 @@ function Client:_text_document_did_open_handler(bufnr) if not api.nvim_buf_is_loaded(bufnr) then return end - local filetype = vim.bo[bufnr].filetype - local params = { + local filetype = vim.bo[bufnr].filetype + self.notify(ms.textDocument_didOpen, { textDocument = { - version = 0, + version = lsp.util.buf_versions[bufnr], 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() @@ -938,7 +938,6 @@ function Client:_text_document_did_open_handler(bufnr) 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) @@ -1061,7 +1060,6 @@ function Client:_on_exit(code, signal) ) end ---- @package --- Add a directory to the workspace folders. --- @param dir string? function Client:_add_workspace_folder(dir) @@ -1084,7 +1082,6 @@ function Client:_add_workspace_folder(dir) vim.list_extend(self.workspace_folders, wf) end ---- @package --- Remove a directory to the workspace folders. --- @param dir string? function Client:_remove_workspace_folder(dir) diff --git a/runtime/lua/vim/lsp/codelens.lua b/runtime/lua/vim/lsp/codelens.lua index c85bb6aa32..c1b6bfb28c 100644 --- a/runtime/lua/vim/lsp/codelens.lua +++ b/runtime/lua/vim/lsp/codelens.lua @@ -307,7 +307,13 @@ function M.refresh(opts) } active_refreshes[buf] = true - local request_ids = vim.lsp.buf_request(buf, ms.textDocument_codeLens, params, M.on_codelens) + local request_ids = vim.lsp.buf_request( + buf, + ms.textDocument_codeLens, + params, + M.on_codelens, + function() end + ) if vim.tbl_isempty(request_ids) then active_refreshes[buf] = nil end diff --git a/runtime/lua/vim/lsp/completion.lua b/runtime/lua/vim/lsp/completion.lua new file mode 100644 index 0000000000..71ea2df100 --- /dev/null +++ b/runtime/lua/vim/lsp/completion.lua @@ -0,0 +1,754 @@ +local M = {} + +local api = vim.api +local lsp = vim.lsp +local protocol = lsp.protocol +local ms = protocol.Methods + +local rtt_ms = 50 +local ns_to_ms = 0.000001 + +--- @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). +--- @nodoc +--- @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 + +--- @nodoc +--- @class vim.lsp.completion.BufHandle +--- @field clients table<integer, vim.lsp.Client> +--- @field triggers table<string, vim.lsp.Client[]> +--- @field convert? fun(item: lsp.CompletionItem): table + +--- @type table<integer, vim.lsp.completion.BufHandle> +local buf_handles = {} + +--- @nodoc +--- @class vim.lsp.completion.Context +local Context = { + cursor = nil, --- @type [integer, integer]? + last_request_time = nil, --- @type integer? + pending_requests = {}, --- @type function[] + isIncomplete = false, +} + +--- @nodoc +function Context:cancel_pending() + for _, cancel in ipairs(self.pending_requests) do + cancel() + end + + self.pending_requests = {} +end + +--- @nodoc +function Context:reset() + -- Note that the cursor isn't reset here, it needs to survive a `CompleteDone` event. + self.isIncomplete = false + self.last_request_time = nil + self:cancel_pending() +end + +--- @type uv.uv_timer_t? +local completion_timer = nil + +--- @return uv.uv_timer_t +local function new_timer() + return assert(vim.uv.new_timer()) +end + +local function reset_timer() + if completion_timer then + completion_timer:stop() + completion_timer:close() + end + + completion_timer = nil +end + +--- @param window integer +--- @param warmup integer +--- @return fun(sample: number): number +local function exp_avg(window, warmup) + local count = 0 + local sum = 0 + local value = 0 + + return function(sample) + if count < warmup then + count = count + 1 + sum = sum + sample + value = sum / count + else + local factor = 2.0 / (window + 1) + value = value * (1 - factor) + sample * factor + end + return value + end +end +local compute_new_average = exp_avg(10, 10) + +--- @return number +local function next_debounce() + if not Context.last_request_time then + return rtt_ms + end + + local ms_since_request = (vim.uv.hrtime() - Context.last_request_time) * ns_to_ms + return math.max((ms_since_request - rtt_ms) * -1, 0) +end + +--- @param input string Unparsed snippet +--- @return string # Parsed snippet if successful, else returns its input +local function parse_snippet(input) + local ok, parsed = pcall(function() + return lsp._snippet_grammar.parse(input) + end) + return ok and tostring(parsed) or input +end + +--- @param item lsp.CompletionItem +--- @param suffix? string +local function apply_snippet(item, suffix) + if item.textEdit then + vim.snippet.expand(item.textEdit.newText .. suffix) + elseif item.insertText then + vim.snippet.expand(item.insertText .. suffix) + end +end + +--- Returns text that should be inserted when a selecting completion item. The +--- precedence is as follows: textEdit.newText > insertText > label +--- +--- See https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion +--- +--- @param item lsp.CompletionItem +--- @return string +local function get_completion_word(item) + if item.insertTextFormat == protocol.InsertTextFormat.Snippet then + if item.textEdit then + -- Use label instead of text if text has different starting characters. + -- label is used as abbr (=displayed), but word is used for filtering + -- This is required for things like postfix completion. + -- E.g. in lua: + -- + -- local f = {} + -- f@| + -- ▲ + -- └─ cursor + -- + -- item.textEdit.newText: table.insert(f, $0) + -- label: insert + -- + -- Typing `i` would remove the candidate because newText starts with `t`. + local text = parse_snippet(item.insertText or item.textEdit.newText) + return #text < #item.label and vim.fn.matchstr(text, '\\k*') or item.label + elseif item.insertText and item.insertText ~= '' then + return parse_snippet(item.insertText) + else + return item.label + end + elseif item.textEdit then + local word = item.textEdit.newText + return word:match('^(%S*)') or word + elseif item.insertText and item.insertText ~= '' then + return item.insertText + end + return item.label +end + +--- 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 or item.label + 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 + -- When we have a list, apply the defaults and return an array of items. + for _, item in ipairs(result.items) do + ---@diagnostic disable-next-line: param-type-mismatch + apply_defaults(item, result.itemDefaults) + end + return result.items + else + -- Else just return the items as they are. + return result + end +end + +---@param item lsp.CompletionItem +---@return string +local function get_doc(item) + local doc = item.documentation + if not doc then + return '' + end + if type(doc) == 'string' then + return doc + end + if type(doc) == 'table' and type(doc.value) == 'string' then + return doc.value + end + + vim.notify('invalid documentation value: ' .. vim.inspect(doc), vim.log.levels.WARN) + return '' +end + +--- Turns the result of a `textDocument/completion` request into vim-compatible +--- |complete-items|. +--- +--- @private +--- @param result vim.lsp.CompletionResult Result of `textDocument/completion` +--- @param prefix string prefix to filter the completion items +--- @param client_id integer? Client ID +--- @return table[] +--- @see complete-items +function M._lsp_to_complete_items(result, prefix, client_id) + local items = get_items(result) + if vim.tbl_isempty(items) then + return {} + end + + ---@type fun(item: lsp.CompletionItem):boolean + local matches + if not prefix:find('%w') then + matches = function(_) + return true + end + else + ---@param item lsp.CompletionItem + matches = function(item) + local text = item.filterText or item.label + return next(vim.fn.matchfuzzy({ text }, prefix)) ~= nil + end + end + + local candidates = {} + local bufnr = api.nvim_get_current_buf() + local user_convert = vim.tbl_get(buf_handles, bufnr, 'convert') + for _, item in ipairs(items) do + if matches(item) then + local word = get_completion_word(item) + local hl_group = '' + if + item.deprecated + or vim.list_contains((item.tags or {}), protocol.CompletionTag.Deprecated) + then + hl_group = 'DiagnosticDeprecated' + end + local completion_item = { + word = word, + abbr = item.label, + kind = protocol.CompletionItemKind[item.kind] or 'Unknown', + menu = item.detail or '', + info = get_doc(item), + icase = 1, + dup = 1, + empty = 1, + hl_group = hl_group, + user_data = { + nvim = { + lsp = { + completion_item = item, + client_id = client_id, + }, + }, + }, + } + if user_convert then + completion_item = vim.tbl_extend('keep', user_convert(item), completion_item) + end + table.insert(candidates, completion_item) + end + end + ---@diagnostic disable-next-line: no-unknown + table.sort(candidates, function(a, b) + ---@type lsp.CompletionItem + local itema = a.user_data.nvim.lsp.completion_item + ---@type lsp.CompletionItem + local itemb = b.user_data.nvim.lsp.completion_item + return (itema.sortText or itema.label) < (itemb.sortText or itemb.label) + end) + + return candidates +end + +--- @param lnum integer 0-indexed +--- @param line string +--- @param items lsp.CompletionItem[] +--- @param encoding string +--- @return integer? +local function adjust_start_col(lnum, line, items, encoding) + local min_start_char = nil + for _, item in pairs(items) do + if item.textEdit and item.textEdit.range.start.line == lnum then + if min_start_char and min_start_char ~= item.textEdit.range.start.character then + return nil + end + min_start_char = item.textEdit.range.start.character + end + end + if min_start_char then + return lsp.util._str_byteindex_enc(line, min_start_char, encoding) + else + return nil + end +end + +--- @private +--- @param line string line content +--- @param lnum integer 0-indexed line number +--- @param cursor_col integer +--- @param client_id integer client ID +--- @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 vim.lsp.CompletionResult +--- @param encoding string +--- @return table[] matches +--- @return integer? server_start_boundary +function M._convert_results( + line, + lnum, + cursor_col, + client_id, + client_start_boundary, + server_start_boundary, + result, + encoding +) + -- Completion response items may be relative to a position different than `client_start_boundary`. + -- Concrete example, with lua-language-server: + -- + -- require('plenary.asy| + -- ▲ ▲ ▲ + -- │ │ └── cursor_pos: 20 + -- │ └────── client_start_boundary: 17 + -- └────────────── textEdit.range.start.character: 9 + -- .newText = 'plenary.async' + -- ^^^ + -- prefix (We'd remove everything not starting with `asy`, + -- so we'd eliminate the `plenary.async` result + -- + -- `adjust_start_col` is used to prefer the language server boundary. + -- + local candidates = get_items(result) + local curstartbyte = adjust_start_col(lnum, line, candidates, encoding) + if server_start_boundary == nil then + server_start_boundary = curstartbyte + elseif curstartbyte ~= nil and curstartbyte ~= server_start_boundary then + server_start_boundary = client_start_boundary + end + local prefix = line:sub((server_start_boundary or client_start_boundary) + 1, cursor_col) + local matches = M._lsp_to_complete_items(result, prefix, client_id) + return matches, server_start_boundary +end + +--- @param clients table<integer, vim.lsp.Client> # keys != client_id +--- @param bufnr integer +--- @param win integer +--- @param callback fun(responses: table<integer, { err: lsp.ResponseError, result: vim.lsp.CompletionResult }>) +--- @return function # Cancellation function +local function request(clients, bufnr, win, callback) + local responses = {} --- @type table<integer, { err: lsp.ResponseError, result: any }> + local request_ids = {} --- @type table<integer, integer> + local remaining_requests = vim.tbl_count(clients) + + for _, client in pairs(clients) do + local client_id = client.id + local params = lsp.util.make_position_params(win, client.offset_encoding) + local ok, request_id = client.request(ms.textDocument_completion, params, function(err, result) + responses[client_id] = { err = err, result = result } + remaining_requests = remaining_requests - 1 + if remaining_requests == 0 then + callback(responses) + end + end, bufnr) + + if ok then + request_ids[client_id] = request_id + end + end + + return function() + for client_id, request_id in pairs(request_ids) do + local client = lsp.get_client_by_id(client_id) + if client then + client.cancel_request(request_id) + end + end + end +end + +local function trigger(bufnr, clients) + reset_timer() + Context:cancel_pending() + + if tonumber(vim.fn.pumvisible()) == 1 and not Context.isIncomplete then + return + end + + local win = api.nvim_get_current_win() + local cursor_row, cursor_col = unpack(api.nvim_win_get_cursor(win)) --- @type integer, integer + local line = api.nvim_get_current_line() + local line_to_cursor = line:sub(1, cursor_col) + local word_boundary = vim.fn.match(line_to_cursor, '\\k*$') + local start_time = vim.uv.hrtime() + Context.last_request_time = start_time + + local cancel_request = request(clients, bufnr, win, function(responses) + local end_time = vim.uv.hrtime() + rtt_ms = compute_new_average((end_time - start_time) * ns_to_ms) + + Context.pending_requests = {} + Context.isIncomplete = false + + local row_changed = api.nvim_win_get_cursor(win)[1] ~= cursor_row + local mode = api.nvim_get_mode().mode + if row_changed or not (mode == 'i' or mode == 'ic') then + return + end + + local matches = {} + local server_start_boundary --- @type integer? + for client_id, response in pairs(responses) do + if response.err then + vim.notify_once(response.err.message, vim.log.levels.warn) + end + + local result = response.result + if result then + Context.isIncomplete = Context.isIncomplete or result.isIncomplete + local client = lsp.get_client_by_id(client_id) + local encoding = client and client.offset_encoding or 'utf-16' + local client_matches + client_matches, server_start_boundary = M._convert_results( + line, + cursor_row - 1, + cursor_col, + client_id, + word_boundary, + nil, + result, + encoding + ) + vim.list_extend(matches, client_matches) + end + end + local start_col = (server_start_boundary or word_boundary) + 1 + vim.fn.complete(start_col, matches) + end) + + table.insert(Context.pending_requests, cancel_request) +end + +--- @param handle vim.lsp.completion.BufHandle +local function on_insert_char_pre(handle) + if tonumber(vim.fn.pumvisible()) == 1 then + if Context.isIncomplete then + reset_timer() + + local debounce_ms = next_debounce() + if debounce_ms == 0 then + vim.schedule(M.trigger) + else + completion_timer = new_timer() + completion_timer:start(debounce_ms, 0, vim.schedule_wrap(M.trigger)) + end + end + + return + end + + local char = api.nvim_get_vvar('char') + if not completion_timer and handle.triggers[char] then + completion_timer = assert(vim.uv.new_timer()) + completion_timer:start(25, 0, function() + reset_timer() + vim.schedule(M.trigger) + end) + end +end + +local function on_insert_leave() + reset_timer() + Context.cursor = nil + Context:reset() +end + +local function on_complete_done() + local completed_item = api.nvim_get_vvar('completed_item') + if not completed_item or not completed_item.user_data or not completed_item.user_data.nvim then + Context:reset() + return + end + + local cursor_row, cursor_col = unpack(api.nvim_win_get_cursor(0)) --- @type integer, integer + cursor_row = cursor_row - 1 + local completion_item = completed_item.user_data.nvim.lsp.completion_item --- @type lsp.CompletionItem + local client_id = completed_item.user_data.nvim.lsp.client_id --- @type integer + if not completion_item or not client_id then + Context:reset() + return + end + + local bufnr = api.nvim_get_current_buf() + local expand_snippet = completion_item.insertTextFormat == protocol.InsertTextFormat.Snippet + and (completion_item.textEdit ~= nil or completion_item.insertText ~= nil) + + Context:reset() + + local client = lsp.get_client_by_id(client_id) + if not client then + return + end + + local offset_encoding = client.offset_encoding or 'utf-16' + local resolve_provider = (client.server_capabilities.completionProvider or {}).resolveProvider + + local function clear_word() + if not expand_snippet then + return nil + end + + -- Remove the already inserted word. + local start_char = cursor_col - #completed_item.word + local line = api.nvim_buf_get_lines(bufnr, cursor_row, cursor_row + 1, true)[1] + api.nvim_buf_set_text(bufnr, cursor_row, start_char, cursor_row, #line, { '' }) + return line:sub(cursor_col + 1) + end + + --- @param suffix? string + local function apply_snippet_and_command(suffix) + if expand_snippet then + apply_snippet(completion_item, suffix) + end + + local command = completion_item.command + if command then + client:_exec_cmd(command, { bufnr = bufnr }, nil, function() + vim.lsp.log.warn( + string.format( + 'Language server `%s` does not support command `%s`. This command may require a client extension.', + client.name, + command.command + ) + ) + end) + end + end + + if completion_item.additionalTextEdits and next(completion_item.additionalTextEdits) then + local suffix = clear_word() + lsp.util.apply_text_edits(completion_item.additionalTextEdits, bufnr, offset_encoding) + apply_snippet_and_command(suffix) + elseif resolve_provider and type(completion_item) == 'table' then + local changedtick = vim.b[bufnr].changedtick + + --- @param result lsp.CompletionItem + client.request(ms.completionItem_resolve, completion_item, function(err, result) + if changedtick ~= vim.b[bufnr].changedtick then + return + end + + local suffix = clear_word() + if err then + vim.notify_once(err.message, vim.log.levels.WARN) + elseif result and result.additionalTextEdits then + lsp.util.apply_text_edits(result.additionalTextEdits, bufnr, offset_encoding) + if result.command then + completion_item.command = result.command + end + end + + apply_snippet_and_command(suffix) + end, bufnr) + else + local suffix = clear_word() + apply_snippet_and_command(suffix) + end +end + +--- @class vim.lsp.completion.BufferOpts +--- @field autotrigger? boolean Whether to trigger completion automatically. Default: false +--- @field convert? fun(item: lsp.CompletionItem): table Transforms an LSP CompletionItem to |complete-items|. + +---@param client_id integer +---@param bufnr integer +---@param opts vim.lsp.completion.BufferOpts +local function enable_completions(client_id, bufnr, opts) + local buf_handle = buf_handles[bufnr] + if not buf_handle then + buf_handle = { clients = {}, triggers = {}, convert = opts.convert } + buf_handles[bufnr] = buf_handle + + -- Attach to buffer events. + api.nvim_buf_attach(bufnr, false, { + on_detach = function(_, buf) + buf_handles[buf] = nil + end, + on_reload = function(_, buf) + M.enable(true, client_id, buf, opts) + end, + }) + + -- Set up autocommands. + local group = + api.nvim_create_augroup(string.format('vim/lsp/completion-%d', bufnr), { clear = true }) + api.nvim_create_autocmd('CompleteDone', { + group = group, + buffer = bufnr, + callback = function() + local reason = api.nvim_get_vvar('event').reason --- @type string + if reason == 'accept' then + on_complete_done() + end + end, + }) + if opts.autotrigger then + api.nvim_create_autocmd('InsertCharPre', { + group = group, + buffer = bufnr, + callback = function() + on_insert_char_pre(buf_handles[bufnr]) + end, + }) + api.nvim_create_autocmd('InsertLeave', { + group = group, + buffer = bufnr, + callback = on_insert_leave, + }) + end + end + + if not buf_handle.clients[client_id] then + local client = lsp.get_client_by_id(client_id) + assert(client, 'invalid client ID') + + -- Add the new client to the buffer's clients. + buf_handle.clients[client_id] = client + + -- Add the new client to the clients that should be triggered by its trigger characters. + --- @type string[] + local triggers = vim.tbl_get( + client.server_capabilities, + 'completionProvider', + 'triggerCharacters' + ) or {} + for _, char in ipairs(triggers) do + local clients_for_trigger = buf_handle.triggers[char] + if not clients_for_trigger then + clients_for_trigger = {} + buf_handle.triggers[char] = clients_for_trigger + end + local client_exists = vim.iter(clients_for_trigger):any(function(c) + return c.id == client_id + end) + if not client_exists then + table.insert(clients_for_trigger, client) + end + end + end +end + +--- @param client_id integer +--- @param bufnr integer +local function disable_completions(client_id, bufnr) + local handle = buf_handles[bufnr] + if not handle then + return + end + + handle.clients[client_id] = nil + if not next(handle.clients) then + buf_handles[bufnr] = nil + api.nvim_del_augroup_by_name(string.format('vim/lsp/completion-%d', bufnr)) + else + for char, clients in pairs(handle.triggers) do + --- @param c vim.lsp.Client + handle.triggers[char] = vim.tbl_filter(function(c) + return c.id ~= client_id + end, clients) + end + end +end + +--- Enables or disables completions from the given language client in the given buffer. +--- +--- @param enable boolean True to enable, false to disable +--- @param client_id integer Client ID +--- @param bufnr integer Buffer handle, or 0 for the current buffer +--- @param opts? vim.lsp.completion.BufferOpts +function M.enable(enable, client_id, bufnr, opts) + bufnr = (bufnr == 0 and api.nvim_get_current_buf()) or bufnr + + if enable then + enable_completions(client_id, bufnr, opts or {}) + else + disable_completions(client_id, bufnr) + end +end + +--- Trigger LSP completion in the current buffer. +function M.trigger() + local bufnr = api.nvim_get_current_buf() + local clients = (buf_handles[bufnr] or {}).clients or {} + trigger(bufnr, clients) +end + +--- Implements 'omnifunc' compatible LSP completion. +--- +--- @see |complete-functions| +--- @see |complete-items| +--- @see |CompleteDone| +--- +--- @param findstart integer 0 or 1, decides behavior +--- @param base integer findstart=0, text to match against +--- +--- @return integer|table Decided by {findstart}: +--- - findstart=0: column where the completion starts, or -2 or -3 +--- - findstart=1: list of matches (actually just calls |complete()|) +function M._omnifunc(findstart, base) + vim.lsp.log.debug('omnifunc.findstart', { findstart = findstart, base = base }) + assert(base) -- silence luals + local bufnr = api.nvim_get_current_buf() + local clients = lsp.get_clients({ bufnr = bufnr, method = ms.textDocument_completion }) + local remaining = #clients + if remaining == 0 then + return findstart == 1 and -1 or {} + end + + trigger(bufnr, clients) + + -- Return -2 to signal that we should continue completion so that we can + -- async complete. + return -2 +end + +return M diff --git a/runtime/lua/vim/lsp/diagnostic.lua b/runtime/lua/vim/lsp/diagnostic.lua index 08cea13548..c10312484b 100644 --- a/runtime/lua/vim/lsp/diagnostic.lua +++ b/runtime/lua/vim/lsp/diagnostic.lua @@ -110,6 +110,14 @@ local function diagnostic_lsp_to_vim(diagnostics, bufnr, client_id) return vim.tbl_map(function(diagnostic) local start = diagnostic.range.start local _end = diagnostic.range['end'] + local message = diagnostic.message + if type(message) ~= 'string' then + vim.notify_once( + string.format('Unsupported Markup message from LSP client %d', client_id), + vim.lsp.log_levels.ERROR + ) + message = diagnostic.message.value + end --- @type vim.Diagnostic return { lnum = start.line, @@ -117,18 +125,12 @@ local function diagnostic_lsp_to_vim(diagnostics, bufnr, client_id) end_lnum = _end.line, end_col = line_byte_from_position(buf_lines, _end.line, _end.character, offset_encoding), severity = severity_lsp_to_vim(diagnostic.severity), - message = diagnostic.message, + message = message, source = diagnostic.source, code = diagnostic.code, _tags = tags_lsp_to_vim(diagnostic, client_id), user_data = { - lsp = { - -- usage of user_data.lsp.code is deprecated in favor of the top-level code field - code = diagnostic.code, - codeDescription = diagnostic.codeDescription, - relatedInformation = diagnostic.relatedInformation, - data = diagnostic.data, - }, + lsp = diagnostic, }, } end, diagnostics) @@ -151,14 +153,18 @@ local function tags_vim_to_lsp(diagnostic) return tags end +--- Converts the input `vim.Diagnostic`s to LSP diagnostics. --- @param diagnostics vim.Diagnostic[] --- @return lsp.Diagnostic[] -local function diagnostic_vim_to_lsp(diagnostics) +function M.from(diagnostics) ---@param diagnostic vim.Diagnostic ---@return lsp.Diagnostic return vim.tbl_map(function(diagnostic) - return vim.tbl_extend('keep', { - -- "keep" the below fields over any duplicate fields in diagnostic.user_data.lsp + local user_data = diagnostic.user_data or {} + if user_data.lsp then + return user_data.lsp + end + return { range = { start = { line = diagnostic.lnum, @@ -174,7 +180,7 @@ local function diagnostic_vim_to_lsp(diagnostics) 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 @@ -366,6 +372,7 @@ end --- Structured: { [1] = {...}, [5] = {.... } } ---@private function M.get_line_diagnostics(bufnr, line_nr, opts, client_id) + vim.deprecate('vim.lsp.diagnostic.get_line_diagnostics', 'vim.diagnostic.get', '0.12') convert_severity(opts) local diag_opts = {} --- @type vim.diagnostic.GetOpts @@ -379,7 +386,7 @@ function M.get_line_diagnostics(bufnr, line_nr, opts, client_id) diag_opts.lnum = line_nr or (api.nvim_win_get_cursor(0)[1] - 1) - return diagnostic_vim_to_lsp(vim.diagnostic.get(bufnr, diag_opts)) + return M.from(vim.diagnostic.get(bufnr, diag_opts)) end --- Clear diagnostics from pull based clients diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua index f9d394642c..44548fec92 100644 --- a/runtime/lua/vim/lsp/handlers.lua +++ b/runtime/lua/vim/lsp/handlers.lua @@ -3,7 +3,7 @@ local protocol = require('vim.lsp.protocol') local ms = protocol.Methods local util = require('vim.lsp.util') local api = vim.api -local completion = require('vim.lsp._completion') +local completion = require('vim.lsp.completion') --- @type table<string,lsp.Handler> local M = {} @@ -646,6 +646,7 @@ M[ms.window_showMessage] = function(_, result, ctx, _) if message_type == protocol.MessageType.Error then err_message('LSP[', client_name, '] ', message) else + --- @type string local message_type_name = protocol.MessageType[message_type] api.nvim_out_write(string.format('LSP[%s][%s] %s\n', client_name, message_type_name, message)) end diff --git a/runtime/lua/vim/lsp/health.lua b/runtime/lua/vim/lsp/health.lua index a79ae76eb9..18066a84db 100644 --- a/runtime/lua/vim/lsp/health.lua +++ b/runtime/lua/vim/lsp/health.lua @@ -33,16 +33,25 @@ local function check_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 {}), ',') - report_info( + local cmd ---@type string + if type(client.config.cmd) == 'table' then + cmd = table.concat(client.config.cmd --[[@as table]], ' ') + elseif type(client.config.cmd) == 'function' then + cmd = tostring(client.config.cmd) + end + report_info(table.concat({ + string.format('%s (id: %d)', client.name, client.id), string.format( - '%s (id=%s, root_dir=%s, attached_to=[%s])', - client.name, - client.id, - vim.fn.fnamemodify(client.root_dir, ':~'), - attached_to - ) - ) + ' Root directory: %s', + client.root_dir and vim.fn.fnamemodify(client.root_dir, ':~') or nil + ), + string.format(' Command: %s', cmd), + string.format(' Settings: %s', vim.inspect(client.settings, { newline = '\n ' })), + string.format( + ' Attached buffers: %s', + vim.iter(pairs(client.attached_buffers)):map(tostring):join(', ') + ), + }, '\n')) end else report_info('No active clients') @@ -50,7 +59,7 @@ local function check_active_clients() end local function check_watcher() - vim.health.start('vim.lsp: File watcher') + vim.health.start('vim.lsp: File Watcher') -- Only run the check if file watching has been enabled by a client. local clients = vim.lsp.get_clients() @@ -81,8 +90,8 @@ local function check_watcher() watchfunc_name = 'libuv-watch' elseif watchfunc == vim._watch.watchdirs then watchfunc_name = 'libuv-watchdirs' - elseif watchfunc == vim._watch.fswatch then - watchfunc_name = 'fswatch' + elseif watchfunc == vim._watch.inotifywait then + watchfunc_name = 'inotifywait' else local nm = debug.getinfo(watchfunc, 'S').source watchfunc_name = string.format('Custom (%s)', nm) @@ -90,7 +99,63 @@ local function check_watcher() report_info('File watch backend: ' .. watchfunc_name) if watchfunc_name == 'libuv-watchdirs' then - report_warn('libuv-watchdirs has known performance issues. Consider installing fswatch.') + report_warn('libuv-watchdirs has known performance issues. Consider installing inotify-tools.') + end +end + +local function check_position_encodings() + vim.health.start('vim.lsp: Position Encodings') + local clients = vim.lsp.get_clients() + if next(clients) then + local position_encodings = {} ---@type table<integer, table<string, integer[]>> + for _, client in pairs(clients) do + for bufnr in pairs(client.attached_buffers) do + if not position_encodings[bufnr] then + position_encodings[bufnr] = {} + end + if not position_encodings[bufnr][client.offset_encoding] then + position_encodings[bufnr][client.offset_encoding] = {} + end + table.insert(position_encodings[bufnr][client.offset_encoding], client.id) + end + end + + -- Check if any buffers are attached to multiple clients with different position encodings + local buffers = {} ---@type integer[] + for bufnr, encodings in pairs(position_encodings) do + local list = {} ---@type string[] + for k in pairs(encodings) do + list[#list + 1] = k + end + + if #list > 1 then + buffers[#buffers + 1] = bufnr + end + end + + if #buffers > 0 then + local lines = + { 'Found buffers attached to multiple clients with different position encodings.' } + for _, bufnr in ipairs(buffers) do + local encodings = position_encodings[bufnr] + local parts = {} + for encoding, client_ids in pairs(encodings) do + table.insert( + parts, + string.format('%s (client id(s): %s)', encoding:upper(), table.concat(client_ids, ', ')) + ) + end + table.insert(lines, string.format('- Buffer %d: %s', bufnr, table.concat(parts, ', '))) + end + report_warn( + table.concat(lines, '\n'), + 'Use the positionEncodings client capability to ensure all clients use the same position encoding' + ) + else + report_info('No buffers contain mixed position encodings') + end + else + report_info('No active clients') end end @@ -99,6 +164,7 @@ function M.check() check_log() check_active_clients() check_watcher() + check_position_encodings() end return M diff --git a/runtime/lua/vim/lsp/inlay_hint.lua b/runtime/lua/vim/lsp/inlay_hint.lua index f98496456b..61059180fe 100644 --- a/runtime/lua/vim/lsp/inlay_hint.lua +++ b/runtime/lua/vim/lsp/inlay_hint.lua @@ -43,17 +43,16 @@ function M.on_inlayhint(err, result, ctx, _) return end local bufnr = assert(ctx.bufnr) - if util.buf_versions[bufnr] ~= ctx.version then + if + util.buf_versions[bufnr] ~= ctx.version + or not result + or not api.nvim_buf_is_loaded(bufnr) + or not bufstates[bufnr].enabled + then return end local client_id = ctx.client_id - if not result then - return - end local bufstate = bufstates[bufnr] - if not bufstate.enabled then - return - end if not (bufstate.client_hints and bufstate.version) then bufstate.client_hints = vim.defaulttable() bufstate.version = ctx.version @@ -77,12 +76,7 @@ function M.on_inlayhint(err, result, ctx, _) local col = position.character if col > 0 then local line = lines[position.line + 1] or '' - local ok, convert_result - ok, convert_result = pcall(util._str_byteindex_enc, line, col, client.offset_encoding) - if ok then - return convert_result - end - return math.min(#line, col) + return util._str_byteindex_enc(line, col, client.offset_encoding) end return col end @@ -336,6 +330,8 @@ api.nvim_set_decoration_provider(namespace, { for lnum = topline, botline do if bufstate.applied[lnum] ~= bufstate.version then api.nvim_buf_clear_namespace(bufnr, namespace, lnum, lnum + 1) + + local hint_virtual_texts = {} --- @type table<integer, [string, string?][]> for _, lnum_hints in pairs(client_hints) do local hints = lnum_hints[lnum] or {} for _, hint in pairs(hints) do @@ -348,7 +344,7 @@ api.nvim_set_decoration_provider(namespace, { text = text .. part.value end end - local vt = {} --- @type {[1]: string, [2]: string?}[] + local vt = hint_virtual_texts[hint.position.character] or {} if hint.paddingLeft then vt[#vt + 1] = { ' ' } end @@ -356,13 +352,18 @@ api.nvim_set_decoration_provider(namespace, { if hint.paddingRight then vt[#vt + 1] = { ' ' } end - api.nvim_buf_set_extmark(bufnr, namespace, lnum, hint.position.character, { - virt_text_pos = 'inline', - ephemeral = false, - virt_text = vt, - }) + hint_virtual_texts[hint.position.character] = vt end end + + for pos, vt in pairs(hint_virtual_texts) do + api.nvim_buf_set_extmark(bufnr, namespace, lnum, pos, { + virt_text_pos = 'inline', + ephemeral = false, + virt_text = vt, + }) + end + bufstate.applied[lnum] = bufstate.version end end @@ -370,7 +371,7 @@ api.nvim_set_decoration_provider(namespace, { }) --- Query whether inlay hint is enabled in the {filter}ed scope ---- @param filter vim.lsp.inlay_hint.enable.Filter +--- @param filter? vim.lsp.inlay_hint.enable.Filter --- @return boolean --- @since 12 function M.is_enabled(filter) diff --git a/runtime/lua/vim/lsp/log.lua b/runtime/lua/vim/lsp/log.lua index 9f2bd71158..4f177b47fd 100644 --- a/runtime/lua/vim/lsp/log.lua +++ b/runtime/lua/vim/lsp/log.lua @@ -9,7 +9,7 @@ local log_levels = vim.log.levels --- 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> +--- @type table<string,integer> | table<integer, string> --- @nodoc log.levels = vim.deepcopy(log_levels) @@ -19,7 +19,7 @@ local current_log_level = log_levels.WARN local log_date_format = '%F %H:%M:%S' local function format_func(arg) - return vim.inspect(arg, { newline = '' }) + return vim.inspect(arg, { newline = ' ', indent = '' }) end local function notify(msg, level) diff --git a/runtime/lua/vim/lsp/protocol.lua b/runtime/lua/vim/lsp/protocol.lua index 419c2ff644..1699fff0c1 100644 --- a/runtime/lua/vim/lsp/protocol.lua +++ b/runtime/lua/vim/lsp/protocol.lua @@ -12,9 +12,6 @@ end local sysname = vim.uv.os_uname().sysname --- Protocol for the Microsoft Language Server Protocol (mslsp) -local protocol = {} - local constants = { --- @enum lsp.DiagnosticSeverity DiagnosticSeverity = { @@ -46,6 +43,8 @@ local constants = { Info = 3, -- A log message. Log = 4, + -- A debug message. + Debug = 5, }, -- The file event type. @@ -100,6 +99,13 @@ local constants = { TriggerForIncompleteCompletions = 3, }, + -- Completion item tags are extra annotations that tweak the rendering of a + -- completion item + CompletionTag = { + -- Render a completion as obsolete, usually using a strike-out. + Deprecated = 1, + }, + -- A document highlight kind. DocumentHighlightKind = { -- A textual occurrence. @@ -308,326 +314,18 @@ local constants = { }, } -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 +-- Protocol for the Microsoft Language Server Protocol (mslsp) +local protocol = {} + +--- @diagnostic disable:no-unknown +for k1, v1 in pairs(vim.deepcopy(constants, true)) do + for _, k2 in ipairs(vim.tbl_keys(v1)) do + local v2 = v1[k2] + v1[v2] = k2 end - protocol[k1] = tbl + protocol[k1] = v1 end - ---[=[ ---Text document specific client capabilities. -export interface TextDocumentClientCapabilities { - synchronization?: { - --Whether text document synchronization supports dynamic registration. - dynamicRegistration?: boolean; - --The client supports sending will save notifications. - 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. - willSaveWaitUntil?: boolean; - --The client supports did save notifications. - didSave?: boolean; - } - --Capabilities specific to the `textDocument/completion` - completion?: { - --Whether completion supports dynamic registration. - dynamicRegistration?: boolean; - --The client supports the following `CompletionItem` specific - --capabilities. - completionItem?: { - --The client supports snippets as insert text. - -- - --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. Placeholders with equal identifiers are linked, - --that is typing in one will update others too. - snippetSupport?: boolean; - --The client supports commit characters on a completion item. - commitCharactersSupport?: boolean - --The client supports the following content formats for the documentation - --property. The order describes the preferred format of the client. - documentationFormat?: MarkupKind[]; - --The client supports the deprecated property on a completion item. - deprecatedSupport?: boolean; - --The client supports the preselect property on a completion item. - preselectSupport?: boolean; - } - 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 - --to a default value when unknown. - -- - --If this property is not present the client only supports - --the completion items kinds from `Text` to `Reference` as defined in - --the initial version of the protocol. - valueSet?: CompletionItemKind[]; - }, - --The client supports to send additional context information for a - --`textDocument/completion` request. - contextSupport?: boolean; - }; - --Capabilities specific to the `textDocument/hover` - hover?: { - --Whether hover supports dynamic registration. - dynamicRegistration?: boolean; - --The client supports the follow content formats for the content - --property. The order describes the preferred format of the client. - contentFormat?: MarkupKind[]; - }; - --Capabilities specific to the `textDocument/signatureHelp` - signatureHelp?: { - --Whether signature help supports dynamic registration. - dynamicRegistration?: boolean; - --The client supports the following `SignatureInformation` - --specific properties. - signatureInformation?: { - --The client supports the follow content formats for the documentation - --property. The order describes the preferred format of the client. - documentationFormat?: MarkupKind[]; - --Client capabilities specific to parameter information. - parameterInformation?: { - --The client supports processing label offsets instead of a - --simple label string. - -- - --Since 3.14.0 - labelOffsetSupport?: boolean; - } - }; - }; - --Capabilities specific to the `textDocument/references` - references?: { - --Whether references supports dynamic registration. - dynamicRegistration?: boolean; - }; - --Capabilities specific to the `textDocument/documentHighlight` - documentHighlight?: { - --Whether document highlight supports dynamic registration. - dynamicRegistration?: boolean; - }; - --Capabilities specific to the `textDocument/documentSymbol` - documentSymbol?: { - --Whether document symbol supports dynamic registration. - dynamicRegistration?: boolean; - --Specific capabilities for the `SymbolKind`. - 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 - --to a default value when unknown. - -- - --If this property is not present the client only supports - --the symbol kinds from `File` to `Array` as defined in - --the initial version of the protocol. - valueSet?: SymbolKind[]; - } - --The client supports hierarchical document symbols. - hierarchicalDocumentSymbolSupport?: boolean; - }; - --Capabilities specific to the `textDocument/formatting` - formatting?: { - --Whether formatting supports dynamic registration. - dynamicRegistration?: boolean; - }; - --Capabilities specific to the `textDocument/rangeFormatting` - rangeFormatting?: { - --Whether range formatting supports dynamic registration. - dynamicRegistration?: boolean; - }; - --Capabilities specific to the `textDocument/onTypeFormatting` - onTypeFormatting?: { - --Whether on type formatting supports dynamic registration. - dynamicRegistration?: boolean; - }; - --Capabilities specific to the `textDocument/declaration` - declaration?: { - --Whether declaration 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. - dynamicRegistration?: boolean; - --The client supports additional metadata in the form of declaration links. - -- - --Since 3.14.0 - linkSupport?: boolean; - }; - --Capabilities specific to the `textDocument/definition`. - -- - --Since 3.14.0 - definition?: { - --Whether definition supports dynamic registration. - dynamicRegistration?: boolean; - --The client supports additional metadata in the form of definition links. - linkSupport?: boolean; - }; - --Capabilities specific to the `textDocument/typeDefinition` - -- - --Since 3.6.0 - typeDefinition?: { - --Whether typeDefinition 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. - dynamicRegistration?: boolean; - --The client supports additional metadata in the form of definition links. - -- - --Since 3.14.0 - linkSupport?: boolean; - }; - --Capabilities specific to the `textDocument/implementation`. - -- - --Since 3.6.0 - implementation?: { - --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. - dynamicRegistration?: boolean; - --The client supports additional metadata in the form of definition links. - -- - --Since 3.14.0 - linkSupport?: boolean; - }; - --Capabilities specific to the `textDocument/codeAction` - codeAction?: { - --Whether code action supports dynamic registration. - dynamicRegistration?: boolean; - --The client support code action literals as a valid - --response of the `textDocument/codeAction` request. - -- - --Since 3.8.0 - codeActionLiteralSupport?: { - --The code action kind is support with the following value - --set. - 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. - valueSet: CodeActionKind[]; - }; - }; - }; - --Capabilities specific to the `textDocument/codeLens` - codeLens?: { - --Whether code lens supports dynamic registration. - dynamicRegistration?: boolean; - }; - --Capabilities specific to the `textDocument/documentLink` - documentLink?: { - --Whether document link supports dynamic registration. - dynamicRegistration?: boolean; - }; - --Capabilities specific to the `textDocument/documentColor` and the - --`textDocument/colorPresentation` request. - -- - --Since 3.6.0 - colorProvider?: { - --Whether colorProvider supports dynamic registration. If this is set to `true` - --the client supports the new `(ColorProviderOptions & TextDocumentRegistrationOptions & StaticRegistrationOptions)` - --return value for the corresponding server capability as well. - dynamicRegistration?: boolean; - } - --Capabilities specific to the `textDocument/rename` - rename?: { - --Whether rename supports dynamic registration. - dynamicRegistration?: boolean; - --The client supports testing for validity of rename operations - --before execution. - prepareSupport?: boolean; - }; - --Capabilities specific to `textDocument/publishDiagnostics`. - publishDiagnostics?: { - --Whether the clients accepts diagnostics with related information. - 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 - tagSupport?: { - --The tags supported by this client - valueSet: DiagnosticTag[]; - }; - }; - --Capabilities specific to `textDocument/foldingRange` requests. - -- - --Since 3.10.0 - foldingRange?: { - --Whether implementation supports dynamic registration for folding range providers. If this is set to `true` - --the client supports the new `(FoldingRangeProviderOptions & TextDocumentRegistrationOptions & StaticRegistrationOptions)` - --return value for the corresponding server capability as well. - 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. - rangeLimit?: number; - --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. - lineFoldingOnly?: boolean; - }; -} ---]=] - ---[=[ ---Workspace specific client capabilities. -export interface WorkspaceClientCapabilities { - --The client supports applying batch edits to the workspace by supporting - --the request 'workspace/applyEdit' - applyEdit?: boolean; - --Capabilities specific to `WorkspaceEdit`s - workspaceEdit?: { - --The client supports versioned document changes in `WorkspaceEdit`s - documentChanges?: boolean; - --The resource operations the client supports. Clients should at least - --support 'create', 'rename' and 'delete' files and folders. - resourceOperations?: ResourceOperationKind[]; - --The failure handling strategy of a client if applying the workspace edit - --fails. - failureHandling?: FailureHandlingKind; - }; - --Capabilities specific to the `workspace/didChangeConfiguration` notification. - didChangeConfiguration?: { - --Did change configuration notification supports dynamic registration. - dynamicRegistration?: boolean; - }; - --Capabilities specific to the `workspace/didChangeWatchedFiles` notification. - didChangeWatchedFiles?: { - --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. - dynamicRegistration?: boolean; - }; - --Capabilities specific to the `workspace/symbol` request. - symbol?: { - --Symbol request supports dynamic registration. - dynamicRegistration?: boolean; - --Specific capabilities for the `SymbolKind` in the `workspace/symbol` request. - 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 - --to a default value when unknown. - -- - --If this property is not present the client only supports - --the symbol kinds from `File` to `Array` as defined in - --the initial version of the protocol. - valueSet?: SymbolKind[]; - } - }; - --Capabilities specific to the `workspace/executeCommand` request. - executeCommand?: { - --Execute command supports dynamic registration. - dynamicRegistration?: boolean; - }; - --The client has support for workspace folders. - -- - --Since 3.6.0 - workspaceFolders?: boolean; - --The client supports `workspace/configuration` requests. - -- - --Since 3.6.0 - configuration?: boolean; -} ---]=] +--- @diagnostic enable:no-unknown --- Gets a new ClientCapabilities object describing the LSP client --- capabilities. @@ -729,23 +427,35 @@ function protocol.make_client_capabilities() properties = { 'edit' }, }, }, + codeLens = { + dynamicRegistration = false, + resolveSupport = { + properties = { 'command' }, + }, + }, formatting = { dynamicRegistration = true, }, rangeFormatting = { dynamicRegistration = true, + rangesSupport = true, }, completion = { dynamicRegistration = false, completionItem = { - -- Until we can actually expand snippet, move cursor and allow for true snippet experience, - -- this should be disabled out of the box. - -- However, users can turn this back on if they have a snippet plugin. - snippetSupport = false, + snippetSupport = true, commitCharactersSupport = false, preselectSupport = false, - deprecatedSupport = false, + deprecatedSupport = true, documentationFormat = { constants.MarkupKind.Markdown, constants.MarkupKind.PlainText }, + resolveSupport = { + properties = { + 'additionalTextEdits', + }, + }, + tagSupport = { + valueSet = get_value_set(constants.CompletionTag), + }, }, completionItemKind = { valueSet = get_value_set(constants.CompletionItemKind), @@ -852,7 +562,7 @@ function protocol.make_client_capabilities() workDoneProgress = true, showMessage = { messageActionItem = { - additionalPropertiesSupport = false, + additionalPropertiesSupport = true, }, }, showDocument = { @@ -905,9 +615,10 @@ function protocol.resolve_capabilities(server_capabilities) end -- Generated by gen_lsp.lua, keep at end of file. ---- LSP method names. --- +---@enum vim.lsp.protocol.Methods ---@see https://microsoft.github.io/language-server-protocol/specification/#metaModel +--- LSP method names. protocol.Methods = { --- A request to resolve the incoming calls for a given `CallHierarchyItem`. --- @since 3.16.0 @@ -1170,14 +881,14 @@ protocol.Methods = { --- symbol's location. --- @since 3.17.0 workspaceSymbol_resolve = 'workspaceSymbol/resolve', - --- A request sent from the server to the client to modify certain resources. + --- A request sent from the server to the client to modified 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 where the client signaled configuration change via an + --- This pull model replaces the old push model were 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. @@ -1210,7 +921,7 @@ protocol.Methods = { --- files were renamed from within the client. --- @since 3.16.0 workspace_didRenameFiles = 'workspace/didRenameFiles', - --- A request sent from the client to the server to execute a command. The request might return + --- A request send 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 @@ -1248,14 +959,5 @@ protocol.Methods = { --- The `workspace/workspaceFolders` is sent from the server to the client to fetch the open workspace folders. workspace_workspaceFolders = 'workspace/workspaceFolders', } -local function freeze(t) - return setmetatable({}, { - __index = t, - __newindex = function() - error('cannot modify immutable table') - end, - }) -end -protocol.Methods = freeze(protocol.Methods) return protocol diff --git a/runtime/lua/vim/lsp/rpc.lua b/runtime/lua/vim/lsp/rpc.lua index 3c63a12da2..e79dbd2db3 100644 --- a/runtime/lua/vim/lsp/rpc.lua +++ b/runtime/lua/vim/lsp/rpc.lua @@ -3,7 +3,7 @@ local log = require('vim.lsp.log') local protocol = require('vim.lsp.protocol') local validate, schedule, schedule_wrap = vim.validate, vim.schedule, vim.schedule_wrap -local is_win = uv.os_uname().version:find('Windows') +local is_win = vim.fn.has('win32') == 1 --- Checks whether a given path exists and is a directory. ---@param filename string path to check @@ -140,7 +140,7 @@ local client_errors = { SERVER_RESULT_CALLBACK_ERROR = 7, } ---- @type table<string|integer, string|integer> +--- @type table<string,integer> | table<integer,string> --- @nodoc M.client_errors = vim.deepcopy(client_errors) for k, v in pairs(client_errors) do @@ -407,7 +407,9 @@ function Client:handle_body(body) end log.debug('rpc.receive', decoded) - if type(decoded.method) == 'string' and decoded.id then + if type(decoded) ~= 'table' then + self:on_error(M.client_errors.INVALID_SERVER_MESSAGE, decoded) + elseif type(decoded.method) == 'string' and decoded.id then 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. @@ -502,7 +504,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, @@ -548,7 +550,7 @@ local function new_client(dispatchers, transport) end ---@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 request fun(method: string, params: table?, callback: fun(err: lsp.ResponseError|nil, result: any), notify_reply_callback: fun(message_id: 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() @@ -701,7 +703,9 @@ function M.connect(host_or_path, port) if port == nil then handle:connect(host_or_path, on_connect) else - handle:connect(host_or_path, port, on_connect) + local info = uv.getaddrinfo(host_or_path, nil) + local resolved_host = info and info[1] and info[1].addr or host_or_path + handle:connect(resolved_host, port, on_connect) end return public_client(client) diff --git a/runtime/lua/vim/lsp/semantic_tokens.lua b/runtime/lua/vim/lsp/semantic_tokens.lua index ef2502b12e..8182457dd0 100644 --- a/runtime/lua/vim/lsp/semantic_tokens.lua +++ b/runtime/lua/vim/lsp/semantic_tokens.lua @@ -140,12 +140,7 @@ local function tokens_to_ranges(data, bufnr, client, request) local function _get_byte_pos(col) if col > 0 then local buf_line = lines[line + 1] or '' - local ok, result - ok, result = pcall(util._str_byteindex_enc, buf_line, col, client.offset_encoding) - if ok then - return result - end - return math.min(#buf_line, col) + return util._str_byteindex_enc(buf_line, col, client.offset_encoding) end return col end @@ -197,12 +192,6 @@ function STHighlighter.new(bufnr) highlighter:send_request() end end, - on_detach = function(_, buf) - local highlighter = STHighlighter.active[buf] - if highlighter then - highlighter:destroy() - end - end, }) api.nvim_create_autocmd({ 'BufWinEnter', 'InsertLeave' }, { @@ -418,7 +407,7 @@ end function STHighlighter:on_win(topline, botline) for client_id, state in pairs(self.client_state) do local current_result = state.current_result - if current_result.version and current_result.version == util.buf_versions[self.bufnr] then + if current_result.version == util.buf_versions[self.bufnr] then if not current_result.namespace_cleared then api.nvim_buf_clear_namespace(self.bufnr, state.namespace, 0, -1) current_result.namespace_cleared = true @@ -779,7 +768,6 @@ 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 diff --git a/runtime/lua/vim/lsp/sync.lua b/runtime/lua/vim/lsp/sync.lua index 936579e003..bdfe8d51b8 100644 --- a/runtime/lua/vim/lsp/sync.lua +++ b/runtime/lua/vim/lsp/sync.lua @@ -212,7 +212,8 @@ end ---@param lastline integer ---@param new_lastline integer ---@param offset_encoding string ----@return vim.lsp.sync.Range, vim.lsp.sync.Range +---@return vim.lsp.sync.Range prev_end_range +---@return vim.lsp.sync.Range curr_end_range local function compute_end_range( prev_lines, curr_lines, @@ -222,6 +223,16 @@ local function compute_end_range( new_lastline, offset_encoding ) + -- A special case for the following `firstline == new_lastline` case where lines are deleted. + -- Even if the buffer has become empty, nvim behaves as if it has an empty line with eol. + if #curr_lines == 1 and curr_lines[1] == '' then + local prev_line = prev_lines[lastline - 1] + return { + line_idx = lastline - 1, + byte_idx = #prev_line + 1, + char_idx = compute_line_length(prev_line, offset_encoding) + 1, + }, { line_idx = 1, byte_idx = 1, char_idx = 1 } + end -- If firstline == new_lastline, the first change occurred on a line that was deleted. -- In this case, the last_byte... if firstline == new_lastline then diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 5a229a1169..882ec22ca6 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -99,9 +99,26 @@ local function get_border_size(opts) return { height = height, width = width } end -local function split_lines(value) - value = string.gsub(value, '\r\n?', '\n') - return split(value, '\n', { plain = true, trimempty = true }) +--- Splits string at newlines, optionally removing unwanted blank lines. +--- +--- @param s string Multiline string +--- @param no_blank boolean? Drop blank lines for each @param/@return (except one empty line +--- separating each). Workaround for https://github.com/LuaLS/lua-language-server/issues/2333 +local function split_lines(s, no_blank) + s = string.gsub(s, '\r\n?', '\n') + local lines = {} + local in_desc = true -- Main description block, before seeing any @foo. + for line in vim.gsplit(s, '\n', { plain = true, trimempty = true }) do + local start_annotation = not not line:find('^ ?%@.?[pr]') + in_desc = (not start_annotation) and in_desc or false + if start_annotation and no_blank and not (lines[#lines] or ''):find('^%s*$') then + table.insert(lines, '') -- Separate each @foo with a blank line. + end + if in_desc or not no_blank or not line:find('^%s*$') then + table.insert(lines, line) + end + end + return lines end local function create_window_without_focus() @@ -116,9 +133,10 @@ end --- Convenience wrapper around vim.str_utfindex ---@param line string line to be indexed ---@param index integer|nil byte index (utf-8), or `nil` for length ----@param encoding string|nil utf-8|utf-16|utf-32|nil defaults to utf-16 +---@param encoding 'utf-8'|'utf-16'|'utf-32'|nil defaults to utf-16 ---@return integer `encoding` index of `index` in `line` function M._str_utfindex_enc(line, index, encoding) + local len32, len16 = vim.str_utfindex(line) if not encoding then encoding = 'utf-16' end @@ -129,9 +147,15 @@ function M._str_utfindex_enc(line, index, encoding) return #line end elseif encoding == 'utf-16' then + if not index or index > len16 then + return len16 + end local _, col16 = vim.str_utfindex(line, index) return col16 elseif encoding == 'utf-32' then + if not index or index > len32 then + return len32 + end local col32, _ = vim.str_utfindex(line, index) return col32 else @@ -147,6 +171,12 @@ end ---@param encoding string utf-8|utf-16|utf-32| defaults to utf-16 ---@return integer byte (utf-8) index of `encoding` index `index` in `line` function M._str_byteindex_enc(line, index, encoding) + local len = #line + if index > len then + -- LSP spec: if character > line length, default to the line length. + -- https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#position + return len + end if not encoding then encoding = 'utf-16' end @@ -154,7 +184,7 @@ function M._str_byteindex_enc(line, index, encoding) if index then return index else - return #line + return len end elseif encoding == 'utf-16' then return vim.str_byteindex(line, index, true) @@ -165,19 +195,16 @@ function M._str_byteindex_enc(line, index, encoding) end end -local _str_utfindex_enc = M._str_utfindex_enc -local _str_byteindex_enc = M._str_byteindex_enc - --- Replaces text in a range with new text. --- --- CAUTION: Changes in-place! --- ---@deprecated ----@param lines (table) Original list of strings ----@param A (table) Start position; a 2-tuple of {line,col} numbers ----@param B (table) End position; a 2-tuple of {line,col} numbers ----@param new_lines (table) list of strings to replace the original ----@return table The modified {lines} object +---@param lines string[] Original list of strings +---@param A [integer, integer] Start position; a 2-tuple of {line,col} numbers +---@param B [integer, integer] End position; a 2-tuple {line,col} numbers +---@param new_lines string[] list of strings to replace the original +---@return string[] 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 @@ -238,6 +265,7 @@ end ---@param rows integer[] zero-indexed line numbers ---@return table<integer, string>|string a table mapping rows to lines local function get_lines(bufnr, rows) + --- @type integer[] rows = type(rows) == 'table' and rows or { rows } -- This is needed for bufload and bufloaded @@ -246,7 +274,7 @@ local function get_lines(bufnr, rows) end local function buf_lines() - local lines = {} + local lines = {} --- @type table<integer,string> for _, row in ipairs(rows) do lines[row] = (api.nvim_buf_get_lines(bufnr, row, row + 1, false) or { '' })[1] end @@ -268,17 +296,20 @@ local function get_lines(bufnr, rows) end local filename = api.nvim_buf_get_name(bufnr) + if vim.fn.isdirectory(filename) ~= 0 then + return {} + end -- get the data from the file local fd = uv.fs_open(filename, 'r', 438) if not fd then return '' end - local stat = uv.fs_fstat(fd) - local data = uv.fs_read(fd, stat.size, 0) + local stat = assert(uv.fs_fstat(fd)) + local data = assert(uv.fs_read(fd, stat.size, 0)) uv.fs_close(fd) - local lines = {} -- rows we need to retrieve + local lines = {} --- @type table<integer,true|string> rows we need to retrieve local need = 0 -- keep track of how many unique rows we need for _, row in pairs(rows) do if not lines[row] then @@ -307,7 +338,7 @@ local function get_lines(bufnr, rows) lines[i] = '' end end - return lines + return lines --[[@as table<integer,string>]] end --- Gets the zero-indexed line from the given buffer. @@ -322,7 +353,8 @@ local function get_line(bufnr, row) end --- Position is a https://microsoft.github.io/language-server-protocol/specifications/specification-current/#position ----@param offset_encoding string|nil utf-8|utf-16|utf-32 +---@param position lsp.Position +---@param offset_encoding? string utf-8|utf-16|utf-32 ---@return integer local function get_line_byte_from_position(bufnr, position, offset_encoding) -- LSP's line and characters are 0-indexed @@ -332,18 +364,13 @@ local function get_line_byte_from_position(bufnr, position, offset_encoding) -- character if col > 0 then local line = get_line(bufnr, position.line) or '' - local ok, result - ok, result = pcall(_str_byteindex_enc, line, col, offset_encoding) - if ok then - return result - end - return math.min(#line, col) + return M._str_byteindex_enc(line, col, offset_encoding or 'utf-16') end return col end --- Applies a list of text edits to a buffer. ----@param text_edits table list of `TextEdit` objects +---@param text_edits lsp.TextEdit[] ---@param bufnr integer Buffer id ---@param offset_encoding string utf-8|utf-16|utf-32 ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textEdit @@ -366,6 +393,7 @@ function M.apply_text_edits(text_edits, bufnr, offset_encoding) -- Fix reversed range and indexing each text_edits local index = 0 + --- @param text_edit lsp.TextEdit text_edits = vim.tbl_map(function(text_edit) index = index + 1 text_edit._index = index @@ -383,6 +411,9 @@ function M.apply_text_edits(text_edits, bufnr, offset_encoding) end, text_edits) -- Sort text_edits + ---@param a lsp.TextEdit | { _index: integer } + ---@param b lsp.TextEdit | { _index: integer } + ---@return boolean table.sort(text_edits, function(a, b) if a.range.start.line ~= b.range.start.line then return a.range.start.line > b.range.start.line @@ -390,13 +421,11 @@ function M.apply_text_edits(text_edits, bufnr, offset_encoding) if a.range.start.character ~= b.range.start.character then return a.range.start.character > b.range.start.character end - if a._index ~= b._index then - return a._index > b._index - end + return a._index > b._index end) -- save and restore local marks since they get deleted by nvim_buf_set_lines - local marks = {} + local marks = {} --- @type table<string,[integer,integer]> 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 @@ -432,14 +461,15 @@ function M.apply_text_edits(text_edits, bufnr, offset_encoding) e.end_col = last_line_len has_eol_text_edit = true else - -- If the replacement is over the end of a line (i.e. e.end_col is out of bounds and the + -- If the replacement is over the end of a line (i.e. e.end_col is equal to the line length and the -- replacement text ends with a newline We can likely assume that the replacement is assumed -- to be meant to replace the newline with another newline and we need to make sure this -- doesn't add an extra empty line. E.g. when the last line to be replaced contains a '\r' -- in the file some servers (clangd on windows) will include that character in the line -- while nvim_buf_set_text doesn't count it as part of the line. if - e.end_col > last_line_len + e.end_col >= last_line_len + and text_edit.range['end'].character > e.end_col and #text_edit.newText > 0 and string.sub(text_edit.newText, -1) == '\n' then @@ -481,8 +511,8 @@ end --- Applies a `TextDocumentEdit`, which is a list of changes to a single --- document. --- ----@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 text_document_edit lsp.TextDocumentEdit +---@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) @@ -509,7 +539,6 @@ function M.apply_text_document_edit(text_document_edit, index, offset_encoding) and ( text_document.version and text_document.version > 0 - and M.buf_versions[bufnr] and M.buf_versions[bufnr] > text_document.version ) then @@ -534,6 +563,7 @@ local function path_under_prefix(path, prefix) end --- Get list of buffers whose filename matches the given path prefix (normalized full path) +---@param prefix string ---@return integer[] local function get_bufs_with_prefix(prefix) prefix = path_components(prefix) @@ -616,7 +646,7 @@ function M.rename(old_fname, new_fname, opts) buf_rename[b] = { from = old_bname, to = new_bname } end - local newdir = assert(vim.fs.dirname(new_fname)) + local newdir = vim.fs.dirname(new_fname) vim.fn.mkdir(newdir, 'p') local ok, err = os.rename(old_fname_full, new_fname) @@ -625,7 +655,7 @@ function M.rename(old_fname, new_fname, opts) 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') + vim.fn.mkdir(vim.fs.dirname(new_undofile), 'p') os.rename(old_undofile, new_undofile) end @@ -633,7 +663,7 @@ function M.rename(old_fname, new_fname, opts) -- 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._with({ buf = 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 @@ -642,6 +672,7 @@ function M.rename(old_fname, new_fname, opts) end end +--- @param change lsp.CreateFile local function create_file(change) local opts = change.options or {} -- from spec: Overwrite wins over `ignoreIfExists` @@ -656,29 +687,21 @@ local function create_file(change) vim.fn.bufadd(fname) end +--- @param change lsp.DeleteFile local function delete_file(change) local opts = change.options or {} local fname = vim.uri_to_fname(change.uri) - local stat = uv.fs_stat(fname) - if opts.ignoreIfNotExists and not stat then - return - end - assert(stat, 'Cannot delete not existing file or folder ' .. fname) - local flags - if stat and stat.type == 'directory' then - flags = opts.recursive and 'rf' or 'd' - else - flags = '' - end local bufnr = vim.fn.bufadd(fname) - local result = tonumber(vim.fn.delete(fname, flags)) - assert(result == 0, 'Could not delete file: ' .. fname .. ', stat: ' .. vim.inspect(stat)) + vim.fs.rm(fname, { + force = opts.ignoreIfNotExists, + recursive = opts.recursive, + }) api.nvim_buf_delete(bufnr, { force = true }) end --- Applies a `WorkspaceEdit`. --- ----@param workspace_edit table `WorkspaceEdit` +---@param workspace_edit lsp.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 function M.apply_workspace_edit(workspace_edit, offset_encoding) @@ -724,21 +747,21 @@ 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 (lsp.MarkedString | lsp.MarkedString[] | lsp.MarkupContent) ----@param contents (table|nil) List of strings to extend with converted lines. Defaults to {}. +---@param input lsp.MarkedString|lsp.MarkedString[]|lsp.MarkupContent +---@param contents string[]|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 function M.convert_input_to_markdown_lines(input, contents) contents = contents or {} -- MarkedString variation 1 if type(input) == 'string' then - list_extend(contents, split_lines(input)) + list_extend(contents, split_lines(input, true)) else assert(type(input) == 'table', 'Expected a table for LSP input') -- MarkupContent if input.kind then local value = input.value or '' - list_extend(contents, split_lines(value)) + list_extend(contents, split_lines(value, true)) -- MarkupString variation 2 elseif input.language then table.insert(contents, '```' .. input.language) @@ -760,11 +783,11 @@ end --- Converts `textDocument/signatureHelp` response to markdown lines. --- ----@param signature_help table Response of `textDocument/SignatureHelp` +---@param signature_help lsp.SignatureHelp Response of `textDocument/SignatureHelp` ---@param ft string|nil filetype that will be use as the `lang` for the label markdown code block ---@param triggers table|nil list of trigger characters from the lsp server. used to better determine parameter offsets ----@return table|nil table list of lines of converted markdown. ----@return table|nil table of active hl +---@return string[]|nil table list of lines of converted markdown. +---@return number[]|nil table of active hl ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_signatureHelp function M.convert_signature_help_to_markdown_lines(signature_help, ft, triggers) if not signature_help.signatures then @@ -961,7 +984,7 @@ end --- Shows document and optionally jumps to the location. --- ----@param location table (`Location`|`LocationLink`) +---@param location lsp.Location|lsp.LocationLink ---@param offset_encoding string|nil utf-8|utf-16|utf-32 ---@param opts table|nil options --- - reuse_win (boolean) Jump to existing window if buffer is already open. @@ -1007,7 +1030,7 @@ function M.show_document(location, offset_encoding, opts) 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 }) - api.nvim_win_call(win, function() + vim._with({ win = win }, function() -- Open folds under the cursor vim.cmd('normal! zv') end) @@ -1018,7 +1041,7 @@ end --- Jumps to a location. --- ----@param location table (`Location`|`LocationLink`) +---@param location lsp.Location|lsp.LocationLink ---@param offset_encoding string|nil utf-8|utf-16|utf-32 ---@param reuse_win boolean|nil Jump to existing window if buffer is already open. ---@return boolean `true` if the jump succeeded @@ -1039,7 +1062,7 @@ end --- - for Location, range is shown (e.g., function definition) --- - for LocationLink, targetRange is shown (e.g., body of function definition) --- ----@param location table a single `Location` or `LocationLink` +---@param location lsp.Location|lsp.LocationLink ---@param opts table ---@return integer|nil buffer id of float window ---@return integer|nil window id of float window @@ -1155,7 +1178,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 contents string[] of lines to show in window ---@param opts table with optional fields --- - height of floating window --- - width of floating window @@ -1327,7 +1350,7 @@ function M.stylize_markdown(bufnr, contents, opts) end -- needs to run in the buffer for the regions to work - api.nvim_buf_call(bufnr, function() + vim._with({ buf = bufnr }, function() -- we need to apply lsp_markdown regions speperately, since otherwise -- markdown regions can "bleed" through the other syntax regions -- and mess up the formatting @@ -1438,7 +1461,7 @@ end --- Computes size of float needed to show contents (with optional wrapping) --- ---@param contents table of lines to show in window ----@param opts table with optional fields +---@param opts? table with optional fields --- - height of floating window --- - width of floating window --- - wrap_at character to wrap at for computing height @@ -1630,10 +1653,9 @@ function M.open_floating_preview(contents, syntax, opts) if do_stylize then vim.wo[floating_winnr].conceallevel = 2 end - -- disable folding - vim.wo[floating_winnr].foldenable = false - -- soft wrapping - vim.wo[floating_winnr].wrap = opts.wrap + vim.wo[floating_winnr].foldenable = false -- Disable folding. + vim.wo[floating_winnr].wrap = opts.wrap -- Soft wrapping. + vim.wo[floating_winnr].breakindent = true -- Slightly better list presentation. vim.bo[floating_bufnr].modifiable = false vim.bo[floating_bufnr].bufhidden = 'wipe' @@ -1670,7 +1692,7 @@ do --[[ References ]] --- Shows a list of document highlights for a certain buffer. --- ---@param bufnr integer Buffer id - ---@param references table List of `DocumentHighlight` objects to highlight + ---@param references lsp.DocumentHighlight[] objects to highlight ---@param offset_encoding string One of "utf-8", "utf-16", "utf-32". ---@see https://microsoft.github.io/language-server-protocol/specification/#textDocumentContentChangeEvent function M.buf_highlight_references(bufnr, references, offset_encoding) @@ -1721,7 +1743,9 @@ end) ---@inlinedoc ---@field filename string ---@field lnum integer 1-indexed line number +---@field end_lnum integer 1-indexed end line number ---@field col integer 1-indexed column +---@field end_col integer 1-indexed end column ---@field text string ---@field user_data lsp.Location|lsp.LocationLink @@ -1748,7 +1772,7 @@ function M.locations_to_items(locations, offset_encoding) end local items = {} - ---@type table<string, {start: lsp.Position, location: lsp.Location|lsp.LocationLink}[]> + ---@type table<string, {start: lsp.Position, end: lsp.Position, location: lsp.Location|lsp.LocationLink}[]> local grouped = setmetatable({}, { __index = function(t, k) local v = {} @@ -1760,7 +1784,7 @@ function M.locations_to_items(locations, offset_encoding) -- locations may be Location or LocationLink local uri = d.uri or d.targetUri local range = d.range or d.targetSelectionRange - table.insert(grouped[uri], { start = range.start, location = d }) + table.insert(grouped[uri], { start = range.start, ['end'] = range['end'], location = d }) end ---@type string[] @@ -1775,6 +1799,9 @@ function M.locations_to_items(locations, offset_encoding) local line_numbers = {} for _, temp in ipairs(rows) do table.insert(line_numbers, temp.start.line) + if temp.start.line ~= temp['end'].line then + table.insert(line_numbers, temp['end'].line) + end end -- get all the lines for this uri @@ -1782,13 +1809,20 @@ function M.locations_to_items(locations, offset_encoding) for _, temp in ipairs(rows) do local pos = temp.start + local end_pos = temp['end'] local row = pos.line + local end_row = end_pos.line local line = lines[row] or '' + local end_line = lines[end_row] or '' local col = M._str_byteindex_enc(line, pos.character, offset_encoding) + local end_col = M._str_byteindex_enc(end_line, end_pos.character, offset_encoding) + table.insert(items, { filename = filename, lnum = row + 1, + end_lnum = end_row + 1, col = col + 1, + end_col = end_col + 1, text = line, user_data = temp.location, }) @@ -1807,7 +1841,7 @@ end --- Converts symbols to quickfix list items. --- ---@param symbols table DocumentSymbol[] or SymbolInformation[] ----@param bufnr integer +---@param bufnr? integer function M.symbols_to_items(symbols, bufnr) local function _symbols_to_items(_symbols, _items, _bufnr) for _, symbol in ipairs(_symbols) do @@ -1874,7 +1908,7 @@ end --- CAUTION: Modifies the input in-place! --- ---@deprecated ----@param lines table list of lines +---@param lines string[] 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') @@ -1899,7 +1933,7 @@ function M.try_trim_markdown_code_blocks(lines) end ---@param window integer|nil: window handle or 0 for current, defaults to current ----@param offset_encoding string utf-8|utf-16|utf-32|nil defaults to `offset_encoding` of first client of buffer of `window` +---@param offset_encoding? string utf-8|utf-16|utf-32|nil defaults to `offset_encoding` of first client of buffer of `window` local function make_position_param(window, offset_encoding) window = window or 0 local buf = api.nvim_win_get_buf(window) @@ -1911,7 +1945,7 @@ local function make_position_param(window, offset_encoding) return { line = 0, character = 0 } end - col = _str_utfindex_enc(line, col, offset_encoding) + col = M._str_utfindex_enc(line, col, offset_encoding) return { line = row, character = col } end @@ -1920,7 +1954,7 @@ end --- ---@param window integer|nil: window handle or 0 for current, defaults to current ---@param offset_encoding string|nil utf-8|utf-16|utf-32|nil defaults to `offset_encoding` of first client of buffer of `window` ----@return table `TextDocumentPositionParams` object +---@return lsp.TextDocumentPositionParams ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentPositionParams function M.make_position_params(window, offset_encoding) window = window or 0 @@ -1933,7 +1967,7 @@ function M.make_position_params(window, offset_encoding) end --- Utility function for getting the encoding of the first LSP client on the given buffer. ----@param bufnr (integer) buffer handle or 0 for current, defaults to current +---@param bufnr integer buffer handle or 0 for current, defaults to current ---@return string encoding first client if there is one, nil otherwise function M._get_offset_encoding(bufnr) validate({ @@ -2034,15 +2068,16 @@ end --- Creates a `TextDocumentIdentifier` object for the current buffer. --- ---@param bufnr integer|nil: Buffer handle, defaults to current ----@return table `TextDocumentIdentifier` +---@return lsp.TextDocumentIdentifier ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocumentIdentifier function M.make_text_document_params(bufnr) return { uri = vim.uri_from_bufnr(bufnr or 0) } end --- Create the workspace params ----@param added table ----@param removed table +---@param added lsp.WorkspaceFolder[] +---@param removed lsp.WorkspaceFolder[] +---@return lsp.WorkspaceFoldersChangeEvent function M.make_workspace_params(added, removed) return { event = { added = added, removed = removed } } end @@ -2050,8 +2085,8 @@ end --- Returns indentation size. --- ---@see 'shiftwidth' ----@param bufnr (integer|nil): Buffer handle, defaults to current ----@return (integer) indentation size +---@param bufnr integer|nil: Buffer handle, defaults to current +---@return integer indentation size function M.get_effective_tabstop(bufnr) validate({ bufnr = { bufnr, 'n', true } }) local bo = bufnr and vim.bo[bufnr] or vim.bo @@ -2061,7 +2096,7 @@ end --- Creates a `DocumentFormattingParams` object for the current buffer and cursor position. --- ----@param options table|nil with valid `FormattingOptions` entries +---@param options lsp.FormattingOptions|nil with valid `FormattingOptions` entries ---@return lsp.DocumentFormattingParams object ---@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_formatting function M.make_formatting_params(options) @@ -2092,11 +2127,7 @@ function M.character_offset(buf, row, col, offset_encoding) ) offset_encoding = vim.lsp.get_clients({ bufnr = buf })[1].offset_encoding end - -- If the col is past the EOL, use the line length. - if col > #line then - return _str_utfindex_enc(line, nil, offset_encoding) - end - return _str_utfindex_enc(line, col, offset_encoding) + return M._str_utfindex_enc(line, col, offset_encoding) end --- Helper function to return nested values in language server settings @@ -2203,6 +2234,11 @@ end M._get_line_byte_from_position = get_line_byte_from_position ---@nodoc -M.buf_versions = {} ---@type table<integer,integer> +---@type table<integer,integer> +M.buf_versions = setmetatable({}, { + __index = function(t, bufnr) + return rawget(t, bufnr) or 0 + end, +}) return M diff --git a/runtime/lua/vim/provider/health.lua b/runtime/lua/vim/provider/health.lua index 63e0da448a..47c2080e3c 100644 --- a/runtime/lua/vim/provider/health.lua +++ b/runtime/lua/vim/provider/health.lua @@ -1,8 +1,114 @@ local health = vim.health -local iswin = vim.uv.os_uname().sysname == 'Windows_NT' +local iswin = vim.fn.has('win32') == 1 local M = {} +local function cmd_ok(cmd) + local out = vim.fn.system(cmd) + return vim.v.shell_error == 0, out +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 + +-- 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 + +--- @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) +local function 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.uv.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.uv.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 vim.trim(vim.fn.system(cmd)), shell_error_code +end + +---@param provider string +local function provider_disabled(provider) + local loaded_var = 'loaded_' .. provider .. '_provider' + local v = vim.g[loaded_var] + if v == 0 then + health.info('Disabled (' .. loaded_var .. '=' .. v .. ').') + return true + end + return false +end + local function clipboard() health.start('Clipboard (optional)') @@ -10,7 +116,7 @@ local function clipboard() os.getenv('TMUX') and vim.fn.executable('tmux') == 1 and vim.fn.executable('pbpaste') == 1 - and not health._cmd_ok('pbpaste') + and not cmd_ok('pbpaste') then local tmux_version = string.match(vim.fn.system('tmux -V'), '%d+%.%d+') local advice = { @@ -20,9 +126,9 @@ local function clipboard() health.error('pbcopy does not work with tmux version: ' .. tmux_version, advice) end - local clipboard_tool = vim.fn['provider#clipboard#Executable']() + local clipboard_tool = vim.fn['provider#clipboard#Executable']() ---@type string if vim.g.clipboard ~= nil and clipboard_tool == '' then - local error_message = vim.fn['provider#clipboard#Error']() + local error_message = vim.fn['provider#clipboard#Error']() ---@type string health.error( error_message, "Use the example in :help g:clipboard as a template, or don't set g:clipboard at all." @@ -40,7 +146,7 @@ end local function node() health.start('Node.js provider (optional)') - if health._provider_disabled('node') then + if provider_disabled('node') then return end @@ -60,7 +166,7 @@ local function node() end -- local node_v = vim.fn.split(system({'node', '-v'}), "\n")[1] or '' - local ok, node_v = health._cmd_ok({ 'node', '-v' }) + local ok, node_v = cmd_ok({ 'node', '-v' }) health.info('Node.js: ' .. node_v) if not ok or vim.version.lt(node_v, '6.0.0') then health.warn('Nvim node.js host does not support Node ' .. node_v) @@ -73,7 +179,7 @@ local function node() ) end - local node_detect_table = vim.fn['provider#node#Detect']() + local node_detect_table = vim.fn['provider#node#Detect']() ---@type string[] local host = node_detect_table[1] if host:find('^%s*$') then health.warn('Missing "neovim" npm (or yarn, pnpm) package.', { @@ -97,7 +203,7 @@ local function node() iswin and 'cmd /c ' .. manager .. ' info neovim --json' or manager .. ' info neovim --json' ) local latest_npm - ok, latest_npm = health._cmd_ok(vim.split(latest_npm_cmd, ' ')) + ok, latest_npm = cmd_ok(vim.split(latest_npm_cmd, ' ')) if not ok or latest_npm:find('^%s$') then health.error( 'Failed to run: ' .. latest_npm_cmd, @@ -115,7 +221,7 @@ local function node() local current_npm_cmd = { 'node', host, '--version' } local current_npm - ok, current_npm = health._cmd_ok(current_npm_cmd) + ok, current_npm = cmd_ok(current_npm_cmd) if not ok then health.error( 'Failed to run: ' .. table.concat(current_npm_cmd, ' '), @@ -143,7 +249,7 @@ end local function perl() health.start('Perl provider (optional)') - if health._provider_disabled('perl') then + if provider_disabled('perl') then return end @@ -162,7 +268,7 @@ local function perl() -- we cannot use cpanm that is on the path, as it may not be for the perl -- set with g:perl_host_prog - local ok = health._cmd_ok({ perl_exec, '-W', '-MApp::cpanminus', '-e', '' }) + local ok = cmd_ok({ perl_exec, '-W', '-MApp::cpanminus', '-e', '' }) if not ok then return { perl_exec, '"App::cpanminus" module is not installed' } end @@ -174,7 +280,7 @@ local function perl() 'my $app = App::cpanminus::script->new; $app->parse_options ("--info", "-q", "Neovim::Ext"); exit $app->doit', } local latest_cpan - ok, latest_cpan = health._cmd_ok(latest_cpan_cmd) + ok, latest_cpan = cmd_ok(latest_cpan_cmd) if not ok or latest_cpan:find('^%s*$') then health.error( 'Failed to run: ' .. table.concat(latest_cpan_cmd, ' '), @@ -184,7 +290,7 @@ local function perl() elseif latest_cpan[1] == '!' then local cpanm_errs = vim.split(latest_cpan, '!') if cpanm_errs[1]:find("Can't write to ") then - local advice = {} + local advice = {} ---@type string[] for i = 2, #cpanm_errs do advice[#advice + 1] = cpanm_errs[i] end @@ -197,7 +303,7 @@ local function perl() return end end - latest_cpan = vim.fn.matchstr(latest_cpan, [[\(\.\?\d\)\+]]) + latest_cpan = tostring(vim.fn.matchstr(latest_cpan, [[\(\.\?\d\)\+]])) if latest_cpan:find('^%s*$') then health.error('Cannot parse version number from cpanm output: ' .. latest_cpan) return @@ -205,7 +311,7 @@ local function perl() local current_cpan_cmd = { perl_exec, '-W', '-MNeovim::Ext', '-e', 'print $Neovim::Ext::VERSION' } local current_cpan - ok, current_cpan = health._cmd_ok(current_cpan_cmd) + ok, current_cpan = cmd_ok(current_cpan_cmd) if not ok then health.error( 'Failed to run: ' .. table.concat(current_cpan_cmd, ' '), @@ -243,9 +349,11 @@ local function python_exepath(invocation) return vim.fs.normalize(vim.trim(p.stdout)) end --- Check if pyenv is available and a valid pyenv root can be found, then return --- their respective paths. If either of those is invalid, return two empty --- strings, effectively ignoring pyenv. +--- Check if pyenv is available and a valid pyenv root can be found, then return +--- their respective paths. If either of those is invalid, return two empty +--- strings, effectively ignoring pyenv. +--- +--- @return [string, string] local function check_for_pyenv() local pyenv_path = vim.fn.resolve(vim.fn.exepath('pyenv')) @@ -258,7 +366,17 @@ local function check_for_pyenv() local pyenv_root = vim.fn.resolve(os.getenv('PYENV_ROOT') or '') if pyenv_root == '' then - pyenv_root = vim.fn.system({ pyenv_path, 'root' }) + local p = vim.system({ pyenv_path, 'root' }):wait() + if p.code ~= 0 then + local message = string.format( + 'pyenv: Failed to infer the root of pyenv by running `%s root` : %s. Ignoring pyenv for all following checks.', + pyenv_path, + p.stderr + ) + health.warn(message) + return { '', '' } + end + pyenv_root = vim.trim(p.stdout) health.info('pyenv: $PYENV_ROOT is not set. Infer from `pyenv root`.') end @@ -288,24 +406,29 @@ local function check_bin(bin) return true end --- Fetch the contents of a URL. +--- Fetch the contents of a URL. +--- +--- @param url string local function download(url) local has_curl = vim.fn.executable('curl') == 1 if has_curl and vim.fn.system({ 'curl', '-V' }):find('Protocols:.*https') then - local out, rc = health._system({ 'curl', '-sL', url }, { stderr = true, ignore_error = true }) + local out, rc = system({ 'curl', '-sL', url }, { stderr = true, ignore_error = true }) if rc ~= 0 then return 'curl error with ' .. url .. ': ' .. rc else return out end elseif vim.fn.executable('python') == 1 then - local script = "try:\n\ - from urllib.request import urlopen\n\ - except ImportError:\n\ - from urllib2 import urlopen\n\ - response = urlopen('" .. url .. "')\n\ - print(response.read().decode('utf8'))\n" - local out, rc = health._system({ 'python', '-c', script }) + local script = ([[ +try: + from urllib.request import urlopen +except ImportError: + from urllib2 import urlopen + +response = urlopen('%s') +print(response.read().decode('utf8')) +]]):format(url) + local out, rc = system({ 'python', '-c', script }) if out == '' and rc ~= 0 then return 'python urllib.request error: ' .. rc else @@ -323,25 +446,24 @@ local function download(url) return message end --- Get the latest Nvim Python client (pynvim) version from PyPI. +--- Get the latest Nvim Python client (pynvim) version from PyPI. local function latest_pypi_version() local pypi_version = 'unable to get pypi response' local pypi_response = download('https://pypi.python.org/pypi/pynvim/json') if pypi_response ~= '' then local pcall_ok, output = pcall(vim.fn.json_decode, pypi_response) - local pypi_data - if pcall_ok then - pypi_data = output - else + if not pcall_ok then return 'error: ' .. pypi_response end + local pypi_data = output local pypi_element = pypi_data['info'] or {} pypi_version = pypi_element['version'] or 'unable to parse' end return pypi_version end +--- @param s string local function is_bad_response(s) local lower = s:lower() return vim.startswith(lower, 'unable') @@ -349,20 +471,22 @@ local function is_bad_response(s) or vim.startswith(lower, 'outdated') end --- Get version information using the specified interpreter. The interpreter is --- used directly in case breaking changes were introduced since the last time --- Nvim's Python client was updated. --- --- Returns: { --- {python executable version}, --- {current nvim version}, --- {current pypi nvim status}, --- {installed version status} --- } +--- Get version information using the specified interpreter. The interpreter is +--- used directly in case breaking changes were introduced since the last time +--- Nvim's Python client was updated. +--- +--- @param python string +--- +--- Returns: { +--- {python executable version}, +--- {current nvim version}, +--- {current pypi nvim status}, +--- {installed version status} +--- } local function version_info(python) local pypi_version = latest_pypi_version() - local python_version, rc = health._system({ + local python_version, rc = system({ python, '-c', 'import sys; print(".".join(str(x) for x in sys.version_info[:3]))', @@ -373,7 +497,7 @@ local function version_info(python) end local nvim_path - nvim_path, rc = health._system({ + nvim_path, rc = system({ python, '-c', 'import sys; sys.path = [p for p in sys.path if p != ""]; import neovim; print(neovim.__file__)', @@ -398,7 +522,7 @@ local function version_info(python) -- Try to get neovim.VERSION (added in 0.1.11dev). local nvim_version - nvim_version, rc = health._system({ + nvim_version, rc = system({ python, '-c', 'from neovim import VERSION as v; print("{}.{}.{}{}".format(v.major, v.minor, v.patch, v.prerelease))', @@ -406,9 +530,9 @@ local function version_info(python) if rc ~= 0 or nvim_version == '' then nvim_version = 'unable to find pynvim module version' local base = vim.fs.basename(nvim_path) - local metas = vim.fn.glob(base .. '-*/METADATA', 1, 1) - vim.list_extend(metas, vim.fn.glob(base .. '-*/PKG-INFO', 1, 1)) - vim.list_extend(metas, vim.fn.glob(base .. '.egg-info/PKG-INFO', 1, 1)) + local metas = vim.fn.glob(base .. '-*/METADATA', true, 1) + vim.list_extend(metas, vim.fn.glob(base .. '-*/PKG-INFO', true, 1)) + vim.list_extend(metas, vim.fn.glob(base .. '.egg-info/PKG-INFO', true, 1)) metas = table.sort(metas, compare) if metas and next(metas) ~= nil then @@ -438,14 +562,13 @@ end local function python() health.start('Python 3 provider (optional)') - local pyname = 'python3' ---@type string? local python_exe = '' local virtual_env = os.getenv('VIRTUAL_ENV') local venv = virtual_env and vim.fn.resolve(virtual_env) or '' - local host_prog_var = pyname .. '_host_prog' - local python_multiple = {} + local host_prog_var = 'python3_host_prog' + local python_multiple = {} ---@type string[] - if health._provider_disabled(pyname) then + if provider_disabled('python3') then return end @@ -458,8 +581,7 @@ local function python() health.info(message) end - local pythonx_warnings - pyname, pythonx_warnings = vim.provider.python.detect_by_module('neovim') + local pyname, pythonx_warnings = vim.provider.python.detect_by_module('neovim') if not pyname then health.warn( @@ -487,7 +609,7 @@ local function python() end if pyenv ~= '' then - python_exe = health._system({ pyenv, 'which', pyname }, { stderr = true }) + python_exe = system({ pyenv, 'which', pyname }, { stderr = true }) if python_exe == '' then health.warn('pyenv could not find ' .. pyname .. '.') end @@ -547,12 +669,7 @@ local function python() ) health.warn('pyenv is not set up optimally.', advice) elseif venv ~= '' then - local venv_root - if pyenv_root ~= '' then - venv_root = pyenv_root - else - venv_root = vim.fs.dirname(venv) - end + local venv_root = pyenv_root ~= '' and pyenv_root or vim.fs.dirname(venv) if vim.startswith(vim.fn.resolve(python_exe), venv_root .. '/') then local advice = string.format( @@ -637,9 +754,9 @@ local function python() health.ok('no $VIRTUAL_ENV') return end - local errors = {} + local errors = {} ---@type string[] -- Keep hints as dict keys in order to discard duplicates. - local hints = {} + local hints = {} ---@type table<string, boolean> -- The virtualenv should contain some Python executables, and those -- executables should be first both on Nvim's $PATH and the $PATH of -- subshells launched from Nvim. @@ -647,7 +764,7 @@ local function python() local venv_bins = vim.fn.glob(string.format('%s/%s/python*', virtual_env, bin_dir), true, true) venv_bins = vim.tbl_filter(function(v) -- XXX: Remove irrelevant executables found in bin/. - return not v:match('python%-config') + return not v:match('python.*%-config') end, venv_bins) if vim.tbl_count(venv_bins) > 0 then for _, venv_bin in pairs(venv_bins) do @@ -710,9 +827,7 @@ local function python() health.info(msg) health.info( 'Python version: ' - .. health._system( - 'python -c "import platform, sys; sys.stdout.write(platform.python_version())"' - ) + .. system('python -c "import platform, sys; sys.stdout.write(platform.python_version())"') ) health.ok('$VIRTUAL_ENV provides :!python.') end @@ -721,7 +836,7 @@ end local function ruby() health.start('Ruby provider (optional)') - if health._provider_disabled('ruby') then + if provider_disabled('ruby') then return end @@ -732,7 +847,7 @@ local function ruby() ) return end - health.info('Ruby: ' .. health._system({ 'ruby', '-v' })) + health.info('Ruby: ' .. system({ 'ruby', '-v' })) local host, _ = vim.provider.ruby.detect() if (not host) or host:find('^%s*$') then @@ -748,7 +863,7 @@ local function ruby() health.info('Host: ' .. host) local latest_gem_cmd = (iswin and 'cmd /c gem list -ra "^^neovim$"' or 'gem list -ra ^neovim$') - local ok, latest_gem = health._cmd_ok(vim.split(latest_gem_cmd, ' ')) + local ok, latest_gem = cmd_ok(vim.split(latest_gem_cmd, ' ')) if not ok or latest_gem:find('^%s*$') then health.error( 'Failed to run: ' .. latest_gem_cmd, @@ -761,7 +876,7 @@ local function ruby() local current_gem_cmd = { host, '--version' } local current_gem - ok, current_gem = health._cmd_ok(current_gem_cmd) + ok, current_gem = cmd_ok(current_gem_cmd) if not ok then health.error( 'Failed to run: ' .. table.concat(current_gem_cmd, ' '), diff --git a/runtime/lua/vim/secure.lua b/runtime/lua/vim/secure.lua index 41a3d3ba25..266725cce2 100644 --- a/runtime/lua/vim/secure.lua +++ b/runtime/lua/vim/secure.lua @@ -41,6 +41,7 @@ end --- trusted. The user's choice is persisted in a trust database at --- $XDG_STATE_HOME/nvim/trust. --- +---@since 11 ---@see |:trust| --- ---@param path (string) Path to a file to read. @@ -126,6 +127,7 @@ end --- --- The trust database is located at |$XDG_STATE_HOME|/nvim/trust. --- +---@since 11 ---@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 diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua index e9e4326057..4d06cdd77d 100644 --- a/runtime/lua/vim/shared.lua +++ b/runtime/lua/vim/shared.lua @@ -214,7 +214,7 @@ end ---@param t table<T, any> (table) Table ---@return T[] : List of keys function vim.tbl_keys(t) - vim.validate({ t = { t, 't' } }) + vim.validate('t', t, 'table') --- @cast t table<any,any> local keys = {} @@ -231,7 +231,7 @@ end ---@param t table<any, T> (table) Table ---@return T[] : List of values function vim.tbl_values(t) - vim.validate({ t = { t, 't' } }) + vim.validate('t', t, 'table') local values = {} for _, v in @@ -332,7 +332,7 @@ end ---@param value any Value to compare ---@return boolean `true` if `t` contains `value` function vim.list_contains(t, value) - vim.validate({ t = { t, 't' } }) + vim.validate('t', t, 'table') --- @cast t table<any,any> for _, v in ipairs(t) do @@ -350,41 +350,32 @@ end ---@param t table Table to check ---@return boolean `true` if `t` is empty function vim.tbl_isempty(t) - vim.validate({ t = { t, 't' } }) + vim.validate('t', t, 'table') return next(t) == nil end ---- We only merge empty tables or tables that are not an array (indexed by integers) +--- We only merge empty tables or tables that are not list-like (indexed by consecutive integers +--- starting from 1) local function can_merge(v) - return type(v) == 'table' and (vim.tbl_isempty(v) or not vim.isarray(v)) + return type(v) == 'table' and (vim.tbl_isempty(v) or not vim.islist(v)) end -local function tbl_extend(behavior, deep_extend, ...) - if behavior ~= 'error' and behavior ~= 'keep' and behavior ~= 'force' then - error('invalid "behavior": ' .. tostring(behavior)) - end - - if select('#', ...) < 2 then - error( - 'wrong number of arguments (given ' - .. tostring(1 + select('#', ...)) - .. ', expected at least 3)' - ) - end - +--- Recursive worker for tbl_extend +--- @param behavior 'error'|'keep'|'force' +--- @param deep_extend boolean +--- @param ... table<any,any> +local function tbl_extend_rec(behavior, deep_extend, ...) 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 for i = 1, select('#', ...) do - local tbl = select(i, ...) - vim.validate({ ['after the second argument'] = { tbl, 't' } }) - --- @cast tbl table<any,any> + local tbl = select(i, ...) --[[@as 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 - ret[k] = tbl_extend(behavior, true, ret[k], v) + ret[k] = tbl_extend_rec(behavior, true, ret[k], v) elseif behavior ~= 'force' and ret[k] ~= nil then if behavior == 'error' then error('key found in more than one map: ' .. k) @@ -395,9 +386,31 @@ local function tbl_extend(behavior, deep_extend, ...) end end end + return ret end +--- @param behavior 'error'|'keep'|'force' +--- @param deep_extend boolean +--- @param ... table<any,any> +local function tbl_extend(behavior, deep_extend, ...) + if behavior ~= 'error' and behavior ~= 'keep' and behavior ~= 'force' then + error('invalid "behavior": ' .. tostring(behavior)) + end + + local nargs = select('#', ...) + + if nargs < 2 then + error(('wrong number of arguments (given %d, expected at least 3)'):format(1 + nargs)) + end + + for i = 1, nargs do + vim.validate('after the second argument', select(i, ...), 'table') + end + + return tbl_extend_rec(behavior, deep_extend, ...) +end + --- Merges two or more tables. --- ---@see |extend()| @@ -414,6 +427,11 @@ end --- Merges recursively two or more tables. --- +--- Only values that are empty tables or tables that are not |lua-list|s (indexed by consecutive +--- integers starting from 1) are merged recursively. This is useful for merging nested tables +--- like default and user configurations where lists should be treated as literals (i.e., are +--- overwritten instead of merged). +--- ---@see |vim.tbl_extend()| --- ---@generic T1: table @@ -580,7 +598,7 @@ end ---@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', ('expected table, got %s'):format(type(t))) + vim.validate('t', t, 'table') --- @cast t table<any,any> -- collect the keys @@ -691,7 +709,7 @@ end ---@param t table Table ---@return integer : Number of non-nil values in table function vim.tbl_count(t) - vim.validate({ t = { t, 't' } }) + vim.validate('t', t, 'table') --- @cast t table<any,any> local count = 0 @@ -723,7 +741,7 @@ end ---@param s string String to trim ---@return string String with whitespace removed from its beginning and end function vim.trim(s) - vim.validate({ s = { s, 's' } }) + vim.validate('s', s, 'string') return s:match('^%s*(.*%S)') or '' end @@ -733,7 +751,7 @@ end ---@param s string String to escape ---@return string %-escaped pattern string function vim.pesc(s) - vim.validate({ s = { s, 's' } }) + vim.validate('s', s, 'string') return (s:gsub('[%(%)%.%%%+%-%*%?%[%]%^%$]', '%%%1')) end @@ -743,7 +761,8 @@ end ---@param prefix string Prefix to match ---@return boolean `true` if `prefix` is a prefix of `s` function vim.startswith(s, prefix) - vim.validate({ s = { s, 's' }, prefix = { prefix, 's' } }) + vim.validate('s', s, 'string') + vim.validate('prefix', prefix, 'string') return s:sub(1, #prefix) == prefix end @@ -753,7 +772,8 @@ end ---@param suffix string Suffix to match ---@return boolean `true` if `suffix` is a suffix of `s` function vim.endswith(s, suffix) - vim.validate({ s = { s, 's' }, suffix = { suffix, 's' } }) + vim.validate('s', s, 'string') + vim.validate('suffix', suffix, 'string') return #suffix == 0 or s:sub(-#suffix) == suffix end @@ -787,7 +807,7 @@ do } --- @nodoc - --- @class vim.validate.Spec {[1]: any, [2]: string|string[], [3]: boolean } + --- @class vim.validate.Spec [any, string|string[], boolean] --- @field [1] any Argument value --- @field [2] string|string[]|fun(v:any):boolean, string? Type name, or callable --- @field [3]? boolean @@ -877,8 +897,30 @@ do return true end - --- Validates a parameter specification (types and values). Specs are evaluated in alphanumeric - --- order, until the first failure. + --- Validate function arguments. + --- + --- This function has two valid forms: + --- + --- 1. vim.validate(name: str, value: any, type: string, optional?: bool) + --- 2. vim.validate(spec: table) + --- + --- Form 1 validates that argument {name} with value {value} has the type + --- {type}. {type} must be a value returned by |lua-type()|. If {optional} is + --- true, then {value} may be null. This form is significantly faster and + --- should be preferred for simple cases. + --- + --- Example: + --- + --- ```lua + --- function vim.startswith(s, prefix) + --- vim.validate('s', s, 'string') + --- vim.validate('prefix', prefix, 'string') + --- ... + --- end + --- ``` + --- + --- Form 2 validates a parameter specification (types and values). Specs are + --- evaluated in alphanumeric order, until the first failure. --- --- Usage example: --- @@ -930,8 +972,32 @@ do --- only if the argument is valid. Can optionally return an additional --- informative error message as the second returned value. --- - msg: (optional) error string if validation fails - function vim.validate(opt) - local ok, err_msg = is_valid(opt) + --- @overload fun(name: string, val: any, expected: string, optional?: boolean) + function vim.validate(opt, ...) + local ok = false + local err_msg ---@type string? + local narg = select('#', ...) + if narg == 0 then + ok, err_msg = is_valid(opt) + elseif narg >= 2 then + -- Overloaded signature for fast/simple cases + local name = opt --[[@as string]] + local v, expected, optional = ... ---@type string, string, boolean? + local actual = type(v) + + ok = (actual == expected) or (v == nil and optional == true) + if not ok then + err_msg = ('%s: expected %s, got %s%s'):format( + name, + expected, + actual, + v and (' (%s)'):format(v) or '' + ) + end + else + error('invalid arguments') + end + if not ok then error(err_msg, 2) end @@ -949,7 +1015,7 @@ function vim.is_callable(f) if m == nil then return false end - return type(m.__call) == 'function' + return type(rawget(m, '__call')) == 'function' end --- Creates a table whose missing keys are provided by {createfn} (like Python's "defaultdict"). @@ -1091,4 +1157,165 @@ function vim._defer_require(root, mod) }) end +--- @nodoc +--- @class vim.context.mods +--- @field bo? table<string, any> +--- @field buf? integer +--- @field emsg_silent? boolean +--- @field env? table<string, any> +--- @field go? table<string, any> +--- @field hide? boolean +--- @field keepalt? boolean +--- @field keepjumps? boolean +--- @field keepmarks? boolean +--- @field keeppatterns? boolean +--- @field lockmarks? boolean +--- @field noautocmd? boolean +--- @field o? table<string, any> +--- @field sandbox? boolean +--- @field silent? boolean +--- @field unsilent? boolean +--- @field win? integer +--- @field wo? table<string, any> + +--- @nodoc +--- @class vim.context.state +--- @field bo? table<string, any> +--- @field env? table<string, any> +--- @field go? table<string, any> +--- @field wo? table<string, any> + +local scope_map = { buf = 'bo', global = 'go', win = 'wo' } +local scope_order = { 'o', 'wo', 'bo', 'go', 'env' } +local state_restore_order = { 'bo', 'wo', 'go', 'env' } + +--- Gets data about current state, enough to properly restore specified options/env/etc. +--- @param context vim.context.mods +--- @return vim.context.state +local get_context_state = function(context) + local res = { bo = {}, env = {}, go = {}, wo = {} } + + -- Use specific order from possibly most to least intrusive + for _, scope in ipairs(scope_order) do + for name, _ in pairs(context[scope] or {}) do + local sc = scope == 'o' and scope_map[vim.api.nvim_get_option_info2(name, {}).scope] or scope + + -- Do not override already set state and fall back to `vim.NIL` for + -- state `nil` values (which still needs restoring later) + res[sc][name] = res[sc][name] or vim[sc][name] or vim.NIL + + -- Always track global option value to properly restore later. + -- This matters for at least `o` and `wo` (which might set either/both + -- local and global option values). + if sc ~= 'env' then + res.go[name] = res.go[name] or vim.go[name] + end + end + end + + return res +end + +--- Executes function `f` with the given context specification. +--- +--- Notes: +--- - Context `{ buf = buf }` has no guarantees about current window when +--- inside context. +--- - Context `{ buf = buf, win = win }` is yet not allowed, but this seems +--- to be an implementation detail. +--- - There should be no way to revert currently set `context.sandbox = true` +--- (like with nested `vim._with()` calls). Otherwise it kind of breaks the +--- whole purpose of sandbox execution. +--- - Saving and restoring option contexts (`bo`, `go`, `o`, `wo`) trigger +--- `OptionSet` events. This is an implementation issue because not doing it +--- seems to mean using either 'eventignore' option or extra nesting with +--- `{ noautocmd = true }` (which itself is a wrapper for 'eventignore'). +--- As `{ go = { eventignore = '...' } }` is a valid context which should be +--- properly set and restored, this is not a good approach. +--- Not triggering `OptionSet` seems to be a good idea, though. So probably +--- only moving context save and restore to lower level might resolve this. +--- +--- @param context vim.context.mods +--- @param f function +--- @return any +function vim._with(context, f) + vim.validate('context', context, 'table') + vim.validate('f', f, 'function') + + vim.validate('context.bo', context.bo, 'table', true) + vim.validate('context.buf', context.buf, 'number', true) + vim.validate('context.emsg_silent', context.emsg_silent, 'boolean', true) + vim.validate('context.env', context.env, 'table', true) + vim.validate('context.go', context.go, 'table', true) + vim.validate('context.hide', context.hide, 'boolean', true) + vim.validate('context.keepalt', context.keepalt, 'boolean', true) + vim.validate('context.keepjumps', context.keepjumps, 'boolean', true) + vim.validate('context.keepmarks', context.keepmarks, 'boolean', true) + vim.validate('context.keeppatterns', context.keeppatterns, 'boolean', true) + vim.validate('context.lockmarks', context.lockmarks, 'boolean', true) + vim.validate('context.noautocmd', context.noautocmd, 'boolean', true) + vim.validate('context.o', context.o, 'table', true) + vim.validate('context.sandbox', context.sandbox, 'boolean', true) + vim.validate('context.silent', context.silent, 'boolean', true) + vim.validate('context.unsilent', context.unsilent, 'boolean', true) + vim.validate('context.win', context.win, 'number', true) + vim.validate('context.wo', context.wo, 'table', true) + + -- Check buffer exists + if context.buf then + if not vim.api.nvim_buf_is_valid(context.buf) then + error('Invalid buffer id: ' .. context.buf) + end + end + + -- Check window exists + if context.win then + if not vim.api.nvim_win_is_valid(context.win) then + error('Invalid window id: ' .. context.win) + end + -- TODO: Maybe allow it? + if context.buf and vim.api.nvim_win_get_buf(context.win) ~= context.buf then + error('Can not set both `buf` and `win` context.') + end + end + + -- Decorate so that save-set-restore options is done in correct window-buffer + local callback = function() + -- Cache current values to be changed by context + -- Abort early in case of bad context value + local ok, state = pcall(get_context_state, context) + if not ok then + error(state, 0) + end + + -- Apply some parts of the context in specific order + -- NOTE: triggers `OptionSet` event + for _, scope in ipairs(scope_order) do + for name, context_value in pairs(context[scope] or {}) do + vim[scope][name] = context_value + end + end + + -- Execute + local res = { pcall(f) } + + -- Restore relevant cached values in specific order, global scope last + -- NOTE: triggers `OptionSet` event + for _, scope in ipairs(state_restore_order) do + for name, cached_value in pairs(state[scope]) do + vim[scope][name] = cached_value + end + end + + -- Return + if not res[1] then + error(res[2], 0) + end + table.remove(res, 1) + return unpack(res, 1, table.maxn(res)) + end + + return vim._with_c(context, callback) +end + return vim diff --git a/runtime/lua/vim/snippet.lua b/runtime/lua/vim/snippet.lua index 3d8f73f362..af7e3c6d33 100644 --- a/runtime/lua/vim/snippet.lua +++ b/runtime/lua/vim/snippet.lua @@ -2,6 +2,8 @@ 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' +local jump_forward_key = '<tab>' +local jump_backward_key = '<s-tab>' --- Returns the 0-based cursor position. --- @@ -182,6 +184,8 @@ end --- @field extmark_id integer --- @field tabstops table<integer, vim.snippet.Tabstop[]> --- @field current_tabstop vim.snippet.Tabstop +--- @field tab_keymaps { i: table<string, any>?, s: table<string, any>? } +--- @field shift_tab_keymaps { i: table<string, any>?, s: table<string, any>? } local Session = {} --- Creates a new snippet session in the current buffer. @@ -197,6 +201,8 @@ function Session.new(bufnr, snippet_extmark, tabstop_data) extmark_id = snippet_extmark, tabstops = {}, current_tabstop = Tabstop.new(0, bufnr, { 0, 0, 0, 0 }), + tab_keymaps = { i = nil, s = nil }, + shift_tab_keymaps = { i = nil, s = nil }, }, { __index = Session }) -- Create the tabstops. @@ -207,9 +213,64 @@ function Session.new(bufnr, snippet_extmark, tabstop_data) end end + self:set_keymaps() + return self end +--- Sets the snippet navigation keymaps. +--- +--- @package +function Session:set_keymaps() + local function maparg(key, mode) + local map = vim.fn.maparg(key, mode, false, true) --[[ @as table ]] + if not vim.tbl_isempty(map) and map.buffer == 1 then + return map + else + return nil + end + end + + local function set(jump_key, direction) + vim.keymap.set({ 'i', 's' }, jump_key, function() + return vim.snippet.active({ direction = direction }) + and '<cmd>lua vim.snippet.jump(' .. direction .. ')<cr>' + or jump_key + end, { expr = true, silent = true, buffer = self.bufnr }) + end + + self.tab_keymaps = { + i = maparg(jump_forward_key, 'i'), + s = maparg(jump_forward_key, 's'), + } + self.shift_tab_keymaps = { + i = maparg(jump_backward_key, 'i'), + s = maparg(jump_backward_key, 's'), + } + set(jump_forward_key, 1) + set(jump_backward_key, -1) +end + +--- Restores/deletes the keymaps used for snippet navigation. +--- +--- @package +function Session:restore_keymaps() + local function restore(keymap, lhs, mode) + if keymap then + vim._with({ buf = self.bufnr }, function() + vim.fn.mapset(keymap) + end) + else + vim.api.nvim_buf_del_keymap(self.bufnr, mode, lhs) + end + end + + restore(self.tab_keymaps.i, jump_forward_key, 'i') + restore(self.tab_keymaps.s, jump_forward_key, 's') + restore(self.shift_tab_keymaps.i, jump_backward_key, 'i') + restore(self.shift_tab_keymaps.s, jump_backward_key, 's') +end + --- Returns the destination tabstop index when jumping in the given direction. --- --- @package @@ -315,7 +376,7 @@ local function select_tabstop(tabstop) move_cursor_to(range[1] + 1, range[2] + 1) feedkeys('v') move_cursor_to(range[3] + 1, range[4]) - feedkeys('o<c-g>') + feedkeys('o<c-g><c-r>_') end end @@ -395,6 +456,15 @@ local function setup_autocmds(bufnr) end end, }) + + vim.api.nvim_create_autocmd('BufLeave', { + group = snippet_group, + desc = 'Stop the snippet session when leaving the buffer', + buffer = bufnr, + callback = function() + M.stop() + end, + }) end --- Expands the given snippet text. @@ -444,7 +514,7 @@ function M.expand(input) local snippet_lines = text_to_lines(snippet_text) -- Get the base indentation based on the current line and the last line of the snippet. if #snippet_lines > 0 then - base_indent = base_indent .. (snippet_lines[#snippet_lines]:match('(^%s*)%S') or '') --- @type string + base_indent = base_indent .. (snippet_lines[#snippet_lines]:match('(^%s+)%S') or '') --- @type string end local shiftwidth = vim.fn.shiftwidth() @@ -539,7 +609,7 @@ end --- ```lua --- vim.keymap.set({ 'i', 's' }, '<Tab>', function() --- if vim.snippet.active({ direction = 1 }) then ---- return '<cmd>lua vim.snippet.jump(1)<cr>' +--- return '<Cmd>lua vim.snippet.jump(1)<CR>' --- else --- return '<Tab>' --- end @@ -591,7 +661,7 @@ end --- ```lua --- vim.keymap.set({ 'i', 's' }, '<Tab>', function() --- if vim.snippet.active({ direction = 1 }) then ---- return '<cmd>lua vim.snippet.jump(1)<cr>' +--- return '<Cmd>lua vim.snippet.jump(1)<CR>' --- else --- return '<Tab>' --- end @@ -619,6 +689,8 @@ function M.stop() return end + M._session:restore_keymaps() + vim.api.nvim_clear_autocmds({ group = snippet_group, buffer = M._session.bufnr }) vim.api.nvim_buf_clear_namespace(M._session.bufnr, snippet_ns, 0, -1) diff --git a/runtime/lua/vim/text.lua b/runtime/lua/vim/text.lua index bc90d490aa..d45c8021c6 100644 --- a/runtime/lua/vim/text.lua +++ b/runtime/lua/vim/text.lua @@ -7,10 +7,9 @@ local M = {} --- @param str string String to encode --- @return string : Hex encoded string function M.hexencode(str) - local bytes = { str:byte(1, #str) } local enc = {} ---@type string[] - for i = 1, #bytes do - enc[i] = string.format('%02X', bytes[i]) + for i = 1, #str do + enc[i] = string.format('%02X', str:byte(i, i + 1)) end return table.concat(enc) end @@ -18,15 +17,19 @@ end --- Hex decode a string. --- --- @param enc string String to decode ---- @return string : Decoded string +--- @return string? : Decoded string +--- @return string? : Error message, if any function M.hexdecode(enc) - assert(#enc % 2 == 0, 'string must have an even number of hex characters') + if #enc % 2 ~= 0 then + return nil, 'string must have an even number of hex characters' + end + local str = {} ---@type string[] for i = 1, #enc, 2 do local n = assert(tonumber(enc:sub(i, i + 1), 16)) str[#str + 1] = string.char(n) end - return table.concat(str) + return table.concat(str), nil end return M diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index db544c1ab1..ed7d31e1f7 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -76,36 +76,48 @@ end --- --- If needed, this will create the parser. --- +--- If no parser can be created, an error is thrown. Set `opts.error = false` to suppress this and +--- return nil (and an error message) instead. WARNING: This behavior will become default in Nvim +--- 0.12 and the option will be removed. +--- ---@param bufnr (integer|nil) Buffer the parser should be tied to (default: current buffer) ---@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 vim.treesitter.LanguageTree object to use for parsing +---@return vim.treesitter.LanguageTree? object to use for parsing +---@return string? error message, if applicable function M.get_parser(bufnr, lang, opts) opts = opts or {} + local should_error = opts.error == nil or opts.error if bufnr == nil or bufnr == 0 then bufnr = api.nvim_get_current_buf() end if not valid_lang(lang) then - lang = M.language.get_lang(vim.bo[bufnr].filetype) or vim.bo[bufnr].filetype + lang = M.language.get_lang(vim.bo[bufnr].filetype) end if not valid_lang(lang) then if not parsers[bufnr] then - error( - string.format( - 'There is no parser available for buffer %d and one could not be' - .. ' created because lang could not be determined. Either pass lang' - .. ' or set the buffer filetype', - bufnr - ) - ) + local err_msg = + string.format('Parser not found for buffer %s: language could not be determined', bufnr) + if should_error then + error(err_msg) + end + return nil, err_msg end elseif parsers[bufnr] == nil or parsers[bufnr]:lang() ~= lang then - assert(lang, 'lang should be valid') - parsers[bufnr] = M._create_parser(bufnr, lang, opts) + local parser = vim.F.npcall(M._create_parser, bufnr, lang, opts) + if not parser then + local err_msg = + string.format('Parser could not be created for buffer %s and language "%s"', bufnr, lang) + if should_error then + error(err_msg) + end + return nil, err_msg + end + parsers[bufnr] = parser end parsers[bufnr]:register_cbs(opts.buf_attach_cbs) @@ -140,16 +152,8 @@ function M.is_ancestor(dest, source) return false end - local current = source ---@type TSNode? - while current ~= nil do - if current == dest then - return true - end - - current = current:parent() - end - - return false + -- child_containing_descendant returns nil if dest is a direct parent + return source:parent() == dest or dest:child_containing_descendant(source) ~= nil end --- Returns the node's range or an unpacked range table @@ -257,7 +261,7 @@ end ---@param row integer Position row ---@param col integer Position column --- ----@return {capture: string, lang: string, metadata: table}[] +---@return {capture: string, lang: string, metadata: vim.treesitter.query.TSMetadata}[] function M.get_captures_at_pos(bufnr, row, col) if bufnr == 0 then bufnr = api.nvim_get_current_buf() @@ -335,13 +339,16 @@ end --- --- 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 }? +--- @field pos [integer, integer]? --- --- Parser language. (default: from buffer filetype) --- @field lang string? --- --- Ignore injected languages (default true) --- @field ignore_injections boolean? +--- +--- Include anonymous nodes (default false) +--- @field include_anonymous boolean? --- Returns the smallest named node at the given position --- @@ -383,11 +390,14 @@ function M.get_node(opts) local ts_range = { row, col, row, col } - local root_lang_tree = M.get_parser(bufnr, opts.lang) + local root_lang_tree = M.get_parser(bufnr, opts.lang, { error = false }) if not root_lang_tree then return end + if opts.include_anonymous then + return root_lang_tree:node_for_range(ts_range, opts) + end return root_lang_tree:named_node_for_range(ts_range, opts) end @@ -413,7 +423,7 @@ end ---@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) + local parser = assert(M.get_parser(bufnr, lang, { error = false })) M.highlighter.new(parser) end @@ -437,6 +447,7 @@ end --- --- Can also be shown with `:InspectTree`. [:InspectTree]() --- +---@since 11 ---@param opts table|nil Optional options table with the following possible keys: --- - lang (string|nil): The language of the source buffer. If omitted, detect --- from the filetype of the source buffer. @@ -460,6 +471,7 @@ end --- vim.wo.foldexpr = 'v:lua.vim.treesitter.foldexpr()' --- ``` --- +---@since 11 ---@param lnum integer|nil Line number to calculate fold level for ---@return string function M.foldexpr(lnum) diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua index eecf1ad6b1..7237d2e7d4 100644 --- a/runtime/lua/vim/treesitter/_fold.lua +++ b/runtime/lua/vim/treesitter/_fold.lua @@ -87,7 +87,7 @@ end ---@param srow integer ---@param erow integer 0-indexed, exclusive function FoldInfo:add_range(srow, erow) - list_insert(self.levels, srow + 1, erow, '=') + list_insert(self.levels, srow + 1, erow, -1) list_insert(self.levels0, srow + 1, erow, -1) end @@ -114,7 +114,7 @@ local function compute_folds_levels(bufnr, info, srow, erow, parse_injections) srow = srow or 0 erow = erow or api.nvim_buf_line_count(bufnr) - local parser = ts.get_parser(bufnr) + local parser = assert(ts.get_parser(bufnr, nil, { error = false })) parser:parse(parse_injections and { srow, erow } or nil) @@ -131,24 +131,18 @@ local function compute_folds_levels(bufnr, info, srow, erow, parse_injections) -- Collect folds starting from srow - 1, because we should first subtract the folds that end at -- srow - 1 from the level of srow - 1 to get accurate level of srow. - for _, match, metadata in - query:iter_matches(tree:root(), bufnr, math.max(srow - 1, 0), erow, { all = true }) - do + for _, match, metadata in query:iter_matches(tree:root(), bufnr, math.max(srow - 1, 0), erow) do for id, nodes in pairs(match) do if query.captures[id] == 'fold' then local range = ts.get_range(nodes[1], bufnr, metadata[id]) local start, _, stop, stop_col = Range.unpack4(range) - for i = 2, #nodes, 1 do - local node_range = ts.get_range(nodes[i], bufnr, metadata[id]) - local node_start, _, node_stop, node_stop_col = Range.unpack4(node_range) - if node_start < start then - start = node_start - end - if node_stop > stop then - stop = node_stop - stop_col = node_stop_col - end + if #nodes > 1 then + -- assumes nodes are ordered by range + local end_range = ts.get_range(nodes[#nodes], bufnr, metadata[id]) + local _, _, end_stop, end_stop_col = Range.unpack4(end_range) + stop = end_stop + stop_col = end_stop_col end if stop_col == 0 then @@ -268,6 +262,15 @@ end ---@package function FoldInfo:do_foldupdate(bufnr) + -- InsertLeave is not executed when <C-C> is used for exiting the insert mode, leaving + -- do_foldupdate untouched. If another execution of foldupdate consumes foldupdate_range, the + -- InsertLeave do_foldupdate gets nil foldupdate_range. In that case, skip the update. This is + -- correct because the update that consumed the range must have incorporated the range that + -- InsertLeave meant to update. + if not self.foldupdate_range then + return + end + local srow, erow = self.foldupdate_range[1], self.foldupdate_range[2] self.foldupdate_range = nil for _, win in ipairs(vim.fn.win_findbuf(bufnr)) do @@ -383,14 +386,13 @@ local function on_bytes(bufnr, foldinfo, start_row, start_col, old_row, old_col, end end ----@package ---@param lnum integer|nil ---@return string function M.foldexpr(lnum) lnum = lnum or vim.v.lnum local bufnr = api.nvim_get_current_buf() - local parser = vim.F.npcall(ts.get_parser, bufnr) + local parser = ts.get_parser(bufnr, nil, { error = false }) if not parser then return '0' end diff --git a/runtime/lua/vim/treesitter/_meta.lua b/runtime/lua/vim/treesitter/_meta.lua deleted file mode 100644 index 177699a207..0000000000 --- a/runtime/lua/vim/treesitter/_meta.lua +++ /dev/null @@ -1,113 +0,0 @@ ----@meta -error('Cannot require a meta file') - ----@class TSNode: userdata ----@field id fun(self: TSNode): string ----@field tree fun(self: TSNode): TSTree ----@field range fun(self: TSNode, include_bytes: false?): integer, integer, integer, integer ----@field range fun(self: TSNode, include_bytes: true): integer, integer, integer, integer, integer, integer ----@field start fun(self: TSNode): integer, integer, integer ----@field end_ fun(self: TSNode): integer, integer, integer ----@field type fun(self: TSNode): string ----@field symbol fun(self: TSNode): integer ----@field named fun(self: TSNode): boolean ----@field missing fun(self: TSNode): boolean ----@field extra fun(self: TSNode): boolean ----@field child_count fun(self: TSNode): integer ----@field named_child_count fun(self: TSNode): integer ----@field child fun(self: TSNode, index: integer): TSNode? ----@field named_child fun(self: TSNode, index: integer): TSNode? ----@field descendant_for_range fun(self: TSNode, start_row: integer, start_col: integer, end_row: integer, end_col: integer): TSNode? ----@field named_descendant_for_range fun(self: TSNode, start_row: integer, start_col: integer, end_row: integer, end_col: integer): TSNode? ----@field parent fun(self: TSNode): TSNode? ----@field child_containing_descendant fun(self: TSNode, descendant: TSNode): TSNode? ----@field next_sibling fun(self: TSNode): TSNode? ----@field prev_sibling fun(self: TSNode): TSNode? ----@field next_named_sibling fun(self: TSNode): TSNode? ----@field prev_named_sibling fun(self: TSNode): TSNode? ----@field named_children fun(self: TSNode): TSNode[] ----@field has_changes fun(self: TSNode): boolean ----@field has_error fun(self: TSNode): boolean ----@field sexpr fun(self: TSNode): string ----@field equal fun(self: TSNode, other: TSNode): boolean ----@field iter_children fun(self: TSNode): fun(): TSNode, string ----@field field fun(self: TSNode, name: string): TSNode[] ----@field byte_length fun(self: TSNode): integer -local TSNode = {} - ----@alias TSLoggerCallback fun(logtype: 'parse'|'lex', msg: string) - ----@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)[]) ----@field set_timeout fun(self: TSParser, timeout: integer) ----@field timeout fun(self: TSParser): integer ----@field _set_logger fun(self: TSParser, lex: boolean, parse: boolean, cb: TSLoggerCallback) ----@field _logger fun(self: TSParser): TSLoggerCallback - ----@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)[][]> - ---- @param lang string -vim._ts_inspect_language = function(lang) end - ----@return integer -vim._ts_get_language_version = function() end - ---- @param path string ---- @param lang string ---- @param symbol_name? string -vim._ts_add_language = function(path, lang, symbol_name) 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 - ---- @class TSQueryMatch: userdata ---- @field captures fun(self: TSQueryMatch): table<integer,TSNode[]> -local TSQueryMatch = {} - ---- @return integer match_id ---- @return integer pattern_index -function TSQueryMatch:info() end - ---- @class TSQueryCursor: userdata ---- @field remove_match fun(self: TSQueryCursor, id: integer) -local TSQueryCursor = {} - ---- @return integer capture ---- @return TSNode captured_node ---- @return TSQueryMatch match -function TSQueryCursor:next_capture() end - ---- @return TSQueryMatch match -function TSQueryCursor:next_match() end - ---- @param node TSNode ---- @param query TSQuery ---- @param start integer? ---- @param stop integer? ---- @param opts? { max_start_depth?: integer, match_limit?: integer} ---- @return TSQueryCursor -function vim._create_ts_querycursor(node, query, start, stop, opts) end diff --git a/runtime/lua/vim/treesitter/_meta/misc.lua b/runtime/lua/vim/treesitter/_meta/misc.lua new file mode 100644 index 0000000000..33701ef254 --- /dev/null +++ b/runtime/lua/vim/treesitter/_meta/misc.lua @@ -0,0 +1,78 @@ +---@meta +-- luacheck: no unused args +error('Cannot require a meta file') + +---@alias TSLoggerCallback fun(logtype: 'parse'|'lex', msg: string) + +---@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)[]) +---@field set_timeout fun(self: TSParser, timeout: integer) +---@field timeout fun(self: TSParser): integer +---@field _set_logger fun(self: TSParser, lex: boolean, parse: boolean, cb: TSLoggerCallback) +---@field _logger fun(self: TSParser): TSLoggerCallback + +---@class TSQuery: userdata +---@field inspect fun(self: TSQuery): TSQueryInfo + +---@class (exact) TSQueryInfo +---@field captures string[] +---@field patterns table<integer, (integer|string)[][]> + +--- @param lang string +--- @return table +vim._ts_inspect_language = function(lang) end + +---@return integer +vim._ts_get_language_version = function() end + +--- @param path string +--- @param lang string +--- @param symbol_name? string +vim._ts_add_language_from_object = function(path, lang, symbol_name) end + +--- @param path string +--- @param lang string +vim._ts_add_language_from_wasm = function(path, lang) 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 + +--- @class TSQueryMatch: userdata +--- @field captures fun(self: TSQueryMatch): table<integer,TSNode[]> +local TSQueryMatch = {} -- luacheck: no unused + +--- @return integer match_id +--- @return integer pattern_index +function TSQueryMatch:info() end + +--- @class TSQueryCursor: userdata +--- @field remove_match fun(self: TSQueryCursor, id: integer) +local TSQueryCursor = {} -- luacheck: no unused + +--- @return integer capture +--- @return TSNode captured_node +--- @return TSQueryMatch match +function TSQueryCursor:next_capture() end + +--- @return TSQueryMatch match +function TSQueryCursor:next_match() end + +--- @param node TSNode +--- @param query TSQuery +--- @param start integer? +--- @param stop integer? +--- @param opts? { max_start_depth?: integer, match_limit?: integer} +--- @return TSQueryCursor +function vim._create_ts_querycursor(node, query, start, stop, opts) end diff --git a/runtime/lua/vim/treesitter/_meta/tsnode.lua b/runtime/lua/vim/treesitter/_meta/tsnode.lua new file mode 100644 index 0000000000..acc9f8d24e --- /dev/null +++ b/runtime/lua/vim/treesitter/_meta/tsnode.lua @@ -0,0 +1,185 @@ +---@meta +-- luacheck: no unused args +error('Cannot require a meta file') + +--- @brief A "treesitter node" represents one specific element of the parsed contents of a buffer, +--- which can be captured by a |Query| for, e.g., highlighting. It is a |userdata| reference to an +--- object held by the treesitter library. +--- +--- An instance `TSNode` of a treesitter node supports the following methods. + +---@nodoc +---@class TSNode: userdata +---@field named_children fun(self: TSNode): TSNode[] +---@field __has_ancestor fun(self: TSNode, node_types: string[]): boolean +local TSNode = {} -- luacheck: no unused + +--- Get the node's immediate parent. +--- Prefer |TSNode:child_containing_descendant()| +--- for iterating over the node's ancestors. +--- @return TSNode? +function TSNode:parent() end + +--- Get the node's next sibling. +--- @return TSNode? +function TSNode:next_sibling() end + +--- Get the node's previous sibling. +--- @return TSNode? +function TSNode:prev_sibling() end + +--- Get the node's next named sibling. +--- @return TSNode? +function TSNode:next_named_sibling() end + +--- Get the node's previous named sibling. +--- @return TSNode? +function TSNode:prev_named_sibling() end + +--- Iterates over all the direct children of {TSNode}, regardless of whether +--- they are named or not. +--- Returns the child node plus the eventual field name corresponding to this +--- child node. +--- @return fun(): TSNode, string +function TSNode:iter_children() end + +--- Returns a table of the nodes corresponding to the {name} field. +--- @param name string +--- @return TSNode[] +function TSNode:field(name) end + +--- Get the node's number of children. +--- @return integer +function TSNode:child_count() end + +--- Get the node's child at the given {index}, where zero represents the first +--- child. +--- @param index integer +--- @return TSNode? +function TSNode:child(index) end + +--- Get the node's number of named children. +--- @return integer +function TSNode:named_child_count() end + +--- Get the node's named child at the given {index}, where zero represents the +--- first named child. +--- @param index integer +--- @return TSNode? +function TSNode:named_child(index) end + +--- Get the node's child that contains {descendant}. +--- @param descendant TSNode +--- @return TSNode? +function TSNode:child_containing_descendant(descendant) end + +--- Get the node's start position. Return three values: the row, column and +--- total byte count (all zero-based). +--- @return integer, integer, integer +function TSNode:start() end + +--- Get the node's end position. Return three values: the row, column and +--- total byte count (all zero-based). +--- @return integer, integer, integer +function TSNode:end_() end + +--- Get the range of the node. +--- +--- Return four or six values: +--- +--- - start row +--- - start column +--- - start byte (if {include_bytes} is `true`) +--- - end row +--- - end column +--- - end byte (if {include_bytes} is `true`) +--- @param include_bytes boolean? +function TSNode:range(include_bytes) end + +--- @nodoc +--- @param include_bytes false? +--- @return integer, integer, integer, integer +function TSNode:range(include_bytes) end + +--- @nodoc +--- @param include_bytes true +--- @return integer, integer, integer, integer, integer, integer +function TSNode:range(include_bytes) end + +--- Get the node's type as a string. +--- @return string +function TSNode:type() end + +--- Get the node's type as a numerical id. +--- @return integer +function TSNode:symbol() end + +--- Check if the node is named. Named nodes correspond to named rules in the +--- grammar, whereas anonymous nodes correspond to string literals in the +--- grammar. +--- @return boolean +function TSNode:named() end + +--- Check if the node is missing. Missing nodes are inserted by the parser in +--- order to recover from certain kinds of syntax errors. +--- @return boolean +function TSNode:missing() end + +--- Check if the node is extra. Extra nodes represent things like comments, +--- which are not required by the grammar but can appear anywhere. +--- @return boolean +function TSNode:extra() end + +--- Check if a syntax node has been edited. +--- @return boolean +function TSNode:has_changes() end + +--- Check if the node is a syntax error or contains any syntax errors. +--- @return boolean +function TSNode:has_error() end + +--- Get an S-expression representing the node as a string. +--- @return string +function TSNode:sexpr() end + +--- Get a unique identifier for the node inside its own tree. +--- +--- No guarantees are made about this identifier's internal representation, +--- except for being a primitive Lua type with value equality (so not a +--- table). Presently it is a (non-printable) string. +--- +--- Note: The `id` is not guaranteed to be unique for nodes from different +--- trees. +--- @return string +function TSNode:id() end + +--- Get the |TSTree| of the node. +--- @return TSTree +function TSNode:tree() end + +--- Get the smallest node within this node that spans the given range of (row, +--- column) positions +--- @param start_row integer +--- @param start_col integer +--- @param end_row integer +--- @param end_col integer +--- @return TSNode? +function TSNode:descendant_for_range(start_row, start_col, end_row, end_col) end + +--- Get the smallest named node within this node that spans the given range of +--- (row, column) positions +--- @param start_row integer +--- @param start_col integer +--- @param end_row integer +--- @param end_col integer +--- @return TSNode? +function TSNode:named_descendant_for_range(start_row, start_col, end_row, end_col) end + +--- Check if {node} refers to the same node within the same tree. +--- @param node TSNode +--- @return boolean +function TSNode:equal(node) end + +--- Return the number of bytes spanned by this node. +--- @return integer +function TSNode:byte_length() end diff --git a/runtime/lua/vim/treesitter/_meta/tstree.lua b/runtime/lua/vim/treesitter/_meta/tstree.lua new file mode 100644 index 0000000000..24cb60040e --- /dev/null +++ b/runtime/lua/vim/treesitter/_meta/tstree.lua @@ -0,0 +1,44 @@ +---@meta +-- luacheck: no unused args +error('Cannot require a meta file') + +--- @brief A "treesitter tree" represents the parsed contents of a buffer, which can be +--- used to perform further analysis. It is a |userdata| reference to an object +--- held by the treesitter library. +--- +--- An instance `TSTree` of a treesitter tree supports the following methods. + +---@nodoc +---@class TSTree: userdata +local TSTree = {} -- luacheck: no unused + +--- Return the root node of this tree. +---@return TSNode +function TSTree:root() end + +-- stylua: ignore +---@param start_byte integer +---@param end_byte_old integer +---@param end_byte_new integer +---@param start_row integer +---@param start_col integer +---@param end_row_old integer +---@param end_col_old integer +---@param end_row_new integer +---@param end_col_new integer +---@nodoc +function TSTree:edit(start_byte, end_byte_old, end_byte_new, start_row, start_col, end_row_old, end_col_old, end_row_new, end_col_new) end + +--- Returns a copy of the `TSTree`. +---@return TSTree +function TSTree:copy() end + +---@param include_bytes true +---@return Range6[] +---@nodoc +function TSTree:included_ranges(include_bytes) end + +---@param include_bytes false +---@return Range4[] +---@nodoc +function TSTree:included_ranges(include_bytes) end diff --git a/runtime/lua/vim/treesitter/_query_linter.lua b/runtime/lua/vim/treesitter/_query_linter.lua index 12b4cbc7b9..c5e4b86e1e 100644 --- a/runtime/lua/vim/treesitter/_query_linter.lua +++ b/runtime/lua/vim/treesitter/_query_linter.lua @@ -40,7 +40,8 @@ end local function guess_query_lang(buf) local filename = api.nvim_buf_get_name(buf) if filename ~= '' then - return vim.F.npcall(vim.fn.fnamemodify, filename, ':p:h:t') + local resolved_filename = vim.F.npcall(vim.fn.fnamemodify, filename, ':p:h:t') + return resolved_filename and vim.treesitter.language.get_lang(resolved_filename) end end @@ -64,7 +65,7 @@ local function normalize_opts(buf, opts) end local lint_query = [[;; query - (program [(named_node) (list) (grouping)] @toplevel) + (program [(named_node) (anonymous_node) (list) (grouping)] @toplevel) (named_node name: _ @node.named) (anonymous_node @@ -170,17 +171,17 @@ function M.lint(buf, opts) --- @type (table|nil) local parser_info = vim.F.npcall(vim.treesitter.language.inspect, lang) + local lang_context = { + lang = lang, + parser_info = parser_info, + is_first_lang = i == 1, + } - local parser = vim.treesitter.get_parser(buf) + local parser = assert(vim.treesitter.get_parser(buf, nil, { error = false })) parser:parse() parser:for_each_tree(function(tree, ltree) if ltree:lang() == 'query' then - for _, match, _ in query:iter_matches(tree:root(), buf, 0, -1, { all = true }) do - local lang_context = { - lang = lang, - parser_info = parser_info, - is_first_lang = i == 1, - } + for _, match, _ in query:iter_matches(tree:root(), buf, 0, -1) do lint_match(buf, match, query, lang_context, diagnostics) end end @@ -240,7 +241,7 @@ function M.omnifunc(findstart, base) end end for _, s in pairs(parser_info.symbols) do - local text = s[2] and s[1] or '"' .. s[1]:gsub([[\]], [[\\]]) .. '"' ---@type string + local text = s[2] and s[1] or string.format('%q', s[1]):gsub('\n', 'n') ---@type string if text:find(base, 1, true) then table.insert(items, text) end diff --git a/runtime/lua/vim/treesitter/_range.lua b/runtime/lua/vim/treesitter/_range.lua index 8d727c3c52..82ab8517aa 100644 --- a/runtime/lua/vim/treesitter/_range.lua +++ b/runtime/lua/vim/treesitter/_range.lua @@ -3,16 +3,19 @@ local api = vim.api local M = {} ---@class Range2 +---@inlinedoc ---@field [1] integer start row ---@field [2] integer end row ---@class Range4 +---@inlinedoc ---@field [1] integer start row ---@field [2] integer start column ---@field [3] integer end row ---@field [4] integer end column ---@class Range6 +---@inlinedoc ---@field [1] integer start row ---@field [2] integer start column ---@field [3] integer start bytes @@ -150,6 +153,7 @@ function M.contains(r1, r2) return true end +--- @private --- @param source integer|string --- @param index integer --- @return integer diff --git a/runtime/lua/vim/treesitter/dev.lua b/runtime/lua/vim/treesitter/dev.lua index 5c91f101c0..90c3720b80 100644 --- a/runtime/lua/vim/treesitter/dev.lua +++ b/runtime/lua/vim/treesitter/dev.lua @@ -76,10 +76,14 @@ end --- ---@package function TSTreeView:new(bufnr, lang) - local ok, parser = pcall(vim.treesitter.get_parser, bufnr or 0, lang) - if not ok then - local err = parser --[[ @as string ]] - return nil, 'No parser available for the given buffer:\n' .. err + local parser = vim.treesitter.get_parser(bufnr or 0, lang, { error = false }) + if not parser then + return nil, + string.format( + 'Failed to create TSTreeView for buffer %s: no parser for lang "%s"', + bufnr, + lang + ) end -- For each child tree (injected language), find the root of the tree and locate the node within @@ -154,7 +158,8 @@ end ---@param w integer ---@param b integer -local function set_dev_properties(w, b) +---@param opts nil|{ indent?: integer } +local function set_dev_options(w, b, opts) vim.wo[w].scrolloff = 5 vim.wo[w].wrap = false vim.wo[w].foldmethod = 'expr' @@ -165,6 +170,12 @@ local function set_dev_properties(w, b) vim.bo[b].buftype = 'nofile' vim.bo[b].bufhidden = 'wipe' vim.bo[b].filetype = 'query' + vim.bo[b].swapfile = false + + opts = opts or {} + if opts.indent then + vim.bo[b].shiftwidth = opts.indent + end end --- Updates the cursor position in the inspector to match the node under the cursor. @@ -174,7 +185,7 @@ end --- @param source_buf integer --- @param inspect_buf integer --- @param inspect_win integer ---- @param pos? { [1]: integer, [2]: integer } +--- @param pos? [integer, integer] local function set_inspector_cursor(treeview, lang, source_buf, inspect_buf, inspect_win, pos) api.nvim_buf_clear_namespace(inspect_buf, treeview.ns, 0, -1) @@ -183,6 +194,7 @@ local function set_inspector_cursor(treeview, lang, source_buf, inspect_buf, ins lang = lang, pos = pos, ignore_injections = false, + include_anonymous = treeview.opts.anon, }) if not cursor_node then return @@ -220,14 +232,13 @@ function TSTreeView:draw(bufnr) 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 + text = string.format('(%s', item.node:type()) else text = string.format('%q', item.node:type()):gsub('\n', 'n') end + if item.field then + text = string.format('%s: %s', item.field, text) + end local next = self:get(i + 1) if not next or next.depth <= item.depth then @@ -325,7 +336,10 @@ function M.inspect_tree(opts) opts = opts or {} + -- source buffer local buf = api.nvim_get_current_buf() + + -- window id for source buffer local win = api.nvim_get_current_win() local treeview = assert(TSTreeView:new(buf, opts.lang)) @@ -334,12 +348,14 @@ function M.inspect_tree(opts) close_win(vim.b[buf].dev_inspect) end + -- window id for tree buffer local w = opts.winid if not w then vim.cmd(opts.command or '60vnew') w = api.nvim_get_current_win() end + -- tree buffer local b = opts.bufnr if b then api.nvim_win_set_buf(w, b) @@ -350,7 +366,7 @@ function M.inspect_tree(opts) vim.b[buf].dev_inspect = w vim.b[b].dev_base = win -- base window handle vim.b[b].disable_query_linter = true - set_dev_properties(w, b) + set_dev_options(w, b, { indent = treeview.opts.indent }) local title --- @type string? local opts_title = opts.title @@ -375,6 +391,12 @@ function M.inspect_tree(opts) callback = function() local row = api.nvim_win_get_cursor(w)[1] local lnum, col = treeview:get(row).node:start() + + -- update source window if original was closed + if not api.nvim_win_is_valid(win) then + win = vim.fn.win_findbuf(buf)[1] + end + api.nvim_set_current_win(win) api.nvim_win_set_cursor(win, { lnum + 1, col }) end, @@ -432,6 +454,7 @@ function M.inspect_tree(opts) return true end + w = api.nvim_get_current_win() api.nvim_buf_clear_namespace(buf, treeview.ns, 0, -1) local row = api.nvim_win_get_cursor(w)[1] local lnum, col, end_lnum, end_col = treeview:get(row).node:range() @@ -441,6 +464,11 @@ function M.inspect_tree(opts) hl_group = 'Visual', }) + -- update source window if original was closed + if not api.nvim_win_is_valid(win) then + win = vim.fn.win_findbuf(buf)[1] + end + local topline, botline = vim.fn.line('w0', win), vim.fn.line('w$', win) -- Move the cursor if highlighted range is completely out of view @@ -506,7 +534,10 @@ function M.inspect_tree(opts) buffer = buf, once = true, callback = function() - close_win(w) + -- close all tree windows + for _, window in pairs(vim.fn.win_findbuf(b)) do + close_win(window) + end end, }) end @@ -519,7 +550,7 @@ local edit_ns = api.nvim_create_namespace('treesitter/dev-edit') local function update_editor_highlights(query_win, base_win, lang) local base_buf = api.nvim_win_get_buf(base_win) local query_buf = api.nvim_win_get_buf(query_win) - local parser = vim.treesitter.get_parser(base_buf, lang) + local parser = assert(vim.treesitter.get_parser(base_buf, lang, { error = false })) api.nvim_buf_clear_namespace(base_buf, edit_ns, 0, -1) local query_content = table.concat(api.nvim_buf_get_lines(query_buf, 0, -1, false), '\n') @@ -554,6 +585,8 @@ end --- @private --- @param lang? string language to open the query editor for. +--- @return boolean? `true` on success, `nil` on failure +--- @return string? error message, if applicable function M.edit_query(lang) local buf = api.nvim_get_current_buf() local win = api.nvim_get_current_win() @@ -576,9 +609,10 @@ function M.edit_query(lang) end vim.cmd(cmd) - local ok, parser = pcall(vim.treesitter.get_parser, buf, lang) - if not ok then - return nil, 'No parser available for the given buffer' + local parser = vim.treesitter.get_parser(buf, lang, { error = false }) + if not parser then + return nil, + string.format('Failed to show query editor for buffer %s: no parser for lang "%s"', buf, lang) end lang = parser:lang() @@ -587,7 +621,7 @@ function M.edit_query(lang) vim.b[buf].dev_edit = query_win vim.bo[query_buf].omnifunc = 'v:lua.vim.treesitter.query.omnifunc' - set_dev_properties(query_win, query_buf) + set_dev_options(query_win, query_buf) -- Note that omnifunc guesses the language based on the containing folder, -- so we add the parser's language to the buffer's name so that omnifunc @@ -652,6 +686,8 @@ function M.edit_query(lang) }) vim.cmd('normal! G') vim.cmd.startinsert() + + return true end return M diff --git a/runtime/lua/vim/treesitter/health.lua b/runtime/lua/vim/treesitter/health.lua index ed3616ef46..637f9ea543 100644 --- a/runtime/lua/vim/treesitter/health.lua +++ b/runtime/lua/vim/treesitter/health.lua @@ -28,6 +28,9 @@ function M.check() ) end end + + local can_wasm = vim._ts_add_language_from_wasm ~= nil + health.info(string.format('Can load WASM parsers: %s', tostring(can_wasm))) end return M diff --git a/runtime/lua/vim/treesitter/highlighter.lua b/runtime/lua/vim/treesitter/highlighter.lua index d2f986b874..a94c408f4e 100644 --- a/runtime/lua/vim/treesitter/highlighter.lua +++ b/runtime/lua/vim/treesitter/highlighter.lua @@ -47,7 +47,7 @@ function TSHighlighterQuery:get_hl_from_capture(capture) return self.hl_cache[capture] end ----@package +---@nodoc function TSHighlighterQuery:query() return self._query end @@ -75,7 +75,7 @@ local TSHighlighter = { TSHighlighter.__index = TSHighlighter ----@package +---@nodoc --- --- Creates a highlighter for `tree`. --- @@ -139,11 +139,14 @@ function TSHighlighter.new(tree, opts) -- but use synload.vim rather than syntax.vim to not enable -- syntax FileType autocmds. Later on we should integrate with the -- `:syntax` and `set syntax=...` machinery properly. + -- Still need to ensure that syntaxset augroup exists, so that calling :destroy() + -- immediately afterwards will not error. if vim.g.syntax_on ~= 1 then vim.cmd.runtime({ 'syntax/synload.vim', bang = true }) + vim.api.nvim_create_augroup('syntaxset', { clear = false }) end - api.nvim_buf_call(self.bufnr, function() + vim._with({ buf = self.bufnr }, function() vim.opt_local.spelloptions:append('noplainbuffer') end) @@ -232,7 +235,7 @@ function TSHighlighter:on_changedtree(changes) end --- Gets the query used for @param lang ----@package +---@nodoc ---@param lang string Language used by the highlighter. ---@return vim.treesitter.highlighter.Query function TSHighlighter:get_query(lang) @@ -377,11 +380,15 @@ function TSHighlighter._on_spell_nav(_, _, buf, srow, _, erow, _) return end + -- Do not affect potentially populated highlight state. Here we just want a temporary + -- empty state so the C code can detect whether the region should be spell checked. + local highlight_states = self._highlight_states self:prepare_highlight_states(srow, erow) for row = srow, erow do on_line_impl(self, buf, row, true) end + self._highlight_states = highlight_states end ---@private diff --git a/runtime/lua/vim/treesitter/language.lua b/runtime/lua/vim/treesitter/language.lua index d0a74daa6c..9f7807e036 100644 --- a/runtime/lua/vim/treesitter/language.lua +++ b/runtime/lua/vim/treesitter/language.lua @@ -7,11 +7,15 @@ local ft_to_lang = { help = 'vimdoc', } ---- Get the filetypes associated with the parser named {lang}. +--- Returns the filetypes for which a parser named {lang} is used. +--- +--- The list includes {lang} itself plus all filetypes registered via +--- |vim.treesitter.language.register()|. +--- --- @param lang string Name of parser --- @return string[] filetypes function M.get_filetypes(lang) - local r = {} ---@type string[] + local r = { lang } ---@type string[] for ft, p in pairs(ft_to_lang) do if p == lang then r[#r + 1] = ft @@ -20,6 +24,12 @@ function M.get_filetypes(lang) return r end +--- Returns the language name to be used when loading a parser for {filetype}. +--- +--- If no language has been explicitly registered via |vim.treesitter.language.register()|, +--- default to {filetype}. For composite filetypes like `html.glimmer`, only the main filetype is +--- returned. +--- --- @param filetype string --- @return string|nil function M.get_lang(filetype) @@ -29,9 +39,9 @@ function M.get_lang(filetype) if ft_to_lang[filetype] then return ft_to_lang[filetype] end - -- support subfiletypes like html.glimmer + -- for subfiletypes like html.glimmer use only "main" filetype filetype = vim.split(filetype, '.', { plain = true })[1] - return ft_to_lang[filetype] + return ft_to_lang[filetype] or filetype end ---@deprecated @@ -52,17 +62,27 @@ function M.require_language(lang, path, silent, symbol_name) return installed end - M.add(lang, opts) - return true + return M.add(lang, opts) +end + +--- Load wasm or native parser (wrapper) +--- todo(clason): move to C +--- +---@param path string Path of parser library +---@param lang string Language name +---@param symbol_name? string Internal symbol name for the language to load (default lang) +---@return boolean? True if parser is loaded +local function loadparser(path, lang, symbol_name) + if vim.endswith(path, '.wasm') then + return vim._ts_add_language_from_wasm and vim._ts_add_language_from_wasm(path, lang) + else + return vim._ts_add_language_from_object(path, lang, symbol_name) + end end ---@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 --- @@ -71,46 +91,52 @@ end --- Load parser with name {lang} --- ---- Parsers are searched in the `parser` runtime directory, or the provided {path} +--- Parsers are searched in the `parser` runtime directory, or the provided {path}. +--- Can be used to check for available parsers before enabling treesitter features, e.g., +--- ```lua +--- if vim.treesitter.language.add('markdown') then +--- vim.treesitter.start(bufnr, 'markdown') +--- end +--- ``` --- ---@param lang string Name of the parser (alphanumerical and `_` only) ---@param opts? vim.treesitter.language.add.Opts Options: +---@return boolean? True if parser is loaded +---@return string? Error if parser cannot be loaded function M.add(lang, opts) opts = opts or {} local path = opts.path - local filetype = opts.filetype or lang local symbol_name = opts.symbol_name vim.validate({ lang = { lang, 'string' }, path = { path, 'string', true }, symbol_name = { symbol_name, 'string', true }, - filetype = { filetype, { 'string', 'table' }, true }, }) -- parser names are assumed to be lowercase (consistent behavior on case-insensitive file systems) lang = lang:lower() if vim._ts_has_language(lang) then - M.register(lang, filetype) - return + return true end if path == nil then + -- allow only safe language names when looking for libraries to load if not (lang and lang:match('[%w_]+') == lang) then - error("'" .. lang .. "' is not a valid language name") + return nil, string.format('Invalid language name "%s"', lang) end local fname = 'parser/' .. lang .. '.*' local paths = api.nvim_get_runtime_file(fname, false) if #paths == 0 then - error("no parser for '" .. lang .. "' language, see :help treesitter-parsers") + return nil, string.format('No parser for language "%s"', lang) end path = paths[1] end - vim._ts_add_language(path, lang, symbol_name) - M.register(lang, filetype) + return loadparser(path, lang, symbol_name) or nil, + string.format('Cannot load parser %s for language "%s"', path, lang) end --- @param x string|string[] diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index b0812123b9..fd68c2b910 100644 --- a/runtime/lua/vim/treesitter/languagetree.lua +++ b/runtime/lua/vim/treesitter/languagetree.lua @@ -98,9 +98,9 @@ local LanguageTree = {} LanguageTree.__index = LanguageTree ---- @package +--- @nodoc --- ---- |LanguageTree| contains a tree of parsers: the root treesitter parser for {lang} and any +--- LanguageTree contains a tree of parsers: the root treesitter parser for {lang} and any --- "injected" language parsers, which themselves may inject other languages, recursively. --- ---@param source (integer|string) Buffer or text string to parse @@ -108,7 +108,7 @@ LanguageTree.__index = LanguageTree ---@param opts vim.treesitter.LanguageTree.new.Opts? ---@return vim.treesitter.LanguageTree parser object function LanguageTree.new(source, lang, opts) - language.add(lang) + assert(language.add(lang)) opts = opts or {} if source == 0 then @@ -638,6 +638,8 @@ end ---Gets the set of included regions managed by this LanguageTree. This can be different from the ---regions set by injection query, because a partial |LanguageTree:parse()| drops the regions ---outside the requested range. +---Each list represents a range in the form of +---{ {start_row}, {start_col}, {start_bytes}, {end_row}, {end_col}, {end_bytes} }. ---@return table<integer, Range6[]> function LanguageTree:included_regions() if self._regions then @@ -732,7 +734,7 @@ local function add_injection(t, tree_index, pattern, lang, combined, ranges) table.insert(t[tree_index][lang][pattern].regions, ranges) end --- TODO(clason): replace by refactored `ts.has_parser` API (without registering) +-- TODO(clason): replace by refactored `ts.has_parser` API (without side effects) --- The result of this function is cached to prevent nvim_get_runtime_file from being --- called too often --- @param lang string parser name @@ -831,13 +833,7 @@ function LanguageTree:_get_injections() local start_line, _, end_line, _ = root_node:range() for pattern, match, metadata in - self._injection_query:iter_matches( - root_node, - self._source, - start_line, - end_line + 1, - { all = true } - ) + self._injection_query:iter_matches(root_node, self._source, start_line, end_line + 1) do local lang, combined, ranges = self:_get_injection(match, metadata) if lang then @@ -951,7 +947,7 @@ function LanguageTree:_edit( end end ----@package +---@nodoc ---@param bufnr integer ---@param changed_tick integer ---@param start_row integer @@ -1023,12 +1019,12 @@ function LanguageTree:_on_bytes( ) end ----@package +---@nodoc function LanguageTree:_on_reload() self:invalidate(true) end ----@package +---@nodoc function LanguageTree:_on_detach(...) self:invalidate(true) self:_do_callback('detach', ...) @@ -1087,7 +1083,7 @@ end --- Determines whether {range} is contained in the |LanguageTree|. --- ----@param range Range4 `{ start_line, start_col, end_line, end_col }` +---@param range Range4 ---@return boolean function LanguageTree:contains(range) for _, tree in pairs(self._trees) do @@ -1108,7 +1104,7 @@ end --- Gets the tree that contains {range}. --- ----@param range Range4 `{ start_line, start_col, end_line, end_col }` +---@param range Range4 ---@param opts? vim.treesitter.LanguageTree.tree_for_range.Opts ---@return TSTree? function LanguageTree:tree_for_range(range, opts) @@ -1133,9 +1129,21 @@ function LanguageTree:tree_for_range(range, opts) return nil end +--- Gets the smallest node that contains {range}. +--- +---@param range Range4 +---@param opts? vim.treesitter.LanguageTree.tree_for_range.Opts +---@return TSNode? +function LanguageTree:node_for_range(range, opts) + local tree = self:tree_for_range(range, opts) + if tree then + return tree:root():descendant_for_range(unpack(range)) + end +end + --- Gets the smallest named node that contains {range}. --- ----@param range Range4 `{ start_line, start_col, end_line, end_col }` +---@param range Range4 ---@param opts? vim.treesitter.LanguageTree.tree_for_range.Opts ---@return TSNode? function LanguageTree:named_node_for_range(range, opts) @@ -1147,7 +1155,7 @@ end --- Gets the appropriate language that contains {range}. --- ----@param range Range4 `{ start_line, start_col, end_line, end_col }` +---@param range Range4 ---@return vim.treesitter.LanguageTree tree Managing {range} function LanguageTree:language_for_range(range) for _, child in pairs(self._children) do diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua index ef5c2143a7..4614967799 100644 --- a/runtime/lua/vim/treesitter/query.lua +++ b/runtime/lua/vim/treesitter/query.lua @@ -247,8 +247,7 @@ end) --- ---@see [vim.treesitter.query.get()] M.parse = memoize('concat-2', function(lang, query) - language.add(lang) - + assert(language.add(lang)) local ts_query = vim._ts_parse_query(lang, query) return Query.new(lang, ts_query) end) @@ -487,8 +486,8 @@ predicate_handlers['any-vim-match?'] = predicate_handlers['any-match?'] ---@class vim.treesitter.query.TSMetadata ---@field range? Range ---@field conceal? string ----@field [integer] vim.treesitter.query.TSMetadata ----@field [string] integer|string +---@field [integer]? vim.treesitter.query.TSMetadata +---@field [string]? integer|string ---@alias TSDirective fun(match: table<integer,TSNode[]>, _, _, predicate: (string|integer)[], metadata: vim.treesitter.query.TSMetadata) @@ -620,16 +619,16 @@ local directive_handlers = { --- @field force? boolean --- --- Use the correct implementation of the match table where capture IDs map to ---- a list of nodes instead of a single node. Defaults to false (for backward ---- compatibility). This option will eventually become the default and removed. +--- a list of nodes instead of a single node. Defaults to true. This option will +--- be removed in a future release. --- @field all? boolean --- Adds a new predicate to be used in queries --- ---@param name string Name of the predicate, without leading # ----@param handler fun(match: table<integer,TSNode[]>, pattern: integer, source: integer|string, predicate: any[], metadata: table) +---@param handler fun(match: table<integer,TSNode[]>, pattern: integer, source: integer|string, predicate: any[], metadata: vim.treesitter.query.TSMetadata) --- - see |vim.treesitter.query.add_directive()| for argument meanings ----@param opts vim.treesitter.query.add_predicate.Opts +---@param opts? vim.treesitter.query.add_predicate.Opts function M.add_predicate(name, handler, opts) -- Backward compatibility: old signature had "force" as boolean argument if type(opts) == 'boolean' then @@ -642,7 +641,7 @@ function M.add_predicate(name, handler, opts) error(string.format('Overriding existing predicate %s', name)) end - if opts.all then + if opts.all ~= false then predicate_handlers[name] = handler else --- @param match table<integer, TSNode[]> @@ -667,7 +666,7 @@ end --- metadata table `metadata[capture_id].key = value` --- ---@param name string Name of the directive, without leading # ----@param handler fun(match: table<integer,TSNode[]>, pattern: integer, source: integer|string, predicate: any[], metadata: table) +---@param handler fun(match: table<integer,TSNode[]>, pattern: integer, source: integer|string, predicate: any[], metadata: vim.treesitter.query.TSMetadata) --- - 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. @@ -894,16 +893,10 @@ end --- index of the pattern in the query, a table mapping capture indices to a list --- of nodes, and metadata from any directives processing the match. --- ---- WARNING: Set `all=true` to ensure all matching nodes in a match are ---- returned, otherwise only the last node in a match is returned, breaking captures ---- involving quantifiers such as `(comment)+ @comment`. The default option ---- `all=false` is only provided for backward compatibility and will be removed ---- after Nvim 0.10. ---- --- Example: --- --- ```lua ---- for pattern, match, metadata in cquery:iter_matches(tree:root(), bufnr, 0, -1, { all = true }) do +--- for pattern, match, metadata in cquery:iter_matches(tree:root(), bufnr, 0, -1) do --- for id, nodes in pairs(match) do --- local name = query.captures[id] --- for _, node in ipairs(nodes) do @@ -925,11 +918,11 @@ end --- - max_start_depth (integer) if non-zero, sets the maximum start depth --- for each match. This is used to prevent traversing too deep into a tree. --- - match_limit (integer) Set the maximum number of in-progress matches (Default: 256). ---- - all (boolean) When set, the returned match table maps capture IDs to a list of nodes. ---- Older versions of iter_matches incorrectly mapped capture IDs to a single node, which is ---- incorrect behavior. This option will eventually become the default and removed. +--- - all (boolean) When `false` (default `true`), the returned table maps capture IDs to a single +--- (last) node instead of the full list of matching nodes. This option is only for backward +--- compatibility and will be removed in a future release. --- ----@return (fun(): integer, table<integer, TSNode[]>, table): pattern id, match, metadata +---@return (fun(): integer, table<integer, TSNode[]>, vim.treesitter.query.TSMetadata): pattern id, match, metadata function Query:iter_matches(node, source, start, stop, opts) opts = opts or {} opts.match_limit = opts.match_limit or 256 @@ -960,10 +953,10 @@ function Query:iter_matches(node, source, start, stop, opts) local captures = match:captures() - if not opts.all then + if opts.all == false then -- Convert the match table into the old buggy version for backward - -- compatibility. This is slow. Plugin authors, if you're reading this, set the "all" - -- option! + -- compatibility. This is slow, but we only do it when the caller explicitly opted into it by + -- setting `all` to `false`. local old_match = {} ---@type table<integer, TSNode> for k, v in pairs(captures or {}) do old_match[k] = v[#v] @@ -1034,7 +1027,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) - vim.treesitter.dev.edit_query(lang) + assert(vim.treesitter.dev.edit_query(lang)) end return M diff --git a/runtime/lua/vim/ui.lua b/runtime/lua/vim/ui.lua index 99b9b78e2a..532decf5e9 100644 --- a/runtime/lua/vim/ui.lua +++ b/runtime/lua/vim/ui.lua @@ -117,6 +117,8 @@ end --- -- Asynchronous. --- vim.ui.open("https://neovim.io/") --- vim.ui.open("~/path/to/file") +--- -- Use the "osurl" command to handle the path or URL. +--- vim.ui.open("gh#neovim/neovim!29490", { cmd = { 'osurl' } }) --- -- Synchronous (wait until the process exits). --- local cmd, err = vim.ui.open("$VIMRUNTIME") --- if cmd then @@ -125,23 +127,29 @@ end --- ``` --- ---@param path string Path or URL to open +---@param opt? { cmd?: string[] } Options +--- - cmd string[]|nil Command used to open the path or URL. --- ---@return vim.SystemObj|nil # Command object, or nil if not found. ---@return nil|string # Error message on failure, or nil on success. --- ---@see |vim.system()| -function M.open(path) +function M.open(path, opt) vim.validate({ path = { path, 'string' }, }) local is_uri = path:match('%w+:') if not is_uri then - path = vim.fn.expand(path) + path = vim.fs.normalize(path) end - local cmd --- @type string[] + opt = opt or {} + local cmd ---@type string[] + local job_opt = { text = true, detach = true } --- @type vim.SystemOpts - if vim.fn.has('mac') == 1 then + if opt.cmd then + cmd = vim.list_extend(opt.cmd --[[@as string[] ]], { path }) + elseif vim.fn.has('mac') == 1 then cmd = { 'open', path } elseif vim.fn.has('win32') == 1 then if vim.fn.executable('rundll32') == 1 then @@ -149,37 +157,79 @@ function M.open(path) else return nil, 'vim.ui.open: rundll32 not found' end + elseif vim.fn.executable('xdg-open') == 1 then + cmd = { 'xdg-open', path } + job_opt.stdout = false + job_opt.stderr = false 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, explorer.exe, xdg-open)' end - return vim.system(cmd, { text = true, detach = true }), nil + return vim.system(cmd, job_opt), nil end ---- Gets the URL at cursor, if any. -function M._get_url() - if vim.bo.filetype == 'markdown' then - local range = vim.api.nvim_win_get_cursor(0) - vim.treesitter.get_parser():parse(range) - -- marking the node as `markdown_inline` is required. Setting it to `markdown` does not - -- work. - local current_node = vim.treesitter.get_node { lang = 'markdown_inline' } - while current_node do - local type = current_node:type() - if type == 'inline_link' or type == 'image' then - local child = assert(current_node:named_child(1)) - return vim.treesitter.get_node_text(child, 0) +--- Returns all URLs at cursor, if any. +--- @return string[] +function M._get_urls() + local urls = {} ---@type string[] + + local bufnr = vim.api.nvim_get_current_buf() + local cursor = vim.api.nvim_win_get_cursor(0) + local row = cursor[1] - 1 + local col = cursor[2] + local extmarks = vim.api.nvim_buf_get_extmarks(bufnr, -1, { row, col }, { row, col }, { + details = true, + type = 'highlight', + overlap = true, + }) + for _, v in ipairs(extmarks) do + local details = v[4] + if details and details.url then + urls[#urls + 1] = details.url + end + end + + local highlighter = vim.treesitter.highlighter.active[bufnr] + if highlighter then + local range = { row, col, row, col } + local ltree = highlighter.tree:language_for_range(range) + local lang = ltree:lang() + local query = vim.treesitter.query.get(lang, 'highlights') + if query then + local tree = assert(ltree:tree_for_range(range)) + for _, match, metadata in query:iter_matches(tree:root(), bufnr, row, row + 1) do + for id, nodes in pairs(match) do + for _, node in ipairs(nodes) do + if vim.treesitter.node_contains(node, range) then + local url = metadata[id] and metadata[id].url + if url and match[url] then + for _, n in ipairs(match[url]) do + urls[#urls + 1] = + vim.treesitter.get_node_text(n, bufnr, { metadata = metadata[url] }) + end + end + end + end + end end - current_node = current_node:parent() end end - return vim.fn.expand('<cfile>') + + if #urls == 0 then + -- If all else fails, use the filename under the cursor + table.insert( + urls, + vim._with({ go = { isfname = vim.o.isfname .. ',@-@' } }, function() + return vim.fn.expand('<cfile>') + end) + ) + end + + return urls end return M diff --git a/runtime/lua/vim/version.lua b/runtime/lua/vim/version.lua index 0b149700b5..d64ef98d2d 100644 --- a/runtime/lua/vim/version.lua +++ b/runtime/lua/vim/version.lua @@ -174,6 +174,10 @@ function M._version(version, strict) -- Adapted from https://github.com/folke/la version = version:match('%d[^ ]*') end + if version == nil then + return nil + end + local prerel = version:match('%-([^+]*)') local prerel_strict = version:match('%-([0-9A-Za-z-]*)') if @@ -272,6 +276,7 @@ end --- ``` --- --- @see # https://github.com/npm/node-semver#ranges +--- @since 11 --- --- @param spec string Version range "spec" --- @return vim.VersionRange? @@ -371,6 +376,7 @@ end --- ``` --- --- @note Per semver, build metadata is ignored when comparing two otherwise-equivalent versions. +--- @since 11 --- ---@param v1 vim.Version|number[]|string Version object. ---@param v2 vim.Version|number[]|string Version to compare with `v1`. @@ -388,6 +394,7 @@ function M.cmp(v1, v2) end ---Returns `true` if the given versions are equal. See |vim.version.cmp()| for usage. +---@since 11 ---@param v1 vim.Version|number[]|string ---@param v2 vim.Version|number[]|string ---@return boolean @@ -396,6 +403,7 @@ function M.eq(v1, v2) end ---Returns `true` if `v1 <= v2`. See |vim.version.cmp()| for usage. +---@since 12 ---@param v1 vim.Version|number[]|string ---@param v2 vim.Version|number[]|string ---@return boolean @@ -404,6 +412,7 @@ function M.le(v1, v2) end ---Returns `true` if `v1 < v2`. See |vim.version.cmp()| for usage. +---@since 11 ---@param v1 vim.Version|number[]|string ---@param v2 vim.Version|number[]|string ---@return boolean @@ -412,6 +421,7 @@ function M.lt(v1, v2) end ---Returns `true` if `v1 >= v2`. See |vim.version.cmp()| for usage. +---@since 12 ---@param v1 vim.Version|number[]|string ---@param v2 vim.Version|number[]|string ---@return boolean @@ -420,6 +430,7 @@ function M.ge(v1, v2) end ---Returns `true` if `v1 > v2`. See |vim.version.cmp()| for usage. +---@since 11 ---@param v1 vim.Version|number[]|string ---@param v2 vim.Version|number[]|string ---@return boolean @@ -434,7 +445,8 @@ end --- { major = 1, minor = 0, patch = 1, prerelease = "rc1", build = "build.2" } --- ``` --- ---- @see # https://semver.org/spec/v2.0.0.html +---@see # https://semver.org/spec/v2.0.0.html +---@since 11 --- ---@param version string Version string to parse. ---@param opts table|nil Optional keyword arguments: diff --git a/runtime/lua/vim/vimhelp.lua b/runtime/lua/vim/vimhelp.lua index 4af6866d48..5579cc0174 100644 --- a/runtime/lua/vim/vimhelp.lua +++ b/runtime/lua/vim/vimhelp.lua @@ -30,4 +30,42 @@ function M.highlight_groups(patterns) vim.fn.setpos('.', save_cursor) end +--- Show a table of contents for the help buffer in a loclist +function M.show_toc() + local bufnr = vim.api.nvim_get_current_buf() + local parser = assert(vim.treesitter.get_parser(bufnr, 'vimdoc', { error = false })) + local query = vim.treesitter.query.parse( + parser:lang(), + [[ + (h1 (heading) @h1) + (h2 (heading) @h2) + (h3 (heading) @h3) + (column_heading (heading) @h4) + ]] + ) + local root = parser:parse()[1]:root() + local headings = {} + for id, node, _, _ in query:iter_captures(root, bufnr) do + local text = vim.treesitter.get_node_text(node, bufnr) + local capture = query.captures[id] + local row, col = node:start() + -- only column_headings at col 1 are headings, otherwise it's code examples + local is_code = (capture == 'h4' and col > 0) + -- ignore tabular material + local is_table = (capture == 'h4' and (text:find('\t') or text:find(' '))) + -- ignore tag-only headings + local is_tag = node:child_count() == 1 and node:child(0):type() == 'tag' + if not (is_code or is_table or is_tag) then + table.insert(headings, { + bufnr = bufnr, + lnum = row + 1, + text = (capture == 'h3' or capture == 'h4') and ' ' .. text or text, + }) + end + end + vim.fn.setloclist(0, headings, ' ') + vim.fn.setloclist(0, {}, 'a', { title = 'Help TOC' }) + vim.cmd.lopen() +end + return M |