diff options
Diffstat (limited to 'runtime/lua/vim')
42 files changed, 591 insertions, 733 deletions
diff --git a/runtime/lua/vim/_comment.lua b/runtime/lua/vim/_comment.lua index 044cd69716..efe289b3e1 100644 --- a/runtime/lua/vim/_comment.lua +++ b/runtime/lua/vim/_comment.lua @@ -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 79fe5a8513..f417bda3fb 100644 --- a/runtime/lua/vim/_defaults.lua +++ b/runtime/lua/vim/_defaults.lua @@ -282,6 +282,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.wrap = false + vim.wo.list = false + + -- This is gross. Proper list options support when? + local winhl = vim.o.winhighlight + if winhl ~= '' then + winhl = winhl .. ',' + end + vim.wo.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', @@ -470,10 +490,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 9f952db4fc..c7c8362bfb 100644 --- a/runtime/lua/vim/_editor.lua +++ b/runtime/lua/vim/_editor.lua @@ -608,10 +608,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. diff --git a/runtime/lua/vim/_meta/api.lua b/runtime/lua/vim/_meta/api.lua index c99eefa4f6..d91b2d08bf 100644 --- a/runtime/lua/vim/_meta/api.lua +++ b/runtime/lua/vim/_meta/api.lua @@ -100,6 +100,25 @@ function vim.api.nvim__inspect_cell(grid, row, col) end 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 +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. @@ -144,36 +163,6 @@ 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 @@ -276,14 +265,14 @@ function vim.api.nvim_buf_add_highlight(buffer, ns_id, hl_group, line, col_start --- @return boolean 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. +--- 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()`. @@ -686,8 +675,6 @@ function vim.api.nvim_buf_line_count(buffer) end --- • 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 function vim.api.nvim_buf_set_extmark(buffer, ns_id, line, col, opts) end @@ -1953,7 +1940,7 @@ function vim.api.nvim_set_current_win(window) end --- --- • on_win: called when starting to redraw a specific window. --- ``` ---- ["win", winid, bufnr, topline, botline] +--- ["win", winid, bufnr, toprow, botrow] --- ``` --- --- • on_line: called for each buffer line being redrawn. (The 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..e1f12868d0 100644 --- a/runtime/lua/vim/_meta/api_keysets_extra.lua +++ b/runtime/lua/vim/_meta/api_keysets_extra.lua @@ -26,13 +26,13 @@ error('Cannot require a meta file') --- @field url? boolean --- @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 --- diff --git a/runtime/lua/vim/_meta/builtin.lua b/runtime/lua/vim/_meta/builtin.lua index 75737bd040..3aca3cdfa5 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. diff --git a/runtime/lua/vim/_meta/builtin_types.lua b/runtime/lua/vim/_meta/builtin_types.lua index 9f0d2e7038..9afb8c84f4 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 diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua index 428b7c4d4f..155c93726b 100644 --- a/runtime/lua/vim/_meta/options.lua +++ b/runtime/lua/vim/_meta/options.lua @@ -974,8 +974,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 = "" @@ -1061,6 +1061,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 +1073,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 @@ -5781,8 +5791,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 +5814,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 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 84bb26a135..ad057d902b 100644 --- a/runtime/lua/vim/_meta/vimfn.lua +++ b/runtime/lua/vim/_meta/vimfn.lua @@ -2766,8 +2766,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. @@ -2837,8 +2838,9 @@ 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 @@ -3249,8 +3251,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 @@ -4969,10 +4971,21 @@ function vim.fn.libcallnr(libname, funcname, argument) end --- 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. +--- 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. --- To get the column number use |col()|. To get both use @@ -5297,8 +5310,9 @@ function vim.fn.mapcheck(name, mode, abbr) end --- 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 @@ -7634,8 +7648,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 any +--- @param middle any +--- @param end_ any +--- @param flags? string +--- @param skip? any +--- @param stopline? any +--- @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 +7668,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 any +--- @param middle any +--- @param end_ any +--- @param flags? string +--- @param skip? any +--- @param stopline? any +--- @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| @@ -9796,7 +9824,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 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/deprecated/health.lua b/runtime/lua/vim/deprecated/health.lua index 64a755b248..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 = {} ---@type {[1]: string, [2]: table, [3]: string}[] +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 dca7698356..c8e34258f5 100644 --- a/runtime/lua/vim/diagnostic.lua +++ b/runtime/lua/vim/diagnostic.lua @@ -108,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()|. @@ -122,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 @@ -203,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' @@ -247,9 +247,11 @@ local M = {} --- @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()|. @@ -1252,7 +1254,7 @@ end --- 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? {[1]:integer,[2]:integer} +--- @field pos? [integer,integer] --- --- Whether to loop around file or not. Similar to 'wrapscan'. --- (default: `true`) @@ -1857,16 +1859,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 diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua index 2ab6cc6059..cc2fbe0cec 100644 --- a/runtime/lua/vim/filetype.lua +++ b/runtime/lua/vim/filetype.lua @@ -4,7 +4,7 @@ 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.maptbl [string|vim.filetype.mapfn, {priority:integer}] --- @alias vim.filetype.mapping.value string|vim.filetype.mapfn|vim.filetype.maptbl --- @alias vim.filetype.mapping table<string,vim.filetype.mapping.value> @@ -873,6 +873,7 @@ local extension = { t6 = 'raku', p6 = 'raku', raml = 'raml', + rasi = 'rasi', rbs = 'rbs', rego = 'rego', rem = 'remind', @@ -1450,6 +1451,7 @@ local filename = { ['.firebaserc'] = 'json', ['.prettierrc'] = 'json', ['.stylelintrc'] = 'json', + ['.lintstagedrc'] = 'json', ['flake.lock'] = 'json', ['.babelrc'] = 'jsonc', ['.eslintrc'] = 'jsonc', diff --git a/runtime/lua/vim/filetype/detect.lua b/runtime/lua/vim/filetype/detect.lua index 58d2666564..c56ece6289 100644 --- a/runtime/lua/vim/filetype/detect.lua +++ b/runtime/lua/vim/filetype/detect.lua @@ -594,7 +594,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 @@ -1355,7 +1355,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) diff --git a/runtime/lua/vim/glob.lua b/runtime/lua/vim/glob.lua index ad4a915a94..6de2bc3e94 100644 --- a/runtime/lua/vim/glob.lua +++ b/runtime/lua/vim/glob.lua @@ -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) @@ -63,15 +65,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 = Cf((V('Ques') + V('Class') + V('Literal') - S(',}')) ^ 1, mul) + Cc(P(0)), Literal = P(1) / P, End = P(-1) * Cc(P(-1)), }) diff --git a/runtime/lua/vim/highlight.lua b/runtime/lua/vim/highlight.lua index 89298ce568..233bc50237 100644 --- a/runtime/lua/vim/highlight.lua +++ b/runtime/lua/vim/highlight.lua @@ -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,12 +45,23 @@ 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 + + local v_maxcol = vim.v.maxcol local pos1 = type(start) == 'string' and vim.fn.getpos(start) - or { bufnr, start[1] + 1, start[2] + 1, 0 } + 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, 0 } + 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) @@ -63,10 +72,14 @@ function M.range(bufnr, ns, higroup, start, finish, opts) end vim.api.nvim_buf_call(bufnr, function() - local max_col1 = vim.fn.col({ pos1[2], '$' }) - pos1[3] = math.min(pos1[3], max_col1) - local max_col2 = vim.fn.col({ pos2[2], '$' }) - pos2[3] = math.min(pos2[3], max_col2) + 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 end) local region = vim.fn.getregionpos(pos1, pos2, { @@ -77,6 +90,14 @@ function M.range(bufnr, ns, higroup, start, finish, opts) -- 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 @@ -84,17 +105,12 @@ function M.range(bufnr, ns, higroup, start, finish, opts) local start_col = res[1][3] - 1 local end_row = res[2][2] - 1 local end_col = res[2][3] - if regtype == 'V' then - end_row = end_row + 1 - end_col = 0 - end api.nvim_buf_set_extmark(bufnr, ns, start_row, start_col, { hl_group = higroup, end_row = end_row, end_col = end_col, priority = priority, strict = false, - scoped = scoped, }) end end @@ -158,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..6bddf0bc5e 100644 --- a/runtime/lua/vim/iter.lua +++ b/runtime/lua/vim/iter.lua @@ -276,7 +276,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 diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua index c2deac0113..623ccdd5cd 100644 --- a/runtime/lua/vim/lsp.lua +++ b/runtime/lua/vim/lsp.lua @@ -201,10 +201,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 @@ -393,7 +393,7 @@ local function on_client_exit(code, signal, client_id) vim.schedule(function() for bufnr in pairs(client.attached_buffers) do - if client and client.attached_buffers[bufnr] then + if client and client.attached_buffers[bufnr] and api.nvim_buf_is_valid(bufnr) then api.nvim_exec_autocmds('LspDetach', { buffer = bufnr, modeline = false, @@ -484,6 +484,7 @@ local function text_document_did_save_handler(bufnr) text = lsp._buf_get_full_text(bufnr), }, }) + util.buf_versions[bufnr] = 0 end local save_capability = vim.tbl_get(client.server_capabilities, 'textDocumentSync', 'save') if save_capability then @@ -574,11 +575,12 @@ local function buf_attach(bufnr) }) -- First time, so attach and set up stuff. api.nvim_buf_attach(bufnr, false, { - on_lines = function(_, _, _, firstline, lastline, new_lastline) + on_lines = function(_, _, changedtick, firstline, lastline, new_lastline) if #lsp.get_clients({ bufnr = bufnr }) == 0 then -- 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) end, @@ -602,6 +604,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 diff --git a/runtime/lua/vim/lsp/_changetracking.lua b/runtime/lua/vim/lsp/_changetracking.lua index ce701f0772..b2be53269f 100644 --- a/runtime/lua/vim/lsp/_changetracking.lua +++ b/runtime/lua/vim/lsp/_changetracking.lua @@ -1,5 +1,6 @@ local protocol = require('vim.lsp.protocol') local sync = require('vim.lsp.sync') +local util = require('vim.lsp.util') local api = vim.api local uv = vim.uv @@ -276,7 +277,7 @@ local function send_changes(bufnr, sync_kind, state, buf_state) client.notify(protocol.Methods.textDocument_didChange, { textDocument = { uri = uri, - version = vim.b[bufnr].changedtick, + version = util.buf_versions[bufnr], }, contentChanges = changes, }) 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..cbddd24630 100644 --- a/runtime/lua/vim/lsp/_meta/protocol.lua +++ b/runtime/lua/vim/lsp/_meta/protocol.lua @@ -3235,7 +3235,7 @@ error('Cannot require a meta file') --- ---*Note*: a label of type string should be a substring of its containing signature label. ---Its intended use case is to highlight the parameter label part in the `SignatureInformation.label`. ----@field label string|{ [1]: uinteger, [2]: uinteger } +---@field label string|[uinteger, uinteger] --- ---The human-readable doc-comment of this parameter. Will be shown ---in the UI but can be omitted. diff --git a/runtime/lua/vim/lsp/client.lua b/runtime/lua/vim/lsp/client.lua index b28fe2f797..d3ff918792 100644 --- a/runtime/lua/vim/lsp/client.lua +++ b/runtime/lua/vim/lsp/client.lua @@ -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) @@ -535,7 +535,7 @@ function Client:_run_callbacks(cbs, error_id, ...) end end ---- @package +--- @nodoc function Client:initialize() local config = self.config @@ -656,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 @@ -673,8 +673,8 @@ function Client:_request(method, params, handler, bufnr) end -- Ensure pending didChange notifications are sent so that the server doesn't operate on a stale state changetracking.flush(self, bufnr) + local version = lsp.util.buf_versions[bufnr] bufnr = resolve_bufnr(bufnr) - local version = vim.b[bufnr].changedtick log.debug(self._log_prefix, 'client.request', self.id, method, params, handler, bufnr) local success, request_id = self.rpc.request(method, params, function(err, result) local context = { @@ -861,7 +861,6 @@ 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) --- @@ -906,7 +905,6 @@ function Client:_exec_cmd(command, context, handler, on_unsupported) 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 @@ -922,13 +920,14 @@ function Client:_text_document_did_open_handler(bufnr) local params = { textDocument = { - version = vim.b[bufnr].changedtick, + version = 0, uri = vim.uri_from_bufnr(bufnr), languageId = self.get_language_id(bufnr, filetype), text = lsp._buf_get_full_text(bufnr), }, } self.notify(ms.textDocument_didOpen, params) + lsp.util.buf_versions[bufnr] = params.textDocument.version -- Next chance we get, we should re-do the diagnostics vim.schedule(function() @@ -941,7 +940,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) @@ -1064,7 +1062,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) @@ -1087,7 +1084,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/completion.lua b/runtime/lua/vim/lsp/completion.lua index 39c0c5fa29..4b7deabf41 100644 --- a/runtime/lua/vim/lsp/completion.lua +++ b/runtime/lua/vim/lsp/completion.lua @@ -30,7 +30,7 @@ local buf_handles = {} --- @nodoc --- @class vim.lsp.completion.Context local Context = { - cursor = nil, --- @type { [1]: integer, [2]: integer }? + cursor = nil, --- @type [integer, integer]? last_request_time = nil, --- @type integer? pending_requests = {}, --- @type function[] isIncomplete = false, @@ -153,7 +153,8 @@ local function get_completion_word(item) return item.label end elseif item.textEdit then - return item.textEdit.newText + local word = item.textEdit.newText + return word:match('^(%S*)') or word elseif item.insertText and item.insertText ~= '' then return item.insertText end @@ -201,6 +202,24 @@ local function get_items(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|. --- @@ -216,58 +235,48 @@ function M._lsp_to_complete_items(result, prefix, client_id) return {} end - if prefix ~= '' then - ---@param item lsp.CompletionItem - local function match_prefix(item) - if item.filterText then - return next(vim.fn.matchfuzzy({ item.filterText }, prefix)) - end - return true + local matches = prefix == '' and function() + return true + end or function(item) + if item.filterText then + return next(vim.fn.matchfuzzy({ item.filterText }, prefix)) end - - items = vim.tbl_filter(match_prefix, items) --[[@as lsp.CompletionItem[]|]] + return true end - table.sort(items, function(a, b) - return (a.sortText or a.label) < (b.sortText or b.label) - end) - - local matches = {} + local candidates = {} 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 '', - icase = 1, - dup = 1, - empty = 1, - user_data = { - nvim = { - lsp = { - completion_item = item, - client_id = client_id, + if matches(item) then + local word = get_completion_word(item) + table.insert(candidates, { + 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, + user_data = { + nvim = { + lsp = { + completion_item = item, + client_id = client_id, + }, }, }, - }, - }) + }) + end end - return matches + ---@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 @@ -340,79 +349,7 @@ function M._convert_results( return matches, server_start_boundary 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 - - 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 - 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.id, - 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 - ---- @param clients table<integer, vim.lsp.Client> +--- @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 }>) @@ -422,7 +359,8 @@ local function request(clients, bufnr, win, callback) local request_ids = {} --- @type table<integer, integer> local remaining_requests = vim.tbl_count(clients) - for client_id, client in pairs(clients) do + 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 } @@ -447,6 +385,64 @@ local function request(clients, bufnr, win, callback) end end +local function trigger(bufnr, clients) + reset_timer() + Context:cancel_pending() + + 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 @@ -581,8 +577,10 @@ end ---@param bufnr integer ---@param opts vim.lsp.completion.BufferOpts local function enable_completions(client_id, bufnr, opts) - if not buf_handles[bufnr] then - buf_handles[bufnr] = { clients = {}, triggers = {} } + local buf_handle = buf_handles[bufnr] + if not buf_handle then + buf_handle = { clients = {}, triggers = {} } + buf_handles[bufnr] = buf_handle -- Attach to buffer events. api.nvim_buf_attach(bufnr, false, { @@ -623,12 +621,12 @@ local function enable_completions(client_id, bufnr, opts) end end - if not buf_handles[bufnr].clients[client_id] then + 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_handles[bufnr].clients[client_id] = client + buf_handle.clients[client_id] = client -- Add the new client to the clients that should be triggered by its trigger characters. --- @type string[] @@ -638,10 +636,10 @@ local function enable_completions(client_id, bufnr, opts) 'triggerCharacters' ) or {} for _, char in ipairs(triggers) do - local clients_for_trigger = buf_handles[bufnr].triggers[char] + local clients_for_trigger = buf_handle.triggers[char] if not clients_for_trigger then clients_for_trigger = {} - buf_handles[bufnr].triggers[char] = 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 @@ -693,63 +691,38 @@ end --- Trigger LSP completion in the current buffer. function M.trigger() - reset_timer() - Context:cancel_pending() - - local win = api.nvim_get_current_win() local bufnr = api.nvim_get_current_buf() - 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 clients = (buf_handles[bufnr] or {}).clients or {} - 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 + trigger(bufnr, clients) +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 +--- 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 - 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) + trigger(bufnr, clients) - table.insert(Context.pending_requests, cancel_request) + -- 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/handlers.lua b/runtime/lua/vim/lsp/handlers.lua index 38c43893eb..44548fec92 100644 --- a/runtime/lua/vim/lsp/handlers.lua +++ b/runtime/lua/vim/lsp/handlers.lua @@ -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/inlay_hint.lua b/runtime/lua/vim/lsp/inlay_hint.lua index 78f309abf7..2d784816cb 100644 --- a/runtime/lua/vim/lsp/inlay_hint.lua +++ b/runtime/lua/vim/lsp/inlay_hint.lua @@ -43,7 +43,7 @@ function M.on_inlayhint(err, result, ctx, _) return end local bufnr = assert(ctx.bufnr) - if vim.b[bufnr].changedtick ~= ctx.version then + if util.buf_versions[bufnr] ~= ctx.version then return end local client_id = ctx.client_id @@ -324,7 +324,7 @@ api.nvim_set_decoration_provider(namespace, { return end - if bufstate.version ~= vim.b[bufnr].changedtick then + if bufstate.version ~= util.buf_versions[bufnr] then return end @@ -348,7 +348,7 @@ api.nvim_set_decoration_provider(namespace, { text = text .. part.value end end - local vt = {} --- @type {[1]: string, [2]: string?}[] + local vt = {} --- @type [string, string?][] if hint.paddingLeft then vt[#vt + 1] = { ' ' } end diff --git a/runtime/lua/vim/lsp/log.lua b/runtime/lua/vim/lsp/log.lua index 9f2bd71158..0438ca84af 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) diff --git a/runtime/lua/vim/lsp/protocol.lua b/runtime/lua/vim/lsp/protocol.lua index 8ac4cc794b..eb18043843 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. @@ -308,326 +307,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. @@ -1250,14 +941,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..5e2b041a0a 100644 --- a/runtime/lua/vim/lsp/rpc.lua +++ b/runtime/lua/vim/lsp/rpc.lua @@ -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 @@ -502,7 +502,7 @@ function Client:handle_body(body) if decoded.error then decoded.error = setmetatable(decoded.error, { __tostring = M.format_rpc_error, - }) --- @type table + }) end self:try_call( M.client_errors.SERVER_RESULT_CALLBACK_ERROR, diff --git a/runtime/lua/vim/lsp/semantic_tokens.lua b/runtime/lua/vim/lsp/semantic_tokens.lua index 278014a4ea..f92c0eb2e6 100644 --- a/runtime/lua/vim/lsp/semantic_tokens.lua +++ b/runtime/lua/vim/lsp/semantic_tokens.lua @@ -116,7 +116,7 @@ local function tokens_to_ranges(data, bufnr, client, request) if elapsed_ns > yield_interval_ns then vim.schedule(function() - coroutine.resume(co, vim.b[bufnr].changedtick) + coroutine.resume(co, util.buf_versions[bufnr]) end) if request.version ~= coroutine.yield() then -- request became stale since the last time the coroutine ran. @@ -197,12 +197,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' }, { @@ -275,7 +269,7 @@ end --- ---@package function STHighlighter:send_request() - local version = vim.b[self.bufnr].changedtick + local version = util.buf_versions[self.bufnr] self:reset_timer() @@ -418,7 +412,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 == vim.b[self.bufnr].changedtick then + if current_result.version and 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 +773,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/util.lua b/runtime/lua/vim/lsp/util.lua index d1f0e97065..ae6de591b3 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -173,11 +173,11 @@ local _str_byteindex_enc = M._str_byteindex_enc --- 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 +238,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 +247,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 @@ -274,11 +275,11 @@ local function get_lines(bufnr, rows) 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 +308,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 +323,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 @@ -343,7 +345,7 @@ local function get_line_byte_from_position(bufnr, position, offset_encoding) 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 +368,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 +386,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 @@ -391,12 +397,13 @@ function M.apply_text_edits(text_edits, bufnr, offset_encoding) return a.range.start.character > b.range.start.character end if a._index ~= b._index then - return a._index > b._index + return a._index < b._index end + return false 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 @@ -481,8 +488,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 +516,8 @@ function M.apply_text_document_edit(text_document_edit, index, offset_encoding) and ( text_document.version and text_document.version > 0 - and vim.b[bufnr].changedtick > text_document.version + and M.buf_versions[bufnr] + and M.buf_versions[bufnr] > text_document.version ) then print('Buffer ', text_document.uri, ' newer than edits.') @@ -533,6 +541,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) @@ -677,7 +686,7 @@ 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) @@ -723,8 +732,8 @@ 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) @@ -759,11 +768,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 @@ -960,7 +969,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. @@ -1017,7 +1026,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 @@ -1038,7 +1047,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 @@ -1154,7 +1163,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 @@ -1669,7 +1678,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) @@ -1720,7 +1729,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 @@ -1747,7 +1758,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 = {} @@ -1759,7 +1770,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[] @@ -1774,6 +1785,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 @@ -1781,13 +1795,18 @@ 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 col = M._str_byteindex_enc(line, pos.character, offset_encoding) + local end_col = M._str_byteindex_enc(lines[end_row] or '', 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, }) @@ -1873,7 +1892,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') @@ -1898,7 +1917,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) @@ -1919,7 +1938,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 @@ -1932,7 +1951,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({ @@ -2033,15 +2052,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 @@ -2049,8 +2069,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 @@ -2060,7 +2080,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) @@ -2199,16 +2219,9 @@ function M._refresh(method, opts) end end ----@nodoc ----@deprecated ----@type table<integer,integer> -M.buf_versions = setmetatable({}, { - __index = function(_, bufnr) - vim.deprecate('vim.lsp.util.buf_versions', 'vim.b.changedtick', '0.13') - return vim.b[bufnr].changedtick - end, -}) - M._get_line_byte_from_position = get_line_byte_from_position +---@nodoc +M.buf_versions = {} ---@type table<integer,integer> + return M diff --git a/runtime/lua/vim/provider/health.lua b/runtime/lua/vim/provider/health.lua index fa2c452268..860f839f23 100644 --- a/runtime/lua/vim/provider/health.lua +++ b/runtime/lua/vim/provider/health.lua @@ -353,7 +353,7 @@ end --- their respective paths. If either of those is invalid, return two empty --- strings, effectively ignoring pyenv. --- ---- @return {[1]: string, [2]: string} +--- @return [string, string] local function check_for_pyenv() local pyenv_path = vim.fn.resolve(vim.fn.exepath('pyenv')) diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua index 0ec79e1dc7..7fd29d5f7b 100644 --- a/runtime/lua/vim/shared.lua +++ b/runtime/lua/vim/shared.lua @@ -789,7 +789,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 @@ -1139,4 +1139,82 @@ function vim._defer_require(root, mod) }) end +--- @nodoc +--- @class vim.context.mods +--- @field buf? integer +--- @field emsg_silent? boolean +--- @field hide? boolean +--- @field horizontal? boolean +--- @field keepalt? boolean +--- @field keepjumps? boolean +--- @field keepmarks? boolean +--- @field keeppatterns? boolean +--- @field lockmarks? boolean +--- @field noautocmd? boolean +--- @field options? table<string, any> +--- @field sandbox? boolean +--- @field silent? boolean +--- @field unsilent? boolean +--- @field win? integer + +--- Executes function `f` with the given context specification. +--- +--- @param context vim.context.mods +function vim._with(context, f) + vim.validate('context', context, 'table') + vim.validate('f', f, 'function') + + vim.validate('context.buf', context.buf, 'number', true) + vim.validate('context.emsg_silent', context.emsg_silent, 'boolean', true) + vim.validate('context.hide', context.hide, 'boolean', true) + vim.validate('context.horizontal', context.horizontal, '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.options', context.options, '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) + + -- 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 + end + + -- Store original options + local previous_options ---@type table<string, any> + if context.options then + previous_options = {} + for k, v in pairs(context.options) do + previous_options[k] = + vim.api.nvim_get_option_value(k, { win = context.win, buf = context.buf }) + vim.api.nvim_set_option_value(k, v, { win = context.win, buf = context.buf }) + end + end + + local retval = { vim._with_c(context, f) } + + -- Restore original options + if previous_options then + for k, v in pairs(previous_options) do + vim.api.nvim_set_option_value(k, v, { win = context.win, buf = context.buf }) + end + end + + return unpack(retval) +end + return vim diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index db544c1ab1..e36aacfd94 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -335,7 +335,7 @@ 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? diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua index eecf1ad6b1..04a3c62cf1 100644 --- a/runtime/lua/vim/treesitter/_fold.lua +++ b/runtime/lua/vim/treesitter/_fold.lua @@ -383,7 +383,6 @@ 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) diff --git a/runtime/lua/vim/treesitter/_meta.lua b/runtime/lua/vim/treesitter/_meta.lua index 177699a207..2aedf5754e 100644 --- a/runtime/lua/vim/treesitter/_meta.lua +++ b/runtime/lua/vim/treesitter/_meta.lua @@ -33,6 +33,7 @@ error('Cannot require a meta file') ---@field iter_children fun(self: TSNode): fun(): TSNode, string ---@field field fun(self: TSNode, name: string): TSNode[] ---@field byte_length fun(self: TSNode): integer +---@field __has_ancestor fun(self: TSNode, node_types: string[]): boolean local TSNode = {} ---@alias TSLoggerCallback fun(logtype: 'parse'|'lex', msg: string) @@ -62,6 +63,7 @@ local TSNode = {} ---@field patterns table<integer, (integer|string)[][]> --- @param lang string +--- @return table vim._ts_inspect_language = function(lang) end ---@return integer diff --git a/runtime/lua/vim/treesitter/dev.lua b/runtime/lua/vim/treesitter/dev.lua index ca8cf85eda..56608bbf14 100644 --- a/runtime/lua/vim/treesitter/dev.lua +++ b/runtime/lua/vim/treesitter/dev.lua @@ -174,7 +174,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) diff --git a/runtime/lua/vim/treesitter/highlighter.lua b/runtime/lua/vim/treesitter/highlighter.lua index d2f986b874..003f7e0169 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`. --- @@ -232,7 +232,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) diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua index b0812123b9..3523ea95e0 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 @@ -951,7 +951,7 @@ function LanguageTree:_edit( end end ----@package +---@nodoc ---@param bufnr integer ---@param changed_tick integer ---@param start_row integer @@ -1023,12 +1023,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', ...) diff --git a/runtime/lua/vim/ui.lua b/runtime/lua/vim/ui.lua index 99b9b78e2a..f168da4955 100644 --- a/runtime/lua/vim/ui.lua +++ b/runtime/lua/vim/ui.lua @@ -179,7 +179,13 @@ function M._get_url() current_node = current_node:parent() end end - return vim.fn.expand('<cfile>') + + local old_isfname = vim.o.isfname + vim.cmd [[set isfname+=@-@]] + local url = vim.fn.expand('<cfile>') + vim.o.isfname = old_isfname + + return url end return M diff --git a/runtime/lua/vim/vimhelp.lua b/runtime/lua/vim/vimhelp.lua index 4af6866d48..33324602c9 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 = vim.treesitter.get_parser(bufnr, 'vimdoc') + 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 |