summaryrefslogtreecommitdiffstatshomepage
path: root/runtime/lua/vim/lsp
AgeCommit message (Collapse)AuthorFiles
2026-04-24fix(lsp): handle self-mapped methods in supports_method #39383nightlyTristan Knight1
Problem: The LSP client incorrectly checks for server capabilities when determining support for self-mapped methods (e.g., 'shutdown'), which do not have corresponding capabilities in the server's response. This leads to false negatives when checking if such methods are supported. This was handled correctly for dynamic registrations, but not for static. Methods such as 'shutdown', do not have a related server capability and should be assumed to be supported. Solution: Update the `supports_method` logic to always return true for self-mapped methods.
2026-04-24fix(lsp): more info in error msg, deduplicate test #39359Justin M. Keyes1
2026-04-23fix(lsp): handle null id in JSON-RPC responses #38340atusy1
Problem: LSP spec allows response message to have a null request-id. This may happen when for example client sends unparseable request. https://github.com/microsoft/language-server-protocol/issues/196 Solution: Guard the server response branches against id=vim.NIL (json null), and handle error responses with null id by logging a warning and dispatching on error. Problem: CI (ubuntu asan, ubuntu tsan, windows) reports `uv_loop_close() hang?` from the two new null-id response tests. The leaked handle is the server-side accepted TCP socket created inside `server:listen` callback. The tests closed only the listener but not the accepted socket, so libuv could not finish shutting down the loop and each test session took ~2s extra to exit. Solution: Hoist the accepted socket to the outer `exec_lua` scope and close it at teardown before closing the listener. The close runs synchronously inside `exec_lua`, so the loop has time to dispose the handle before the session exits. * test(lsp): close accepted socket on read-loop exit/error Match the precedent in the handler test ("handler can return false as response") and the shared `_create_tcp_server` helper in `test/functional/plugin/lsp/testutil.lua`: close the accepted socket from inside the `create_read_loop` exit/error callbacks. The teardown close added in the previous commit remains as belt-and-suspenders, so the socket is disposed whether the server goes away first or the client does.
2026-04-23fix(lsp): malformed edit if apply_text_edits() is called twice #34954geril071
Problem: Use vim.lsp.util.apply_text_edits to re-apply the same textedit causes an incorrect edit, because apply_text_edits silently modifies the parameter. Solution: - Avoid changing `text_edit._index`. - Document this fun feature. Helped-by: Riley Bruins <ribru17@hotmail.com> Helped-by: Yi Ming <ofseed@foxmail.com> Co-authored-by: Justin M. Keyes <justinkz@gmail.com>
2026-04-23fix(lsp): callHierarchy/outgoingCalls ranges are relative to caller, not ↵Ashley Hauck1
callee #39336 Problem: The fromRanges field of the result of callHierarchy/outgoingCalls is documented as being relative to the caller. Using vim.lsp.buf.outgoing_calls() opened the qflist with an entry with the callee's filename, but the caller's line number. Solution: Open the qflist with the callers file (the bufnr from the request), rather than the callees (the uri from the resulting CallHierarchyItem)
2026-04-23fix(lsp): filter code_action diagnostics to the cursor #38988Barrett Ruth1
Problem: Cursor-position `vim.lsp.buf.code_action()` requests include all diagnostics on the current line, so unrelated same-line diagnostics affect the returned actions. Solution: Filter same-line diagnostics to the cursor position for cursor-position requests.
2026-04-22perf(lsp): clear table by table.clear() #39222Yi Ming2
benchmark: https://gist.github.com/ofseed/6224529d77c016c36f7ab2f977059848 local rounds = tonumber(arg[1]) or 1000 local count = tonumber(arg[2]) or 1000 -- Load the table.clear function. local clear = require("table.clear") local function fill(t, n) for i = 1, n do t[i] = i end end local function bench_reassign(n_rounds, n_items) local t = {} local start = os.clock() for _ = 1, n_rounds do t = {} collectgarbage("collect") fill(t, n_items) end return os.clock() - start end local function bench_reassign_no_gc(n_rounds, n_items) local t = {} local start = os.clock() for _ = 1, n_rounds do t = {} fill(t, n_items) end return os.clock() - start end local function bench_clear(n_rounds, n_items) local t = {} local start = os.clock() for _ = 1, n_rounds do clear(t) fill(t, n_items) end return os.clock() - start end -- Warm up LuaJIT before the real benchmark. do local t = {} for _ = 1, 2000 do clear(t) fill(t, count) end end collectgarbage("collect") local reassign_time = bench_reassign(rounds, count) collectgarbage("collect") local reassign_no_gc_time = bench_reassign_no_gc(rounds, count) collectgarbage("collect") local clear_time = bench_clear(rounds, count) print(string.format("rounds=%d count=%d", rounds, count)) print(string.format("t = {} + GC : %.6f s", reassign_time)) print(string.format("t = {} : %.6f s", reassign_no_gc_time)) print(string.format("table.clear : %.6f s", clear_time)) print(string.format("vs + GC : %.2fx", reassign_time / clear_time)) print(string.format("vs no GC : %.2fx", reassign_no_gc_time / clear_time)) benchmark result: rounds=1000 count=1000 t = {} + GC : 0.022469 s t = {} : 0.002570 s table.clear : 0.000387 s vs + GC : 58.06x vs no GC : 6.64x `count` is how many items the table has, and `round` is how many rounds we fill the table, clear, and then refill it. `table = {}` is clear the table by resigning a new empty one, because this script does not run persistently like nvim so GC is not triggered, so I added another extreme control group that manually triggers GC.
2026-04-19fix(lsp): notify when maximum created `hl` groups is reached #39231Maria Solano1
2026-04-19docs(lsp): description for `on_list` example #39230Maria Solano1
2026-04-18fix(lsp): stale codelens after external file change (#39203)Jaehwang Jung1
Problem: Codelens virtual lines remain on stale rows after an external file change and buffer reload. Solution: Clear codelens extmarks and cached row/version state in `on_reload` before requesting fresh code lenses.
2026-04-18fix(lsp): show CompletionItem.detail in info popup #38904glepnir1
Problem: completionItem/resolve response's `detail` field is silently dropped. Only `documentation` is shown in the popup. Solution: Prepend `detail` as a fenced code block before `documentation` in the info popup, skipping if documentation already contains it.
2026-04-18docs: misc #39045Justin M. Keyes1
2026-04-18fix(lsp): skip codelens refresh redraw for deleted buffer #39193Jaehwang Jung1
Problem: After on_refresh() sends a textDocument/codeLens request, the buffer may be deleted before the response arrives. The response callback then tries to redraw that deleted buffer and raises Invalid buffer id error. Solution: Check buffer validity before redrawing. AI-assisted: Codex Co-authored-by: Yi Ming <ofseed@foxmail.com>
2026-04-17fix(lsp): limit number of created highlight groups (#39133)Evgeni Chasnovski1
* fix(api): allow silencing "Too many highlight groups" error Problem: Using Lua's `vim.api.nvim_set_hl(0, 'New', {...})` can fail if there are too many existing highlight groups. However, this error can not be silenced with `pcall`. Solution: Make it possible to silence in `nvim_set_hl` and `nvim_get_hl_id_by_name`. * fix(lsp): limit number of groups created by `document_color()` Problem: A file can contain many string colors that would be highlighted by an LSP server. If this number crosses 19999 (maximum number of allowed highlight groups), there are general issues with creating other highlight groups, which can break functionality outside of `vim.lsp.document_color`. Solution: Limit number of highlight groups that are created by `vim.lsp.document_color` to 10000 (half of allowed maximum). This is not a 100% solution (since there can exist more than 10000 other highlight groups), but explicitly checking number of groups is slow and 10000 should (hopefully) be enough for most use cases.
2026-04-16refactor(lsp): provide a default list handler example #39005Yi Ming1
Problem: Difficult for us to provide default handlers for functions like `vim.lsp.buf.definition`. When users wanted to fine-tune the default behavior, they don't know how. Solution: - Document an example providing `on_list` boilerplate to make it easier for users to modify and override. - Also, considering that the parameters of the previous `on_list`(`vim.lsp.ListOpts.OnList`) are compatible with the parameters of `setqflist`, remove that custom type in favor of passing `vim.fn.setqflist.what`.
2026-04-16test: lint naming conventions #39117Justin M. Keyes1
Problem: Naming conventions are not automatically checked. Solution: Add a check to the doc generator. Eventually we should extract this somehow, but that will require refactoring the doc generator... Note: this also checks non-public functions, basically anything that passes through `gen_eval_files.lua` and `gen_vimdoc.lua`. And that's a good thing.
2026-04-16refactor(lsp): fix typing for LSP methods #39099Luis Calle1
Problem Some variables use the wrong type (ClientToServer instead of ServerToClient) and some use vaguer types that could be more strict. Solution Use the correct types.
2026-04-15fix(lsp): set 'winfixbuf' in open_floating_preview() window #39058Raizento1
Problem: The window opened by `vim.lsp.util.open_floating_preview()` allows its buffer to be switched. Presumably that only happens by accident and is disorienting. Solution: Set 'winfixbuf' in the open_floating_preview() window.
2026-04-15refactor: update usages of deprecated "buffer" param #39089Justin M. Keyes3
2026-04-15refactor(api): rename "window" to "win" (positional parameters) #39083Justin M. Keyes1
continues d0af4cd9094f. This commit renames positional parameters. This is only "cosmetic", but is intended to make it extra clear which name is preferred, since people often copy existing code despite the guidelines in `:help dev-naming`.
2026-04-15feat(lsp): highlight foldtext via treesitter #38789Yi Ming1
Problem: To support `collapsedText`, which allows the LSP server to determine the content of the foldtext, we provided `vim.lsp.foldtext()`. However, such content does not have highlighting. Solution Treat the filetype of `collapsedText` as the filetype of the corresponding buffer and use tree-sitter to highlight it.
2026-04-14docs: lsp, options, api #38980Justin M. Keyes3
docs: lsp, options - revert bogus change to `_meta/builtin_types.lua` from 3a4a66017b74 Close #38991 Co-authored-by: David Mejorado <david.mejorado@gmail.com>
2026-04-13feat(completion): completeopt=preselect, LSP CompletionItem.preselect #36613glepnir2
Problem: LSP CompletionItem.preselect is not supported. https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#completionClientCapabilities Solution: - Add "preselect" field to complete-items and "preselect" flag to 'completeopt'. - Set preselectSupport=true in LSP client capabilities.
2026-04-12fix(lsp): show_document can't position cursor past EOL in insert-mode #38566Lars Debor1
Problem: vim.lsp.util.show_document insert mode is unable to set the cursor after the target character position if the target character is at end of line. Solution: Move cursor after the target character (in append position) in this case.
2026-04-12fix(lsp): send didOpen on save to all clients+groups #37454Emilv21
Problem: _get_and_set_name edits the name for the whole group, thus only one client per group gets the didOpen message. Solution: move the logic to _changetracking and loop over every client per group.
2026-04-11fix(lsp): check stale context in hover/signature callback #38724glepnir1
Problem: hover/signature callback lacked consistency checks, so slow LSP servers could open a float after the cursor had already moved away. Solution: guard the callback with buf validity, buf version, and cursor position checks before opening the float. Also fix table capacity calculation.
2026-04-11fix(lsp): codelens text flickers #38782Jaehwang Jung1
Problem: When a new textDocument/codeLens response arrives with unresolved lenses, on_win clears the existing codelens row before codeLens/resolve completes. This causes the displayed codelens text to flicker while typing. Solution: Keep the current virtual lines if any of the refreshed lenses are still unresolved. Clear the old virtual lines only when the line no longer has lenses or all its lenses are resolved. A trade-off is that the user may temporarily see outdated codelenses. However, that's preferable to spamming updates on every refresh. AI-assisted: Codex
2026-04-10feat(ex): add `:log` commandOlivia Kinnear1
2026-04-09refactor(lsp): share code by `get_locations` #38902Yi Ming1
Problem: `vim.lsp.buf.definition`/`vim.lsp.buf.declaration` use the same underlying code via `get_locations`, whereas `vim.lsp.buf.reference` does not. This is because `buf.reference` does not perform a jump when there is only one item. Solution: In #38510, I simplified the jump logic using `:cfirst`, so they can now share code more easily. Additionally, this PR enables `buf.definition` to display the corresponding qflist name.
2026-04-08feat(api): rename buffer to buf #35330Jordan7
Problem: `:help dev-name-common` states that "buf" should be used instead of "buffer" but there are cases where buffer is mentioned in the lua API. Solution: - Rename occurrences of "buffer" to "buf" for consistency with the documentation. - Support (but deprecate) "buffer" for backwards compatibility. Co-authored-by: Justin M. Keyes <justinkz@gmail.com>
2026-04-08fix(lsp): apply_text_edits causes unwanted BufDelete events #38778glepnir1
Problem: Since 2f6d1d3c887a87d9402137425b418dd12a904aac, `apply_text_edits` unconditionally sets `buflisted=true`, causing spurious BufDelete events if plugins restore the original 'buflisted' state on unlisted buffers: https://github.com/neovim/neovim/blob/65ef6cec1cb766334c59d3255595dfe523b11020/src/nvim/option.c#L2159-L2169 Solution: - Don't set 'buflisted' in `apply_text_edits`. Set it more narrowly, in `apply_workspace_edit` where the semantics requires affected buffers to be visible to the user. - Also skip setting 'buflisted' if it would not be changed, to avoid redundant `OptionSet` events.
2026-04-08fix(diagnostics)!: restore `is_pull` namespace argument #38698Maria Solano2
Problem: The current LSP diagnostic implementation can't differ between a pull diagnostic with no identifier and a set of diagnostics provided via push diagnostics. "Anonymous pull providers" are expected by the protocol https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#diagnosticOptions , depending on how the capability was registered: - Dynamic registrations have an identifier. - Static registrations will not. Solution: Restore the `is_pull` argument removed in https://github.com/neovim/neovim/pull/37938, keeping the identifier of pull diagnostic collections.
2026-04-08refactor(filetype): move _get_known_filetypes, return a Set #38886Justin M. Keyes1
- `_get_known_filetypes` may be useful for other internal code, so move it to an internal function. - Use a set instead of a list, for performance.
2026-04-08fix(health): misleading warnings re filetypes registered w/ ↵Barrett Ruth1
vim.filetype.add() #38867 Problem: `:checkhealth vim.lsp` validates configured filetypes against `getcompletion('', 'filetype')`. This only reflects runtime support files. This causes false warnings in `:checkhealth vim.lsp` for configured filetypes that are known to the Lua filetype registry, including values added with `vim.filetype.add()` and built-in registry-only filetypes. Solution: Build the healthcheck's known-filetype set from both `getcompletion('', 'filetype')` and `vim.filetype.inspect()`.
2026-04-06feat(vim.pos)!: require `buf` param on vim.pos, vim.range #38665Luis Calle3
Problem: `buf` is optional even though its needed to perform conversions and the ordering of `(buf, row, col)` is not consistent. Solution: make `buf` mandatory on `vim.range` and `vim.pos` and enforce the `buf, row, col` ordering
2026-04-05fix(lsp): do not respond to codelens refresh if a request is already ↵Yi Ming1
scheduled (#38801)
2026-04-02refactor(lsp): remove implicit rpc error tostring #38707Yi Ming4
Problem: LSP error responses implicitly rely on a custom `__tostring` function (`vim.lsp.rpc.format_rpc_error`) for formatting. This causes errors that are not created via `vim.lsp.rpc.error` to behave inconsistently with those that are. Furthermore, we usually use `log.error` to print these errors, which uses `vim.inspect` under the hood, so the custom `__tostring` provides little benefit. This increases the difficulty of refactoring the code, as it tightly couples RPC error handling with the LSP. Solution: Convert every potential `__tostring` call to an explicit one. Since we don't describe this behavior in the documentation, this should not be a breaking change.
2026-04-01docs: misc #38584Justin M. Keyes1
2026-04-01Merge #38560 refactor vim.lsp.rpcJustin M. Keyes3
2026-04-01refactor(lsp): better encapsulation and readability, inline unnecessaryYi Ming2
2026-03-31fix(lsp): use `winresetview()` to avoid switching to normal mode (#38641)Yi Ming1
2026-03-30docs: fix syntax errors in examples #38606skewb1k1
2026-03-30feat(lsp): respect 'switchbuf' for jump commands, drop `reuse_win` #38510Yi Ming1
Problem: LSP jump operations such as `buf.definition`/`buf.type_definition` do not follow the 'switchbuf' option. Instead their behavior is controlled by `vim.lsp.LocationOpts.reuse_win`. When `reuse_win=true`, the effect is very similar to `set switchbuf=useopen`. Note that functions like `buf.definition` open the quickfix window when there are multiple results, and jumping between quickfix entries already follows 'switchbuf', so unifying the behavior is more intuitive. Solution: Follow the 'switchbuf' option and drop `reuse_win`. We can achieve this behavior by using :cfirst when the quickfix list has only one item, rather than customizing the jump logic as before.
2026-03-30refactor(lsp): merge `vim.lsp.rpc.Client` and `vim.lsp.rpc.PublicClient`Yi Ming2
2026-03-29fix(lsp): reset document color processed version on clear (#38582)Maria Solano1
2026-03-29fix(lsp): highlight snippet preview when server can't completionItem/resolve ↵Marcus Caisey1
(#38534) Problem: The snippet preview is not being highlighted by treesitter for completion items from servers which don't support `completionItem/resolve` (like gopls). This was broken by #38428. Solution: Call `update_popup_window` after updating the completion item with the snippet preview. I've added assertions to the `selecting an item triggers completionItem/resolve + (snippet) preview` test case which covers the snippet preview being shown since no tests failed when I removed the `nvim__complete_set` call which actually populates the preview on this codepath.
2026-03-29docs: misc #38532Justin M. Keyes1
Close #38431 Close #38521 Close #38530 Co-authored-by: tayheau <thopsore@pasteur.fr> Co-authored-by: zeertzjq <zeertzjq@outlook.com> Co-authored-by: Olivia Kinnear <git@superatomic.dev>
2026-03-29feat: extend vim.Pos, vim.Range #36397Luis Calle3
Problem: Using nested `vim.Pos` objects to represent each `vim.Range` object requires 3 tables for each `vim.Range`, which may be undesirable in performance critical code. Using key-value tables performs worse than using array-like tables (lists). Solution: Use array-like indices for the internal fields of both `vim.Pos` and `vim.Range` objects. Use a metatable to allow users to access them like if they were key-value tables. --- Problem: The `vim.Pos` conversion interface for `extmark` indexing does not take into account the difference in how a position on top of a newline is represented in `vim.Pos` and `extmark`. - `vim.Pos`: for a newline at the end of row `n`, `row` takes the value `n + 1` and `col` takes the value `0`. - `extmark`: for a newline at the end of for `n`, `row` takes the value `n` and `col` takes the value `#row_text`. Solution: Handle this in the `extmark` interface. --- Problem: Not all `to_xxx` interfaces have wrapping objects like `to_lsp`. Solution: Return unwrapped values in `to_xxx` interfaces where it makes sense. Accept unwrapped values in "from" interfaces where it makes sense. --- Problem: `start` and `end` positions have different semantics, so they can't be compared. `vim.Range` relies on comparing the `end` and `start` of two ranges to decide which one is greater, which doesn't work as expected because this of the different semantics. For example, for the ranges: local a = { start = { row = 0, col = 22, }, end_ = { row = 0, col = 24, }, } local b = { start = { row = 0, col = 17, }, end_ = { row = 0, col = 22, }, } in this code: local foo, bar = "foo", "bar" -- |---||-| -- b a The range `b` is smaller than the range `a`, but the current implementation compares `b._end` (`col = 22`) and `a.start` (`col = 22`) and concludes that, since `b.col` is not smaller than `a.col`, `b` should be greater than `a`. Solution: - Use a `to_inclusive_pos` to normalize end positions inside of `vim.Range` whenever a comparison between a start and an end position is necessary.
2026-03-27docs(lsp): add `init_options` to Copilot example #38502Maria Solano1
Problem: When following this example from our docs the Copilot LSP won't attach. Solution: Add `init_options` as done by [`nvim-lspconfig`](https://github.com/neovim/nvim-lspconfig/blob/1a6d69206749a646ef28bfb39460610b14baff40/lsp/copilot.lua#L112-L121).
2026-03-23fix(lsp): get_namespace signature (#38449)Tristan Knight2
Problem: Since the change to `pull_id` in #37938 we used the deprecated signature internally Solution: Don't